Squid Web Cache master
Loading...
Searching...
No Matches
HttpHdrRange.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/* DEBUG: section 64 HTTP Range Header */
10
11#include "squid.h"
12#include "client_side_request.h"
13#include "http/Stream.h"
14#include "HttpHeaderRange.h"
15#include "HttpHeaderTools.h"
16#include "HttpReply.h"
17#include "Store.h"
18#include "StrList.h"
19
20#include <limits>
21
22/*
23 * Currently only byte ranges are supported
24 *
25 * Essentially, there are three types of byte ranges:
26 *
27 * 1) first-byte-pos "-" last-byte-pos // range
28 * 2) first-byte-pos "-" // trailer
29 * 3) "-" suffix-length // suffix (last length bytes)
30 *
31 *
32 * When Range field is parsed, we have no clue about the content
33 * length of the document. Thus, we simply code an "absent" part
34 * using HttpHdrRangeSpec::UnknownPosition constant.
35 *
36 * Note: when response length becomes known, we convert any range
37 * spec into type one above. (Canonization process).
38 */
39
40/* local routines */
41#define known_spec(s) ((s) > HttpHdrRangeSpec::UnknownPosition)
42
43/* globals */
45int64_t const HttpHdrRangeSpec::UnknownPosition = -1;
46
47/*
48 * Range-Spec
49 */
50
51HttpHdrRangeSpec::HttpHdrRangeSpec() : offset(UnknownPosition), length(UnknownPosition) {}
52
53/* parses range-spec and returns new object on success */
55HttpHdrRangeSpec::Create(const char *field, int flen)
56{
58
59 if (!spec.parseInit(field, flen))
60 return nullptr;
61
62 return new HttpHdrRangeSpec(spec);
63}
64
65bool
66HttpHdrRangeSpec::parseInit(const char *field, int flen)
67{
68 const char *p;
69
70 if (flen < 2)
71 return false;
72
73 /* is it a suffix-byte-range-spec ? */
74 if (*field == '-') {
75 if (!httpHeaderParseOffset(field + 1, &length) || !known_spec(length))
76 return false;
77 } else
78 /* must have a '-' somewhere in _this_ field */
79 if (!((p = strchr(field, '-')) && (p - field < flen))) {
80 debugs(64, 2, "invalid (missing '-') range-spec near: '" << field << "'");
81 return false;
82 } else {
84 return false;
85
86 ++p;
87
88 /* do we have last-pos ? */
89 if (p - field < flen) {
90 int64_t last_pos;
91
92 if (!httpHeaderParseOffset(p, &last_pos) || !known_spec(last_pos))
93 return false;
94
95 // RFC 2616 s14.35.1 MUST: last-byte-pos >= first-byte-pos
96 if (last_pos < offset) {
97 debugs(64, 2, "invalid (last-byte-pos < first-byte-pos) range-spec near: " << field);
98 return false;
99 }
100
101 // prevent `last_pos + 1` overflows
102 if (last_pos == std::numeric_limits<decltype(last_pos)>::max()) {
103 debugs(64, 2, "unsupported (last-byte-pos too large) range-spec near: " << field);
104 return false;
105 }
106
107 HttpHdrRangeSpec::HttpRange aSpec (offset, last_pos + 1);
108
109 length = aSpec.size();
110 }
111 }
112
113 return true;
114}
115
116void
118{
119 if (!known_spec(offset)) /* suffix */
120 p->appendf("-%" PRId64, length);
121 else if (!known_spec(length)) /* trailer */
122 p->appendf("%" PRId64 "-", offset);
123 else /* range */
124 p->appendf("%" PRId64 "-%" PRId64, offset, offset + length - 1);
125}
126
127void
128HttpHdrRangeSpec::outputInfo( char const *note) const
129{
130 debugs(64, 5, "HttpHdrRangeSpec::canonize: " << note << ": [" <<
131 offset << ", " << offset + length <<
132 ") len: " << length);
133}
134
135/* fills "absent" positions in range specification based on response body size
136 * returns true if the range is still valid
137 * range is valid if its intersection with [0,length-1] is not empty
138 */
139int
141{
142 outputInfo ("have");
143 HttpRange object(0, clen);
144
145 if (!known_spec(offset)) { /* suffix */
147 offset = object.intersection(HttpRange (clen - length, clen)).start;
148 } else if (!known_spec(length)) { /* trailer */
150 HttpRange newRange = object.intersection(HttpRange (offset, clen));
151 length = newRange.size();
152 }
153 /* we have a "range" now, adjust length if needed */
155
157
158 HttpRange newRange = object.intersection (HttpRange (offset, offset + length));
159
160 length = newRange.size();
161
162 outputInfo ("done");
163
164 return length > 0;
165}
166
167/* merges recipient with donor if possible; returns true on success
168 * both specs must be canonized prior to merger, of course */
169bool
171{
172 bool merged (false);
173#if MERGING_BREAKS_NOTHING
174 /* Note: this code works, but some clients may not like its effects */
175 int64_t rhs = offset + length; /* no -1 ! */
176 const int64_t donor_rhs = donor->offset + donor->length; /* no -1 ! */
178 assert(known_spec(donor->offset));
179 assert(length > 0);
180 assert(donor->length > 0);
181 /* do we have a left hand side overlap? */
182
183 if (donor->offset < offset && offset <= donor_rhs) {
184 offset = donor->offset; /* decrease left offset */
185 merged = 1;
186 }
187
188 /* do we have a right hand side overlap? */
189 if (donor->offset <= rhs && rhs < donor_rhs) {
190 rhs = donor_rhs; /* increase right offset */
191 merged = 1;
192 }
193
194 /* adjust length if offsets have been changed */
195 if (merged) {
196 assert(rhs > offset);
197 length = rhs - offset;
198 } else {
199 /* does recipient contain donor? */
200 merged =
201 offset <= donor->offset && donor->offset < rhs;
202 }
203
204#else
205 (void)donor;
206#endif
207 return merged;
208}
209
210/*
211 * Range
212 */
213
215{}
216
219{
220 HttpHdrRange *r = new HttpHdrRange;
221
222 if (!r->parseInit(range_spec)) {
223 delete r;
224 r = nullptr;
225 }
226
227 return r;
228}
229
230/* returns true if ranges are valid; inits HttpHdrRange */
231bool
233{
234 const char *item;
235 const char *pos = nullptr;
236 int ilen;
237 assert(range_spec);
238 ++ParsedCount;
239 debugs(64, 8, "parsing range field: '" << *range_spec << "'");
240 /* check range type */
241
242 if (range_spec->caseCmp("bytes=", 6))
243 return 0;
244
245 /* skip "bytes="; hack! */
246 pos = range_spec->termedBuf() + 6;
247
248 /* iterate through comma separated list */
249 while (strListGetItem(range_spec, ',', &item, &ilen, &pos)) {
250 HttpHdrRangeSpec *spec = HttpHdrRangeSpec::Create(item, ilen);
251 /*
252 * RFC 2616 section 14.35.1: MUST ignore Range with
253 * at least one syntactically invalid byte-range-specs.
254 */
255 if (!spec) {
256 while (!specs.empty()) {
257 delete specs.back();
258 specs.pop_back();
259 }
260 debugs(64, 2, "ignoring invalid range field: '" << *range_spec << "'");
261 break;
262 }
263
264 specs.push_back(spec);
265 }
266
267 debugs(64, 8, "got range specs: " << specs.size());
268 return !specs.empty();
269}
270
272{
273 while (!specs.empty()) {
274 delete specs.back();
275 specs.pop_back();
276 }
277}
278
280 specs(),
281 clen(HttpHdrRangeSpec::UnknownPosition)
282{
283 specs.reserve(old.specs.size());
284
285 for (const_iterator i = old.begin(); i != old.end(); ++i)
286 specs.push_back(new HttpHdrRangeSpec ( **i));
287
288 assert(old.specs.size() == specs.size());
289}
290
293{
294 return specs.begin();
295}
296
299{
300 return specs.end();
301}
302
305{
306 return specs.begin();
307}
308
311{
312 return specs.end();
313}
314
315void
317{
318 const_iterator pos = begin();
319
320 while (pos != end()) {
321 if (pos != begin())
322 packer->append(",", 1);
323
324 (*pos)->packInto(packer);
325
326 ++pos;
327 }
328}
329
330void
331HttpHdrRange::merge (std::vector<HttpHdrRangeSpec *> &basis)
332{
333 /* reset old array */
334 specs.clear();
335 /* merge specs:
336 * take one spec from "goods" and merge it with specs from
337 * "specs" (if any) until there is no overlap */
338 iterator i = basis.begin();
339
340 while (i != basis.end()) {
341 if (specs.size() && (*i)->mergeWith(specs.back())) {
342 /* merged with current so get rid of the prev one */
343 delete specs.back();
344 specs.pop_back();
345 continue; /* re-iterate */
346 }
347
348 specs.push_back (*i);
349 ++i; /* progress */
350 }
351
352 debugs(64, 3, "HttpHdrRange::merge: had " << basis.size() <<
353 " specs, merged " << basis.size() - specs.size() << " specs");
354}
355
356void
357HttpHdrRange::getCanonizedSpecs(std::vector<HttpHdrRangeSpec *> &copy)
358{
359 /* canonize each entry and destroy bad ones if any */
360
361 for (iterator pos (begin()); pos != end(); ++pos) {
362 if ((*pos)->canonize(clen))
363 copy.push_back (*pos);
364 else
365 delete (*pos);
366 }
367
368 debugs(64, 3, "found " << specs.size() - copy.size() << " bad specs");
369}
370
371#include "HttpHdrContRange.h"
372
373/*
374 * canonizes all range specs within a set preserving the order
375 * returns true if the set is valid after canonization;
376 * the set is valid if
377 * - all range specs are valid and
378 * - there is at least one range spec
379 */
380int
382{
383 assert(rep);
384
385 if (rep->contentRange())
386 clen = rep->contentRange()->elength;
387 else
388 clen = rep->content_length;
389
390 return canonize (clen);
391}
392
393int
394HttpHdrRange::canonize (int64_t newClen)
395{
396 clen = newClen;
397 debugs(64, 3, "HttpHdrRange::canonize: started with " << specs.size() <<
398 " specs, clen: " << clen);
399 std::vector<HttpHdrRangeSpec*> goods;
400 getCanonizedSpecs(goods);
401 merge (goods);
402 debugs(64, 3, "HttpHdrRange::canonize: finished with " << specs.size() <<
403 " specs");
404 return specs.size() > 0; // TODO: should return bool
405}
406
407/* hack: returns true if range specs are too "complex" for Squid to handle */
408/* requires that specs are "canonized" first! */
409bool
411{
412 int64_t offset = 0;
413 /* check that all rangers are in "strong" order */
414 const_iterator pos (begin());
415
416 while (pos != end()) {
417 /* Ensure typecasts is safe */
418 assert ((*pos)->offset >= 0);
419
420 if ((*pos)->offset < offset)
421 return 1;
422
423 offset = (*pos)->offset + (*pos)->length;
424
425 ++pos;
426 }
427
428 return 0;
429}
430
431/*
432 * hack: returns true if range specs may be too "complex" when "canonized".
433 * see also: HttpHdrRange::isComplex.
434 */
435bool
437{
438 /* check that all rangers are in "strong" order, */
439 /* as far as we can tell without the content length */
440 int64_t offset = 0;
441
442 for (const_iterator pos (begin()); pos != end(); ++pos) {
443 if (!known_spec((*pos)->offset)) /* ignore unknowns */
444 continue;
445
446 /* Ensure typecasts is safe */
447 assert ((*pos)->offset >= 0);
448
449 if ((*pos)->offset < offset)
450 return true;
451
452 offset = (*pos)->offset;
453
454 if (known_spec((*pos)->length)) /* avoid unknowns */
455 offset += (*pos)->length;
456 }
457
458 return false;
459}
460
461/*
462 * Returns lowest known offset in range spec(s),
463 * or HttpHdrRangeSpec::UnknownPosition
464 * this is used for size limiting
465 */
466int64_t
468{
469 int64_t offset = HttpHdrRangeSpec::UnknownPosition;
470 const_iterator pos = begin();
471
472 while (pos != end()) {
473 if ((*pos)->offset < offset || !known_spec(offset))
474 offset = (*pos)->offset;
475
476 ++pos;
477 }
478
479 return offset;
480}
481
482/*
483 * Returns lowest offset in range spec(s), 0 if unknown.
484 * This is used for finding out where we need to start if all
485 * ranges are combined into one, for example FTP REST.
486 * Use 0 for size if unknown
487 */
488int64_t
490{
491 int64_t offset = HttpHdrRangeSpec::UnknownPosition;
492 const_iterator pos = begin();
493
494 while (pos != end()) {
495 int64_t current = (*pos)->offset;
496
497 if (!known_spec(current)) {
498 if ((*pos)->length > size || !known_spec((*pos)->length))
499 return 0; /* Unknown. Assume start of file */
500
501 current = size - (*pos)->length;
502 }
503
504 if (current < offset || !known_spec(offset))
505 offset = current;
506
507 ++pos;
508 }
509
510 return known_spec(offset) ? offset : 0;
511}
512
513/*
514 * \retval true Fetch only requested ranges. The first range is larger that configured limit.
515 * \retval false Full download. Not a range request, no limit, or the limit is not yet reached.
516 */
517bool
518HttpHdrRange::offsetLimitExceeded(const int64_t limit) const
519{
520 if (limit == 0)
521 /* 0 == disabled */
522 return true;
523
524 if (-1 == limit)
525 /* 'none' == forced */
526 return false;
527
528 if (firstOffset() == -1)
529 /* tail request */
530 return true;
531
532 if (limit >= firstOffset())
533 /* below the limit */
534 return false;
535
536 return true;
537}
538
539const HttpHdrRangeSpec *
541{
542 if (pos != end)
543 return *pos;
544
545 return nullptr;
546}
547
548void
550{
551 assert (debt_size == 0);
552 assert (valid);
553
554 if (pos != end) {
555 debt(currentSpec()->length);
556 }
557}
558
559int64_t
561{
562 debugs(64, 3, "HttpHdrRangeIter::debt: debt is " << debt_size);
563 return debt_size;
564}
565
566void HttpHdrRangeIter::debt(int64_t newDebt)
567{
568 debugs(64, 3, "HttpHdrRangeIter::debt: was " << debt_size << " now " << newDebt);
569 debt_size = newDebt;
570}
571
#define known_spec(s)
bool httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
int size
Definition ModDevPoll.cc:70
int strListGetItem(const String *str, char del, const char **item, int *ilen, const char **pos)
Definition StrList.cc:78
#define assert(EX)
Definition assert.h:17
HttpHdrRange::iterator pos
HttpHdrRange::iterator end
const HttpHdrRangeSpec * currentSpec() const
int64_t debt() const
void packInto(Packable *p) const
bool parseInit(const char *field, int flen)
static HttpHdrRangeSpec * Create(const char *field, int fieldLen)
bool mergeWith(const HttpHdrRangeSpec *donor)
static int64_t const UnknownPosition
int canonize(int64_t clen)
void outputInfo(char const *note) const
int canonize(int64_t)
std::vector< HttpHdrRangeSpec * >::iterator iterator
void getCanonizedSpecs(std::vector< HttpHdrRangeSpec * > &copy)
bool offsetLimitExceeded(const int64_t limit) const
void merge(std::vector< HttpHdrRangeSpec * > &basis)
static size_t ParsedCount
iterator begin()
std::vector< HttpHdrRangeSpec * >::const_iterator const_iterator
iterator end()
bool willBeComplex() const
bool isComplex() const
std::vector< HttpHdrRangeSpec * > specs
bool parseInit(const String *range_spec)
int64_t lowestOffset(int64_t) const
void packInto(Packable *p) const
static HttpHdrRange * ParseCreate(const String *range_spec)
int64_t firstOffset() const
const HttpHdrContRange * contentRange() const
Definition HttpReply.cc:345
int64_t content_length
Definition Message.h:83
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition Packable.h:61
virtual void append(const char *buf, int size)=0
Appends a c-string to existing packed data.
Definition Range.h:19
Range intersection(Range const &) const
Definition Range.h:46
S size() const
Definition Range.h:61
char const * termedBuf() const
Definition SquidString.h:97
int caseCmp(char const *) const
Definition String.cc:273
A const & max(A const &lhs, A const &rhs)
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define PRId64
Definition types.h:104