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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * byterange_filter.c --- HTTP byterange filter and friends.
22 #include "apr_strings.h"
23 #include "apr_buckets.h"
25 #include "apr_signal.h"
27 #define APR_WANT_STDIO /* for sscanf */
28 #define APR_WANT_STRFUNC
29 #define APR_WANT_MEMFUNC
32 #include "util_filter.h"
33 #include "ap_config.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
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"
56 #if APR_HAVE_PROCESS_H
57 #include <process.h> /* for getpid() on Win32 */
60 APLOG_USE_MODULE(http);
62 static int parse_byterange(char *range, apr_off_t clength,
63 apr_off_t *start, apr_off_t *end)
65 char *dash = strchr(range, '-');
73 if ((dash == range)) {
74 /* In the form "-5" */
75 if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
78 *start = clength - number;
83 if (apr_strtoff(&number, range, &errp, 10) || *errp) {
88 if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
102 if (*end >= clength) {
110 return (*start > 0 || *end < clength);
113 static int ap_set_byterange(request_rec *r);
115 typedef struct byterange_ctx {
116 apr_bucket_brigade *bb;
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.
129 static int use_range_x(request_rec *r)
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")));
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 "]"
141 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
142 apr_bucket_brigade *bb)
144 #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
145 request_rec *r = f->r;
146 conn_rec *c = r->connection;
149 apr_bucket_brigade *bsend;
150 apr_off_t range_start;
153 apr_off_t clength = 0;
158 /* Iterate through the brigade until reaching EOS or a bucket with
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;
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);
176 num_ranges = ap_set_byterange(r);
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);
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);
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());
195 ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
196 use_range_x(r) ? "/x-" : "/",
197 "byteranges; boundary=",
198 ctx->boundary, NULL));
201 ctx->bound_head = apr_pstrcat(r->pool,
202 CRLF "--", ctx->boundary,
203 CRLF "Content-type: ",
205 CRLF "Content-range: bytes ",
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 ",
215 ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
218 /* this brigade holds what we will be sending */
219 bsend = apr_brigade_create(r->pool, c->bucket_alloc);
221 while ((current = ap_getword(r->pool, &r->range, ','))
222 && (rv = parse_byterange(current, clength, &range_start,
231 /* These calls to apr_brigage_partition should only fail in
232 * pathological cases, e.g. a file being truncated whilst
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);
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);
247 /* For single range requests, we must produce Content-Range header.
248 * Otherwise, we need to produce the multipart boundaries.
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));
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);
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,
267 APR_BRIGADE_INSERT_TAIL(bsend, e);
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);
284 APR_BRIGADE_INSERT_TAIL(bsend, foo);
285 ec = APR_BUCKET_NEXT(ec);
290 ap_remove_output_filter(f);
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);
301 if (ctx->num_ranges > 1) {
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);
311 e = apr_bucket_eos_create(c->bucket_alloc);
312 APR_BRIGADE_INSERT_TAIL(bsend, e);
314 /* we're done with the original content - all of our data is in bsend. */
315 apr_brigade_cleanup(bb);
317 /* send our multipart output */
318 return ap_pass_brigade(f->next, bsend);
321 static int ap_set_byterange(request_rec *r)
324 const char *if_range;
329 if (r->assbackwards) {
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).
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.
343 if (!(range = apr_table_get(r->headers_in, "Range"))) {
344 range = apr_table_get(r->headers_in, "Request-Range");
347 if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
351 /* is content already a single range? */
352 if (apr_table_get(r->headers_out, "Content-Range")) {
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))) {
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.
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)) {
374 else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
375 || (strcmp(if_range, match) != 0)) {
380 if (!ap_strchr_c(range, ',')) {
385 /* a multiple range */
389 r->status = HTTP_PARTIAL_CONTENT;
390 r->range = range + 6;