]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
merge totally reversed ranges like 4-5,1-2 into 1-5
[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 ap_set_byterange(request_rec *r, apr_off_t clength,
65                             apr_array_header_t *indexes);
66
67 /*
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.
73  */
74 static int use_range_x(request_rec *r)
75 {
76     const char *ua;
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")));
80 }
81
82 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
83
84 static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
85                                        apr_bucket_brigade *bbout,
86                                        apr_off_t start,
87                                        apr_off_t end)
88 {
89     apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
90     apr_uint64_t pos = 0, off_first = 0, off_last = 0;
91     apr_status_t rv;
92     const char *s;
93     apr_size_t len;
94     apr_uint64_t start64, end64;
95     apr_off_t pofft;
96
97     /*
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.
101      */
102     start64 = (apr_uint64_t)start;
103     end64 = (apr_uint64_t)end;
104
105     if (start < 0 || end < 0 || start64 > end64)
106         return APR_EINVAL;
107
108     for (e = APR_BRIGADE_FIRST(bb);
109          e != APR_BRIGADE_SENTINEL(bb);
110          e = APR_BUCKET_NEXT(e))
111     {
112         apr_uint64_t elen64;
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)) {
117             first = e;
118             off_first = pos;
119         }
120         if (elen64 + pos > end64) {
121             last = e;
122             off_last = pos;
123             break;
124         }
125         pos += elen64;
126     }
127     if (!first || !last)
128         return APR_EINVAL;
129
130     e = first;
131     while (1)
132     {
133         apr_bucket *copy;
134         AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
135         rv = apr_bucket_copy(e, &copy);
136         if (rv != APR_SUCCESS) {
137             apr_brigade_cleanup(bbout);
138             return rv;
139         }
140
141         APR_BRIGADE_INSERT_TAIL(bbout, copy);
142         if (e == first) {
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);
149                         return rv;
150                     }
151                     /*
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.
155                      */
156                     while (start64 - off_first > (apr_uint64_t)copy->length) {
157                         apr_bucket *tmp;
158                         int i = 0;
159                         if (i++ >= 99999)
160                             return APR_EINVAL;
161
162                         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, 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 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
258                                                          apr_bucket_brigade *bb)
259 {
260     request_rec *r = f->r;
261     conn_rec *c = r->connection;
262     apr_bucket *e;
263     apr_bucket_brigade *bsend;
264     apr_bucket_brigade *tmpbb;
265     apr_off_t range_start;
266     apr_off_t range_end;
267     apr_off_t clength = 0;
268     apr_status_t rv;
269     int found = 0;
270     int num_ranges;
271     char *boundary = NULL;
272     char *bound_head = NULL;
273     apr_array_header_t *indexes;
274     indexes_t *idx;
275     int i;
276
277     indexes = apr_array_make(r->pool, 10, sizeof(indexes_t));
278     
279     /* Iterate through the brigade until reaching EOS or a bucket with
280      * unknown length. */
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;
286     }
287
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);
295     }
296
297     num_ranges = ap_set_byterange(r, clength, indexes);
298
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);
303     }
304
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());
310
311         ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
312                                            use_range_x(r) ? "/x-" : "/",
313                                            "byteranges; boundary=",
314                                            boundary, NULL));
315
316         if (orig_ct) {
317             bound_head = apr_pstrcat(r->pool,
318                                      CRLF "--", boundary,
319                                      CRLF "Content-type: ",
320                                      orig_ct,
321                                      CRLF "Content-range: bytes ",
322                                      NULL);
323         }
324         else {
325             /* if we have no type for the content, do our best */
326             bound_head = apr_pstrcat(r->pool,
327                                      CRLF "--", boundary,
328                                      CRLF "Content-range: bytes ",
329                                      NULL);
330         }
331         ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
332     }
333
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);
337
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;
342
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);
349             continue;
350         }
351         found = 1;
352
353         /* For single range requests, we must produce Content-Range header.
354          * Otherwise, we need to produce the multipart boundaries.
355          */
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));
360         }
361         else {
362             char *ts;
363
364             e = apr_bucket_pool_create(bound_head, strlen(bound_head),
365                                        r->pool, c->bucket_alloc);
366             APR_BRIGADE_INSERT_TAIL(bsend, e);
367
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,
372                                        c->bucket_alloc);
373             APR_BRIGADE_INSERT_TAIL(bsend, e);
374         }
375
376         APR_BRIGADE_CONCAT(bsend, tmpbb);
377     }
378
379     if (found == 0) {
380         ap_remove_output_filter(f);
381         r->status = HTTP_OK;
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);
389     }
390
391     if (num_ranges > 1) {
392         char *end;
393
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);
399     }
400
401     e = apr_bucket_eos_create(c->bucket_alloc);
402     APR_BRIGADE_INSERT_TAIL(bsend, e);
403
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);
407
408     /* send our multipart output */
409     return ap_pass_brigade(f->next, bsend);
410 }
411
412 static int ap_set_byterange(request_rec *r, apr_off_t clength,
413                             apr_array_header_t *indexes)
414 {
415     const char *range, *or;
416     const char *if_range;
417     const char *match;
418     const char *ct;
419     char *cur, **new;
420     apr_array_header_t *merged;
421     int num_ranges = 0;
422     apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
423     int in_merge = 0;
424     indexes_t *idx;
425     int overlaps = 0, reversals = 0;
426
427     if (r->assbackwards) {
428         return 0;
429     }
430
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).
434      *
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.
439      */
440
441     if (!(range = apr_table_get(r->headers_in, "Range"))) {
442         range = apr_table_get(r->headers_in, "Request-Range");
443     }
444
445     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
446         return 0;
447     }
448
449     /* is content already a single range? */
450     if (apr_table_get(r->headers_out, "Content-Range")) {
451        return 0;
452     }
453
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))) {
458        return 0;
459     }
460
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.
464      */
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)) {
469                 return 0;
470             }
471         }
472         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
473                  || (strcmp(if_range, match) != 0)) {
474             return 0;
475         }
476     }
477
478     range += 6;
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, ','))) {
482         char *dash;
483         char *errp;
484         apr_off_t number, start, end;
485         
486         if (!(dash = strchr(cur, '-'))) {
487             break;
488         }
489
490         if (dash == range) {
491             /* In the form "-5" */
492             if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
493                 break;
494             }
495             start = clength - number;
496             end = clength - 1;
497         }
498         else {
499             *dash++ = '\0';
500             if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
501                 break;
502             }
503             start = number;
504             if (*dash) {
505                 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
506                     break;
507                 }
508                 end = number;
509             }
510             else {                  /* "5-" */
511                 end = clength - 1;
512             }
513         }
514         
515         if (start < 0) {
516             start = 0;
517         }
518         if (end >= clength) {
519             end = clength - 1;
520         }
521
522         if (start > end) {
523             /* ignore? count? */
524             break;
525         }
526         if (!in_merge) {
527             /* new set */
528             ostart = start;
529             oend = end;
530             in_merge = 1;
531             continue;
532         }
533         in_merge = 0;
534
535         AP_DEBUG_ASSERT((start <= end) && (ostart <= oend));
536         
537         if (start-1 < oend) {
538             if (start < ostart) {
539                 ostart = start;
540                 reversals++;
541                 in_merge = 1;
542             }
543             else if (start < oend || start == ostart) {
544                 in_merge = 1;
545             }
546             if (end >= oend && (start-1) <= oend) {
547                 oend = end;
548                 in_merge = 1;
549             }
550             else if (end > ostart && end <= oend) {
551                 in_merge = 1;
552             }
553         }
554         if (in_merge) {
555             overlaps++;
556             continue;
557         } else {
558             new = (char **)apr_array_push(merged);
559             *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
560                                     ostart, oend);
561             idx = (indexes_t *)apr_array_push(indexes);
562             idx->start = ostart;
563             idx->end = oend;
564             sum_lengths += oend - ostart + 1;
565             /* new set again */
566             in_merge = 1;
567             ostart = start;
568             oend = end;
569             num_ranges++;
570         }
571     }
572
573     if (in_merge) {
574         new = (char **)apr_array_push(merged);
575         *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
576                             ostart, oend);
577         idx = (indexes_t *)apr_array_push(indexes);
578         idx->start = ostart;
579         idx->end = oend;
580         sum_lengths += oend - ostart + 1;
581         num_ranges++;
582     }
583     if (sum_lengths >= clength) {
584         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
585                       "Sum of ranges not smaller than file, ignoring.");
586         return 0;
587     }
588         
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);
594
595     return num_ranges;
596 }