]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHdrRange.cc
Author: wessels & Christos Tsantilas
[thirdparty/squid.git] / src / HttpHdrRange.cc
CommitLineData
b5107edb 1
2/*
47f6e231 3 * $Id: HttpHdrRange.cc,v 1.45 2007/08/13 17:20:51 hno Exp $
b5107edb 4 *
d76fcfa7 5 * DEBUG: section 64 HTTP Range Header
b5107edb 6 * AUTHOR: Alex Rousskov
7 *
2b6662ba 8 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 9 * ----------------------------------------------------------
b5107edb 10 *
2b6662ba 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.
b5107edb 19 *
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.
24 *
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.
29 *
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
cbdec147 32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 33 *
b5107edb 34 */
35
36#include "squid.h"
e6ccf245 37#include "Store.h"
528b2c61 38#include "HttpHeaderRange.h"
39#include "client_side_request.h"
924f73bc 40#include "HttpReply.h"
b5107edb 41
b644367b 42/*
43 * Currently only byte ranges are supported
44 *
45 * Essentially, there are three types of byte ranges:
46 *
47 * 1) first-byte-pos "-" last-byte-pos // range
48 * 2) first-byte-pos "-" // trailer
49 * 3) "-" suffix-length // suffix (last length bytes)
50 *
51 *
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
528b2c61 54 * using HttpHdrRangeSpec::UnknownPosition constant.
b644367b 55 *
56 * Note: when response length becomes known, we convert any range
57 * spec into type one above. (Canonization process).
58 */
b5107edb 59
60
b5107edb 61/* local routines */
528b2c61 62#define known_spec(s) ((s) > HttpHdrRangeSpec::UnknownPosition)
b5107edb 63
64/* globals */
528b2c61 65size_t HttpHdrRange::ParsedCount = 0;
47f6e231 66int64_t const HttpHdrRangeSpec::UnknownPosition = -1;
b5107edb 67
68/*
69 * Range-Spec
70 */
71
528b2c61 72HttpHdrRangeSpec::HttpHdrRangeSpec() : offset(UnknownPosition), length(UnknownPosition){}
73
b5107edb 74/* parses range-spec and returns new object on success */
528b2c61 75HttpHdrRangeSpec *
76HttpHdrRangeSpec::Create(const char *field, int flen)
77{
78 HttpHdrRangeSpec spec;
62e76326 79
528b2c61 80 if (!spec.parseInit(field, flen))
62e76326 81 return NULL;
82
528b2c61 83 return new HttpHdrRangeSpec(spec);
84}
85
86bool
87HttpHdrRangeSpec::parseInit(const char *field, int flen)
b5107edb 88{
b5107edb 89 const char *p;
62e76326 90
b5107edb 91 if (flen < 2)
62e76326 92 return false;
93
b5107edb 94 /* is it a suffix-byte-range-spec ? */
95 if (*field == '-') {
47f6e231 96 if (!httpHeaderParseOffset(field + 1, &length))
62e76326 97 return false;
b5107edb 98 } else
62e76326 99 /* must have a '-' somewhere in _this_ field */
100 if (!((p = strchr(field, '-')) || (p - field >= flen))) {
bf8fe701 101 debugs(64, 2, "ignoring invalid (missing '-') range-spec near: '" << field << "'");
62e76326 102 return false;
103 } else {
47f6e231 104 if (!httpHeaderParseOffset(field, &offset))
62e76326 105 return false;
106
107 p++;
108
109 /* do we have last-pos ? */
110 if (p - field < flen) {
47f6e231 111 int64_t last_pos;
62e76326 112
47f6e231 113 if (!httpHeaderParseOffset(p, &last_pos))
62e76326 114 return false;
115
116 HttpHdrRangeSpec::HttpRange aSpec (offset, last_pos + 1);
117
118 length = aSpec.size();
119 }
120 }
121
b5107edb 122 /* we managed to parse, check if the result makes sence */
528b2c61 123 if (length == 0) {
bf8fe701 124 debugs(64, 2, "ignoring invalid (zero length) range-spec near: '" << field << "'");
62e76326 125 return false;
b5107edb 126 }
62e76326 127
528b2c61 128 return true;
b5107edb 129}
130
528b2c61 131void
132HttpHdrRangeSpec::packInto(Packer * packer) const
02922e76 133{
528b2c61 134 if (!known_spec(offset)) /* suffix */
47f6e231 135 packerPrintf(packer, "-%"PRId64, length);
528b2c61 136 else if (!known_spec(length)) /* trailer */
47f6e231 137 packerPrintf(packer, "%"PRId64"-", offset);
528b2c61 138 else /* range */
47f6e231 139 packerPrintf(packer, "%"PRId64"-%"PRId64,
140 offset, offset + length - 1);
02922e76 141}
142
528b2c61 143void
144HttpHdrRangeSpec::outputInfo( char const *note) const
d76fcfa7 145{
bf8fe701 146 debugs(64, 5, "HttpHdrRangeSpec::canonize: " << note << ": [" <<
47f6e231 147 offset << ", " << offset + length <<
148 ") len: " << length);
d76fcfa7 149}
150
62e76326 151/* fills "absent" positions in range specification based on response body size
b644367b 152 * returns true if the range is still valid
153 * range is valid if its intersection with [0,length-1] is not empty
154 */
528b2c61 155int
47f6e231 156HttpHdrRangeSpec::canonize(int64_t clen)
528b2c61 157{
158 outputInfo ("have");
159 HttpRange object(0, clen);
62e76326 160
161 if (!known_spec(offset)) /* suffix */
162 {
163 assert(known_spec(length));
164 offset = object.intersection(HttpRange (clen - length, clen)).start;
165 } else if (!known_spec(length)) /* trailer */
166 {
167 assert(known_spec(offset));
168 HttpRange newRange = object.intersection(HttpRange (offset, clen));
169 length = newRange.size();
528b2c61 170 }
b5107edb 171 /* we have a "range" now, adjust length if needed */
528b2c61 172 assert(known_spec(length));
62e76326 173
528b2c61 174 assert(known_spec(offset));
62e76326 175
528b2c61 176 HttpRange newRange = object.intersection (HttpRange (offset, offset + length));
62e76326 177
528b2c61 178 length = newRange.size();
62e76326 179
528b2c61 180 outputInfo ("done");
62e76326 181
528b2c61 182 return length > 0;
b5107edb 183}
184
62e76326 185/* merges recepient with donor if possible; returns true on success
889b534a 186 * both specs must be canonized prior to merger, of course */
62e76326 187bool
528b2c61 188HttpHdrRangeSpec::mergeWith(const HttpHdrRangeSpec * donor)
889b534a 189{
528b2c61 190 bool merged (false);
67bff3c8 191#if MERGING_BREAKS_NOTHING
192 /* Note: this code works, but some clients may not like its effects */
47f6e231 193 int64_t rhs = offset + length; /* no -1 ! */
194 const int64_t donor_rhs = donor->offset + donor->length; /* no -1 ! */
528b2c61 195 assert(known_spec(offset));
889b534a 196 assert(known_spec(donor->offset));
528b2c61 197 assert(length > 0);
889b534a 198 assert(donor->length > 0);
199 /* do we have a left hand side overlap? */
62e76326 200
528b2c61 201 if (donor->offset < offset && offset <= donor_rhs) {
62e76326 202 offset = donor->offset; /* decrease left offset */
203 merged = 1;
889b534a 204 }
62e76326 205
889b534a 206 /* do we have a right hand side overlap? */
207 if (donor->offset <= rhs && rhs < donor_rhs) {
62e76326 208 rhs = donor_rhs; /* increase right offset */
209 merged = 1;
889b534a 210 }
62e76326 211
889b534a 212 /* adjust length if offsets have been changed */
213 if (merged) {
62e76326 214 assert(rhs > offset);
215 length = rhs - offset;
889b534a 216 } else {
62e76326 217 /* does recepient contain donor? */
218 merged =
219 offset <= donor->offset && donor->offset < rhs;
889b534a 220 }
62e76326 221
67bff3c8 222#endif
889b534a 223 return merged;
224}
225
b5107edb 226/*
227 * Range
228 */
229
528b2c61 230HttpHdrRange::HttpHdrRange () : clen (HttpHdrRangeSpec::UnknownPosition)
62e76326 231{}
b5107edb 232
233HttpHdrRange *
30abd221 234HttpHdrRange::ParseCreate(const String * range_spec)
b5107edb 235{
528b2c61 236 HttpHdrRange *r = new HttpHdrRange;
62e76326 237
528b2c61 238 if (!r->parseInit(range_spec)) {
00d77d6b 239 delete r;
62e76326 240 r = NULL;
b5107edb 241 }
62e76326 242
b5107edb 243 return r;
244}
245
246/* returns true if ranges are valid; inits HttpHdrRange */
528b2c61 247bool
30abd221 248HttpHdrRange::parseInit(const String * range_spec)
b5107edb 249{
250 const char *item;
251 const char *pos = NULL;
252 int ilen;
99edd1c3 253 int count = 0;
528b2c61 254 assert(this && range_spec);
255 ++ParsedCount;
30abd221 256 debugs(64, 8, "parsing range field: '" << range_spec->buf() << "'");
d0700d54 257 /* check range type */
62e76326 258
30abd221 259 if (range_spec->caseCmp("bytes=", 6))
62e76326 260 return 0;
261
99edd1c3 262 /* skip "bytes="; hack! */
30abd221 263 pos = range_spec->buf() + 5;
62e76326 264
b5107edb 265 /* iterate through comma separated list */
528b2c61 266 while (strListGetItem(range_spec, ',', &item, &ilen, &pos)) {
62e76326 267 HttpHdrRangeSpec *spec = HttpHdrRangeSpec::Create(item, ilen);
268 /*
269 * HTTP/1.1 draft says we must ignore the whole header field if one spec
270 * is invalid. However, RFC 2068 just says that we must ignore that spec.
271 */
272
273 if (spec)
274 specs.push_back(spec);
275
276 ++count;
b5107edb 277 }
62e76326 278
30abd221 279 debugs(64, 8, "parsed range range count: " << count << ", kept " <<
280 specs.size());
528b2c61 281 return specs.count != 0;
282}
283
284HttpHdrRange::~HttpHdrRange()
285{
286 while (specs.size())
00d77d6b 287 delete specs.pop_back();
528b2c61 288}
289
290HttpHdrRange::HttpHdrRange(HttpHdrRange const &old) : specs()
291{
292 specs.reserve(old.specs.size());
62e76326 293
528b2c61 294 for (const_iterator i = old.begin(); i != old.end(); ++i)
62e76326 295 specs.push_back(new HttpHdrRangeSpec ( **i));
296
528b2c61 297 assert(old.specs.size() == specs.size());
298}
299
300HttpHdrRange::iterator
301HttpHdrRange::begin()
302{
303 return specs.begin();
304}
305
306HttpHdrRange::iterator
307HttpHdrRange::end()
308{
309 return specs.end();
310}
311
312HttpHdrRange::const_iterator
313HttpHdrRange::begin() const
314{
315 return specs.begin();
316}
317
318HttpHdrRange::const_iterator
319HttpHdrRange::end() const
320{
321 return specs.end();
b5107edb 322}
323
324void
528b2c61 325HttpHdrRange::packInto(Packer * packer) const
b5107edb 326{
528b2c61 327 const_iterator pos = begin();
328 assert(this);
62e76326 329
528b2c61 330 while (pos != end()) {
62e76326 331 if (pos != begin())
332 packerAppend(packer, ",", 1);
333
334 (*pos)->packInto(packer);
335
336 ++pos;
528b2c61 337 }
b5107edb 338}
339
528b2c61 340void
341HttpHdrRange::merge (Vector<HttpHdrRangeSpec *> &basis)
02922e76 342{
528b2c61 343 /* reset old array */
344 specs.clean();
345 /* merge specs:
346 * take one spec from "goods" and merge it with specs from
347 * "specs" (if any) until there is no overlap */
348 iterator i = basis.begin();
62e76326 349
528b2c61 350 while (i != basis.end()) {
62e76326 351 if (specs.size() && (*i)->mergeWith(specs.back())) {
352 /* merged with current so get rid of the prev one */
00d77d6b 353 delete specs.pop_back();
62e76326 354 continue; /* re-iterate */
355 }
356
357 specs.push_back (*i);
358 ++i; /* progress */
528b2c61 359 }
62e76326 360
e4049756 361 debugs(64, 3, "HttpHdrRange::merge: had " << basis.size() <<
362 " specs, merged " << basis.size() - specs.size() << " specs");
02922e76 363}
364
528b2c61 365
02922e76 366void
528b2c61 367HttpHdrRange::getCanonizedSpecs (Vector<HttpHdrRangeSpec *> &copy)
368{
369 /* canonize each entry and destroy bad ones if any */
62e76326 370
528b2c61 371 for (iterator pos (begin()); pos != end(); ++pos) {
62e76326 372 if ((*pos)->canonize(clen))
373 copy.push_back (*pos);
374 else
00d77d6b 375 delete (*pos);
02922e76 376 }
62e76326 377
e4049756 378 debugs(64, 3, "HttpHdrRange::getCanonizedSpecs: found " <<
379 specs.size() - copy.size() << " bad specs");
02922e76 380}
381
528b2c61 382#include "HttpHdrContRange.h"
383
b5107edb 384/*
385 * canonizes all range specs within a set preserving the order
386 * returns true if the set is valid after canonization;
387 * the set is valid if
388 * - all range specs are valid and
389 * - there is at least one range spec
390 */
391int
528b2c61 392HttpHdrRange::canonize(HttpReply *rep)
b5107edb 393{
528b2c61 394 assert(this && rep);
62e76326 395
528b2c61 396 if (rep->content_range)
62e76326 397 clen = rep->content_range->elength;
528b2c61 398 else
62e76326 399 clen = rep->content_length;
400
528b2c61 401 return canonize (clen);
b5107edb 402}
403
528b2c61 404int
47f6e231 405HttpHdrRange::canonize (int64_t newClen)
b5107edb 406{
528b2c61 407 clen = newClen;
e4049756 408 debugs(64, 3, "HttpHdrRange::canonize: started with " << specs.count <<
409 " specs, clen: " << clen);
528b2c61 410 Vector<HttpHdrRangeSpec*> goods;
411 getCanonizedSpecs(goods);
412 merge (goods);
e4049756 413 debugs(64, 3, "HttpHdrRange::canonize: finished with " << specs.count <<
414 " specs");
528b2c61 415 return specs.count > 0;
b5107edb 416}
d192d11f 417
418/* hack: returns true if range specs are too "complex" for Squid to handle */
67bff3c8 419/* requires that specs are "canonized" first! */
528b2c61 420bool
421HttpHdrRange::isComplex() const
d192d11f 422{
47f6e231 423 int64_t offset = 0;
528b2c61 424 assert(this);
d192d11f 425 /* check that all rangers are in "strong" order */
528b2c61 426 const_iterator pos (begin());
62e76326 427
528b2c61 428 while (pos != end()) {
62e76326 429 /* Ensure typecasts is safe */
430 assert ((*pos)->offset >= 0);
431
47f6e231 432 if ((*pos)->offset < offset)
62e76326 433 return 1;
434
435 offset = (*pos)->offset + (*pos)->length;
436
437 ++pos;
d192d11f 438 }
62e76326 439
d192d11f 440 return 0;
441}
442
9bc73deb 443/*
444 * hack: returns true if range specs may be too "complex" when "canonized".
528b2c61 445 * see also: HttpHdrRange::isComplex.
9bc73deb 446 */
528b2c61 447bool
448HttpHdrRange::willBeComplex() const
67bff3c8 449{
528b2c61 450 assert(this);
67bff3c8 451 /* check that all rangers are in "strong" order, */
452 /* as far as we can tell without the content length */
47f6e231 453 int64_t offset = 0;
62e76326 454
528b2c61 455 for (const_iterator pos (begin()); pos != end(); ++pos) {
62e76326 456 if (!known_spec((*pos)->offset)) /* ignore unknowns */
457 continue;
458
459 /* Ensure typecasts is safe */
460 assert ((*pos)->offset >= 0);
461
47f6e231 462 if ((*pos)->offset < offset)
62e76326 463 return true;
464
465 offset = (*pos)->offset;
466
467 if (known_spec((*pos)->length)) /* avoid unknowns */
468 offset += (*pos)->length;
67bff3c8 469 }
62e76326 470
528b2c61 471 return false;
67bff3c8 472}
473
9bc73deb 474/*
528b2c61 475 * Returns lowest known offset in range spec(s),
476 * or HttpHdrRangeSpec::UnknownPosition
9bc73deb 477 * this is used for size limiting
478 */
47f6e231 479int64_t
528b2c61 480HttpHdrRange::firstOffset() const
481{
47f6e231 482 int64_t offset = HttpHdrRangeSpec::UnknownPosition;
528b2c61 483 assert(this);
484 const_iterator pos = begin();
62e76326 485
528b2c61 486 while (pos != end()) {
62e76326 487 if ((*pos)->offset < offset || !known_spec(offset))
488 offset = (*pos)->offset;
489
490 ++pos;
c68e9c6b 491 }
62e76326 492
cfbf5373 493 return offset;
c68e9c6b 494}
495
9bc73deb 496/*
497 * Returns lowest offset in range spec(s), 0 if unknown.
498 * This is used for finding out where we need to start if all
cfbf5373 499 * ranges are combined into one, for example FTP REST.
500 * Use 0 for size if unknown
501 */
47f6e231 502int64_t
503HttpHdrRange::lowestOffset(int64_t size) const
528b2c61 504{
47f6e231 505 int64_t offset = HttpHdrRangeSpec::UnknownPosition;
528b2c61 506 const_iterator pos = begin();
507 assert(this);
62e76326 508
528b2c61 509 while (pos != end()) {
47f6e231 510 int64_t current = (*pos)->offset;
62e76326 511
512 if (!known_spec(current)) {
513 if ((*pos)->length > size || !known_spec((*pos)->length))
514 return 0; /* Unknown. Assume start of file */
515
516 current = size - (*pos)->length;
517 }
518
519 if (current < offset || !known_spec(offset))
520 offset = current;
521
522 ++pos;
cfbf5373 523 }
62e76326 524
cfbf5373 525 return known_spec(offset) ? offset : 0;
526}
527
62e76326 528/*
7b1e21ff 529 * Return true if the first range offset is larger than the configured
530 * limit.
7f1b1350 531 * Note that exceeding the limit (returning true) results in only
532 * grabbing the needed range elements from the origin.
7b1e21ff 533 */
528b2c61 534bool
535HttpHdrRange::offsetLimitExceeded() const
7b1e21ff 536{
528b2c61 537 if (NULL == this)
62e76326 538 /* not a range request */
539 return false;
540
47f6e231 541 if (-1 == Config.rangeOffsetLimit)
62e76326 542 /* disabled */
543 return false;
544
7f1b1350 545 if (firstOffset() == -1)
546 /* tail request */
8c362252 547 return true;
7f1b1350 548
47f6e231 549 if (Config.rangeOffsetLimit >= firstOffset())
62e76326 550 /* below the limit */
551 return false;
552
528b2c61 553 return true;
554}
555
fedd1531 556bool
557HttpHdrRange::contains(HttpHdrRangeSpec& r) const
558{
559 assert(r.length >= 0);
560 HttpHdrRangeSpec::HttpRange rrange(r.offset, r.offset + r.length);
561
562 for (const_iterator i = begin(); i != end(); ++i) {
563 HttpHdrRangeSpec::HttpRange irange((*i)->offset, (*i)->offset + (*i)->length);
564 HttpHdrRangeSpec::HttpRange intersection = rrange.intersection(irange);
565
566 if (intersection.start == irange.start && intersection.size() == irange.size())
567 return true;
568 }
569
570 return false;
571}
572
528b2c61 573const HttpHdrRangeSpec *
574HttpHdrRangeIter::currentSpec() const
575{
576 if (pos.incrementable())
62e76326 577 return *pos;
578
528b2c61 579 return NULL;
580}
581
582void
583HttpHdrRangeIter::updateSpec()
584{
585 assert (debt_size == 0);
b6012c1a 586 assert (valid);
62e76326 587
528b2c61 588 if (pos.incrementable()) {
62e76326 589 debt(currentSpec()->length);
528b2c61 590 }
591}
592
47f6e231 593int64_t
528b2c61 594HttpHdrRangeIter::debt() const
595{
4a7a3d56 596 debugs(64, 3, "HttpHdrRangeIter::debt: debt is " << debt_size);
528b2c61 597 return debt_size;
598}
599
47f6e231 600void HttpHdrRangeIter::debt(int64_t newDebt)
528b2c61 601{
4a7a3d56 602 debugs(64, 3, "HttpHdrRangeIter::debt: was " << debt_size << " now " << newDebt);
528b2c61 603 debt_size = newDebt;
7b1e21ff 604}