1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * byterange_filter.c --- HTTP byterange filter and friends.
23 #include "apr_strings.h"
24 #include "apr_buckets.h"
26 #include "apr_signal.h"
28 #define APR_WANT_STDIO /* for sscanf */
29 #define APR_WANT_STRFUNC
30 #define APR_WANT_MEMFUNC
33 #include "util_filter.h"
34 #include "ap_config.h"
36 #include "http_config.h"
37 #include "http_core.h"
38 #include "http_protocol.h"
39 #include "http_main.h"
40 #include "http_request.h"
41 #include "http_vhost.h"
42 #include "http_log.h" /* For errors detected in basic auth common
44 #include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */
45 #include "util_charset.h"
46 #include "util_ebcdic.h"
47 #include "util_time.h"
58 #ifndef AP_DEFAULT_MAX_RANGES
59 #define AP_DEFAULT_MAX_RANGES 200
61 #ifndef AP_DEFAULT_MAX_OVERLAPS
62 #define AP_DEFAULT_MAX_OVERLAPS 20
64 #ifndef AP_DEFAULT_MAX_REVERSALS
65 #define AP_DEFAULT_MAX_REVERSALS 20
68 #define MAX_PREALLOC_RANGES 100
70 APLOG_USE_MODULE(http);
72 typedef struct indexes_t {
78 * Returns: number of ranges (merged) or -1 for no-good
80 static int ap_set_byterange(request_rec *r, apr_off_t clength,
81 apr_array_header_t **indexes,
82 int *overlaps, int *reversals)
89 apr_array_header_t *merged;
90 int num_ranges = 0, unsatisfiable = 0;
91 apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
101 if (r->assbackwards) {
106 * Check for Range request-header (HTTP/1.1) or Request-Range for
107 * backwards-compatibility with second-draft Luotonen/Franks
108 * byte-ranges (e.g. Netscape Navigator 2-3).
110 * We support this form, with Request-Range, and (farther down) we
111 * send multipart/x-byteranges instead of multipart/byteranges for
112 * Request-Range based requests to work around a bug in Netscape
113 * Navigator 2-3 and MSIE 3.
116 if (!(range = apr_table_get(r->headers_in, "Range"))) {
117 range = apr_table_get(r->headers_in, "Request-Range");
120 if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
124 /* is content already a single range? */
125 if (apr_table_get(r->headers_out, "Content-Range")) {
129 /* is content already a multiple range? */
130 if ((ct = apr_table_get(r->headers_out, "Content-Type"))
131 && (!strncasecmp(ct, "multipart/byteranges", 20)
132 || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
137 * Check the If-Range header for Etag or Date.
138 * Note that this check will return false (as required) if either
139 * of the two etags are weak.
141 if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
142 if (if_range[0] == '"') {
143 if (!(match = apr_table_get(r->headers_out, "Etag"))
144 || (strcmp(if_range, match) != 0)) {
148 else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
149 || (strcmp(if_range, match) != 0)) {
162 if (ranges > MAX_PREALLOC_RANGES) {
163 ranges = MAX_PREALLOC_RANGES;
165 *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
166 while ((cur = ap_getword(r->pool, &range, ','))) {
169 apr_off_t number, start, end;
175 * Per RFC 2616 14.35.1: If there is at least one syntactically invalid
176 * byte-range-spec, we must ignore the whole header.
179 if (!(dash = strchr(cur, '-'))) {
184 /* In the form "-5" */
185 if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
191 start = clength - number;
196 if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
201 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
217 if (start >= clength) {
221 if (end >= clength) {
234 if (start >= ostart && end <= oend) {
238 if (start < ostart && end >= ostart-1) {
243 if (end >= oend && start <= oend+1 ) {
252 idx = (indexes_t *)apr_array_push(*indexes);
255 sum_lengths += oend - ostart + 1;
265 idx = (indexes_t *)apr_array_push(*indexes);
268 sum_lengths += oend - ostart + 1;
271 else if (num_ranges == 0 && unsatisfiable) {
272 /* If all ranges are unsatisfiable, we should return 416 */
275 if (sum_lengths >= clength) {
276 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
277 "Sum of ranges not smaller than file, ignoring.");
282 * create the merged table now, now that we know we need it
284 merged = apr_array_make(r->pool, num_ranges, sizeof(char *));
285 idx = (indexes_t *)(*indexes)->elts;
286 for (i = 0; i < (*indexes)->nelts; i++, idx++) {
287 char **new = (char **)apr_array_push(merged);
288 *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
289 idx->start, idx->end);
292 r->status = HTTP_PARTIAL_CONTENT;
293 r->range = apr_array_pstrcat(r->pool, merged, ',');
294 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
295 "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
296 it, r->range, *overlaps, *reversals, clength);
302 * Here we try to be compatible with clients that want multipart/x-byteranges
303 * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
304 * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
305 * that the browser supports an older protocol. We also check User-Agent
306 * for Microsoft Internet Explorer 3, which needs this as well.
308 static int use_range_x(request_rec *r)
311 return (apr_table_get(r->headers_in, "Request-Range")
312 || ((ua = apr_table_get(r->headers_in, "User-Agent"))
313 && ap_strstr_c(ua, "MSIE 3")));
316 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
318 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
319 apr_bucket_brigade *bbout,
323 apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
324 apr_uint64_t pos = 0, off_first = 0, off_last = 0;
326 apr_uint64_t start64, end64;
330 * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
331 * See the comments in apr_brigade_partition why.
332 * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
334 start64 = (apr_uint64_t)start;
335 end64 = (apr_uint64_t)end;
337 if (start < 0 || end < 0 || start64 > end64)
340 for (e = APR_BRIGADE_FIRST(bb);
341 e != APR_BRIGADE_SENTINEL(bb);
342 e = APR_BUCKET_NEXT(e))
345 /* we know that no bucket has undefined length (-1) */
346 AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
347 elen64 = (apr_uint64_t)e->length;
348 if (!first && (elen64 + pos > start64)) {
352 if (elen64 + pos > end64) {
366 AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
367 rv = apr_bucket_copy(e, ©);
368 if (rv != APR_SUCCESS) {
369 apr_brigade_cleanup(bbout);
373 APR_BRIGADE_INSERT_TAIL(bbout, copy);
375 if (off_first != start64) {
376 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
377 if (rv != APR_SUCCESS) {
378 apr_brigade_cleanup(bbout);
381 out_first = APR_BUCKET_NEXT(copy);
382 APR_BUCKET_REMOVE(copy);
383 apr_bucket_destroy(copy);
391 off_last += start64 - off_first;
394 if (end64 - off_last != (apr_uint64_t)e->length) {
395 rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
396 if (rv != APR_SUCCESS) {
397 apr_brigade_cleanup(bbout);
400 copy = APR_BUCKET_NEXT(copy);
401 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
402 APR_BUCKET_REMOVE(copy);
403 apr_bucket_destroy(copy);
408 e = APR_BUCKET_NEXT(e);
411 AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
412 pos = (apr_uint64_t)pofft;
413 AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
417 static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb)
420 conn_rec *c = f->r->connection;
421 ap_remove_output_filter(f);
422 f->r->status = HTTP_OK;
423 e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
424 f->r->pool, c->bucket_alloc);
425 APR_BRIGADE_INSERT_TAIL(tmpbb, e);
426 e = apr_bucket_eos_create(c->bucket_alloc);
427 APR_BRIGADE_INSERT_TAIL(tmpbb, e);
428 return ap_pass_brigade(f->next, tmpbb);
431 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
432 apr_bucket_brigade *bb)
434 request_rec *r = f->r;
435 conn_rec *c = r->connection;
437 apr_bucket_brigade *bsend;
438 apr_bucket_brigade *tmpbb;
439 apr_off_t range_start;
441 apr_off_t clength = 0;
445 char *bound_head = NULL;
446 apr_array_header_t *indexes;
450 int max_ranges, max_overlaps, max_reversals;
451 int overlaps = 0, reversals = 0;
452 core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
454 max_ranges = ( (core_conf->max_ranges >= 0 || core_conf->max_ranges == AP_MAXRANGES_UNLIMITED)
455 ? core_conf->max_ranges
456 : AP_DEFAULT_MAX_RANGES );
457 max_overlaps = ( (core_conf->max_overlaps >= 0 || core_conf->max_overlaps == AP_MAXRANGES_UNLIMITED)
458 ? core_conf->max_overlaps
459 : AP_DEFAULT_MAX_OVERLAPS );
460 max_reversals = ( (core_conf->max_reversals >= 0 || core_conf->max_reversals == AP_MAXRANGES_UNLIMITED)
461 ? core_conf->max_reversals
462 : AP_DEFAULT_MAX_REVERSALS );
464 * Iterate through the brigade until reaching EOS or a bucket with
467 for (e = APR_BRIGADE_FIRST(bb);
468 (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
469 && e->length != (apr_size_t)-1);
470 e = APR_BUCKET_NEXT(e)) {
471 clength += e->length;
475 * Don't attempt to do byte range work if this brigade doesn't
476 * contain an EOS, or if any of the buckets has an unknown length;
477 * this avoids the cases where it is expensive to perform
478 * byteranging (i.e. may require arbitrary amounts of memory).
480 if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
481 ap_remove_output_filter(f);
482 return ap_pass_brigade(f->next, bb);
485 original_status = r->status;
486 num_ranges = ap_set_byterange(r, clength, &indexes, &overlaps, &reversals);
488 /* No Ranges or we hit a limit? We have nothing to do, get out of the way. */
489 if (num_ranges == 0 ||
490 (max_ranges >= 0 && num_ranges > max_ranges) ||
491 (max_overlaps >= 0 && overlaps > max_overlaps) ||
492 (max_reversals >= 0 && reversals > max_reversals)) {
493 r->status = original_status;
494 ap_remove_output_filter(f);
495 return ap_pass_brigade(f->next, bb);
498 /* this brigade holds what we will be sending */
499 bsend = apr_brigade_create(r->pool, c->bucket_alloc);
502 return send_416(f, bsend);
504 if (num_ranges > 1) {
505 /* Is ap_make_content_type required here? */
506 const char *orig_ct = ap_make_content_type(r, r->content_type);
508 ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
509 use_range_x(r) ? "/x-" : "/",
510 "byteranges; boundary=",
511 ap_multipart_boundary, NULL));
514 bound_head = apr_pstrcat(r->pool,
515 CRLF "--", ap_multipart_boundary,
516 CRLF "Content-type: ",
518 CRLF "Content-range: bytes ",
522 /* if we have no type for the content, do our best */
523 bound_head = apr_pstrcat(r->pool,
524 CRLF "--", ap_multipart_boundary,
525 CRLF "Content-range: bytes ",
528 ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
531 tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
533 idx = (indexes_t *)indexes->elts;
534 for (i = 0; i < indexes->nelts; i++, idx++) {
535 range_start = idx->start;
536 range_end = idx->end;
538 rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
539 if (rv != APR_SUCCESS ) {
540 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
541 "copy_brigade_range() failed [%" APR_OFF_T_FMT
542 "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
543 range_start, range_end, clength);
549 * For single range requests, we must produce Content-Range header.
550 * Otherwise, we need to produce the multipart boundaries.
552 if (num_ranges == 1) {
553 apr_table_setn(r->headers_out, "Content-Range",
554 apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
555 range_start, range_end, clength));
560 e = apr_bucket_pool_create(bound_head, strlen(bound_head),
561 r->pool, c->bucket_alloc);
562 APR_BRIGADE_INSERT_TAIL(bsend, e);
564 ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
565 range_start, range_end, clength);
566 ap_xlate_proto_to_ascii(ts, strlen(ts));
567 e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
569 APR_BRIGADE_INSERT_TAIL(bsend, e);
572 APR_BRIGADE_CONCAT(bsend, tmpbb);
573 if (i && !(i & 0x1F)) {
575 * Every now and then, pass what we have down the filter chain.
576 * In this case, the content-length filter cannot calculate and
577 * set the content length and we must remove any Content-Length
578 * header already present.
580 apr_table_unset(r->headers_out, "Content-Length");
581 if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
583 apr_brigade_cleanup(bsend);
588 /* bsend is assumed to be empty if we get here. */
589 return send_416(f, bsend);
592 if (num_ranges > 1) {
595 /* add the final boundary */
596 end = apr_pstrcat(r->pool, CRLF "--", ap_multipart_boundary, "--" CRLF,
598 ap_xlate_proto_to_ascii(end, strlen(end));
599 e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
600 APR_BRIGADE_INSERT_TAIL(bsend, e);
603 e = apr_bucket_eos_create(c->bucket_alloc);
604 APR_BRIGADE_INSERT_TAIL(bsend, e);
606 /* we're done with the original content - all of our data is in bsend. */
607 apr_brigade_cleanup(bb);
608 apr_brigade_destroy(tmpbb);
610 /* send our multipart output */
611 return ap_pass_brigade(f->next, bsend);