--- modules/http/http_protocol.c.orig 2011-09-08 15:17:28.000000000 -0400 +++ modules/http/http_protocol.c 2011-09-09 06:44:26.000000000 -0400 @@ -58,6 +58,10 @@ #include #endif +#ifndef AP_DEFAULT_MAX_RANGES +#define AP_DEFAULT_MAX_RANGES 200 +#endif + /* New Apache routine to map status codes into array indicies * e.g. 100 -> 0, 101 -> 1, 200 -> 2 ... * The number of status lines must equal the value of RESPONSE_CODES (httpd.h) @@ -2893,55 +2897,8 @@ apr_table_setn(r->headers_out, "ETag", etag); } -static int parse_byterange(char *range, apr_off_t clength, - apr_off_t *start, apr_off_t *end) -{ - char *dash = strchr(range, '-'); - - if (!dash) { - return 0; - } - - if ((dash == range)) { - /* In the form "-5" */ - *start = clength - apr_atoi64(dash + 1); - *end = clength - 1; - } - else { - *dash = '\0'; - dash++; - *start = apr_atoi64(range); - if (*dash) { - *end = apr_atoi64(dash); - } - else { /* "5-" */ - *end = clength - 1; - } - } - - if (*start < 0) { - *start = 0; - } - - if (*end >= clength) { - *end = clength - 1; - } - - if (*start > *end) { - return -1; - } - - return (*start > 0 || *end < clength); -} - -static int ap_set_byterange(request_rec *r); - -typedef struct byterange_ctx { - apr_bucket_brigade *bb; - int num_ranges; - char *boundary; - char *bound_head; -} byterange_ctx; +static int ap_set_byterange(request_rec *r, apr_off_t clength, + apr_array_header_t **indexes); /* * Here we try to be compatible with clients that want multipart/x-byteranges @@ -2959,27 +2916,150 @@ } #define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT -#define PARTITION_ERR_FMT "apr_brigade_partition() failed " \ - "[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]" + +static apr_status_t copy_brigade_range(apr_bucket_brigade *bb, + apr_bucket_brigade *bbout, + apr_off_t start, + apr_off_t end) +{ + apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e; + apr_uint64_t pos = 0, off_first = 0, off_last = 0; + apr_status_t rv; + apr_uint64_t start64, end64; + apr_off_t pofft = 0; + + /* + * Once we know that start and end are >= 0 convert everything to apr_uint64_t. + * See the comments in apr_brigade_partition why. + * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t. + */ + start64 = (apr_uint64_t)start; + end64 = (apr_uint64_t)end; + + if (start < 0 || end < 0 || start64 > end64) + return APR_EINVAL; + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { + apr_uint64_t elen64; + /* we know that no bucket has undefined length (-1) */ + AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1)); + elen64 = (apr_uint64_t)e->length; + if (!first && (elen64 + pos > start64)) { + first = e; + off_first = pos; + } + if (elen64 + pos > end64) { + last = e; + off_last = pos; + break; + } + pos += elen64; + } + if (!first || !last) + return APR_EINVAL; + + e = first; + while (1) + { + apr_bucket *copy; + AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb)); + rv = apr_bucket_copy(e, ©); + if (rv != APR_SUCCESS) { + apr_brigade_cleanup(bbout); + return rv; + } + + APR_BRIGADE_INSERT_TAIL(bbout, copy); + if (e == first) { + if (off_first != start64) { + rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first)); + if (rv != APR_SUCCESS) { + apr_brigade_cleanup(bbout); + return rv; + } + out_first = APR_BUCKET_NEXT(copy); + APR_BUCKET_REMOVE(copy); + apr_bucket_destroy(copy); + } + else { + out_first = copy; + } + } + if (e == last) { + if (e == first) { + off_last += start64 - off_first; + copy = out_first; + } + if (end64 - off_last != (apr_uint64_t)e->length) { + rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last)); + if (rv != APR_SUCCESS) { + apr_brigade_cleanup(bbout); + return rv; + } + copy = APR_BUCKET_NEXT(copy); + if (copy != APR_BRIGADE_SENTINEL(bbout)) { + APR_BUCKET_REMOVE(copy); + apr_bucket_destroy(copy); + } + } + break; + } + e = APR_BUCKET_NEXT(e); + } + + AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft)); + pos = (apr_uint64_t)pofft; + AP_DEBUG_ASSERT(pos == end64 - start64 + 1); + return APR_SUCCESS; +} + +typedef struct indexes_t { + apr_off_t start; + apr_off_t end; +} indexes_t; + +static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb) +{ + apr_bucket *e; + conn_rec *c = f->r->connection; + ap_remove_output_filter(f); + f->r->status = HTTP_OK; + e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL, + f->r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(tmpbb, e); + e = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(tmpbb, e); + return ap_pass_brigade(f->next, tmpbb); +} AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, apr_bucket_brigade *bb) { -#define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1) request_rec *r = f->r; conn_rec *c = r->connection; - byterange_ctx *ctx; apr_bucket *e; apr_bucket_brigade *bsend; + apr_bucket_brigade *tmpbb; apr_off_t range_start; apr_off_t range_end; - char *current; apr_off_t clength = 0; apr_status_t rv; int found = 0; + int num_ranges; + char *boundary = NULL; + char *bound_head = NULL; + apr_array_header_t *indexes; + indexes_t *idx; + int original_status; + int i; - /* Iterate through the brigade until reaching EOS or a bucket with - * unknown length. */ + /* + * Iterate through the brigade until reaching EOS or a bucket with + * unknown length. + */ for (e = APR_BRIGADE_FIRST(bb); (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e) && e->length != (apr_size_t)-1); @@ -2987,84 +3067,75 @@ clength += e->length; } - /* Don't attempt to do byte range work if this brigade doesn't + /* + * Don't attempt to do byte range work if this brigade doesn't * contain an EOS, or if any of the buckets has an unknown length; * this avoids the cases where it is expensive to perform - * byteranging (i.e. may require arbitrary amounts of memory). */ + * byteranging (i.e. may require arbitrary amounts of memory). + */ if (!APR_BUCKET_IS_EOS(e) || clength <= 0) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } - { - int num_ranges = ap_set_byterange(r); + original_status = r->status; + num_ranges = ap_set_byterange(r, clength, &indexes); - /* We have nothing to do, get out of the way. */ - if (num_ranges == 0) { - ap_remove_output_filter(f); - return ap_pass_brigade(f->next, bb); - } - - ctx = apr_pcalloc(r->pool, sizeof(*ctx)); - ctx->num_ranges = num_ranges; - /* create a brigade in case we never call ap_save_brigade() */ - ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); - - if (ctx->num_ranges > 1) { - /* Is ap_make_content_type required here? */ - const char *orig_ct = ap_make_content_type(r, r->content_type); - /* need APR_TIME_T_FMT_HEX */ - ctx->boundary = apr_psprintf(r->pool, "%qx%lx", - r->request_time, (long) getpid()); - - ap_set_content_type(r, apr_pstrcat(r->pool, "multipart", - use_range_x(r) ? "/x-" : "/", - "byteranges; boundary=", - ctx->boundary, NULL)); - - ctx->bound_head = apr_pstrcat(r->pool, - CRLF "--", ctx->boundary, - CRLF "Content-type: ", - orig_ct, - CRLF "Content-range: bytes ", - NULL); - ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head)); - } + /* We have nothing to do, get out of the way. */ + if (num_ranges == 0 || (AP_DEFAULT_MAX_RANGES >= 0 && num_ranges > AP_DEFAULT_MAX_RANGES)) { + r->status = original_status; + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); } /* this brigade holds what we will be sending */ bsend = apr_brigade_create(r->pool, c->bucket_alloc); - while ((current = ap_getword(r->pool, &r->range, ',')) - && (rv = parse_byterange(current, clength, &range_start, - &range_end))) { - apr_bucket *e2; - apr_bucket *ec; + if (num_ranges < 0) + return send_416(f, bsend); + + if (num_ranges > 1) { + /* Is ap_make_content_type required here? */ + const char *orig_ct = ap_make_content_type(r, r->content_type); + boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx", + (apr_uint64_t)r->request_time, c->id); + + ap_set_content_type(r, apr_pstrcat(r->pool, "multipart", + use_range_x(r) ? "/x-" : "/", + "byteranges; boundary=", + boundary, NULL)); + + bound_head = apr_pstrcat(r->pool, + CRLF "--", boundary, + CRLF "Content-type: ", + orig_ct, + CRLF "Content-range: bytes ", + NULL); + ap_xlate_proto_to_ascii(bound_head, strlen(bound_head)); + } - if (rv == -1) { - continue; - } + tmpbb = apr_brigade_create(r->pool, c->bucket_alloc); - /* these calls to apr_brigade_partition() should theoretically - * never fail because of the above call to apr_brigade_length(), - * but what the heck, we'll check for an error anyway */ - if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - PARTITION_ERR_FMT, range_start, clength); - continue; - } - if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) { + idx = (indexes_t *)indexes->elts; + for (i = 0; i < indexes->nelts; i++, idx++) { + range_start = idx->start; + range_end = idx->end; + + rv = copy_brigade_range(bb, tmpbb, range_start, range_end); + if (rv != APR_SUCCESS ) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - PARTITION_ERR_FMT, range_end+1, clength); + "copy_brigade_range() failed [%" APR_OFF_T_FMT + "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]", + range_start, range_end, clength); continue; } - found = 1; - /* For single range requests, we must produce Content-Range header. + /* + * For single range requests, we must produce Content-Range header. * Otherwise, we need to produce the multipart boundaries. */ - if (ctx->num_ranges == 1) { + if (num_ranges == 1) { apr_table_setn(r->headers_out, "Content-Range", apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, range_start, range_end, clength)); @@ -3072,7 +3143,7 @@ else { char *ts; - e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head), + e = apr_bucket_pool_create(bound_head, strlen(bound_head), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); @@ -3084,44 +3155,31 @@ APR_BRIGADE_INSERT_TAIL(bsend, e); } - do { - apr_bucket *foo; - const char *str; - apr_size_t len; - - if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) { - /* this shouldn't ever happen due to the call to - * apr_brigade_length() above which normalizes - * indeterminate-length buckets. just to be sure, - * though, this takes care of uncopyable buckets that - * do somehow manage to slip through. - */ - /* XXX: check for failure? */ - apr_bucket_read(ec, &str, &len, APR_BLOCK_READ); - apr_bucket_copy(ec, &foo); - } - APR_BRIGADE_INSERT_TAIL(bsend, foo); - ec = APR_BUCKET_NEXT(ec); - } while (ec != e2); + APR_BRIGADE_CONCAT(bsend, tmpbb); + if (i && !(i & 0x1F)) { + /* + * Every now and then, pass what we have down the filter chain. + * In this case, the content-length filter cannot calculate and + * set the content length and we must remove any Content-Length + * header already present. + */ + apr_table_unset(r->headers_out, "Content-Length"); + if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS) + return rv; + apr_brigade_cleanup(bsend); + } } if (found == 0) { - ap_remove_output_filter(f); - r->status = HTTP_OK; /* bsend is assumed to be empty if we get here. */ - e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL, - r->pool, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bsend, e); - e = apr_bucket_eos_create(c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bsend, e); - return ap_pass_brigade(f->next, bsend); + return send_416(f, bsend); } - if (ctx->num_ranges > 1) { + if (num_ranges > 1) { char *end; /* add the final boundary */ - end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL); + end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL); ap_xlate_proto_to_ascii(end, strlen(end)); e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); @@ -3131,25 +3189,49 @@ APR_BRIGADE_INSERT_TAIL(bsend, e); /* we're done with the original content - all of our data is in bsend. */ - apr_brigade_destroy(bb); + apr_brigade_cleanup(bb); + apr_brigade_destroy(tmpbb); /* send our multipart output */ return ap_pass_brigade(f->next, bsend); } -static int ap_set_byterange(request_rec *r) +/* for consistency with 2.2 code which uses apr_strtoff() + * (missing from apr 0.9) + */ +static apr_status_t strtoff(apr_off_t *offset, const char *nptr, + char **endptr, int base) +{ + errno = 0; + if (sizeof(apr_off_t) == 4) { + *offset = strtol(nptr, endptr, base); + } + else { + *offset = apr_strtoi64(nptr, endptr, base); + } + return APR_FROM_OS_ERROR(errno); +} + +static int ap_set_byterange(request_rec *r, apr_off_t clength, + apr_array_header_t **indexes) { const char *range; const char *if_range; const char *match; const char *ct; - int num_ranges; + char *cur; + int num_ranges = 0, unsatisfiable = 0; + apr_off_t sum_lengths = 0; + indexes_t *idx; + int ranges = 1; + const char *it; if (r->assbackwards) { return 0; } - /* Check for Range request-header (HTTP/1.1) or Request-Range for + /* + * Check for Range request-header (HTTP/1.1) or Request-Range for * backwards-compatibility with second-draft Luotonen/Franks * byte-ranges (e.g. Netscape Navigator 2-3). * @@ -3179,7 +3261,8 @@ return 0; } - /* Check the If-Range header for Etag or Date. + /* + * Check the If-Range header for Etag or Date. * Note that this check will return false (as required) if either * of the two etags are weak. */ @@ -3196,17 +3279,94 @@ } } - if (!ap_strchr_c(range, ',')) { - /* a single range */ - num_ranges = 1; + range += 6; + it = range; + while (*it) { + if (*it++ == ',') { + ranges++; + } + } + it = range; + *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t)); + while ((cur = ap_getword(r->pool, &range, ','))) { + char *dash; + char *errp; + apr_off_t number, start, end; + + if (!*cur) + break; + + /* + * Per RFC 2616 14.35.1: If there is at least one syntactically invalid + * byte-range-spec, we must ignore the whole header. + */ + + if (!(dash = strchr(cur, '-'))) { + return 0; + } + + if (dash == cur) { + /* In the form "-5" */ + if (strtoff(&number, dash+1, &errp, 10) || *errp) { + return 0; + } + if (number < 1) { + return 0; + } + start = clength - number; + end = clength - 1; + } + else { + *dash++ = '\0'; + if (strtoff(&number, cur, &errp, 10) || *errp) { + return 0; + } + start = number; + if (*dash) { + if (strtoff(&number, dash, &errp, 10) || *errp) { + return 0; + } + end = number; + if (start > end) { + return 0; + } + } + else { /* "5-" */ + end = clength - 1; + } + } + + if (start < 0) { + start = 0; + } + if (start >= clength) { + unsatisfiable = 1; + continue; + } + if (end >= clength) { + end = clength - 1; + } + + idx = (indexes_t *)apr_array_push(*indexes); + idx->start = start; + idx->end = end; + sum_lengths += end - start + 1; + /* new set again */ + num_ranges++; + } + + if (num_ranges == 0 && unsatisfiable) { + /* If all ranges are unsatisfiable, we should return 416 */ + return -1; } - else { - /* a multiple range */ - num_ranges = 2; + if (sum_lengths >= clength) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "Sum of ranges not smaller than file, ignoring."); + return 0; } r->status = HTTP_PARTIAL_CONTENT; - r->range = range + 6; + r->range = it; return num_ranges; }