]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
* Fix error message
[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 #if APR_HAVE_PROCESS_H
24 #include <process.h>            /* for getpid() on Win32 */
25 #endif
26
27 #include "apr_strings.h"
28 #include "apr_buckets.h"
29 #include "apr_lib.h"
30 #include "apr_signal.h"
31
32 #define APR_WANT_STDIO          /* for sscanf */
33 #define APR_WANT_STRFUNC
34 #define APR_WANT_MEMFUNC
35 #include "apr_want.h"
36
37 #include "util_filter.h"
38 #include "ap_config.h"
39 #include "httpd.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
47                                  * support code... */
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"
52
53 #include "mod_core.h"
54
55 #if APR_HAVE_STDARG_H
56 #include <stdarg.h>
57 #endif
58 #if APR_HAVE_UNISTD_H
59 #include <unistd.h>
60 #endif
61
62 #ifndef DEFAULT_MAX_RANGES
63 #define DEFAULT_MAX_RANGES 200
64 #endif
65
66 APLOG_USE_MODULE(http);
67
68 static int ap_set_byterange(request_rec *r, apr_off_t clength,
69                             apr_array_header_t **indexes);
70
71 /*
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.
77  */
78 static int use_range_x(request_rec *r)
79 {
80     const char *ua;
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")));
84 }
85
86 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
87 #define MAX_PREALLOC_RANGES 100
88
89 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
90                                        apr_bucket_brigade *bbout,
91                                        apr_off_t start,
92                                        apr_off_t end)
93 {
94     apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
95     apr_uint64_t pos = 0, off_first = 0, off_last = 0;
96     apr_status_t rv;
97     const char *s;
98     apr_size_t len;
99     apr_uint64_t start64, end64;
100     apr_off_t pofft = 0;
101
102     /*
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.
106      */
107     start64 = (apr_uint64_t)start;
108     end64 = (apr_uint64_t)end;
109
110     if (start < 0 || end < 0 || start64 > end64)
111         return APR_EINVAL;
112
113     for (e = APR_BRIGADE_FIRST(bb);
114          e != APR_BRIGADE_SENTINEL(bb);
115          e = APR_BUCKET_NEXT(e))
116     {
117         apr_uint64_t elen64;
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)) {
122             first = e;
123             off_first = pos;
124         }
125         if (elen64 + pos > end64) {
126             last = e;
127             off_last = pos;
128             break;
129         }
130         pos += elen64;
131     }
132     if (!first || !last)
133         return APR_EINVAL;
134
135     e = first;
136     while (1)
137     {
138         apr_bucket *copy;
139         AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
140         rv = apr_bucket_copy(e, &copy);
141         if (rv != APR_SUCCESS) {
142             apr_brigade_cleanup(bbout);
143             return rv;
144         }
145
146         APR_BRIGADE_INSERT_TAIL(bbout, copy);
147         if (e == first) {
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);
154                         return rv;
155                     }
156                     /*
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.
160                      */
161                     while (start64 - off_first > (apr_uint64_t)copy->length) {
162                         apr_bucket *tmp = APR_BUCKET_NEXT(copy);
163                         off_first += (apr_uint64_t)copy->length;
164                         APR_BUCKET_REMOVE(copy);
165                         apr_bucket_destroy(copy);
166                         copy = tmp;
167                         rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
168                         if (rv != APR_SUCCESS) {
169                             apr_brigade_cleanup(bbout);
170                             return rv;
171                         }
172                     }
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);
177                             return rv;
178                         }
179                     }
180                     else {
181                         copy = APR_BUCKET_PREV(copy);
182                     }
183                 }
184                 else if (rv != APR_SUCCESS) {
185                         apr_brigade_cleanup(bbout);
186                         return rv;
187                 }
188                 out_first = APR_BUCKET_NEXT(copy);
189                 APR_BUCKET_REMOVE(copy);
190                 apr_bucket_destroy(copy);
191             }
192             else {
193                 out_first = copy;
194             }
195         }
196         if (e == last) {
197             if (e == first) {
198                 off_last += start64 - off_first;
199                 copy = out_first;
200             }
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);
207                         return rv;
208                     }
209                     /*
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.
213                      */
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);
220                             return rv;
221                         }
222                     }
223                     if (end64 < off_last + (apr_uint64_t)copy->length - 1) {
224                         rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
225                         if (rv != APR_SUCCESS) {
226                             apr_brigade_cleanup(bbout);
227                             return rv;
228                         }
229                     }
230                 }
231                 else if (rv != APR_SUCCESS) {
232                         apr_brigade_cleanup(bbout);
233                         return rv;
234                 }
235                 copy = APR_BUCKET_NEXT(copy);
236                 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
237                     APR_BUCKET_REMOVE(copy);
238                     apr_bucket_destroy(copy);
239                 }
240             }
241             break;
242         }
243         e = APR_BUCKET_NEXT(e);
244     }
245
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);
249     return APR_SUCCESS;
250 }
251
252 typedef struct indexes_t {
253     apr_off_t start;
254     apr_off_t end;
255 } indexes_t;
256
257 static int get_max_ranges(request_rec *r) { 
258     core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
259     return core_conf->max_ranges == -1 ? DEFAULT_MAX_RANGES : core_conf->max_ranges;
260 }
261
262 static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb)
263 {
264     apr_bucket *e;
265     conn_rec *c = f->r->connection;
266     ap_remove_output_filter(f);
267     f->r->status = HTTP_OK;
268     e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
269                                f->r->pool, c->bucket_alloc);
270     APR_BRIGADE_INSERT_TAIL(tmpbb, e);
271     e = apr_bucket_eos_create(c->bucket_alloc);
272     APR_BRIGADE_INSERT_TAIL(tmpbb, e);
273     return ap_pass_brigade(f->next, tmpbb);
274 }
275
276 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
277                                                          apr_bucket_brigade *bb)
278 {
279     request_rec *r = f->r;
280     conn_rec *c = r->connection;
281     apr_bucket *e;
282     apr_bucket_brigade *bsend;
283     apr_bucket_brigade *tmpbb;
284     apr_off_t range_start;
285     apr_off_t range_end;
286     apr_off_t clength = 0;
287     apr_status_t rv;
288     int found = 0;
289     int num_ranges;
290     char *boundary = NULL;
291     char *bound_head = NULL;
292     apr_array_header_t *indexes;
293     indexes_t *idx;
294     int i;
295     int original_status;
296     int max_ranges = get_max_ranges(r);
297
298     /*
299      * Iterate through the brigade until reaching EOS or a bucket with
300      * unknown length.
301      */
302     for (e = APR_BRIGADE_FIRST(bb);
303          (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
304           && e->length != (apr_size_t)-1);
305          e = APR_BUCKET_NEXT(e)) {
306         clength += e->length;
307     }
308
309     /*
310      * Don't attempt to do byte range work if this brigade doesn't
311      * contain an EOS, or if any of the buckets has an unknown length;
312      * this avoids the cases where it is expensive to perform
313      * byteranging (i.e. may require arbitrary amounts of memory).
314      */
315     if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
316         ap_remove_output_filter(f);
317         return ap_pass_brigade(f->next, bb);
318     }
319
320     original_status = r->status;
321     num_ranges = ap_set_byterange(r, clength, &indexes);
322
323     /* We have nothing to do, get out of the way. */
324     if (num_ranges == 0 || (max_ranges > 0 && num_ranges > max_ranges)) {
325         r->status = original_status;
326         ap_remove_output_filter(f);
327         return ap_pass_brigade(f->next, bb);
328     }
329
330     /* this brigade holds what we will be sending */
331     bsend = apr_brigade_create(r->pool, c->bucket_alloc);
332
333     if (num_ranges < 0)
334         return send_416(f, bsend);
335
336     if (num_ranges > 1) {
337         /* Is ap_make_content_type required here? */
338         const char *orig_ct = ap_make_content_type(r, r->content_type);
339         boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
340                                 (apr_uint64_t)r->request_time, (long) getpid());
341
342         ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
343                                            use_range_x(r) ? "/x-" : "/",
344                                            "byteranges; boundary=",
345                                            boundary, NULL));
346
347         if (orig_ct) {
348             bound_head = apr_pstrcat(r->pool,
349                                      CRLF "--", boundary,
350                                      CRLF "Content-type: ",
351                                      orig_ct,
352                                      CRLF "Content-range: bytes ",
353                                      NULL);
354         }
355         else {
356             /* if we have no type for the content, do our best */
357             bound_head = apr_pstrcat(r->pool,
358                                      CRLF "--", boundary,
359                                      CRLF "Content-range: bytes ",
360                                      NULL);
361         }
362         ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
363     }
364
365     tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
366
367     idx = (indexes_t *)indexes->elts;
368     for (i = 0; i < indexes->nelts; i++, idx++) {
369         range_start = idx->start;
370         range_end = idx->end;
371
372         rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
373         if (rv != APR_SUCCESS ) {
374             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
375                           "copy_brigade_range() failed [%" APR_OFF_T_FMT
376                           "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
377                           range_start, range_end, clength);
378             continue;
379         }
380         found = 1;
381
382         /*
383          * For single range requests, we must produce Content-Range header.
384          * Otherwise, we need to produce the multipart boundaries.
385          */
386         if (num_ranges == 1) {
387             apr_table_setn(r->headers_out, "Content-Range",
388                            apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
389                                         range_start, range_end, clength));
390         }
391         else {
392             char *ts;
393
394             e = apr_bucket_pool_create(bound_head, strlen(bound_head),
395                                        r->pool, c->bucket_alloc);
396             APR_BRIGADE_INSERT_TAIL(bsend, e);
397
398             ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
399                               range_start, range_end, clength);
400             ap_xlate_proto_to_ascii(ts, strlen(ts));
401             e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
402                                        c->bucket_alloc);
403             APR_BRIGADE_INSERT_TAIL(bsend, e);
404         }
405
406         APR_BRIGADE_CONCAT(bsend, tmpbb);
407         if (i && !(i & 0x1F)) {
408             /*
409              * Every now and then, pass what we have down the filter chain.
410              * In this case, the content-length filter cannot calculate and
411              * set the content length and we must remove any Content-Length
412              * header already present.
413              */
414             apr_table_unset(r->headers_out, "Content-Length");
415             if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
416                 return rv;
417             apr_brigade_cleanup(bsend);
418         }
419     }
420
421     if (found == 0) {
422         /* bsend is assumed to be empty if we get here. */
423         return send_416(f, bsend);
424     }
425
426     if (num_ranges > 1) {
427         char *end;
428
429         /* add the final boundary */
430         end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL);
431         ap_xlate_proto_to_ascii(end, strlen(end));
432         e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
433         APR_BRIGADE_INSERT_TAIL(bsend, e);
434     }
435
436     e = apr_bucket_eos_create(c->bucket_alloc);
437     APR_BRIGADE_INSERT_TAIL(bsend, e);
438
439     /* we're done with the original content - all of our data is in bsend. */
440     apr_brigade_cleanup(bb);
441     apr_brigade_destroy(tmpbb);
442
443     /* send our multipart output */
444     return ap_pass_brigade(f->next, bsend);
445 }
446
447 static int ap_set_byterange(request_rec *r, apr_off_t clength,
448                             apr_array_header_t **indexes)
449 {
450     const char *range;
451     const char *if_range;
452     const char *match;
453     const char *ct;
454     char *cur, **new;
455     apr_array_header_t *merged;
456     int num_ranges = 0, unsatisfiable = 0;
457     apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
458     int in_merge = 0;
459     indexes_t *idx;
460     int overlaps = 0, reversals = 0;
461     int ranges = 1;
462     const char *it;
463
464     if (r->assbackwards) {
465         return 0;
466     }
467
468     /*
469      * Check for Range request-header (HTTP/1.1) or Request-Range for
470      * backwards-compatibility with second-draft Luotonen/Franks
471      * byte-ranges (e.g. Netscape Navigator 2-3).
472      *
473      * We support this form, with Request-Range, and (farther down) we
474      * send multipart/x-byteranges instead of multipart/byteranges for
475      * Request-Range based requests to work around a bug in Netscape
476      * Navigator 2-3 and MSIE 3.
477      */
478
479     if (!(range = apr_table_get(r->headers_in, "Range"))) {
480         range = apr_table_get(r->headers_in, "Request-Range");
481     }
482
483     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
484         return 0;
485     }
486
487     /* is content already a single range? */
488     if (apr_table_get(r->headers_out, "Content-Range")) {
489        return 0;
490     }
491
492     /* is content already a multiple range? */
493     if ((ct = apr_table_get(r->headers_out, "Content-Type"))
494         && (!strncasecmp(ct, "multipart/byteranges", 20)
495             || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
496        return 0;
497     }
498
499     /*
500      * Check the If-Range header for Etag or Date.
501      * Note that this check will return false (as required) if either
502      * of the two etags are weak.
503      */
504     if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
505         if (if_range[0] == '"') {
506             if (!(match = apr_table_get(r->headers_out, "Etag"))
507                 || (strcmp(if_range, match) != 0)) {
508                 return 0;
509             }
510         }
511         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
512                  || (strcmp(if_range, match) != 0)) {
513             return 0;
514         }
515     }
516
517     range += 6;
518     it = range;
519     while (*it) {
520         if (*it++ == ',') {
521             ranges++;
522         }
523     }
524     it = range;
525     if (ranges > MAX_PREALLOC_RANGES) {
526         ranges = MAX_PREALLOC_RANGES;
527     }
528     *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
529     merged = apr_array_make(r->pool, ranges, sizeof(char *));
530     while ((cur = ap_getword(r->pool, &range, ','))) {
531         char *dash;
532         char *errp;
533         apr_off_t number, start, end;
534
535         if (!*cur)
536             break;
537
538         /*
539          * Per RFC 2616 14.35.1: If there is at least one syntactically invalid
540          * byte-range-spec, we must ignore the whole header.
541          */
542
543         if (!(dash = strchr(cur, '-'))) {
544             return 0;
545         }
546
547         if (dash == range) {
548             /* In the form "-5" */
549             if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
550                 return 0;
551             }
552             if (number < 1) {
553                 return 0;
554             }
555             start = clength - number;
556             end = clength - 1;
557         }
558         else {
559             *dash++ = '\0';
560             if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
561                 return 0;
562             }
563             start = number;
564             if (*dash) {
565                 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
566                     return 0;
567                 }
568                 end = number;
569                 if (start > end) {
570                     return 0;
571                 }
572             }
573             else {                  /* "5-" */
574                 end = clength - 1;
575             }
576         }
577
578         if (start < 0) {
579             start = 0;
580         }
581         if (start >= clength) {
582             unsatisfiable = 1;
583             continue;
584         }
585         if (end >= clength) {
586             end = clength - 1;
587         }
588
589         if (!in_merge) {
590             /* new set */
591             ostart = start;
592             oend = end;
593             in_merge = 1;
594             continue;
595         }
596         in_merge = 0;
597
598         if (start >= ostart && end <= oend) {
599             in_merge = 1;
600         }
601
602         if (start < ostart && end >= ostart-1) {
603             ostart = start;
604             reversals++;
605             in_merge = 1;
606         }
607         if (end >= oend && start <= oend+1 ) {
608             oend = end;
609             in_merge = 1;
610         }
611
612         if (in_merge) {
613             overlaps++;
614             continue;
615         } else {
616             new = (char **)apr_array_push(merged);
617             *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
618                                     ostart, oend);
619             idx = (indexes_t *)apr_array_push(*indexes);
620             idx->start = ostart;
621             idx->end = oend;
622             sum_lengths += oend - ostart + 1;
623             /* new set again */
624             in_merge = 1;
625             ostart = start;
626             oend = end;
627             num_ranges++;
628         }
629     }
630
631     if (in_merge) {
632         new = (char **)apr_array_push(merged);
633         *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
634                             ostart, oend);
635         idx = (indexes_t *)apr_array_push(*indexes);
636         idx->start = ostart;
637         idx->end = oend;
638         sum_lengths += oend - ostart + 1;
639         num_ranges++;
640     }
641     else if (num_ranges == 0 && unsatisfiable) {
642         /* If all ranges are unsatisfiable, we should return 416 */
643         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
644                       "All ranges in Range header (%s) are unsatisfiable",
645                       it);
646         return -1;
647     }
648     if (sum_lengths > clength) {
649         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
650                       "Sum of ranges larger than file, ignoring.");
651         return 0;
652     }
653
654     r->status = HTTP_PARTIAL_CONTENT;
655     r->range = apr_array_pstrcat(r->pool, merged, ',');
656     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
657                   "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
658                   it, r->range, overlaps, reversals, clength);
659
660     return num_ranges;
661 }