~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/http/byterange_filter.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

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