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 APLOG_USE_MODULE(http);
64 static int ap_set_byterange(request_rec *r, apr_off_t clength,
65 apr_array_header_t *indexes);
68 * Here we try to be compatible with clients that want multipart/x-byteranges
69 * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
70 * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
71 * that the browser supports an older protocol. We also check User-Agent
72 * for Microsoft Internet Explorer 3, which needs this as well.
74 static int use_range_x(request_rec *r)
77 return (apr_table_get(r->headers_in, "Request-Range")
78 || ((ua = apr_table_get(r->headers_in, "User-Agent"))
79 && ap_strstr_c(ua, "MSIE 3")));
82 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
84 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
85 apr_bucket_brigade *bbout,
89 apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
90 apr_uint64_t pos = 0, off_first = 0, off_last = 0;
94 apr_uint64_t start64, end64;
98 * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
99 * See the comments in apr_brigade_partition why.
100 * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
102 start64 = (apr_uint64_t)start;
103 end64 = (apr_uint64_t)end;
105 if (start < 0 || end < 0 || start64 > end64)
108 for (e = APR_BRIGADE_FIRST(bb);
109 e != APR_BRIGADE_SENTINEL(bb);
110 e = APR_BUCKET_NEXT(e))
113 /* we know that no bucket has undefined length (-1) */
114 AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
115 elen64 = (apr_uint64_t)e->length;
116 if (!first && (elen64 + pos > start64)) {
120 if (elen64 + pos > end64) {
134 AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
135 rv = apr_bucket_copy(e, ©);
136 if (rv != APR_SUCCESS) {
137 apr_brigade_cleanup(bbout);
141 APR_BRIGADE_INSERT_TAIL(bbout, copy);
143 if (off_first != start64) {
144 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
145 if (rv == APR_ENOTIMPL) {
146 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
147 if (rv != APR_SUCCESS) {
148 apr_brigade_cleanup(bbout);
152 * The read above might have morphed copy in a bucket
153 * of shorter length. So read and delete until we reached
154 * the correct bucket for splitting.
156 while (start64 - off_first > (apr_uint64_t)copy->length) {
162 tmp = APR_BUCKET_NEXT(copy);
163 off_first += (apr_uint64_t)copy->length;
164 APR_BUCKET_REMOVE(copy);
165 apr_bucket_destroy(copy);
167 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
168 if (rv != APR_SUCCESS) {
169 apr_brigade_cleanup(bbout);
173 if (start64 > off_first) {
174 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
175 if (rv != APR_SUCCESS) {
176 apr_brigade_cleanup(bbout);
181 copy = APR_BUCKET_PREV(copy);
184 else if (rv != APR_SUCCESS) {
185 apr_brigade_cleanup(bbout);
188 out_first = APR_BUCKET_NEXT(copy);
189 APR_BUCKET_REMOVE(copy);
190 apr_bucket_destroy(copy);
198 off_last += start64 - off_first;
201 if (end64 - off_last != (apr_uint64_t)e->length) {
202 rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
203 if (rv == APR_ENOTIMPL) {
204 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
205 if (rv != APR_SUCCESS) {
206 apr_brigade_cleanup(bbout);
210 * The read above might have morphed copy in a bucket
211 * of shorter length. So read until we reached
212 * the correct bucket for splitting.
214 while (end64 + 1 - off_last > (apr_uint64_t)copy->length) {
215 off_last += (apr_uint64_t)copy->length;
216 copy = APR_BUCKET_NEXT(copy);
217 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
218 if (rv != APR_SUCCESS) {
219 apr_brigade_cleanup(bbout);
223 if (end64 < off_last + (apr_uint64_t)copy->length - 1) {
224 rv = apr_bucket_split(copy, end64 + 1 - off_last);
225 if (rv != APR_SUCCESS) {
226 apr_brigade_cleanup(bbout);
231 else if (rv != APR_SUCCESS) {
232 apr_brigade_cleanup(bbout);
235 copy = APR_BUCKET_NEXT(copy);
236 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
237 APR_BUCKET_REMOVE(copy);
238 apr_bucket_destroy(copy);
243 e = APR_BUCKET_NEXT(e);
246 AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
247 pos = (apr_uint64_t)pofft;
248 AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
252 typedef struct indexes_t {
257 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
258 apr_bucket_brigade *bb)
260 request_rec *r = f->r;
261 conn_rec *c = r->connection;
263 apr_bucket_brigade *bsend;
264 apr_bucket_brigade *tmpbb;
265 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;
277 indexes = apr_array_make(r->pool, 10, sizeof(indexes_t));
279 /* Iterate through the brigade until reaching EOS or a bucket with
281 for (e = APR_BRIGADE_FIRST(bb);
282 (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
283 && e->length != (apr_size_t)-1);
284 e = APR_BUCKET_NEXT(e)) {
285 clength += e->length;
288 /* Don't attempt to do byte range work if this brigade doesn't
289 * contain an EOS, or if any of the buckets has an unknown length;
290 * this avoids the cases where it is expensive to perform
291 * byteranging (i.e. may require arbitrary amounts of memory). */
292 if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
293 ap_remove_output_filter(f);
294 return ap_pass_brigade(f->next, bb);
297 num_ranges = ap_set_byterange(r, clength, indexes);
299 /* We have nothing to do, get out of the way. */
300 if (num_ranges == 0) {
301 ap_remove_output_filter(f);
302 return ap_pass_brigade(f->next, bb);
305 if (num_ranges > 1) {
306 /* Is ap_make_content_type required here? */
307 const char *orig_ct = ap_make_content_type(r, r->content_type);
308 boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
309 (apr_uint64_t)r->request_time, (long) getpid());
311 ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
312 use_range_x(r) ? "/x-" : "/",
313 "byteranges; boundary=",
317 bound_head = apr_pstrcat(r->pool,
319 CRLF "Content-type: ",
321 CRLF "Content-range: bytes ",
325 /* if we have no type for the content, do our best */
326 bound_head = apr_pstrcat(r->pool,
328 CRLF "Content-range: bytes ",
331 ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
334 /* this brigade holds what we will be sending */
335 bsend = apr_brigade_create(r->pool, c->bucket_alloc);
336 tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
338 idx = (indexes_t *)indexes->elts;
339 for (i = 0; i < indexes->nelts; i++, idx++) {
340 range_start = idx->start;
341 range_end = idx->end;
343 rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
344 if (rv != APR_SUCCESS ) {
345 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
346 "copy_brigade_range() failed [%" APR_OFF_T_FMT
347 "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
348 range_start, range_end, clength);
353 /* For single range requests, we must produce Content-Range header.
354 * Otherwise, we need to produce the multipart boundaries.
356 if (num_ranges == 1) {
357 apr_table_setn(r->headers_out, "Content-Range",
358 apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
359 range_start, range_end, clength));
364 e = apr_bucket_pool_create(bound_head, strlen(bound_head),
365 r->pool, c->bucket_alloc);
366 APR_BRIGADE_INSERT_TAIL(bsend, e);
368 ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
369 range_start, range_end, clength);
370 ap_xlate_proto_to_ascii(ts, strlen(ts));
371 e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
373 APR_BRIGADE_INSERT_TAIL(bsend, e);
376 APR_BRIGADE_CONCAT(bsend, tmpbb);
380 ap_remove_output_filter(f);
382 /* bsend is assumed to be empty if we get here. */
383 e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
384 r->pool, c->bucket_alloc);
385 APR_BRIGADE_INSERT_TAIL(bsend, e);
386 e = apr_bucket_eos_create(c->bucket_alloc);
387 APR_BRIGADE_INSERT_TAIL(bsend, e);
388 return ap_pass_brigade(f->next, bsend);
391 if (num_ranges > 1) {
394 /* add the final boundary */
395 end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL);
396 ap_xlate_proto_to_ascii(end, strlen(end));
397 e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
398 APR_BRIGADE_INSERT_TAIL(bsend, e);
401 e = apr_bucket_eos_create(c->bucket_alloc);
402 APR_BRIGADE_INSERT_TAIL(bsend, e);
404 /* we're done with the original content - all of our data is in bsend. */
405 apr_brigade_cleanup(bb);
406 apr_brigade_destroy(tmpbb);
408 /* send our multipart output */
409 return ap_pass_brigade(f->next, bsend);
412 static int ap_set_byterange(request_rec *r, apr_off_t clength,
413 apr_array_header_t *indexes)
415 const char *range, *or;
416 const char *if_range;
420 apr_array_header_t *merged;
422 apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
425 int overlaps = 0, reversals = 0;
427 if (r->assbackwards) {
431 /* Check for Range request-header (HTTP/1.1) or Request-Range for
432 * backwards-compatibility with second-draft Luotonen/Franks
433 * byte-ranges (e.g. Netscape Navigator 2-3).
435 * We support this form, with Request-Range, and (farther down) we
436 * send multipart/x-byteranges instead of multipart/byteranges for
437 * Request-Range based requests to work around a bug in Netscape
438 * Navigator 2-3 and MSIE 3.
441 if (!(range = apr_table_get(r->headers_in, "Range"))) {
442 range = apr_table_get(r->headers_in, "Request-Range");
445 if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
449 /* is content already a single range? */
450 if (apr_table_get(r->headers_out, "Content-Range")) {
454 /* is content already a multiple range? */
455 if ((ct = apr_table_get(r->headers_out, "Content-Type"))
456 && (!strncasecmp(ct, "multipart/byteranges", 20)
457 || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
461 /* Check the If-Range header for Etag or Date.
462 * Note that this check will return false (as required) if either
463 * of the two etags are weak.
465 if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
466 if (if_range[0] == '"') {
467 if (!(match = apr_table_get(r->headers_out, "Etag"))
468 || (strcmp(if_range, match) != 0)) {
472 else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
473 || (strcmp(if_range, match) != 0)) {
479 or = apr_pstrdup(r->pool, range);
480 merged = apr_array_make(r->pool, 10, sizeof(char *));;
481 while ((cur = ap_getword(r->pool, &range, ','))) {
484 apr_off_t number, start, end;
486 if (!(dash = strchr(cur, '-'))) {
491 /* In the form "-5" */
492 if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
495 start = clength - number;
500 if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
505 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
518 if (end >= clength) {
535 AP_DEBUG_ASSERT((start <= end) && (ostart <= oend));
537 if (start-1 < oend) {
538 if (start < ostart) {
543 else if (start < oend || start == ostart) {
546 if (end >= oend && (start-1) <= oend) {
550 else if (end > ostart && end <= oend) {
558 new = (char **)apr_array_push(merged);
559 *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
561 idx = (indexes_t *)apr_array_push(indexes);
564 sum_lengths += oend - ostart + 1;
574 new = (char **)apr_array_push(merged);
575 *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
577 idx = (indexes_t *)apr_array_push(indexes);
580 sum_lengths += oend - ostart + 1;
583 if (sum_lengths >= clength) {
584 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
585 "Sum of ranges not smaller than file, ignoring.");
589 r->status = HTTP_PARTIAL_CONTENT;
590 r->range = apr_array_pstrcat(r->pool, merged, ',');
591 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
592 "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
593 or, r->range, overlaps, reversals, clength);