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