]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpHdrRange.cc
5 * DEBUG: section 64 HTTP Range Header
6 * AUTHOR: Alex Rousskov
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38 #include "HttpHeaderRange.h"
39 #include "client_side_request.h"
40 #include "HttpReply.h"
43 * Currently only byte ranges are supported
45 * Essentially, there are three types of byte ranges:
47 * 1) first-byte-pos "-" last-byte-pos // range
48 * 2) first-byte-pos "-" // trailer
49 * 3) "-" suffix-length // suffix (last length bytes)
52 * When Range field is parsed, we have no clue about the content
53 * length of the document. Thus, we simply code an "absent" part
54 * using HttpHdrRangeSpec::UnknownPosition constant.
56 * Note: when response length becomes known, we convert any range
57 * spec into type one above. (Canonization process).
62 #define known_spec(s) ((s) > HttpHdrRangeSpec::UnknownPosition)
65 size_t HttpHdrRange::ParsedCount
= 0;
66 int64_t const HttpHdrRangeSpec::UnknownPosition
= -1;
72 HttpHdrRangeSpec::HttpHdrRangeSpec() : offset(UnknownPosition
), length(UnknownPosition
) {}
74 /* parses range-spec and returns new object on success */
76 HttpHdrRangeSpec::Create(const char *field
, int flen
)
78 HttpHdrRangeSpec spec
;
80 if (!spec
.parseInit(field
, flen
))
83 return new HttpHdrRangeSpec(spec
);
87 HttpHdrRangeSpec::parseInit(const char *field
, int flen
)
94 /* is it a suffix-byte-range-spec ? */
96 if (!httpHeaderParseOffset(field
+ 1, &length
))
99 /* must have a '-' somewhere in _this_ field */
100 if (!((p
= strchr(field
, '-')) || (p
- field
>= flen
))) {
101 debugs(64, 2, "invalid (missing '-') range-spec near: '" << field
<< "'");
104 if (!httpHeaderParseOffset(field
, &offset
))
109 /* do we have last-pos ? */
110 if (p
- field
< flen
) {
113 if (!httpHeaderParseOffset(p
, &last_pos
))
116 // RFC 2616 s14.35.1 MUST: last-byte-pos >= first-byte-pos
117 if (last_pos
< offset
) {
118 debugs(64, 2, "invalid (last-byte-pos < first-byte-pos) range-spec near: " << field
);
122 HttpHdrRangeSpec::HttpRange
aSpec (offset
, last_pos
+ 1);
124 length
= aSpec
.size();
132 HttpHdrRangeSpec::packInto(Packer
* packer
) const
134 if (!known_spec(offset
)) /* suffix */
135 packerPrintf(packer
, "-%"PRId64
, length
);
136 else if (!known_spec(length
)) /* trailer */
137 packerPrintf(packer
, "%"PRId64
"-", offset
);
139 packerPrintf(packer
, "%"PRId64
"-%"PRId64
,
140 offset
, offset
+ length
- 1);
144 HttpHdrRangeSpec::outputInfo( char const *note
) const
146 debugs(64, 5, "HttpHdrRangeSpec::canonize: " << note
<< ": [" <<
147 offset
<< ", " << offset
+ length
<<
148 ") len: " << length
);
151 /* fills "absent" positions in range specification based on response body size
152 * returns true if the range is still valid
153 * range is valid if its intersection with [0,length-1] is not empty
156 HttpHdrRangeSpec::canonize(int64_t clen
)
159 HttpRange
object(0, clen
);
161 if (!known_spec(offset
)) { /* suffix */
162 assert(known_spec(length
));
163 offset
= object
.intersection(HttpRange (clen
- length
, clen
)).start
;
164 } else if (!known_spec(length
)) { /* trailer */
165 assert(known_spec(offset
));
166 HttpRange newRange
= object
.intersection(HttpRange (offset
, clen
));
167 length
= newRange
.size();
169 /* we have a "range" now, adjust length if needed */
170 assert(known_spec(length
));
172 assert(known_spec(offset
));
174 HttpRange newRange
= object
.intersection (HttpRange (offset
, offset
+ length
));
176 length
= newRange
.size();
183 /* merges recepient with donor if possible; returns true on success
184 * both specs must be canonized prior to merger, of course */
186 HttpHdrRangeSpec::mergeWith(const HttpHdrRangeSpec
* donor
)
189 #if MERGING_BREAKS_NOTHING
190 /* Note: this code works, but some clients may not like its effects */
191 int64_t rhs
= offset
+ length
; /* no -1 ! */
192 const int64_t donor_rhs
= donor
->offset
+ donor
->length
; /* no -1 ! */
193 assert(known_spec(offset
));
194 assert(known_spec(donor
->offset
));
196 assert(donor
->length
> 0);
197 /* do we have a left hand side overlap? */
199 if (donor
->offset
< offset
&& offset
<= donor_rhs
) {
200 offset
= donor
->offset
; /* decrease left offset */
204 /* do we have a right hand side overlap? */
205 if (donor
->offset
<= rhs
&& rhs
< donor_rhs
) {
206 rhs
= donor_rhs
; /* increase right offset */
210 /* adjust length if offsets have been changed */
212 assert(rhs
> offset
);
213 length
= rhs
- offset
;
215 /* does recepient contain donor? */
217 offset
<= donor
->offset
&& donor
->offset
< rhs
;
228 HttpHdrRange::HttpHdrRange () : clen (HttpHdrRangeSpec::UnknownPosition
)
232 HttpHdrRange::ParseCreate(const String
* range_spec
)
234 HttpHdrRange
*r
= new HttpHdrRange
;
236 if (!r
->parseInit(range_spec
)) {
244 /* returns true if ranges are valid; inits HttpHdrRange */
246 HttpHdrRange::parseInit(const String
* range_spec
)
249 const char *pos
= NULL
;
251 assert(this && range_spec
);
253 debugs(64, 8, "parsing range field: '" << range_spec
<< "'");
254 /* check range type */
256 if (range_spec
->caseCmp("bytes=", 6))
259 /* skip "bytes="; hack! */
260 pos
= range_spec
->termedBuf() + 6;
262 /* iterate through comma separated list */
263 while (strListGetItem(range_spec
, ',', &item
, &ilen
, &pos
)) {
264 HttpHdrRangeSpec
*spec
= HttpHdrRangeSpec::Create(item
, ilen
);
266 * RFC 2616 section 14.35.1: MUST ignore Range with
267 * at least one syntactically invalid byte-range-specs.
270 while (!specs
.empty())
271 delete specs
.pop_back();
272 debugs(64, 2, "ignoring invalid range field: '" << range_spec
<< "'");
276 specs
.push_back(spec
);
279 debugs(64, 8, "got range specs: " << specs
.size());
280 return !specs
.empty();
283 HttpHdrRange::~HttpHdrRange()
286 delete specs
.pop_back();
289 HttpHdrRange::HttpHdrRange(HttpHdrRange
const &old
) : specs()
291 specs
.reserve(old
.specs
.size());
293 for (const_iterator i
= old
.begin(); i
!= old
.end(); ++i
)
294 specs
.push_back(new HttpHdrRangeSpec ( **i
));
296 assert(old
.specs
.size() == specs
.size());
299 HttpHdrRange::iterator
300 HttpHdrRange::begin()
302 return specs
.begin();
305 HttpHdrRange::iterator
311 HttpHdrRange::const_iterator
312 HttpHdrRange::begin() const
314 return specs
.begin();
317 HttpHdrRange::const_iterator
318 HttpHdrRange::end() const
324 HttpHdrRange::packInto(Packer
* packer
) const
326 const_iterator pos
= begin();
329 while (pos
!= end()) {
331 packerAppend(packer
, ",", 1);
333 (*pos
)->packInto(packer
);
340 HttpHdrRange::merge (Vector
<HttpHdrRangeSpec
*> &basis
)
342 /* reset old array */
345 * take one spec from "goods" and merge it with specs from
346 * "specs" (if any) until there is no overlap */
347 iterator i
= basis
.begin();
349 while (i
!= basis
.end()) {
350 if (specs
.size() && (*i
)->mergeWith(specs
.back())) {
351 /* merged with current so get rid of the prev one */
352 delete specs
.pop_back();
353 continue; /* re-iterate */
356 specs
.push_back (*i
);
360 debugs(64, 3, "HttpHdrRange::merge: had " << basis
.size() <<
361 " specs, merged " << basis
.size() - specs
.size() << " specs");
366 HttpHdrRange::getCanonizedSpecs (Vector
<HttpHdrRangeSpec
*> ©
)
368 /* canonize each entry and destroy bad ones if any */
370 for (iterator
pos (begin()); pos
!= end(); ++pos
) {
371 if ((*pos
)->canonize(clen
))
372 copy
.push_back (*pos
);
377 debugs(64, 3, "HttpHdrRange::getCanonizedSpecs: found " <<
378 specs
.size() - copy
.size() << " bad specs");
381 #include "HttpHdrContRange.h"
384 * canonizes all range specs within a set preserving the order
385 * returns true if the set is valid after canonization;
386 * the set is valid if
387 * - all range specs are valid and
388 * - there is at least one range spec
391 HttpHdrRange::canonize(HttpReply
*rep
)
395 if (rep
->content_range
)
396 clen
= rep
->content_range
->elength
;
398 clen
= rep
->content_length
;
400 return canonize (clen
);
404 HttpHdrRange::canonize (int64_t newClen
)
407 debugs(64, 3, "HttpHdrRange::canonize: started with " << specs
.count
<<
408 " specs, clen: " << clen
);
409 Vector
<HttpHdrRangeSpec
*> goods
;
410 getCanonizedSpecs(goods
);
412 debugs(64, 3, "HttpHdrRange::canonize: finished with " << specs
.count
<<
414 return specs
.count
> 0;
417 /* hack: returns true if range specs are too "complex" for Squid to handle */
418 /* requires that specs are "canonized" first! */
420 HttpHdrRange::isComplex() const
424 /* check that all rangers are in "strong" order */
425 const_iterator
pos (begin());
427 while (pos
!= end()) {
428 /* Ensure typecasts is safe */
429 assert ((*pos
)->offset
>= 0);
431 if ((*pos
)->offset
< offset
)
434 offset
= (*pos
)->offset
+ (*pos
)->length
;
443 * hack: returns true if range specs may be too "complex" when "canonized".
444 * see also: HttpHdrRange::isComplex.
447 HttpHdrRange::willBeComplex() const
450 /* check that all rangers are in "strong" order, */
451 /* as far as we can tell without the content length */
454 for (const_iterator
pos (begin()); pos
!= end(); ++pos
) {
455 if (!known_spec((*pos
)->offset
)) /* ignore unknowns */
458 /* Ensure typecasts is safe */
459 assert ((*pos
)->offset
>= 0);
461 if ((*pos
)->offset
< offset
)
464 offset
= (*pos
)->offset
;
466 if (known_spec((*pos
)->length
)) /* avoid unknowns */
467 offset
+= (*pos
)->length
;
474 * Returns lowest known offset in range spec(s),
475 * or HttpHdrRangeSpec::UnknownPosition
476 * this is used for size limiting
479 HttpHdrRange::firstOffset() const
481 int64_t offset
= HttpHdrRangeSpec::UnknownPosition
;
483 const_iterator pos
= begin();
485 while (pos
!= end()) {
486 if ((*pos
)->offset
< offset
|| !known_spec(offset
))
487 offset
= (*pos
)->offset
;
496 * Returns lowest offset in range spec(s), 0 if unknown.
497 * This is used for finding out where we need to start if all
498 * ranges are combined into one, for example FTP REST.
499 * Use 0 for size if unknown
502 HttpHdrRange::lowestOffset(int64_t size
) const
504 int64_t offset
= HttpHdrRangeSpec::UnknownPosition
;
505 const_iterator pos
= begin();
508 while (pos
!= end()) {
509 int64_t current
= (*pos
)->offset
;
511 if (!known_spec(current
)) {
512 if ((*pos
)->length
> size
|| !known_spec((*pos
)->length
))
513 return 0; /* Unknown. Assume start of file */
515 current
= size
- (*pos
)->length
;
518 if (current
< offset
|| !known_spec(offset
))
524 return known_spec(offset
) ? offset
: 0;
528 * \retval true Fetch only requested ranges. The first range is larger that configured limit.
529 * \retval false Full download. Not a range request, no limit, or the limit is not yet reached.
532 HttpHdrRange::offsetLimitExceeded(const int64_t limit
) const
535 /* not a range request */
543 /* 'none' == forced */
546 if (firstOffset() == -1)
550 if (limit
>= firstOffset())
551 /* below the limit */
558 HttpHdrRange::contains(HttpHdrRangeSpec
& r
) const
560 assert(r
.length
>= 0);
561 HttpHdrRangeSpec::HttpRange
rrange(r
.offset
, r
.offset
+ r
.length
);
563 for (const_iterator i
= begin(); i
!= end(); ++i
) {
564 HttpHdrRangeSpec::HttpRange
irange((*i
)->offset
, (*i
)->offset
+ (*i
)->length
);
565 HttpHdrRangeSpec::HttpRange intersection
= rrange
.intersection(irange
);
567 if (intersection
.start
== irange
.start
&& intersection
.size() == irange
.size())
574 const HttpHdrRangeSpec
*
575 HttpHdrRangeIter::currentSpec() const
577 if (pos
.incrementable())
584 HttpHdrRangeIter::updateSpec()
586 assert (debt_size
== 0);
589 if (pos
.incrementable()) {
590 debt(currentSpec()->length
);
595 HttpHdrRangeIter::debt() const
597 debugs(64, 3, "HttpHdrRangeIter::debt: debt is " << debt_size
);
601 void HttpHdrRangeIter::debt(int64_t newDebt
)
603 debugs(64, 3, "HttpHdrRangeIter::debt: was " << debt_size
<< " now " << newDebt
);