]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
Update copyright year to 2005 and standardize on current copyright owner line.
[apache] / modules / http / byterange_filter.c
1 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
2  * applicable.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * 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 #include "apr_strings.h"
23 #include "apr_buckets.h"
24 #include "apr_lib.h"
25 #include "apr_signal.h"
26
27 #define APR_WANT_STDIO          /* for sscanf */
28 #define APR_WANT_STRFUNC
29 #define APR_WANT_MEMFUNC
30 #include "apr_want.h"
31
32 #define CORE_PRIVATE
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 static int parse_byterange(char *range, apr_off_t clength,
59                            apr_off_t *start, apr_off_t *end)
60 {
61     char *dash = strchr(range, '-');
62     char *errp;
63     apr_off_t number;
64
65     if (!dash) {
66         return 0;
67     }
68
69     if ((dash == range)) {
70         /* In the form "-5" */
71         if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
72             return 0;
73         }
74         *start = clength - number;
75         *end = clength - 1;
76     }
77     else {
78         *dash++ = '\0';
79         if (apr_strtoff(&number, range, &errp, 10) || *errp) {
80             return 0;
81         }
82         *start = number;
83         if (*dash) {
84             if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
85                 return 0;
86             }
87             *end = number;
88         }
89         else {                  /* "5-" */
90             *end = clength - 1;
91         }
92     }
93
94     if (*start < 0) {
95         *start = 0;
96     }
97
98     if (*end >= clength) {
99         *end = clength - 1;
100     }
101
102     if (*start > *end) {
103         return -1;
104     }
105
106     return (*start > 0 || *end < clength);
107 }
108
109 static int ap_set_byterange(request_rec *r);
110
111 typedef struct byterange_ctx {
112     apr_bucket_brigade *bb;
113     int num_ranges;
114     char *boundary;
115     char *bound_head;
116 } byterange_ctx;
117
118 /*
119  * Here we try to be compatible with clients that want multipart/x-byteranges
120  * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
121  * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
122  * that the browser supports an older protocol. We also check User-Agent
123  * for Microsoft Internet Explorer 3, which needs this as well.
124  */
125 static int use_range_x(request_rec *r)
126 {
127     const char *ua;
128     return (apr_table_get(r->headers_in, "Request-Range")
129             || ((ua = apr_table_get(r->headers_in, "User-Agent"))
130                 && ap_strstr_c(ua, "MSIE 3")));
131 }
132
133 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
134 #define PARTITION_ERR_FMT "apr_brigade_partition() failed " \
135                           "[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]"
136
137 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
138                                                          apr_bucket_brigade *bb)
139 {
140 #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
141     request_rec *r = f->r;
142     conn_rec *c = r->connection;
143     byterange_ctx *ctx = f->ctx;
144     apr_bucket *e;
145     apr_bucket_brigade *bsend;
146     apr_off_t range_start;
147     apr_off_t range_end;
148     char *current;
149     apr_off_t bb_length;
150     apr_off_t clength = 0;
151     apr_status_t rv;
152     int found = 0;
153
154     if (!ctx) {
155         int num_ranges = ap_set_byterange(r);
156
157         /* We have nothing to do, get out of the way. */
158         if (num_ranges == 0) {
159             ap_remove_output_filter(f);
160             return ap_pass_brigade(f->next, bb);
161         }
162
163         ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
164         ctx->num_ranges = num_ranges;
165         /* create a brigade in case we never call ap_save_brigade() */
166         ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
167
168         if (ctx->num_ranges > 1) {
169             /* Is ap_make_content_type required here? */
170             const char *orig_ct = ap_make_content_type(r, r->content_type);
171             ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
172                                          (apr_uint64_t)r->request_time, (long) getpid());
173
174             ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
175                                                use_range_x(r) ? "/x-" : "/",
176                                                "byteranges; boundary=",
177                                                ctx->boundary, NULL));
178
179             ctx->bound_head = apr_pstrcat(r->pool,
180                                     CRLF "--", ctx->boundary,
181                                     CRLF "Content-type: ",
182                                     orig_ct,
183                                     CRLF "Content-range: bytes ",
184                                     NULL);
185             ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
186         }
187     }
188
189     /* We can't actually deal with byte-ranges until we have the whole brigade
190      * because the byte-ranges can be in any order, and according to the RFC,
191      * we SHOULD return the data in the same order it was requested.
192      *
193      * XXX: We really need to dump all bytes prior to the start of the earliest
194      * range, and only slurp up to the end of the latest range.  By this we
195      * mean that we should peek-ahead at the lowest first byte of any range,
196      * and the highest last byte of any range.
197      */
198     if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
199         ap_save_brigade(f, &ctx->bb, &bb, r->pool);
200         return APR_SUCCESS;
201     }
202
203     /* Prepend any earlier saved brigades. */
204     APR_BRIGADE_PREPEND(bb, ctx->bb);
205
206     /* It is possible that we won't have a content length yet, so we have to
207      * compute the length before we can actually do the byterange work.
208      */
209     apr_brigade_length(bb, 1, &bb_length);
210     clength = (apr_off_t)bb_length;
211
212     /* this brigade holds what we will be sending */
213     bsend = apr_brigade_create(r->pool, c->bucket_alloc);
214
215     while ((current = ap_getword(r->pool, &r->range, ','))
216            && (rv = parse_byterange(current, clength, &range_start,
217                                     &range_end))) {
218         apr_bucket *e2;
219         apr_bucket *ec;
220
221         if (rv == -1) {
222             continue;
223         }
224
225         /* these calls to apr_brigade_partition() should theoretically
226          * never fail because of the above call to apr_brigade_length(),
227          * but what the heck, we'll check for an error anyway */
228         if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
229             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
230                           PARTITION_ERR_FMT, range_start, clength);
231             continue;
232         }
233         if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
234             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
235                           PARTITION_ERR_FMT, range_end+1, clength);
236             continue;
237         }
238
239         found = 1;
240
241         /* For single range requests, we must produce Content-Range header.
242          * Otherwise, we need to produce the multipart boundaries.
243          */
244         if (ctx->num_ranges == 1) {
245             apr_table_setn(r->headers_out, "Content-Range",
246                            apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
247                                         range_start, range_end, clength));
248         }
249         else {
250             char *ts;
251
252             e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
253                                        r->pool, c->bucket_alloc);
254             APR_BRIGADE_INSERT_TAIL(bsend, e);
255
256             ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
257                               range_start, range_end, clength);
258             ap_xlate_proto_to_ascii(ts, strlen(ts));
259             e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
260                                        c->bucket_alloc);
261             APR_BRIGADE_INSERT_TAIL(bsend, e);
262         }
263
264         do {
265             apr_bucket *foo;
266             const char *str;
267             apr_size_t len;
268
269             if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
270                 /* this shouldn't ever happen due to the call to
271                  * apr_brigade_length() above which normalizes
272                  * indeterminate-length buckets.  just to be sure,
273                  * though, this takes care of uncopyable buckets that
274                  * do somehow manage to slip through.
275                  */
276                 /* XXX: check for failure? */
277                 apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
278                 apr_bucket_copy(ec, &foo);
279             }
280             APR_BRIGADE_INSERT_TAIL(bsend, foo);
281             ec = APR_BUCKET_NEXT(ec);
282         } while (ec != e2);
283     }
284
285     if (found == 0) {
286         ap_remove_output_filter(f);
287         r->status = HTTP_OK;
288         /* bsend is assumed to be empty if we get here. */
289         e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
290                                    r->pool, c->bucket_alloc);
291         APR_BRIGADE_INSERT_TAIL(bsend, e);
292         e = apr_bucket_eos_create(c->bucket_alloc);
293         APR_BRIGADE_INSERT_TAIL(bsend, e);
294         return ap_pass_brigade(f->next, bsend);
295     }
296
297     if (ctx->num_ranges > 1) {
298         char *end;
299
300         /* add the final boundary */
301         end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
302         ap_xlate_proto_to_ascii(end, strlen(end));
303         e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
304         APR_BRIGADE_INSERT_TAIL(bsend, e);
305     }
306
307     e = apr_bucket_eos_create(c->bucket_alloc);
308     APR_BRIGADE_INSERT_TAIL(bsend, e);
309
310     /* we're done with the original content - all of our data is in bsend. */
311     apr_brigade_destroy(bb);
312
313     /* send our multipart output */
314     return ap_pass_brigade(f->next, bsend);
315 }
316
317 static int ap_set_byterange(request_rec *r)
318 {
319     const char *range;
320     const char *if_range;
321     const char *match;
322     const char *ct;
323     int num_ranges;
324
325     if (r->assbackwards) {
326         return 0;
327     }
328
329     /* Check for Range request-header (HTTP/1.1) or Request-Range for
330      * backwards-compatibility with second-draft Luotonen/Franks
331      * byte-ranges (e.g. Netscape Navigator 2-3).
332      *
333      * We support this form, with Request-Range, and (farther down) we
334      * send multipart/x-byteranges instead of multipart/byteranges for
335      * Request-Range based requests to work around a bug in Netscape
336      * Navigator 2-3 and MSIE 3.
337      */
338
339     if (!(range = apr_table_get(r->headers_in, "Range"))) {
340         range = apr_table_get(r->headers_in, "Request-Range");
341     }
342
343     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
344         return 0;
345     }
346
347     /* is content already a single range? */
348     if (apr_table_get(r->headers_out, "Content-Range")) {
349        return 0;
350     }
351
352     /* is content already a multiple range? */
353     if ((ct = apr_table_get(r->headers_out, "Content-Type"))
354         && (!strncasecmp(ct, "multipart/byteranges", 20)
355             || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
356        return 0;
357     }
358
359     /* Check the If-Range header for Etag or Date.
360      * Note that this check will return false (as required) if either
361      * of the two etags are weak.
362      */
363     if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
364         if (if_range[0] == '"') {
365             if (!(match = apr_table_get(r->headers_out, "Etag"))
366                 || (strcmp(if_range, match) != 0)) {
367                 return 0;
368             }
369         }
370         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
371                  || (strcmp(if_range, match) != 0)) {
372             return 0;
373         }
374     }
375
376     if (!ap_strchr_c(range, ',')) {
377         /* a single range */
378         num_ranges = 1;
379     }
380     else {
381         /* a multiple range */
382         num_ranges = 2;
383     }
384
385     r->status = HTTP_PARTIAL_CONTENT;
386     r->range = range + 6;
387
388     return num_ranges;
389 }