]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
Merge in byteranges
[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 APLOG_USE_MODULE(http);
63
64 static int parse_byterange(char *range, apr_off_t clength,
65                            apr_off_t *start, apr_off_t *end)
66 {
67     char *dash = strchr(range, '-');
68     char *errp;
69     apr_off_t number;
70
71     if (!dash) {
72         return 0;
73     }
74
75     if (dash == range) {
76         /* In the form "-5" */
77         if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
78             return 0;
79         }
80         *start = clength - number;
81         *end = clength - 1;
82     }
83     else {
84         *dash++ = '\0';
85         if (apr_strtoff(&number, range, &errp, 10) || *errp) {
86             return 0;
87         }
88         *start = number;
89         if (*dash) {
90             if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
91                 return 0;
92             }
93             *end = number;
94         }
95         else {                  /* "5-" */
96             *end = clength - 1;
97         }
98     }
99
100     if (*start < 0) {
101         *start = 0;
102     }
103
104     if (*end >= clength) {
105         *end = clength - 1;
106     }
107
108     if (*start > *end) {
109         return -1;
110     }
111
112     return (*start > 0 || *end < clength);
113 }
114
115 static int ap_set_byterange(request_rec *r);
116
117 typedef struct byterange_ctx {
118     apr_bucket_brigade *bb;
119     int num_ranges;
120     char *boundary;
121     char *bound_head;
122 } byterange_ctx;
123
124 /*
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.
130  */
131 static int use_range_x(request_rec *r)
132 {
133     const char *ua;
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")));
137 }
138
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 "]"
142
143 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
144                                        apr_bucket_brigade *bbout,
145                                        apr_off_t start,
146                                        apr_off_t end)
147 {
148     apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
149     apr_uint64_t pos = 0, off_first = 0, off_last = 0;
150     apr_status_t rv;
151     const char *s;
152     apr_size_t len;
153     apr_uint64_t start64, end64;
154     apr_off_t pofft;
155
156     /*
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.
160      */
161     start64 = (apr_uint64_t)start;
162     end64 = (apr_uint64_t)end;
163
164     if (start < 0 || end < 0 || start64 > end64)
165         return APR_EINVAL;
166
167     for (e = APR_BRIGADE_FIRST(bb);
168          e != APR_BRIGADE_SENTINEL(bb);
169          e = APR_BUCKET_NEXT(e))
170     {
171         apr_uint64_t elen64;
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)) {
176             first = e;
177             off_first = pos;
178         }
179         if (elen64 + pos > end64) {
180             last = e;
181             off_last = pos;
182             break;
183         }
184         pos += elen64;
185     }
186     if (!first || !last)
187         return APR_EINVAL;
188
189     e = first;
190     while (1)
191     {
192         apr_bucket *copy;
193         AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
194         rv = apr_bucket_copy(e, &copy);
195         if (rv != APR_SUCCESS) {
196             apr_brigade_cleanup(bbout);
197             return rv;
198         }
199
200         APR_BRIGADE_INSERT_TAIL(bbout, copy);
201         if (e == first) {
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);
208                         return rv;
209                     }
210                     /*
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.
214                      */
215                     while (start64 - off_first > (apr_uint64_t)copy->length) {
216                         apr_bucket *tmp;
217
218                         tmp = APR_BUCKET_NEXT(copy);
219                         off_first += (apr_uint64_t)copy->length;
220                         APR_BUCKET_REMOVE(copy);
221                         apr_bucket_destroy(copy);
222                         copy = tmp;
223                         rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
224                         if (rv != APR_SUCCESS) {
225                             apr_brigade_cleanup(bbout);
226                             return rv;
227                         }
228                     }
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);
233                             return rv;
234                         }
235                     }
236                     else {
237                         copy = APR_BUCKET_PREV(copy);
238                     }
239                 }
240                 else if (rv != APR_SUCCESS) {
241                         apr_brigade_cleanup(bbout);
242                         return rv;
243                 }
244                 out_first = APR_BUCKET_NEXT(copy);
245                 APR_BUCKET_REMOVE(copy);
246                 apr_bucket_destroy(copy);
247             }
248             else {
249                 out_first = copy;
250             }
251         }
252         if (e == last) {
253             if (e == first) {
254                 off_last += start64 - off_first;
255                 copy = out_first;
256             }
257             else {
258                 APR_BRIGADE_INSERT_TAIL(bbout, copy);
259             }
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);
266                         return rv;
267                     }
268                     /*
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.
272                      */
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);
279                             return rv;
280                         }
281                     }
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);
286                             return rv;
287                         }
288                     }
289                 }
290                 else if (rv != APR_SUCCESS) {
291                         apr_brigade_cleanup(bbout);
292                         return rv;
293                 }
294                 copy = APR_BUCKET_NEXT(copy);
295                 if (copy != APR_BRIGADE_SENTINEL(bbout)) {
296                     APR_BUCKET_REMOVE(copy);
297                     apr_bucket_destroy(copy);
298                 }
299             }
300             break;
301         }
302         e = APR_BUCKET_NEXT(e);
303     }
304
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);
308     return APR_SUCCESS;
309 }
310
311 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
312                                                          apr_bucket_brigade *bb)
313 {
314 #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
315     request_rec *r = f->r;
316     conn_rec *c = r->connection;
317     byterange_ctx *ctx;
318     apr_bucket *e;
319     apr_bucket_brigade *bsend;
320     apr_bucket_brigade *tmpbb;
321     apr_off_t range_start;
322     apr_off_t range_end;
323     char *current;
324     apr_off_t clength = 0;
325     apr_status_t rv;
326     int found = 0;
327     int num_ranges;
328
329     /* Iterate through the brigade until reaching EOS or a bucket with
330      * unknown length. */
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;
336     }
337
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);
345     }
346
347     num_ranges = ap_set_byterange(r);
348
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);
353     }
354
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);
359
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());
365
366         ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
367                                            use_range_x(r) ? "/x-" : "/",
368                                            "byteranges; boundary=",
369                                            ctx->boundary, NULL));
370
371         if (orig_ct) {
372             ctx->bound_head = apr_pstrcat(r->pool,
373                                           CRLF "--", ctx->boundary,
374                                           CRLF "Content-type: ",
375                                           orig_ct,
376                                           CRLF "Content-range: bytes ",
377                                           NULL);
378         }
379         else {
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 ",
384                                           NULL);
385         }
386         ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
387     }
388
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);
392
393     while ((current = ap_getword(r->pool, &r->range, ','))
394            && (rv = parse_byterange(current, clength, &range_start,
395                                     &range_end))) {
396         if (rv == -1) {
397             continue;
398         }
399
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 ",%"
405                           APR_OFF_T_FMT "]",
406                           range_start, range_end, clength);
407             continue;
408         }
409         found = 1;
410
411         /* For single range requests, we must produce Content-Range header.
412          * Otherwise, we need to produce the multipart boundaries.
413          */
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));
418         }
419         else {
420             char *ts;
421
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);
425
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,
430                                        c->bucket_alloc);
431             APR_BRIGADE_INSERT_TAIL(bsend, e);
432         }
433
434         APR_BRIGADE_CONCAT(bsend, tmpbb);
435     }
436
437     if (found == 0) {
438         ap_remove_output_filter(f);
439         r->status = HTTP_OK;
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);
447     }
448
449     if (ctx->num_ranges > 1) {
450         char *end;
451
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);
457     }
458
459     e = apr_bucket_eos_create(c->bucket_alloc);
460     APR_BRIGADE_INSERT_TAIL(bsend, e);
461
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);
465
466     /* send our multipart output */
467     return ap_pass_brigade(f->next, bsend);
468 }
469
470 static int ap_set_byterange(request_rec *r)
471 {
472     const char *range, *or;
473     const char *if_range;
474     const char *match;
475     const char *ct;
476     char *merged, *cur;
477     int num_ranges = 0;
478     apr_off_t ostart, oend;
479     int in_merge = 0;
480
481     if (r->assbackwards) {
482         return 0;
483     }
484
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).
488      *
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.
493      */
494
495     if (!(range = apr_table_get(r->headers_in, "Range"))) {
496         range = apr_table_get(r->headers_in, "Request-Range");
497     }
498
499     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
500         return 0;
501     }
502
503     /* is content already a single range? */
504     if (apr_table_get(r->headers_out, "Content-Range")) {
505        return 0;
506     }
507
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))) {
512        return 0;
513     }
514
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.
518      */
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)) {
523                 return 0;
524             }
525         }
526         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
527                  || (strcmp(if_range, match) != 0)) {
528             return 0;
529         }
530     }
531
532     range += 6;
533     or = apr_pstrdup(r->pool, range);
534     merged = apr_pstrdup(r->pool, "");
535     while ((cur = ap_getword(r->pool, &range, ','))) {
536         char *dash;
537         char *errp;
538         apr_off_t number, start, end;
539         
540         if (!(dash = strchr(cur, '-'))) {
541             break;
542         }
543         
544         if (dash == cur) {
545             /* In the form "-5"... leave as is */
546             merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), cur, NULL);
547             continue;
548         }
549         *dash++ = '\0';
550         if (!*dash) {
551             /* form "5-" */
552             merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), cur, "-", NULL);
553             continue;
554         }
555         /*
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
559          * the merged range
560          */
561         if (apr_strtoff(&number, cur, &errp, 10) || *errp || number < 0) {
562             break;
563         }
564         start = number;
565         if (apr_strtoff(&number, dash, &errp, 10) || *errp || number <= 0) {
566             break;
567         }
568         end = number;
569         if (!in_merge) {
570             ostart = start;
571             oend = end;
572         }
573         in_merge = 0;
574         if (start <= ostart) {
575             ostart = start;
576             in_merge = 1;
577         }
578         if (start > ostart && start < oend) {
579             in_merge = 1;
580         }
581         if ((end-1) >= oend) {
582             oend = end;
583             in_merge = 1;
584         }
585         if (end > ostart && end < oend) {
586             in_merge = 1;
587         }
588         if (in_merge) {
589             continue;
590         } else {
591             char *nr = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
592                                     ostart, oend);
593             merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), nr, NULL);
594         }
595     }
596
597     if (in_merge) {
598         char *nr = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
599                                 ostart, oend);
600         merged = apr_pstrcat(r->pool, merged, (num_ranges++ ? "," : ""), nr, NULL);
601     }
602         
603     r->status = HTTP_PARTIAL_CONTENT;
604     r->range = merged;
605     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
606                   "Range: %s | %s", or, merged);
607
608     return num_ranges;
609 }