]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
Added header include for getpid() prototype on Win32.
[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 #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 #include "util_filter.h"
33 #include "ap_config.h"
34 #include "httpd.h"
35 #include "http_config.h"
36 #include "http_core.h"
37 #include "http_protocol.h"
38 #include "http_main.h"
39 #include "http_request.h"
40 #include "http_vhost.h"
41 #include "http_log.h"           /* For errors detected in basic auth common
42                                  * support code... */
43 #include "apr_date.h"           /* For apr_date_parse_http and APR_DATE_BAD */
44 #include "util_charset.h"
45 #include "util_ebcdic.h"
46 #include "util_time.h"
47
48 #include "mod_core.h"
49
50 #if APR_HAVE_STDARG_H
51 #include <stdarg.h>
52 #endif
53 #if APR_HAVE_UNISTD_H
54 #include <unistd.h>
55 #endif
56 #if APR_HAVE_PROCESS_H
57 #include <process.h>            /* for getpid() on Win32 */
58 #endif
59
60 APLOG_USE_MODULE(http);
61
62 static int parse_byterange(char *range, apr_off_t clength,
63                            apr_off_t *start, apr_off_t *end)
64 {
65     char *dash = strchr(range, '-');
66     char *errp;
67     apr_off_t number;
68
69     if (!dash) {
70         return 0;
71     }
72
73     if ((dash == range)) {
74         /* In the form "-5" */
75         if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
76             return 0;
77         }
78         *start = clength - number;
79         *end = clength - 1;
80     }
81     else {
82         *dash++ = '\0';
83         if (apr_strtoff(&number, range, &errp, 10) || *errp) {
84             return 0;
85         }
86         *start = number;
87         if (*dash) {
88             if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
89                 return 0;
90             }
91             *end = number;
92         }
93         else {                  /* "5-" */
94             *end = clength - 1;
95         }
96     }
97
98     if (*start < 0) {
99         *start = 0;
100     }
101
102     if (*end >= clength) {
103         *end = clength - 1;
104     }
105
106     if (*start > *end) {
107         return -1;
108     }
109
110     return (*start > 0 || *end < clength);
111 }
112
113 static int ap_set_byterange(request_rec *r);
114
115 typedef struct byterange_ctx {
116     apr_bucket_brigade *bb;
117     int num_ranges;
118     char *boundary;
119     char *bound_head;
120 } byterange_ctx;
121
122 /*
123  * Here we try to be compatible with clients that want multipart/x-byteranges
124  * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
125  * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
126  * that the browser supports an older protocol. We also check User-Agent
127  * for Microsoft Internet Explorer 3, which needs this as well.
128  */
129 static int use_range_x(request_rec *r)
130 {
131     const char *ua;
132     return (apr_table_get(r->headers_in, "Request-Range")
133             || ((ua = apr_table_get(r->headers_in, "User-Agent"))
134                 && ap_strstr_c(ua, "MSIE 3")));
135 }
136
137 #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
138 #define PARTITION_ERR_FMT "apr_brigade_partition() failed " \
139                           "[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]"
140
141 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
142                                                          apr_bucket_brigade *bb)
143 {
144 #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
145     request_rec *r = f->r;
146     conn_rec *c = r->connection;
147     byterange_ctx *ctx;
148     apr_bucket *e;
149     apr_bucket_brigade *bsend;
150     apr_off_t range_start;
151     apr_off_t range_end;
152     char *current;
153     apr_off_t clength = 0;
154     apr_status_t rv;
155     int found = 0;
156     int num_ranges;
157
158     /* Iterate through the brigade until reaching EOS or a bucket with
159      * unknown length. */
160     for (e = APR_BRIGADE_FIRST(bb);
161          (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
162           && e->length != (apr_size_t)-1);
163          e = APR_BUCKET_NEXT(e)) {
164         clength += e->length;
165     }
166
167     /* Don't attempt to do byte range work if this brigade doesn't
168      * contain an EOS, or if any of the buckets has an unknown length;
169      * this avoids the cases where it is expensive to perform
170      * byteranging (i.e. may require arbitrary amounts of memory). */
171     if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
172         ap_remove_output_filter(f);
173         return ap_pass_brigade(f->next, bb);
174     }
175
176     num_ranges = ap_set_byterange(r);
177
178     /* We have nothing to do, get out of the way. */
179     if (num_ranges == 0) {
180         ap_remove_output_filter(f);
181         return ap_pass_brigade(f->next, bb);
182     }
183
184     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
185     ctx->num_ranges = num_ranges;
186     /* create a brigade in case we never call ap_save_brigade() */
187     ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
188
189     if (ctx->num_ranges > 1) {
190         /* Is ap_make_content_type required here? */
191         const char *orig_ct = ap_make_content_type(r, r->content_type);
192         ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
193                                      (apr_uint64_t)r->request_time, (long) getpid());
194
195         ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
196                                            use_range_x(r) ? "/x-" : "/",
197                                            "byteranges; boundary=",
198                                            ctx->boundary, NULL));
199
200         if (orig_ct) {
201             ctx->bound_head = apr_pstrcat(r->pool,
202                                           CRLF "--", ctx->boundary,
203                                           CRLF "Content-type: ",
204                                           orig_ct,
205                                           CRLF "Content-range: bytes ",
206                                           NULL);
207         }
208         else {
209             /* if we have no type for the content, do our best */
210             ctx->bound_head = apr_pstrcat(r->pool,
211                                           CRLF "--", ctx->boundary,
212                                           CRLF "Content-range: bytes ",
213                                           NULL);
214         }
215         ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
216     }
217
218     /* this brigade holds what we will be sending */
219     bsend = apr_brigade_create(r->pool, c->bucket_alloc);
220
221     while ((current = ap_getword(r->pool, &r->range, ','))
222            && (rv = parse_byterange(current, clength, &range_start,
223                                     &range_end))) {
224         apr_bucket *e2;
225         apr_bucket *ec;
226
227         if (rv == -1) {
228             continue;
229         }
230
231         /* These calls to apr_brigage_partition should only fail in
232          * pathological cases, e.g. a file being truncated whilst
233          * being served. */
234         if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
235             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
236                           PARTITION_ERR_FMT, range_start, clength);
237             continue;
238         }
239         if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
240             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
241                           PARTITION_ERR_FMT, range_end+1, clength);
242             continue;
243         }
244
245         found = 1;
246
247         /* For single range requests, we must produce Content-Range header.
248          * Otherwise, we need to produce the multipart boundaries.
249          */
250         if (ctx->num_ranges == 1) {
251             apr_table_setn(r->headers_out, "Content-Range",
252                            apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
253                                         range_start, range_end, clength));
254         }
255         else {
256             char *ts;
257
258             e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
259                                        r->pool, c->bucket_alloc);
260             APR_BRIGADE_INSERT_TAIL(bsend, e);
261
262             ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
263                               range_start, range_end, clength);
264             ap_xlate_proto_to_ascii(ts, strlen(ts));
265             e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
266                                        c->bucket_alloc);
267             APR_BRIGADE_INSERT_TAIL(bsend, e);
268         }
269
270         do {
271             apr_bucket *foo;
272             const char *str;
273             apr_size_t len;
274
275             if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
276                 /* As above; this should not fail since the bucket has
277                  * a known length, but just to be sure, this takes
278                  * care of uncopyable buckets that do somehow manage
279                  * to slip through.  */
280                 /* XXX: check for failure? */
281                 apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
282                 apr_bucket_copy(ec, &foo);
283             }
284             APR_BRIGADE_INSERT_TAIL(bsend, foo);
285             ec = APR_BUCKET_NEXT(ec);
286         } while (ec != e2);
287     }
288
289     if (found == 0) {
290         ap_remove_output_filter(f);
291         r->status = HTTP_OK;
292         /* bsend is assumed to be empty if we get here. */
293         e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
294                                    r->pool, c->bucket_alloc);
295         APR_BRIGADE_INSERT_TAIL(bsend, e);
296         e = apr_bucket_eos_create(c->bucket_alloc);
297         APR_BRIGADE_INSERT_TAIL(bsend, e);
298         return ap_pass_brigade(f->next, bsend);
299     }
300
301     if (ctx->num_ranges > 1) {
302         char *end;
303
304         /* add the final boundary */
305         end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
306         ap_xlate_proto_to_ascii(end, strlen(end));
307         e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
308         APR_BRIGADE_INSERT_TAIL(bsend, e);
309     }
310
311     e = apr_bucket_eos_create(c->bucket_alloc);
312     APR_BRIGADE_INSERT_TAIL(bsend, e);
313
314     /* we're done with the original content - all of our data is in bsend. */
315     apr_brigade_cleanup(bb);
316
317     /* send our multipart output */
318     return ap_pass_brigade(f->next, bsend);
319 }
320
321 static int ap_set_byterange(request_rec *r)
322 {
323     const char *range;
324     const char *if_range;
325     const char *match;
326     const char *ct;
327     int num_ranges;
328
329     if (r->assbackwards) {
330         return 0;
331     }
332
333     /* Check for Range request-header (HTTP/1.1) or Request-Range for
334      * backwards-compatibility with second-draft Luotonen/Franks
335      * byte-ranges (e.g. Netscape Navigator 2-3).
336      *
337      * We support this form, with Request-Range, and (farther down) we
338      * send multipart/x-byteranges instead of multipart/byteranges for
339      * Request-Range based requests to work around a bug in Netscape
340      * Navigator 2-3 and MSIE 3.
341      */
342
343     if (!(range = apr_table_get(r->headers_in, "Range"))) {
344         range = apr_table_get(r->headers_in, "Request-Range");
345     }
346
347     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
348         return 0;
349     }
350
351     /* is content already a single range? */
352     if (apr_table_get(r->headers_out, "Content-Range")) {
353        return 0;
354     }
355
356     /* is content already a multiple range? */
357     if ((ct = apr_table_get(r->headers_out, "Content-Type"))
358         && (!strncasecmp(ct, "multipart/byteranges", 20)
359             || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
360        return 0;
361     }
362
363     /* Check the If-Range header for Etag or Date.
364      * Note that this check will return false (as required) if either
365      * of the two etags are weak.
366      */
367     if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
368         if (if_range[0] == '"') {
369             if (!(match = apr_table_get(r->headers_out, "Etag"))
370                 || (strcmp(if_range, match) != 0)) {
371                 return 0;
372             }
373         }
374         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
375                  || (strcmp(if_range, match) != 0)) {
376             return 0;
377         }
378     }
379
380     if (!ap_strchr_c(range, ',')) {
381         /* a single range */
382         num_ranges = 1;
383     }
384     else {
385         /* a multiple range */
386         num_ranges = 2;
387     }
388
389     r->status = HTTP_PARTIAL_CONTENT;
390     r->range = range + 6;
391
392     return num_ranges;
393 }