You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by wr...@apache.org on 2011/09/14 08:11:42 UTC
svn commit: r400 - in /release/httpd/patches: apply_to_2.0.64/
apply_to_2.2.14/ apply_to_2.2.19/
Author: wrowe
Date: Wed Sep 14 06:11:40 2011
New Revision: 400
Log:
Patches as prepared by Jeff Trawick, thanks sir!
Added:
release/httpd/patches/apply_to_2.0.64/
release/httpd/patches/apply_to_2.0.64/CVE-2011-3192-2.0.64-byterange-fixes.patch
release/httpd/patches/apply_to_2.2.14/CVE-2011-3192-2.2.14-byterange-fixes.patch
release/httpd/patches/apply_to_2.2.19/
release/httpd/patches/apply_to_2.2.19/CVE-2011-3192-2.2.19-byterange-fixes.patch
Added: release/httpd/patches/apply_to_2.0.64/CVE-2011-3192-2.0.64-byterange-fixes.patch
==============================================================================
--- release/httpd/patches/apply_to_2.0.64/CVE-2011-3192-2.0.64-byterange-fixes.patch (added)
+++ release/httpd/patches/apply_to_2.0.64/CVE-2011-3192-2.0.64-byterange-fixes.patch Wed Sep 14 06:11:40 2011
@@ -0,0 +1,597 @@
+--- 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 <unistd.h>
+ #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;
+ }
Added: release/httpd/patches/apply_to_2.2.14/CVE-2011-3192-2.2.14-byterange-fixes.patch
==============================================================================
--- release/httpd/patches/apply_to_2.2.14/CVE-2011-3192-2.2.14-byterange-fixes.patch (added)
+++ release/httpd/patches/apply_to_2.2.14/CVE-2011-3192-2.2.14-byterange-fixes.patch Wed Sep 14 06:11:40 2011
@@ -0,0 +1,581 @@
+--- modules/http/byterange_filter.c.orig 2011-09-08 12:27:06.000000000 -0400
++++ modules/http/byterange_filter.c 2011-09-08 14:58:07.000000000 -0400
+@@ -55,65 +55,12 @@
+ #include <unistd.h>
+ #endif
+
+-static int parse_byterange(char *range, apr_off_t clength,
+- apr_off_t *start, apr_off_t *end)
+-{
+- char *dash = strchr(range, '-');
+- char *errp;
+- apr_off_t number;
+-
+- if (!dash) {
+- return 0;
+- }
+-
+- if ((dash == range)) {
+- /* In the form "-5" */
+- if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
+- return 0;
+- }
+- *start = clength - number;
+- *end = clength - 1;
+- }
+- else {
+- *dash++ = '\0';
+- if (apr_strtoff(&number, range, &errp, 10) || *errp) {
+- return 0;
+- }
+- *start = number;
+- if (*dash) {
+- if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
+- return 0;
+- }
+- *end = number;
+- }
+- 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);
++#ifndef AP_DEFAULT_MAX_RANGES
++#define AP_DEFAULT_MAX_RANGES 200
++#endif
+
+-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
+@@ -131,28 +78,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;
+-
+- /* Iterate through the brigade until reaching EOS or a bucket with
+- * unknown length. */
++ 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.
++ */
+ for (e = APR_BRIGADE_FIRST(bb);
+ (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
+ && e->length != (apr_size_t)-1);
+@@ -160,90 +229,84 @@
+ 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);
+ }
+
+- 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) {
++ 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);
+ }
+
+- 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);
++ /* this brigade holds what we will be sending */
++ bsend = apr_brigade_create(r->pool, c->bucket_alloc);
++
++ if (num_ranges < 0)
++ return send_416(f, bsend);
+
+- if (ctx->num_ranges > 1) {
++ if (num_ranges > 1) {
+ /* Is ap_make_content_type required here? */
+ const char *orig_ct = ap_make_content_type(r, r->content_type);
+- ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
+- (apr_uint64_t)r->request_time, (long) getpid());
++ 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=",
+- ctx->boundary, NULL));
++ boundary, NULL));
+
+ if (strcasecmp(orig_ct, NO_CONTENT_TYPE)) {
+- ctx->bound_head = apr_pstrcat(r->pool,
+- CRLF "--", ctx->boundary,
+- CRLF "Content-type: ",
+- orig_ct,
+- CRLF "Content-range: bytes ",
+- NULL);
++ bound_head = apr_pstrcat(r->pool,
++ CRLF "--", boundary,
++ CRLF "Content-type: ",
++ orig_ct,
++ CRLF "Content-range: bytes ",
++ NULL);
+ }
+ else {
+ /* if we have no type for the content, do our best */
+- ctx->bound_head = apr_pstrcat(r->pool,
+- CRLF "--", ctx->boundary,
+- CRLF "Content-range: bytes ",
+- NULL);
++ bound_head = apr_pstrcat(r->pool,
++ CRLF "--", boundary,
++ CRLF "Content-range: bytes ",
++ NULL);
+ }
+- ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
++ ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
+ }
+
+- /* 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;
++ tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+- if (rv == -1) {
+- continue;
+- }
++ idx = (indexes_t *)indexes->elts;
++ for (i = 0; i < indexes->nelts; i++, idx++) {
++ range_start = idx->start;
++ range_end = idx->end;
+
+- /* These calls to apr_brigage_partition should only fail in
+- * pathological cases, e.g. a file being truncated whilst
+- * being served. */
+- if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
++ 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_start, clength);
++ "copy_brigade_range() failed [%" APR_OFF_T_FMT
++ "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
++ range_start, range_end, clength);
+ continue;
+ }
+- if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+- PARTITION_ERR_FMT, range_end+1, 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));
+@@ -251,7 +314,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);
+
+@@ -263,42 +326,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) {
+- /* As above; this should not fail since the bucket has
+- * a known length, but just to be sure, 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);
+@@ -309,24 +361,32 @@
+
+ /* we're done with the original content - all of our data is in bsend. */
+ apr_brigade_destroy(bb);
++ apr_brigade_destroy(tmpbb);
+
+ /* send our multipart output */
+ return ap_pass_brigade(f->next, bsend);
+ }
+
+-static int ap_set_byterange(request_rec *r)
++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).
+ *
+@@ -356,7 +416,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.
+ */
+@@ -373,17 +434,94 @@
+ }
+ }
+
+- if (!ap_strchr_c(range, ',')) {
+- /* a single range */
+- num_ranges = 1;
+- }
+- else {
+- /* a multiple range */
+- num_ranges = 2;
++ 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 (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
++ return 0;
++ }
++ if (number < 1) {
++ return 0;
++ }
++ start = clength - number;
++ end = clength - 1;
++ }
++ else {
++ *dash++ = '\0';
++ if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
++ return 0;
++ }
++ start = number;
++ if (*dash) {
++ if (apr_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;
++ }
++ 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;
+ }
Added: release/httpd/patches/apply_to_2.2.19/CVE-2011-3192-2.2.19-byterange-fixes.patch
==============================================================================
--- release/httpd/patches/apply_to_2.2.19/CVE-2011-3192-2.2.19-byterange-fixes.patch (added)
+++ release/httpd/patches/apply_to_2.2.19/CVE-2011-3192-2.2.19-byterange-fixes.patch Wed Sep 14 06:11:40 2011
@@ -0,0 +1,581 @@
+--- modules/http/byterange_filter.c.orig 2011-09-08 12:18:38.000000000 -0400
++++ modules/http/byterange_filter.c 2011-09-08 12:17:20.000000000 -0400
+@@ -55,65 +55,12 @@
+ #include <unistd.h>
+ #endif
+
+-static int parse_byterange(char *range, apr_off_t clength,
+- apr_off_t *start, apr_off_t *end)
+-{
+- char *dash = strchr(range, '-');
+- char *errp;
+- apr_off_t number;
+-
+- if (!dash) {
+- return 0;
+- }
+-
+- if ((dash == range)) {
+- /* In the form "-5" */
+- if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
+- return 0;
+- }
+- *start = clength - number;
+- *end = clength - 1;
+- }
+- else {
+- *dash++ = '\0';
+- if (apr_strtoff(&number, range, &errp, 10) || *errp) {
+- return 0;
+- }
+- *start = number;
+- if (*dash) {
+- if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
+- return 0;
+- }
+- *end = number;
+- }
+- 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);
++#ifndef AP_DEFAULT_MAX_RANGES
++#define AP_DEFAULT_MAX_RANGES 200
++#endif
+
+-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
+@@ -131,28 +78,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;
+-
+- /* Iterate through the brigade until reaching EOS or a bucket with
+- * unknown length. */
++ 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.
++ */
+ for (e = APR_BRIGADE_FIRST(bb);
+ (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
+ && e->length != (apr_size_t)-1);
+@@ -160,90 +229,84 @@
+ 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);
+ }
+
+- 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) {
++ 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);
+ }
+
+- 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);
++ /* this brigade holds what we will be sending */
++ bsend = apr_brigade_create(r->pool, c->bucket_alloc);
++
++ if (num_ranges < 0)
++ return send_416(f, bsend);
+
+- if (ctx->num_ranges > 1) {
++ if (num_ranges > 1) {
+ /* Is ap_make_content_type required here? */
+ const char *orig_ct = ap_make_content_type(r, r->content_type);
+- ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
+- (apr_uint64_t)r->request_time, (long) getpid());
++ 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=",
+- ctx->boundary, NULL));
++ boundary, NULL));
+
+ if (strcasecmp(orig_ct, NO_CONTENT_TYPE)) {
+- ctx->bound_head = apr_pstrcat(r->pool,
+- CRLF "--", ctx->boundary,
+- CRLF "Content-type: ",
+- orig_ct,
+- CRLF "Content-range: bytes ",
+- NULL);
++ bound_head = apr_pstrcat(r->pool,
++ CRLF "--", boundary,
++ CRLF "Content-type: ",
++ orig_ct,
++ CRLF "Content-range: bytes ",
++ NULL);
+ }
+ else {
+ /* if we have no type for the content, do our best */
+- ctx->bound_head = apr_pstrcat(r->pool,
+- CRLF "--", ctx->boundary,
+- CRLF "Content-range: bytes ",
+- NULL);
++ bound_head = apr_pstrcat(r->pool,
++ CRLF "--", boundary,
++ CRLF "Content-range: bytes ",
++ NULL);
+ }
+- ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
++ ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
+ }
+
+- /* 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;
++ tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+- if (rv == -1) {
+- continue;
+- }
++ idx = (indexes_t *)indexes->elts;
++ for (i = 0; i < indexes->nelts; i++, idx++) {
++ range_start = idx->start;
++ range_end = idx->end;
+
+- /* These calls to apr_brigage_partition should only fail in
+- * pathological cases, e.g. a file being truncated whilst
+- * being served. */
+- if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
++ 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_start, clength);
++ "copy_brigade_range() failed [%" APR_OFF_T_FMT
++ "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
++ range_start, range_end, clength);
+ continue;
+ }
+- if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+- PARTITION_ERR_FMT, range_end+1, 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));
+@@ -251,7 +314,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);
+
+@@ -263,42 +326,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) {
+- /* As above; this should not fail since the bucket has
+- * a known length, but just to be sure, 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);
+@@ -309,24 +361,32 @@
+
+ /* we're done with the original content - all of our data is in bsend. */
+ 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)
++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).
+ *
+@@ -356,7 +416,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.
+ */
+@@ -373,17 +434,94 @@
+ }
+ }
+
+- if (!ap_strchr_c(range, ',')) {
+- /* a single range */
+- num_ranges = 1;
+- }
+- else {
+- /* a multiple range */
+- num_ranges = 2;
++ 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 (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
++ return 0;
++ }
++ if (number < 1) {
++ return 0;
++ }
++ start = clength - number;
++ end = clength - 1;
++ }
++ else {
++ *dash++ = '\0';
++ if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
++ return 0;
++ }
++ start = number;
++ if (*dash) {
++ if (apr_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;
++ }
++ 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;
+ }