]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
Cleanup effort in prep for GA push:
[apache] / modules / http / byterange_filter.c
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * byterange_filter.c --- HTTP byterange filter and friends.
19  */
20
21 #include "apr.h"
22
23 #include "apr_strings.h"
24 #include "apr_buckets.h"
25 #include "apr_lib.h"
26 #include "apr_signal.h"
27
28 #define APR_WANT_STDIO          /* for sscanf */
29 #define APR_WANT_STRFUNC
30 #define APR_WANT_MEMFUNC
31 #include "apr_want.h"
32
33 #include "util_filter.h"
34 #include "ap_config.h"
35 #include "httpd.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
43                                  * support code... */
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"
48
49 #include "mod_core.h"
50
51 #if APR_HAVE_STDARG_H
52 #include <stdarg.h>
53 #endif
54 #if APR_HAVE_UNISTD_H
55 #include <unistd.h>
56 #endif
57
58 #ifndef AP_DEFAULT_MAX_RANGES
59 #define AP_DEFAULT_MAX_RANGES 200
60 #endif
61 #ifndef AP_DEFAULT_MAX_OVERLAPS
62 #define AP_DEFAULT_MAX_OVERLAPS 20
63 #endif
64 #ifndef AP_DEFAULT_MAX_REVERSALS
65 #define AP_DEFAULT_MAX_REVERSALS 20
66 #endif
67
68 #define MAX_PREALLOC_RANGES 100
69
70 APLOG_USE_MODULE(http);
71
72 typedef struct indexes_t {
73     apr_off_t start;
74     apr_off_t end;
75 } indexes_t;
76
77 /*
78  * Returns: number of ranges (merged) or -1 for no-good
79  */
80 static int ap_set_byterange(request_rec *r, apr_off_t clength,
81                             apr_array_header_t **indexes,
82                             int *overlaps, int *reversals)
83 {
84     const char *range;
85     const char *if_range;
86     const char *match;
87     const char *ct;
88     char *cur;
89     apr_array_header_t *merged;
90     int num_ranges = 0, unsatisfiable = 0;
91     apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
92     int in_merge = 0;
93     indexes_t *idx;
94     int ranges = 1;
95     int i;
96     const char *it;
97
98     *overlaps = 0;
99     *reversals = 0;
100
101     if (r->assbackwards) {
102         return 0;
103     }
104
105     /*
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).
109      *
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.
114      */
115
116     if (!(range = apr_table_get(r->headers_in, "Range"))) {
117         range = apr_table_get(r->headers_in, "Request-Range");
118     }
119
120     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
121         return 0;
122     }
123
124     /* is content already a single range? */
125     if (apr_table_get(r->headers_out, "Content-Range")) {
126         return 0;
127     }
128
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))) {
133             return 0;
134         }
135
136     /*
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.
140      */
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)) {
145                 return 0;
146             }
147         }
148         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
149                  || (strcmp(if_range, match) != 0)) {
150             return 0;
151         }
152     }
153
154     range += 6;
155     it = range;
156     while (*it) {
157         if (*it++ == ',') {
158             ranges++;
159         }
160     }
161     it = range;
162     if (ranges > MAX_PREALLOC_RANGES) {
163         ranges = MAX_PREALLOC_RANGES;
164     }
165     *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
166     while ((cur = ap_getword(r->pool, &range, ','))) {
167         char *dash;
168         char *errp;
169         apr_off_t number, start, end;
170
171         if (!*cur)
172             break;
173
174         /*
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.
177          */
178
179         if (!(dash = strchr(cur, '-'))) {
180             return 0;
181         }
182
183         if (dash == cur) {
184             /* In the form "-5" */
185             if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
186                 return 0;
187             }
188             if (number < 1) {
189                 return 0;
190             }
191             start = clength - number;
192             end = clength - 1;
193         }
194         else {
195             *dash++ = '\0';
196             if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
197                 return 0;
198             }
199             start = number;
200             if (*dash) {
201                 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
202                     return 0;
203                 }
204                 end = number;
205                 if (start > end) {
206                     return 0;
207                 }
208             }
209             else {                  /* "5-" */
210                 end = clength - 1;
211             }
212         }
213
214         if (start < 0) {
215             start = 0;
216         }
217         if (start >= clength) {
218             unsatisfiable = 1;
219             continue;
220         }
221         if (end >= clength) {
222             end = clength - 1;
223         }
224
225         if (!in_merge) {
226             /* new set */
227             ostart = start;
228             oend = end;
229             in_merge = 1;
230             continue;
231         }
232         in_merge = 0;
233
234         if (start >= ostart && end <= oend) {
235             in_merge = 1;
236         }
237
238         if (start < ostart && end >= ostart-1) {
239             ostart = start;
240             ++*reversals;
241             in_merge = 1;
242         }
243         if (end >= oend && start <= oend+1 ) {
244             oend = end;
245             in_merge = 1;
246         }
247
248         if (in_merge) {
249             ++*overlaps;
250             continue;
251         } else {
252             idx = (indexes_t *)apr_array_push(*indexes);
253             idx->start = ostart;
254             idx->end = oend;
255             sum_lengths += oend - ostart + 1;
256             /* new set again */
257             in_merge = 1;
258             ostart = start;
259             oend = end;
260             num_ranges++;
261         }
262     }
263
264     if (in_merge) {
265         idx = (indexes_t *)apr_array_push(*indexes);
266         idx->start = ostart;
267         idx->end = oend;
268         sum_lengths += oend - ostart + 1;
269         num_ranges++;
270     }
271     else if (num_ranges == 0 && unsatisfiable) {
272         /* If all ranges are unsatisfiable, we should return 416 */
273         return -1;
274     }
275     if (sum_lengths >= clength) {
276         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
277                       "Sum of ranges not smaller than file, ignoring.");
278         return 0;
279     }
280
281     /*
282      * create the merged table now, now that we know we need it
283      */
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);
290     }
291
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);
297
298     return num_ranges;
299 }
300
301 /*
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.
307  */
308 static int use_range_x(request_rec *r)
309 {
310     const char *ua;
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")));
314 }
315
316 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
317
318 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
319                                        apr_bucket_brigade *bbout,
320                                        apr_off_t start,
321                                        apr_off_t end)
322 {
323     apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
324     apr_uint64_t pos = 0, off_first = 0, off_last = 0;
325     apr_status_t rv;
326     apr_uint64_t start64, end64;
327     apr_off_t pofft = 0;
328
329     /*
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.
333      */
334     start64 = (apr_uint64_t)start;
335     end64 = (apr_uint64_t)end;
336
337     if (start < 0 || end < 0 || start64 > end64)
338         return APR_EINVAL;
339
340     for (e = APR_BRIGADE_FIRST(bb);
341          e != APR_BRIGADE_SENTINEL(bb);
342          e = APR_BUCKET_NEXT(e))
343     {
344         apr_uint64_t elen64;
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)) {
349             first = e;
350             off_first = pos;
351         }
352         if (elen64 + pos > end64) {
353             last = e;
354             off_last = pos;
355             break;
356         }
357         pos += elen64;
358     }
359     if (!first || !last)
360         return APR_EINVAL;
361
362     e = first;
363     while (1)
364     {
365         apr_bucket *copy;
366         AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
367         rv = apr_bucket_copy(e, &copy);
368         if (rv != APR_SUCCESS) {
369             apr_brigade_cleanup(bbout);
370             return rv;
371         }
372
373         APR_BRIGADE_INSERT_TAIL(bbout, copy);
374         if (e == first) {
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);
379                     return rv;
380                 }
381                 out_first = APR_BUCKET_NEXT(copy);
382                 APR_BUCKET_REMOVE(copy);
383                 apr_bucket_destroy(copy);
384             }
385             else {
386                 out_first = copy;
387             }
388         }
389         if (e == last) {
390             if (e == first) {
391                 off_last += start64 - off_first;
392                 copy = out_first;
393             }
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);
398                     return rv;
399                 }
400                 copy = APR_BUCKET_NEXT(copy);
401                 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
402                     APR_BUCKET_REMOVE(copy);
403                     apr_bucket_destroy(copy);
404                 }
405             }
406             break;
407         }
408         e = APR_BUCKET_NEXT(e);
409     }
410
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);
414     return APR_SUCCESS;
415 }
416
417 static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb)
418 {
419     apr_bucket *e;
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);
429 }
430
431 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
432                                                          apr_bucket_brigade *bb)
433 {
434     request_rec *r = f->r;
435     conn_rec *c = r->connection;
436     apr_bucket *e;
437     apr_bucket_brigade *bsend;
438     apr_bucket_brigade *tmpbb;
439     apr_off_t range_start;
440     apr_off_t range_end;
441     apr_off_t clength = 0;
442     apr_status_t rv;
443     int found = 0;
444     int num_ranges;
445     char *bound_head = NULL;
446     apr_array_header_t *indexes;
447     indexes_t *idx;
448     int i;
449     int original_status;
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);
453
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 );
463     /*
464      * Iterate through the brigade until reaching EOS or a bucket with
465      * unknown length.
466      */
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;
472     }
473
474     /*
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).
479      */
480     if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
481         ap_remove_output_filter(f);
482         return ap_pass_brigade(f->next, bb);
483     }
484
485     original_status = r->status;
486     num_ranges = ap_set_byterange(r, clength, &indexes, &overlaps, &reversals);
487
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);
496     }
497
498     /* this brigade holds what we will be sending */
499     bsend = apr_brigade_create(r->pool, c->bucket_alloc);
500
501     if (num_ranges < 0)
502         return send_416(f, bsend);
503
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);
507
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));
512
513         if (orig_ct) {
514             bound_head = apr_pstrcat(r->pool,
515                                      CRLF "--", ap_multipart_boundary,
516                                      CRLF "Content-type: ",
517                                      orig_ct,
518                                      CRLF "Content-range: bytes ",
519                                      NULL);
520         }
521         else {
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 ",
526                                      NULL);
527         }
528         ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
529     }
530
531     tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
532
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;
537
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);
544             continue;
545         }
546         found = 1;
547
548         /*
549          * For single range requests, we must produce Content-Range header.
550          * Otherwise, we need to produce the multipart boundaries.
551          */
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));
556         }
557         else {
558             char *ts;
559
560             e = apr_bucket_pool_create(bound_head, strlen(bound_head),
561                                        r->pool, c->bucket_alloc);
562             APR_BRIGADE_INSERT_TAIL(bsend, e);
563
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,
568                                        c->bucket_alloc);
569             APR_BRIGADE_INSERT_TAIL(bsend, e);
570         }
571
572         APR_BRIGADE_CONCAT(bsend, tmpbb);
573         if (i && !(i & 0x1F)) {
574             /*
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.
579              */
580             apr_table_unset(r->headers_out, "Content-Length");
581             if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
582                 return rv;
583             apr_brigade_cleanup(bsend);
584         }
585     }
586
587     if (found == 0) {
588         /* bsend is assumed to be empty if we get here. */
589         return send_416(f, bsend);
590     }
591
592     if (num_ranges > 1) {
593         char *end;
594
595         /* add the final boundary */
596         end = apr_pstrcat(r->pool, CRLF "--", ap_multipart_boundary, "--" CRLF,
597                           NULL);
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);
601     }
602
603     e = apr_bucket_eos_create(c->bucket_alloc);
604     APR_BRIGADE_INSERT_TAIL(bsend, e);
605
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);
609
610     /* send our multipart output */
611     return ap_pass_brigade(f->next, bsend);
612 }