1 Index: modules/http/byterange_filter.c
2 ===================================================================
3 --- modules/http/byterange_filter.c (Revision 1162579)
4 +++ modules/http/byterange_filter.c (Arbeitskopie)
9 -static int parse_byterange(char *range, apr_off_t clength,
10 - apr_off_t *start, apr_off_t *end)
12 - char *dash = strchr(range, '-');
15 +static int ap_set_byterange(request_rec *r, apr_off_t clength,
16 + apr_array_header_t **indexes);
22 - if ((dash == range)) {
23 - /* In the form "-5" */
24 - if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
27 - *start = clength - number;
32 - if (apr_strtoff(&number, range, &errp, 10) || *errp) {
37 - if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
51 - if (*end >= clength) {
55 - if (*start > *end) {
59 - return (*start > 0 || *end < clength);
62 -static int ap_set_byterange(request_rec *r);
64 -typedef struct byterange_ctx {
65 - apr_bucket_brigade *bb;
72 * Here we try to be compatible with clients that want multipart/x-byteranges
73 * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
77 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
78 -#define PARTITION_ERR_FMT "apr_brigade_partition() failed " \
79 - "[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]"
81 +static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
82 + apr_bucket_brigade *bbout,
86 + apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
87 + apr_uint64_t pos = 0, off_first = 0, off_last = 0;
91 + apr_uint64_t start64, end64;
92 + apr_off_t pofft = 0;
95 + * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
96 + * See the comments in apr_brigade_partition why.
97 + * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
99 + start64 = (apr_uint64_t)start;
100 + end64 = (apr_uint64_t)end;
102 + if (start < 0 || end < 0 || start64 > end64)
105 + for (e = APR_BRIGADE_FIRST(bb);
106 + e != APR_BRIGADE_SENTINEL(bb);
107 + e = APR_BUCKET_NEXT(e))
109 + apr_uint64_t elen64;
110 + /* we know that no bucket has undefined length (-1) */
111 + AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
112 + elen64 = (apr_uint64_t)e->length;
113 + if (!first && (elen64 + pos > start64)) {
117 + if (elen64 + pos > end64) {
124 + if (!first || !last)
131 + AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
132 + rv = apr_bucket_copy(e, ©);
133 + if (rv != APR_SUCCESS) {
134 + apr_brigade_cleanup(bbout);
138 + APR_BRIGADE_INSERT_TAIL(bbout, copy);
140 + if (off_first != start64) {
141 + rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
142 + if (rv == APR_ENOTIMPL) {
143 + rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
144 + if (rv != APR_SUCCESS) {
145 + apr_brigade_cleanup(bbout);
149 + * The read above might have morphed copy in a bucket
150 + * of shorter length. So read and delete until we reached
151 + * the correct bucket for splitting.
153 + while (start64 - off_first > (apr_uint64_t)copy->length) {
159 + tmp = APR_BUCKET_NEXT(copy);
160 + off_first += (apr_uint64_t)copy->length;
161 + APR_BUCKET_REMOVE(copy);
162 + apr_bucket_destroy(copy);
164 + rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
165 + if (rv != APR_SUCCESS) {
166 + apr_brigade_cleanup(bbout);
170 + if (start64 > off_first) {
171 + rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
172 + if (rv != APR_SUCCESS) {
173 + apr_brigade_cleanup(bbout);
178 + copy = APR_BUCKET_PREV(copy);
181 + else if (rv != APR_SUCCESS) {
182 + apr_brigade_cleanup(bbout);
185 + out_first = APR_BUCKET_NEXT(copy);
186 + APR_BUCKET_REMOVE(copy);
187 + apr_bucket_destroy(copy);
195 + off_last += start64 - off_first;
198 + if (end64 - off_last != (apr_uint64_t)e->length) {
199 + rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
200 + if (rv == APR_ENOTIMPL) {
201 + rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
202 + if (rv != APR_SUCCESS) {
203 + apr_brigade_cleanup(bbout);
207 + * The read above might have morphed copy in a bucket
208 + * of shorter length. So read until we reached
209 + * the correct bucket for splitting.
211 + while (end64 + 1 - off_last > (apr_uint64_t)copy->length) {
212 + off_last += (apr_uint64_t)copy->length;
213 + copy = APR_BUCKET_NEXT(copy);
214 + rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
215 + if (rv != APR_SUCCESS) {
216 + apr_brigade_cleanup(bbout);
220 + if (end64 < off_last + (apr_uint64_t)copy->length - 1) {
221 + rv = apr_bucket_split(copy, end64 + 1 - off_last);
222 + if (rv != APR_SUCCESS) {
223 + apr_brigade_cleanup(bbout);
228 + else if (rv != APR_SUCCESS) {
229 + apr_brigade_cleanup(bbout);
232 + copy = APR_BUCKET_NEXT(copy);
233 + if (copy != APR_BRIGADE_SENTINEL(bbout)) {
234 + APR_BUCKET_REMOVE(copy);
235 + apr_bucket_destroy(copy);
240 + e = APR_BUCKET_NEXT(e);
243 + AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
244 + pos = (apr_uint64_t)pofft;
245 + AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
246 + return APR_SUCCESS;
249 +typedef struct indexes_t {
254 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
255 apr_bucket_brigade *bb)
257 -#define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
258 request_rec *r = f->r;
259 conn_rec *c = r->connection;
260 - byterange_ctx *ctx;
262 apr_bucket_brigade *bsend;
263 + apr_bucket_brigade *tmpbb;
264 apr_off_t range_start;
267 apr_off_t clength = 0;
271 + char *boundary = NULL;
272 + char *bound_head = NULL;
273 + apr_array_header_t *indexes;
275 + int original_status;
278 - /* Iterate through the brigade until reaching EOS or a bucket with
279 - * unknown length. */
281 + * Iterate through the brigade until reaching EOS or a bucket with
284 for (e = APR_BRIGADE_FIRST(bb);
285 (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
286 && e->length != (apr_size_t)-1);
287 @@ -160,90 +280,80 @@
288 clength += e->length;
291 - /* Don't attempt to do byte range work if this brigade doesn't
293 + * Don't attempt to do byte range work if this brigade doesn't
294 * contain an EOS, or if any of the buckets has an unknown length;
295 * this avoids the cases where it is expensive to perform
296 - * byteranging (i.e. may require arbitrary amounts of memory). */
297 + * byteranging (i.e. may require arbitrary amounts of memory).
299 if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
300 ap_remove_output_filter(f);
301 return ap_pass_brigade(f->next, bb);
304 - num_ranges = ap_set_byterange(r);
305 + original_status = r->status;
306 + num_ranges = ap_set_byterange(r, clength, &indexes);
308 /* We have nothing to do, get out of the way. */
309 if (num_ranges == 0) {
310 + r->status = original_status;
311 ap_remove_output_filter(f);
312 return ap_pass_brigade(f->next, bb);
315 - ctx = apr_pcalloc(r->pool, sizeof(*ctx));
316 - ctx->num_ranges = num_ranges;
317 - /* create a brigade in case we never call ap_save_brigade() */
318 - ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
320 - if (ctx->num_ranges > 1) {
321 + if (num_ranges > 1) {
322 /* Is ap_make_content_type required here? */
323 const char *orig_ct = ap_make_content_type(r, r->content_type);
324 - ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
325 - (apr_uint64_t)r->request_time, (long) getpid());
326 + boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
327 + (apr_uint64_t)r->request_time, (long) getpid());
329 ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
330 use_range_x(r) ? "/x-" : "/",
331 "byteranges; boundary=",
332 - ctx->boundary, NULL));
335 if (strcasecmp(orig_ct, NO_CONTENT_TYPE)) {
336 - ctx->bound_head = apr_pstrcat(r->pool,
337 - CRLF "--", ctx->boundary,
338 - CRLF "Content-type: ",
340 - CRLF "Content-range: bytes ",
342 + bound_head = apr_pstrcat(r->pool,
343 + CRLF "--", boundary,
344 + CRLF "Content-type: ",
346 + CRLF "Content-range: bytes ",
350 /* if we have no type for the content, do our best */
351 - ctx->bound_head = apr_pstrcat(r->pool,
352 - CRLF "--", ctx->boundary,
353 - CRLF "Content-range: bytes ",
355 + bound_head = apr_pstrcat(r->pool,
356 + CRLF "--", boundary,
357 + CRLF "Content-range: bytes ",
360 - ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
361 + ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
364 /* this brigade holds what we will be sending */
365 bsend = apr_brigade_create(r->pool, c->bucket_alloc);
366 + tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
368 - while ((current = ap_getword(r->pool, &r->range, ','))
369 - && (rv = parse_byterange(current, clength, &range_start,
373 + idx = (indexes_t *)indexes->elts;
374 + for (i = 0; i < indexes->nelts; i++, idx++) {
375 + range_start = idx->start;
376 + range_end = idx->end;
382 - /* These calls to apr_brigage_partition should only fail in
383 - * pathological cases, e.g. a file being truncated whilst
385 - if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
386 + rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
387 + if (rv != APR_SUCCESS ) {
388 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
389 - PARTITION_ERR_FMT, range_start, clength);
390 + "copy_brigade_range() failed [%" APR_OFF_T_FMT
391 + "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
392 + range_start, range_end, clength);
395 - if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
396 - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
397 - PARTITION_ERR_FMT, range_end+1, clength);
403 - /* For single range requests, we must produce Content-Range header.
405 + * For single range requests, we must produce Content-Range header.
406 * Otherwise, we need to produce the multipart boundaries.
408 - if (ctx->num_ranges == 1) {
409 + if (num_ranges == 1) {
410 apr_table_setn(r->headers_out, "Content-Range",
411 apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
412 range_start, range_end, clength));
417 - e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
418 + e = apr_bucket_pool_create(bound_head, strlen(bound_head),
419 r->pool, c->bucket_alloc);
420 APR_BRIGADE_INSERT_TAIL(bsend, e);
422 @@ -263,23 +373,19 @@
423 APR_BRIGADE_INSERT_TAIL(bsend, e);
431 - if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
432 - /* As above; this should not fail since the bucket has
433 - * a known length, but just to be sure, this takes
434 - * care of uncopyable buckets that do somehow manage
435 - * to slip through. */
436 - /* XXX: check for failure? */
437 - apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
438 - apr_bucket_copy(ec, &foo);
440 - APR_BRIGADE_INSERT_TAIL(bsend, foo);
441 - ec = APR_BUCKET_NEXT(ec);
442 - } while (ec != e2);
443 + APR_BRIGADE_CONCAT(bsend, tmpbb);
444 + if (i && i % 32 == 0) {
446 + * Every now and then, pass what we have down the filter chain.
447 + * In this case, the content-length filter cannot calculate and
448 + * set the content length and we must remove any Content-Length
449 + * header already present.
451 + apr_table_unset(r->headers_out, "Content-Length");
452 + if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
454 + apr_brigade_cleanup(bsend);
459 @@ -294,11 +400,11 @@
460 return ap_pass_brigade(f->next, bsend);
463 - if (ctx->num_ranges > 1) {
464 + if (num_ranges > 1) {
467 /* add the final boundary */
468 - end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
469 + end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL);
470 ap_xlate_proto_to_ascii(end, strlen(end));
471 e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
472 APR_BRIGADE_INSERT_TAIL(bsend, e);
473 @@ -309,24 +415,32 @@
475 /* we're done with the original content - all of our data is in bsend. */
476 apr_brigade_cleanup(bb);
477 + apr_brigade_destroy(tmpbb);
479 /* send our multipart output */
480 return ap_pass_brigade(f->next, bsend);
483 -static int ap_set_byterange(request_rec *r)
484 +static int ap_set_byterange(request_rec *r, apr_off_t clength,
485 + apr_array_header_t **indexes)
488 const char *if_range;
493 + int num_ranges = 0;
494 + apr_off_t sum_lengths = 0;
499 if (r->assbackwards) {
503 - /* Check for Range request-header (HTTP/1.1) or Request-Range for
505 + * Check for Range request-header (HTTP/1.1) or Request-Range for
506 * backwards-compatibility with second-draft Luotonen/Franks
507 * byte-ranges (e.g. Netscape Navigator 2-3).
513 - /* Check the If-Range header for Etag or Date.
515 + * Check the If-Range header for Etag or Date.
516 * Note that this check will return false (as required) if either
517 * of the two etags are weak.
519 @@ -373,17 +488,77 @@
523 - if (!ap_strchr_c(range, ',')) {
524 - /* a single range */
529 + if (*it++ == ',') {
534 - /* a multiple range */
537 + *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
538 + while ((cur = ap_getword(r->pool, &range, ','))) {
541 + apr_off_t number, start, end;
543 + if (!(dash = strchr(cur, '-'))) {
547 + if (dash == range) {
548 + /* In the form "-5" */
549 + if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
552 + start = clength - number;
557 + if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
562 + if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
575 + if (end >= clength) {
580 + /* ignore? count? */
584 + idx = (indexes_t *)apr_array_push(*indexes);
585 + idx->start = start;
587 + sum_lengths += end - start + 1;
588 + /* new set again */
592 + if (sum_lengths >= clength) {
593 + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
594 + "Sum of ranges not smaller than file, ignoring.");
598 r->status = HTTP_PARTIAL_CONTENT;
599 - r->range = range + 6;