]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpHdrRange.cc
2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 64 HTTP Range Header */
12 #include "client_side_request.h"
13 #include "HttpHeaderRange.h"
14 #include "HttpHeaderTools.h"
15 #include "HttpReply.h"
20 * Currently only byte ranges are supported
22 * Essentially, there are three types of byte ranges:
24 * 1) first-byte-pos "-" last-byte-pos // range
25 * 2) first-byte-pos "-" // trailer
26 * 3) "-" suffix-length // suffix (last length bytes)
29 * When Range field is parsed, we have no clue about the content
30 * length of the document. Thus, we simply code an "absent" part
31 * using HttpHdrRangeSpec::UnknownPosition constant.
33 * Note: when response length becomes known, we convert any range
34 * spec into type one above. (Canonization process).
38 #define known_spec(s) ((s) > HttpHdrRangeSpec::UnknownPosition)
41 size_t HttpHdrRange::ParsedCount
= 0;
42 int64_t const HttpHdrRangeSpec::UnknownPosition
= -1;
48 HttpHdrRangeSpec::HttpHdrRangeSpec() : offset(UnknownPosition
), length(UnknownPosition
) {}
50 /* parses range-spec and returns new object on success */
52 HttpHdrRangeSpec::Create(const char *field
, int flen
)
54 HttpHdrRangeSpec spec
;
56 if (!spec
.parseInit(field
, flen
))
59 return new HttpHdrRangeSpec(spec
);
63 HttpHdrRangeSpec::parseInit(const char *field
, int flen
)
70 /* is it a suffix-byte-range-spec ? */
72 if (!httpHeaderParseOffset(field
+ 1, &length
) || !known_spec(length
))
75 /* must have a '-' somewhere in _this_ field */
76 if (!((p
= strchr(field
, '-')) && (p
- field
< flen
))) {
77 debugs(64, 2, "invalid (missing '-') range-spec near: '" << field
<< "'");
80 if (!httpHeaderParseOffset(field
, &offset
) || !known_spec(offset
))
85 /* do we have last-pos ? */
86 if (p
- field
< flen
) {
89 if (!httpHeaderParseOffset(p
, &last_pos
) || !known_spec(last_pos
))
92 // RFC 2616 s14.35.1 MUST: last-byte-pos >= first-byte-pos
93 if (last_pos
< offset
) {
94 debugs(64, 2, "invalid (last-byte-pos < first-byte-pos) range-spec near: " << field
);
98 HttpHdrRangeSpec::HttpRange
aSpec (offset
, last_pos
+ 1);
100 length
= aSpec
.size();
108 HttpHdrRangeSpec::packInto(Packer
* packer
) const
110 if (!known_spec(offset
)) /* suffix */
111 packerPrintf(packer
, "-%" PRId64
, length
);
112 else if (!known_spec(length
)) /* trailer */
113 packerPrintf(packer
, "%" PRId64
"-", offset
);
115 packerPrintf(packer
, "%" PRId64
"-%" PRId64
,
116 offset
, offset
+ length
- 1);
120 HttpHdrRangeSpec::outputInfo( char const *note
) const
122 debugs(64, 5, "HttpHdrRangeSpec::canonize: " << note
<< ": [" <<
123 offset
<< ", " << offset
+ length
<<
124 ") len: " << length
);
127 /* fills "absent" positions in range specification based on response body size
128 * returns true if the range is still valid
129 * range is valid if its intersection with [0,length-1] is not empty
132 HttpHdrRangeSpec::canonize(int64_t clen
)
135 HttpRange
object(0, clen
);
137 if (!known_spec(offset
)) { /* suffix */
138 assert(known_spec(length
));
139 offset
= object
.intersection(HttpRange (clen
- length
, clen
)).start
;
140 } else if (!known_spec(length
)) { /* trailer */
141 assert(known_spec(offset
));
142 HttpRange newRange
= object
.intersection(HttpRange (offset
, clen
));
143 length
= newRange
.size();
145 /* we have a "range" now, adjust length if needed */
146 assert(known_spec(length
));
148 assert(known_spec(offset
));
150 HttpRange newRange
= object
.intersection (HttpRange (offset
, offset
+ length
));
152 length
= newRange
.size();
159 /* merges recepient with donor if possible; returns true on success
160 * both specs must be canonized prior to merger, of course */
162 HttpHdrRangeSpec::mergeWith(const HttpHdrRangeSpec
* donor
)
165 #if MERGING_BREAKS_NOTHING
166 /* Note: this code works, but some clients may not like its effects */
167 int64_t rhs
= offset
+ length
; /* no -1 ! */
168 const int64_t donor_rhs
= donor
->offset
+ donor
->length
; /* no -1 ! */
169 assert(known_spec(offset
));
170 assert(known_spec(donor
->offset
));
172 assert(donor
->length
> 0);
173 /* do we have a left hand side overlap? */
175 if (donor
->offset
< offset
&& offset
<= donor_rhs
) {
176 offset
= donor
->offset
; /* decrease left offset */
180 /* do we have a right hand side overlap? */
181 if (donor
->offset
<= rhs
&& rhs
< donor_rhs
) {
182 rhs
= donor_rhs
; /* increase right offset */
186 /* adjust length if offsets have been changed */
188 assert(rhs
> offset
);
189 length
= rhs
- offset
;
191 /* does recepient contain donor? */
193 offset
<= donor
->offset
&& donor
->offset
< rhs
;
204 HttpHdrRange::HttpHdrRange() : clen(HttpHdrRangeSpec::UnknownPosition
)
208 HttpHdrRange::ParseCreate(const String
* range_spec
)
210 HttpHdrRange
*r
= new HttpHdrRange
;
212 if (!r
->parseInit(range_spec
)) {
220 /* returns true if ranges are valid; inits HttpHdrRange */
222 HttpHdrRange::parseInit(const String
* range_spec
)
225 const char *pos
= NULL
;
227 assert(this && range_spec
);
229 debugs(64, 8, "parsing range field: '" << range_spec
<< "'");
230 /* check range type */
232 if (range_spec
->caseCmp("bytes=", 6))
235 /* skip "bytes="; hack! */
236 pos
= range_spec
->termedBuf() + 6;
238 /* iterate through comma separated list */
239 while (strListGetItem(range_spec
, ',', &item
, &ilen
, &pos
)) {
240 HttpHdrRangeSpec
*spec
= HttpHdrRangeSpec::Create(item
, ilen
);
242 * RFC 2616 section 14.35.1: MUST ignore Range with
243 * at least one syntactically invalid byte-range-specs.
246 while (!specs
.empty()) {
250 debugs(64, 2, "ignoring invalid range field: '" << range_spec
<< "'");
254 specs
.push_back(spec
);
257 debugs(64, 8, "got range specs: " << specs
.size());
258 return !specs
.empty();
261 HttpHdrRange::~HttpHdrRange()
263 while (!specs
.empty()) {
269 HttpHdrRange::HttpHdrRange(HttpHdrRange
const &old
) :
271 clen(HttpHdrRangeSpec::UnknownPosition
)
273 specs
.reserve(old
.specs
.size());
275 for (const_iterator i
= old
.begin(); i
!= old
.end(); ++i
)
276 specs
.push_back(new HttpHdrRangeSpec ( **i
));
278 assert(old
.specs
.size() == specs
.size());
281 HttpHdrRange::iterator
282 HttpHdrRange::begin()
284 return specs
.begin();
287 HttpHdrRange::iterator
293 HttpHdrRange::const_iterator
294 HttpHdrRange::begin() const
296 return specs
.begin();
299 HttpHdrRange::const_iterator
300 HttpHdrRange::end() const
306 HttpHdrRange::packInto(Packer
* packer
) const
308 const_iterator pos
= begin();
311 while (pos
!= end()) {
313 packerAppend(packer
, ",", 1);
315 (*pos
)->packInto(packer
);
322 HttpHdrRange::merge (std::vector
<HttpHdrRangeSpec
*> &basis
)
324 /* reset old array */
327 * take one spec from "goods" and merge it with specs from
328 * "specs" (if any) until there is no overlap */
329 iterator i
= basis
.begin();
331 while (i
!= basis
.end()) {
332 if (specs
.size() && (*i
)->mergeWith(specs
.back())) {
333 /* merged with current so get rid of the prev one */
336 continue; /* re-iterate */
339 specs
.push_back (*i
);
343 debugs(64, 3, "HttpHdrRange::merge: had " << basis
.size() <<
344 " specs, merged " << basis
.size() - specs
.size() << " specs");
348 HttpHdrRange::getCanonizedSpecs(std::vector
<HttpHdrRangeSpec
*> ©
)
350 /* canonize each entry and destroy bad ones if any */
352 for (iterator
pos (begin()); pos
!= end(); ++pos
) {
353 if ((*pos
)->canonize(clen
))
354 copy
.push_back (*pos
);
359 debugs(64, 3, "found " << specs
.size() - copy
.size() << " bad specs");
362 #include "HttpHdrContRange.h"
365 * canonizes all range specs within a set preserving the order
366 * returns true if the set is valid after canonization;
367 * the set is valid if
368 * - all range specs are valid and
369 * - there is at least one range spec
372 HttpHdrRange::canonize(HttpReply
*rep
)
376 if (rep
->content_range
)
377 clen
= rep
->content_range
->elength
;
379 clen
= rep
->content_length
;
381 return canonize (clen
);
385 HttpHdrRange::canonize (int64_t newClen
)
388 debugs(64, 3, "HttpHdrRange::canonize: started with " << specs
.size() <<
389 " specs, clen: " << clen
);
390 std::vector
<HttpHdrRangeSpec
*> goods
;
391 getCanonizedSpecs(goods
);
393 debugs(64, 3, "HttpHdrRange::canonize: finished with " << specs
.size() <<
395 return specs
.size() > 0; // fixme, should return bool
398 /* hack: returns true if range specs are too "complex" for Squid to handle */
399 /* requires that specs are "canonized" first! */
401 HttpHdrRange::isComplex() const
405 /* check that all rangers are in "strong" order */
406 const_iterator
pos (begin());
408 while (pos
!= end()) {
409 /* Ensure typecasts is safe */
410 assert ((*pos
)->offset
>= 0);
412 if ((*pos
)->offset
< offset
)
415 offset
= (*pos
)->offset
+ (*pos
)->length
;
424 * hack: returns true if range specs may be too "complex" when "canonized".
425 * see also: HttpHdrRange::isComplex.
428 HttpHdrRange::willBeComplex() const
431 /* check that all rangers are in "strong" order, */
432 /* as far as we can tell without the content length */
435 for (const_iterator
pos (begin()); pos
!= end(); ++pos
) {
436 if (!known_spec((*pos
)->offset
)) /* ignore unknowns */
439 /* Ensure typecasts is safe */
440 assert ((*pos
)->offset
>= 0);
442 if ((*pos
)->offset
< offset
)
445 offset
= (*pos
)->offset
;
447 if (known_spec((*pos
)->length
)) /* avoid unknowns */
448 offset
+= (*pos
)->length
;
455 * Returns lowest known offset in range spec(s),
456 * or HttpHdrRangeSpec::UnknownPosition
457 * this is used for size limiting
460 HttpHdrRange::firstOffset() const
462 int64_t offset
= HttpHdrRangeSpec::UnknownPosition
;
464 const_iterator pos
= begin();
466 while (pos
!= end()) {
467 if ((*pos
)->offset
< offset
|| !known_spec(offset
))
468 offset
= (*pos
)->offset
;
477 * Returns lowest offset in range spec(s), 0 if unknown.
478 * This is used for finding out where we need to start if all
479 * ranges are combined into one, for example FTP REST.
480 * Use 0 for size if unknown
483 HttpHdrRange::lowestOffset(int64_t size
) const
485 int64_t offset
= HttpHdrRangeSpec::UnknownPosition
;
486 const_iterator pos
= begin();
489 while (pos
!= end()) {
490 int64_t current
= (*pos
)->offset
;
492 if (!known_spec(current
)) {
493 if ((*pos
)->length
> size
|| !known_spec((*pos
)->length
))
494 return 0; /* Unknown. Assume start of file */
496 current
= size
- (*pos
)->length
;
499 if (current
< offset
|| !known_spec(offset
))
505 return known_spec(offset
) ? offset
: 0;
509 * \retval true Fetch only requested ranges. The first range is larger that configured limit.
510 * \retval false Full download. Not a range request, no limit, or the limit is not yet reached.
513 HttpHdrRange::offsetLimitExceeded(const int64_t limit
) const
516 /* not a range request */
524 /* 'none' == forced */
527 if (firstOffset() == -1)
531 if (limit
>= firstOffset())
532 /* below the limit */
539 HttpHdrRange::contains(HttpHdrRangeSpec
& r
) const
541 assert(r
.length
>= 0);
542 HttpHdrRangeSpec::HttpRange
rrange(r
.offset
, r
.offset
+ r
.length
);
544 for (const_iterator i
= begin(); i
!= end(); ++i
) {
545 HttpHdrRangeSpec::HttpRange
irange((*i
)->offset
, (*i
)->offset
+ (*i
)->length
);
546 HttpHdrRangeSpec::HttpRange intersection
= rrange
.intersection(irange
);
548 if (intersection
.start
== irange
.start
&& intersection
.size() == irange
.size())
555 const HttpHdrRangeSpec
*
556 HttpHdrRangeIter::currentSpec() const
565 HttpHdrRangeIter::updateSpec()
567 assert (debt_size
== 0);
571 debt(currentSpec()->length
);
576 HttpHdrRangeIter::debt() const
578 debugs(64, 3, "HttpHdrRangeIter::debt: debt is " << debt_size
);
582 void HttpHdrRangeIter::debt(int64_t newDebt
)
584 debugs(64, 3, "HttpHdrRangeIter::debt: was " << debt_size
<< " now " << newDebt
);