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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors