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 parse_byterange(char *range, apr_off_t clength,
65 apr_off_t *start, apr_off_t *end)
67 char *dash = strchr(range, '-');
76 /* In the form "-5" */
77 if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
80 *start = clength - number;
85 if (apr_strtoff(&number, range, &errp, 10) || *errp) {
90 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
104 if (*end >= clength) {
112 return (*start > 0 || *end < clength);
115 static int ap_set_byterange(request_rec *r);
117 typedef struct byterange_ctx {
118 apr_bucket_brigade *bb;
125 * Here we try to be compatible with clients that want multipart/x-byteranges
126 * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
127 * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
128 * that the browser supports an older protocol. We also check User-Agent
129 * for Microsoft Internet Explorer 3, which needs this as well.
131 static int use_range_x(request_rec *r)
134 return (apr_table_get(r->headers_in, "Request-Range")
135 || ((ua = apr_table_get(r->headers_in, "User-Agent"))
136 && ap_strstr_c(ua, "MSIE 3")));
139 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
140 #define PARTITION_ERR_FMT "apr_brigade_partition() failed " \
141 "[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]"
143 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
144 apr_bucket_brigade *bbout,
148 apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
149 apr_uint64_t pos = 0, off_first = 0, off_last = 0;
153 apr_uint64_t start64, end64;
157 * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
158 * See the comments in apr_brigade_partition why.
159 * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
161 start64 = (apr_uint64_t)start;
162 end64 = (apr_uint64_t)end;
164 if (start < 0 || end < 0 || start64 > end64)
167 for (e = APR_BRIGADE_FIRST(bb);
168 e != APR_BRIGADE_SENTINEL(bb);
169 e = APR_BUCKET_NEXT(e))
172 /* we know that no bucket has undefined length (-1) */
173 AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
174 elen64 = (apr_uint64_t)e->length;
175 if (!first && (elen64 + pos > start64)) {
179 if (elen64 + pos > end64) {
193 AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
194 rv = apr_bucket_copy(e, ©);
195 if (rv != APR_SUCCESS) {
196 apr_brigade_cleanup(bbout);
200 APR_BRIGADE_INSERT_TAIL(bbout, copy);
202 if (off_first != start64) {
203 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
204 if (rv == APR_ENOTIMPL) {
205 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
206 if (rv != APR_SUCCESS) {
207 apr_brigade_cleanup(bbout);
211 * The read above might have morphed copy in a bucket
212 * of shorter length. So read and delete until we reached
213 * the correct bucket for splitting.
215 while (start64 - off_first > (apr_uint64_t)copy->length) {
218 tmp = APR_BUCKET_NEXT(copy);
219 off_first += (apr_uint64_t)copy->length;
220 APR_BUCKET_REMOVE(copy);
221 apr_bucket_destroy(copy);
223 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
224 if (rv != APR_SUCCESS) {
225 apr_brigade_cleanup(bbout);
229 if (start64 > off_first) {
230 rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
231 if (rv != APR_SUCCESS) {
232 apr_brigade_cleanup(bbout);
237 copy = APR_BUCKET_PREV(copy);
240 else if (rv != APR_SUCCESS) {
241 apr_brigade_cleanup(bbout);
244 out_first = APR_BUCKET_NEXT(copy);
245 APR_BUCKET_REMOVE(copy);
246 apr_bucket_destroy(copy);
254 off_last += start64 - off_first;
258 APR_BRIGADE_INSERT_TAIL(bbout, copy);
260 if (end64 - off_last != (apr_uint64_t)e->length) {
261 rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
262 if (rv == APR_ENOTIMPL) {
263 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
264 if (rv != APR_SUCCESS) {
265 apr_brigade_cleanup(bbout);
269 * The read above might have morphed copy in a bucket
270 * of shorter length. So read until we reached
271 * the correct bucket for splitting.
273 while (end64 + 1 - off_last > (apr_uint64_t)copy->length) {
274 off_last += (apr_uint64_t)copy->length;
275 copy = APR_BUCKET_NEXT(copy);
276 rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
277 if (rv != APR_SUCCESS) {
278 apr_brigade_cleanup(bbout);
282 if (end64 < off_last + (apr_uint64_t)copy->length - 1) {
283 rv = apr_bucket_split(copy, end64 + 1 - off_last);
284 if (rv != APR_SUCCESS) {
285 apr_brigade_cleanup(bbout);
290 else if (rv != APR_SUCCESS) {
291 apr_brigade_cleanup(bbout);
294 copy = APR_BUCKET_NEXT(copy);
295 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
296 APR_BUCKET_REMOVE(copy);
297 apr_bucket_destroy(copy);
302 e = APR_BUCKET_NEXT(e);
305 AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
306 pos = (apr_uint64_t)pofft;
307 AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
311 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
312 apr_bucket_brigade *bb)
314 #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
315 request_rec *r = f->r;
316 conn_rec *c = r->connection;
319 apr_bucket_brigade *bsend;
320 apr_bucket_brigade *tmpbb;
321 apr_off_t range_start;
324 apr_off_t clength = 0;
329 /* Iterate through the brigade until reaching EOS or a bucket with
331 for (e = APR_BRIGADE_FIRST(bb);
332 (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
333 && e->length != (apr_size_t)-1);
334 e = APR_BUCKET_NEXT(e)) {
335 clength += e->length;
338 /* Don't attempt to do byte range work if this brigade doesn't
339 * contain an EOS, or if any of the buckets has an unknown length;
340 * this avoids the cases where it is expensive to perform
341 * byteranging (i.e. may require arbitrary amounts of memory). */
342 if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
343 ap_remove_output_filter(f);
344 return ap_pass_brigade(f->next, bb);
347 num_ranges = ap_set_byterange(r);
349 /* We have nothing to do, get out of the way. */
350 if (num_ranges == 0) {
351 ap_remove_output_filter(f);
352 return ap_pass_brigade(f->next, bb);
355 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
356 ctx->num_ranges = num_ranges;
357 /* create a brigade in case we never call ap_save_brigade() */
358 ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
360 if (ctx->num_ranges > 1) {
361 /* Is ap_make_content_type required here? */
362 const char *orig_ct = ap_make_content_type(r, r->content_type);
363 ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
364 (apr_uint64_t)r->request_time, (long) getpid());
366 ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
367 use_range_x(r) ? "/x-" : "/",
368 "byteranges; boundary=",
369 ctx->boundary, NULL));
372 ctx->bound_head = apr_pstrcat(r->pool,
373 CRLF "--", ctx->boundary,
374 CRLF "Content-type: ",
376 CRLF "Content-range: bytes ",
380 /* if we have no type for the content, do our best */
381 ctx->bound_head = apr_pstrcat(r->pool,
382 CRLF "--", ctx->boundary,
383 CRLF "Content-range: bytes ",
386 ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
389 /* this brigade holds what we will be sending */
390 bsend = apr_brigade_create(r->pool, c->bucket_alloc);
391 tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
393 while ((current = ap_getword(r->pool, &r->range, ','))
394 && (rv = parse_byterange(current, clength, &range_start,
400 rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
401 if (rv != APR_SUCCESS ) {
402 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
403 "brigade_copy_range() failed " "[%" APR_OFF_T_FMT
404 "-%" APR_OFF_T_FMT ",%"
406 range_start, range_end, clength);
411 /* For single range requests, we must produce Content-Range header.
412 * Otherwise, we need to produce the multipart boundaries.
414 if (ctx->num_ranges == 1) {
415 apr_table_setn(r->headers_out, "Content-Range",
416 apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
417 range_start, range_end, clength));
422 e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
423 r->pool, c->bucket_alloc);
424 APR_BRIGADE_INSERT_TAIL(bsend, e);
426 ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
427 range_start, range_end, clength);
428 ap_xlate_proto_to_ascii(ts, strlen(ts));
429 e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
431 APR_BRIGADE_INSERT_TAIL(bsend, e);
434 APR_BRIGADE_CONCAT(bsend, tmpbb);
438 ap_remove_output_filter(f);
440 /* bsend is assumed to be empty if we get here. */
441 e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
442 r->pool, c->bucket_alloc);
443 APR_BRIGADE_INSERT_TAIL(bsend, e);
444 e = apr_bucket_eos_create(c->bucket_alloc);
445 APR_BRIGADE_INSERT_TAIL(bsend, e);
446 return ap_pass_brigade(f->next, bsend);
449 if (ctx->num_ranges > 1) {
452 /* add the final boundary */
453 end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
454 ap_xlate_proto_to_ascii(end, strlen(end));
455 e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
456 APR_BRIGADE_INSERT_TAIL(bsend, e);
459 e = apr_bucket_eos_create(c->bucket_alloc);
460 APR_BRIGADE_INSERT_TAIL(bsend, e);
462 /* we're done with the original content - all of our data is in bsend. */
463 apr_brigade_cleanup(bb);
464 apr_brigade_destroy(tmpbb);
466 /* send our multipart output */
467 return ap_pass_brigade(f->next, bsend);
470 static int ap_set_byterange(request_rec *r)
472 const char *range, *or;
473 const char *if_range;
478 apr_off_t ostart, oend;
481 if (r->assbackwards) {
485 /* Check for Range request-header (HTTP/1.1) or Request-Range for
486 * backwards-compatibility with second-draft Luotonen/Franks
487 * byte-ranges (e.g. Netscape Navigator 2-3).
489 * We support this form, with Request-Range, and (farther down) we
490 * send multipart/x-byteranges instead of multipart/byteranges for
491 * Request-Range based requests to work around a bug in Netscape
492 * Navigator 2-3 and MSIE 3.
495 if (!(range = apr_table_get(r->headers_in, "Range"))) {
496 range = apr_table_get(r->headers_in, "Request-Range");
499 if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
503 /* is content already a single range? */
504 if (apr_table_get(r->headers_out, "Content-Range")) {
508 /* is content already a multiple range? */
509 if ((ct = apr_table_get(r->headers_out, "Content-Type"))
510 && (!strncasecmp(ct, "multipart/byteranges", 20)
511 || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
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 if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
520 if (if_range[0] == '"') {
521 if (!(match = apr_table_get(r->headers_out, "Etag"))
522 || (strcmp(if_range, match) != 0)) {
526 else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
527 || (strcmp(if_range, match) != 0)) {
533 or = apr_pstrdup(r->pool, range);
534 merged = apr_pstrdup(r->pool, "");
535 while ((cur = ap_getword(r->pool, &range, ','))) {
538 apr_off_t number, start, end;
540 if (!(dash = strchr(cur, '-'))) {
545 /* In the form "-5"... leave as is */
546 merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), cur, NULL);
552 merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), cur, "-", NULL);
556 * we have #-#, so we need to grab them... we don't bother
557 * doing this for the #- or -# cases for speed reasons.
558 * After all, those will be fixed when the filter parses
561 if (apr_strtoff(&number, cur, &errp, 10) || *errp || number < 0) {
565 if (apr_strtoff(&number, dash, &errp, 10) || *errp || number <= 0) {
574 if (start <= ostart) {
578 if (start > ostart && start < oend) {
581 if ((end-1) >= oend) {
585 if (end > ostart && end < oend) {
591 char *nr = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
593 merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), nr, NULL);
598 char *nr = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
600 merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), nr, NULL);
603 r->status = HTTP_PARTIAL_CONTENT;
605 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
606 "Range: %s | %s", or, merged);