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 #if APR_HAVE_PROCESS_H
24 #include <process.h> /* for getpid() on Win32 */
27 #include "apr_strings.h"
28 #include "apr_buckets.h"
30 #include "apr_signal.h"
32 #define APR_WANT_STDIO /* for sscanf */
33 #define APR_WANT_STRFUNC
34 #define APR_WANT_MEMFUNC
37 #include "util_filter.h"
38 #include "ap_config.h"
40 #include "http_config.h"
41 #include "http_core.h"
42 #include "http_protocol.h"
43 #include "http_main.h"
44 #include "http_request.h"
45 #include "http_vhost.h"
46 #include "http_log.h" /* For errors detected in basic auth common
48 #include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */
49 #include "util_charset.h"
50 #include "util_ebcdic.h"
51 #include "util_time.h"
62 #ifndef DEFAULT_MAX_RANGES
63 #define DEFAULT_MAX_RANGES 200
66 APLOG_USE_MODULE(http);
68 static int ap_set_byterange(request_rec *r, apr_off_t clength,
69 apr_array_header_t **indexes);
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
74 * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
75 * that the browser supports an older protocol. We also check User-Agent
76 * for Microsoft Internet Explorer 3, which needs this as well.
78 static int use_range_x(request_rec *r)
81 return (apr_table_get(r->headers_in, "Request-Range")
82 || ((ua = apr_table_get(r->headers_in, "User-Agent"))
83 && ap_strstr_c(ua, "MSIE 3")));
86 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
87 #define MAX_PREALLOC_RANGES 100
89 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
90 apr_bucket_brigade *bbout,
94 apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
95 apr_uint64_t pos = 0, off_first = 0, off_last = 0;
99 apr_uint64_t start64, end64;
103 * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
104 * See the comments in apr_brigade_partition why.
105 * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
107 start64 = (apr_uint64_t)start;
108 end64 = (apr_uint64_t)end;
110 if (start < 0 || end < 0 || start64 > end64)
113 for (e = APR_BRIGADE_FIRST(bb);
114 e != APR_BRIGADE_SENTINEL(bb);
115 e = APR_BUCKET_NEXT(e))
118 /* we know that no bucket has undefined length (-1) */
119 AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
120 elen64 = (apr_uint64_t)e->length;
121 if (!first && (elen64 + pos > start64)) {
125 if (elen64 + pos > end64) {
139 AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
140 rv = apr_bucket_copy(e, ©);
141 if (rv != APR_SUCCESS) {
142 apr_brigade_cleanup(bbout);
146 APR_BRIGADE_INSERT_TAIL(bbout, copy);
148 if (off_first != start64) {
149 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
150 if (rv == APR_ENOTIMPL) {
151 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
152 if (rv != APR_SUCCESS) {
153 apr_brigade_cleanup(bbout);
157 * The read above might have morphed copy in a bucket
158 * of shorter length. So read and delete until we reached
159 * the correct bucket for splitting.
161 while (start64 - off_first > (apr_uint64_t)copy->length) {
167 tmp = APR_BUCKET_NEXT(copy);
168 off_first += (apr_uint64_t)copy->length;
169 APR_BUCKET_REMOVE(copy);
170 apr_bucket_destroy(copy);
172 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
173 if (rv != APR_SUCCESS) {
174 apr_brigade_cleanup(bbout);
178 if (start64 > off_first) {
179 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
180 if (rv != APR_SUCCESS) {
181 apr_brigade_cleanup(bbout);
186 copy = APR_BUCKET_PREV(copy);
189 else if (rv != APR_SUCCESS) {
190 apr_brigade_cleanup(bbout);
193 out_first = APR_BUCKET_NEXT(copy);
194 APR_BUCKET_REMOVE(copy);
195 apr_bucket_destroy(copy);
203 off_last += start64 - off_first;
206 if (end64 - off_last != (apr_uint64_t)e->length) {
207 rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
208 if (rv == APR_ENOTIMPL) {
209 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
210 if (rv != APR_SUCCESS) {
211 apr_brigade_cleanup(bbout);
215 * The read above might have morphed copy in a bucket
216 * of shorter length. So read until we reached
217 * the correct bucket for splitting.
219 while (end64 + 1 - off_last > (apr_uint64_t)copy->length) {
220 off_last += (apr_uint64_t)copy->length;
221 copy = APR_BUCKET_NEXT(copy);
222 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
223 if (rv != APR_SUCCESS) {
224 apr_brigade_cleanup(bbout);
228 if (end64 < off_last + (apr_uint64_t)copy->length - 1) {
229 rv = apr_bucket_split(copy, end64 + 1 - off_last);
230 if (rv != APR_SUCCESS) {
231 apr_brigade_cleanup(bbout);
236 else if (rv != APR_SUCCESS) {
237 apr_brigade_cleanup(bbout);
240 copy = APR_BUCKET_NEXT(copy);
241 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
242 APR_BUCKET_REMOVE(copy);
243 apr_bucket_destroy(copy);
248 e = APR_BUCKET_NEXT(e);
251 AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
252 pos = (apr_uint64_t)pofft;
253 AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
257 typedef struct indexes_t {
262 static int get_max_ranges(request_rec *r) {
263 core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
264 return core_conf->max_ranges == -1 ? DEFAULT_MAX_RANGES : core_conf->max_ranges;
267 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
268 apr_bucket_brigade *bb)
270 request_rec *r = f->r;
271 conn_rec *c = r->connection;
273 apr_bucket_brigade *bsend;
274 apr_bucket_brigade *tmpbb;
275 apr_off_t range_start;
277 apr_off_t clength = 0;
281 char *boundary = NULL;
282 char *bound_head = NULL;
283 apr_array_header_t *indexes;
287 int max_ranges = get_max_ranges(r);
290 * Iterate through the brigade until reaching EOS or a bucket with
293 for (e = APR_BRIGADE_FIRST(bb);
294 (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
295 && e->length != (apr_size_t)-1);
296 e = APR_BUCKET_NEXT(e)) {
297 clength += e->length;
301 * Don't attempt to do byte range work if this brigade doesn't
302 * contain an EOS, or if any of the buckets has an unknown length;
303 * this avoids the cases where it is expensive to perform
304 * byteranging (i.e. may require arbitrary amounts of memory).
306 if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
307 ap_remove_output_filter(f);
308 return ap_pass_brigade(f->next, bb);
311 original_status = r->status;
312 num_ranges = ap_set_byterange(r, clength, &indexes);
314 /* We have nothing to do, get out of the way. */
315 if (num_ranges == 0 || (max_ranges > 0 && num_ranges > max_ranges)) {
316 r->status = original_status;
317 ap_remove_output_filter(f);
318 return ap_pass_brigade(f->next, bb);
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 boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
325 (apr_uint64_t)r->request_time, (long) getpid());
327 ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
328 use_range_x(r) ? "/x-" : "/",
329 "byteranges; boundary=",
333 bound_head = apr_pstrcat(r->pool,
335 CRLF "Content-type: ",
337 CRLF "Content-range: bytes ",
341 /* if we have no type for the content, do our best */
342 bound_head = apr_pstrcat(r->pool,
344 CRLF "Content-range: bytes ",
347 ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
350 /* this brigade holds what we will be sending */
351 bsend = apr_brigade_create(r->pool, c->bucket_alloc);
352 tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
354 idx = (indexes_t *)indexes->elts;
355 for (i = 0; i < indexes->nelts; i++, idx++) {
356 range_start = idx->start;
357 range_end = idx->end;
359 rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
360 if (rv != APR_SUCCESS ) {
361 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
362 "copy_brigade_range() failed [%" APR_OFF_T_FMT
363 "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
364 range_start, range_end, clength);
370 * For single range requests, we must produce Content-Range header.
371 * Otherwise, we need to produce the multipart boundaries.
373 if (num_ranges == 1) {
374 apr_table_setn(r->headers_out, "Content-Range",
375 apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
376 range_start, range_end, clength));
381 e = apr_bucket_pool_create(bound_head, strlen(bound_head),
382 r->pool, c->bucket_alloc);
383 APR_BRIGADE_INSERT_TAIL(bsend, e);
385 ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
386 range_start, range_end, clength);
387 ap_xlate_proto_to_ascii(ts, strlen(ts));
388 e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
390 APR_BRIGADE_INSERT_TAIL(bsend, e);
393 APR_BRIGADE_CONCAT(bsend, tmpbb);
394 if (i && !(i & 0x1F)) {
396 * Every now and then, pass what we have down the filter chain.
397 * In this case, the content-length filter cannot calculate and
398 * set the content length and we must remove any Content-Length
399 * header already present.
401 apr_table_unset(r->headers_out, "Content-Length");
402 if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
404 apr_brigade_cleanup(bsend);
409 ap_remove_output_filter(f);
411 /* bsend is assumed to be empty if we get here. */
412 e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
413 r->pool, c->bucket_alloc);
414 APR_BRIGADE_INSERT_TAIL(bsend, e);
415 e = apr_bucket_eos_create(c->bucket_alloc);
416 APR_BRIGADE_INSERT_TAIL(bsend, e);
417 return ap_pass_brigade(f->next, bsend);
420 if (num_ranges > 1) {
423 /* add the final boundary */
424 end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL);
425 ap_xlate_proto_to_ascii(end, strlen(end));
426 e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
427 APR_BRIGADE_INSERT_TAIL(bsend, e);
430 e = apr_bucket_eos_create(c->bucket_alloc);
431 APR_BRIGADE_INSERT_TAIL(bsend, e);
433 /* we're done with the original content - all of our data is in bsend. */
434 apr_brigade_cleanup(bb);
435 apr_brigade_destroy(tmpbb);
437 /* send our multipart output */
438 return ap_pass_brigade(f->next, bsend);
441 static int ap_set_byterange(request_rec *r, apr_off_t clength,
442 apr_array_header_t **indexes)
445 const char *if_range;
449 apr_array_header_t *merged;
451 apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
454 int overlaps = 0, reversals = 0;
458 if (r->assbackwards) {
463 * Check for Range request-header (HTTP/1.1) or Request-Range for
464 * backwards-compatibility with second-draft Luotonen/Franks
465 * byte-ranges (e.g. Netscape Navigator 2-3).
467 * We support this form, with Request-Range, and (farther down) we
468 * send multipart/x-byteranges instead of multipart/byteranges for
469 * Request-Range based requests to work around a bug in Netscape
470 * Navigator 2-3 and MSIE 3.
473 if (!(range = apr_table_get(r->headers_in, "Range"))) {
474 range = apr_table_get(r->headers_in, "Request-Range");
477 if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
481 /* is content already a single range? */
482 if (apr_table_get(r->headers_out, "Content-Range")) {
486 /* is content already a multiple range? */
487 if ((ct = apr_table_get(r->headers_out, "Content-Type"))
488 && (!strncasecmp(ct, "multipart/byteranges", 20)
489 || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
494 * Check the If-Range header for Etag or Date.
495 * Note that this check will return false (as required) if either
496 * of the two etags are weak.
498 if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
499 if (if_range[0] == '"') {
500 if (!(match = apr_table_get(r->headers_out, "Etag"))
501 || (strcmp(if_range, match) != 0)) {
505 else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
506 || (strcmp(if_range, match) != 0)) {
519 if (ranges > MAX_PREALLOC_RANGES) {
520 ranges = MAX_PREALLOC_RANGES;
522 *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
523 merged = apr_array_make(r->pool, ranges, sizeof(char *));
524 while ((cur = ap_getword(r->pool, &range, ','))) {
527 apr_off_t number, start, end;
529 if (!(dash = strchr(cur, '-'))) {
534 /* In the form "-5" */
535 if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
538 start = clength - number;
543 if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
548 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
561 if (end >= clength) {
578 if (start >= ostart && end <= oend) {
582 if (start < ostart && end >= ostart-1) {
587 if (end >= oend && start <= oend+1 ) {
596 new = (char **)apr_array_push(merged);
597 *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
599 idx = (indexes_t *)apr_array_push(*indexes);
602 sum_lengths += oend - ostart + 1;
612 new = (char **)apr_array_push(merged);
613 *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
615 idx = (indexes_t *)apr_array_push(*indexes);
618 sum_lengths += oend - ostart + 1;
621 if (sum_lengths >= clength) {
622 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
623 "Sum of ranges not smaller than file, ignoring.");
627 r->status = HTTP_PARTIAL_CONTENT;
628 r->range = apr_array_pstrcat(r->pool, merged, ',');
629 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
630 "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
631 it, r->range, overlaps, reversals, clength);