]> granicus.if.org Git - apache/blob - modules/http/byterange_filter.c
Remove spurious braces to fix clang warning
[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 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
144                                                          apr_bucket_brigade *bb)
145 {
146 #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
147     request_rec *r = f->r;
148     conn_rec *c = r->connection;
149     byterange_ctx *ctx;
150     apr_bucket *e;
151     apr_bucket_brigade *bsend;
152     apr_off_t range_start;
153     apr_off_t range_end;
154     char *current;
155     apr_off_t clength = 0;
156     apr_status_t rv;
157     int found = 0;
158     int num_ranges;
159
160     /* Iterate through the brigade until reaching EOS or a bucket with
161      * unknown length. */
162     for (e = APR_BRIGADE_FIRST(bb);
163          (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
164           && e->length != (apr_size_t)-1);
165          e = APR_BUCKET_NEXT(e)) {
166         clength += e->length;
167     }
168
169     /* Don't attempt to do byte range work if this brigade doesn't
170      * contain an EOS, or if any of the buckets has an unknown length;
171      * this avoids the cases where it is expensive to perform
172      * byteranging (i.e. may require arbitrary amounts of memory). */
173     if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
174         ap_remove_output_filter(f);
175         return ap_pass_brigade(f->next, bb);
176     }
177
178     num_ranges = ap_set_byterange(r);
179
180     /* We have nothing to do, get out of the way. */
181     if (num_ranges == 0) {
182         ap_remove_output_filter(f);
183         return ap_pass_brigade(f->next, bb);
184     }
185
186     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
187     ctx->num_ranges = num_ranges;
188     /* create a brigade in case we never call ap_save_brigade() */
189     ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
190
191     if (ctx->num_ranges > 1) {
192         /* Is ap_make_content_type required here? */
193         const char *orig_ct = ap_make_content_type(r, r->content_type);
194         ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
195                                      (apr_uint64_t)r->request_time, (long) getpid());
196
197         ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
198                                            use_range_x(r) ? "/x-" : "/",
199                                            "byteranges; boundary=",
200                                            ctx->boundary, NULL));
201
202         if (orig_ct) {
203             ctx->bound_head = apr_pstrcat(r->pool,
204                                           CRLF "--", ctx->boundary,
205                                           CRLF "Content-type: ",
206                                           orig_ct,
207                                           CRLF "Content-range: bytes ",
208                                           NULL);
209         }
210         else {
211             /* if we have no type for the content, do our best */
212             ctx->bound_head = apr_pstrcat(r->pool,
213                                           CRLF "--", ctx->boundary,
214                                           CRLF "Content-range: bytes ",
215                                           NULL);
216         }
217         ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
218     }
219
220     /* this brigade holds what we will be sending */
221     bsend = apr_brigade_create(r->pool, c->bucket_alloc);
222
223     while ((current = ap_getword(r->pool, &r->range, ','))
224            && (rv = parse_byterange(current, clength, &range_start,
225                                     &range_end))) {
226         apr_bucket *e2;
227         apr_bucket *ec;
228
229         if (rv == -1) {
230             continue;
231         }
232
233         /* These calls to apr_brigage_partition should only fail in
234          * pathological cases, e.g. a file being truncated whilst
235          * being served. */
236         if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
237             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
238                           PARTITION_ERR_FMT, range_start, clength);
239             continue;
240         }
241         if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
242             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
243                           PARTITION_ERR_FMT, range_end+1, clength);
244             continue;
245         }
246
247         found = 1;
248
249         /* For single range requests, we must produce Content-Range header.
250          * Otherwise, we need to produce the multipart boundaries.
251          */
252         if (ctx->num_ranges == 1) {
253             apr_table_setn(r->headers_out, "Content-Range",
254                            apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
255                                         range_start, range_end, clength));
256         }
257         else {
258             char *ts;
259
260             e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
261                                        r->pool, c->bucket_alloc);
262             APR_BRIGADE_INSERT_TAIL(bsend, e);
263
264             ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
265                               range_start, range_end, clength);
266             ap_xlate_proto_to_ascii(ts, strlen(ts));
267             e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
268                                        c->bucket_alloc);
269             APR_BRIGADE_INSERT_TAIL(bsend, e);
270         }
271
272         do {
273             apr_bucket *foo;
274             const char *str;
275             apr_size_t len;
276
277             if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
278                 /* As above; this should not fail since the bucket has
279                  * a known length, but just to be sure, this takes
280                  * care of uncopyable buckets that do somehow manage
281                  * to slip through.  */
282                 /* XXX: check for failure? */
283                 apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
284                 apr_bucket_copy(ec, &foo);
285             }
286             APR_BRIGADE_INSERT_TAIL(bsend, foo);
287             ec = APR_BUCKET_NEXT(ec);
288         } while (ec != e2);
289     }
290
291     if (found == 0) {
292         ap_remove_output_filter(f);
293         r->status = HTTP_OK;
294         /* bsend is assumed to be empty if we get here. */
295         e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
296                                    r->pool, c->bucket_alloc);
297         APR_BRIGADE_INSERT_TAIL(bsend, e);
298         e = apr_bucket_eos_create(c->bucket_alloc);
299         APR_BRIGADE_INSERT_TAIL(bsend, e);
300         return ap_pass_brigade(f->next, bsend);
301     }
302
303     if (ctx->num_ranges > 1) {
304         char *end;
305
306         /* add the final boundary */
307         end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
308         ap_xlate_proto_to_ascii(end, strlen(end));
309         e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
310         APR_BRIGADE_INSERT_TAIL(bsend, e);
311     }
312
313     e = apr_bucket_eos_create(c->bucket_alloc);
314     APR_BRIGADE_INSERT_TAIL(bsend, e);
315
316     /* we're done with the original content - all of our data is in bsend. */
317     apr_brigade_cleanup(bb);
318
319     /* send our multipart output */
320     return ap_pass_brigade(f->next, bsend);
321 }
322
323 static int ap_set_byterange(request_rec *r)
324 {
325     const char *range;
326     const char *if_range;
327     const char *match;
328     const char *ct;
329     int num_ranges;
330
331     if (r->assbackwards) {
332         return 0;
333     }
334
335     /* Check for Range request-header (HTTP/1.1) or Request-Range for
336      * backwards-compatibility with second-draft Luotonen/Franks
337      * byte-ranges (e.g. Netscape Navigator 2-3).
338      *
339      * We support this form, with Request-Range, and (farther down) we
340      * send multipart/x-byteranges instead of multipart/byteranges for
341      * Request-Range based requests to work around a bug in Netscape
342      * Navigator 2-3 and MSIE 3.
343      */
344
345     if (!(range = apr_table_get(r->headers_in, "Range"))) {
346         range = apr_table_get(r->headers_in, "Request-Range");
347     }
348
349     if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
350         return 0;
351     }
352
353     /* is content already a single range? */
354     if (apr_table_get(r->headers_out, "Content-Range")) {
355        return 0;
356     }
357
358     /* is content already a multiple range? */
359     if ((ct = apr_table_get(r->headers_out, "Content-Type"))
360         && (!strncasecmp(ct, "multipart/byteranges", 20)
361             || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
362        return 0;
363     }
364
365     /* Check the If-Range header for Etag or Date.
366      * Note that this check will return false (as required) if either
367      * of the two etags are weak.
368      */
369     if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
370         if (if_range[0] == '"') {
371             if (!(match = apr_table_get(r->headers_out, "Etag"))
372                 || (strcmp(if_range, match) != 0)) {
373                 return 0;
374             }
375         }
376         else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
377                  || (strcmp(if_range, match) != 0)) {
378             return 0;
379         }
380     }
381
382     if (!ap_strchr_c(range, ',')) {
383         /* a single range */
384         num_ranges = 1;
385     }
386     else {
387         /* a multiple range */
388         num_ranges = 2;
389     }
390
391     r->status = HTTP_PARTIAL_CONTENT;
392     r->range = range + 6;
393
394     return num_ranges;
395 }