diff --git a/modules/http/byterange_filter.c b/modules/http/byterange_filter.c index 13bf0a1..6a40420 100644 --- a/modules/http/byterange_filter.c +++ b/modules/http/byterange_filter.c @@ -61,6 +61,11 @@ APLOG_USE_MODULE(http); +/* returns + * 0 if range invalid or whole file + * +1 for valid range spec + * -1 for start > end + */ static int parse_byterange(char *range, apr_off_t clength, apr_off_t *start, apr_off_t *end) { @@ -114,13 +119,6 @@ static int parse_byterange(char *range, apr_off_t 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; - /* * Here we try to be compatible with clients that want multipart/x-byteranges * instead of multipart/byteranges (also see above), as per HTTP/1.1. We @@ -136,26 +134,43 @@ static int use_range_x(request_rec *r) && ap_strstr_c(ua, "MSIE 3"))); } +struct range_spec { + apr_off_t start, end; +}; + +static int sort_fn(const void *a_, const void *b_) +{ + const struct range_spec *a = a_, *b = b_; + if (a->start < b->start) + return -1; + else if (a->start == b->start) + return 0; + else + return 1; +} + #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 "]" +#define MAX(o1, o2) ((o1 > o2) ? o1 : o2) 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_off_t range_start; - apr_off_t range_end; + apr_off_t max = 0; char *current; apr_off_t clength = 0; apr_status_t rv; - int found = 0; + int found = 0, i = 0; + int need_sort = 0; int num_ranges; + char *boundary = NULL; + char *bound_head = NULL; + struct range_spec *ranges; /* Iterate through the brigade until reaching EOS or a bucket with * unknown length. */ @@ -183,64 +198,78 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *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) { + 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, (long) getpid()); ap_set_content_type(r, apr_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/", "byteranges; boundary=", - ctx->boundary, NULL)); + boundary, NULL)); if (orig_ct) { - 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)); } + ranges = apr_palloc(r->pool, sizeof(*ranges) * num_ranges); /* 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 (rv == -1) { + while ((current = ap_getword(r->pool, &r->range, ',')) && *current) { + AP_DEBUG_ASSERT(i < num_ranges); + rv = parse_byterange(current, clength, &ranges[i].start, + &ranges[i].end); + if (rv == -1) continue; + else if (rv == 0) + break; + + if (i == 0 || ranges[i].start > max) + max = ranges[i].end; + else + need_sort = 1; + + i++; + } + num_ranges = i; + if (need_sort) { + int j = 0; + qsort(ranges, num_ranges, sizeof(*ranges), sort_fn); + for (i = 1; i < num_ranges; i++) { + if (ranges[j].end + 1 >= ranges[i].start) + ranges[j].end = MAX(ranges[i].end, ranges[j].end); + else + j++; } + num_ranges = j + 1; + } + + for (i = 0; i < num_ranges; i++) { + apr_bucket *e2; + apr_bucket *ec; /* 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) { + if ((rv = apr_brigade_partition(bb, ranges[i].start, &ec)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - PARTITION_ERR_FMT, range_start, clength); + PARTITION_ERR_FMT, ranges[i].start, clength); continue; } - if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) { + if ((rv = apr_brigade_partition(bb, ranges[i].end+1, &e2)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - PARTITION_ERR_FMT, range_end+1, clength); + PARTITION_ERR_FMT, ranges[i].end+1, clength); continue; } @@ -249,20 +278,20 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, /* For single range requests, we must produce Content-Range header. * Otherwise, we need to produce the multipart boundaries. */ - if (ctx->num_ranges == 1) { + if (!boundary) { apr_table_setn(r->headers_out, "Content-Range", apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, - range_start, range_end, clength)); + ranges[i].start, ranges[i].end, clength)); } 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); ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF, - range_start, range_end, clength); + ranges[i].start, ranges[i].end, clength); ap_xlate_proto_to_ascii(ts, strlen(ts)); e = apr_bucket_pool_create(ts, strlen(ts), r->pool, c->bucket_alloc); @@ -300,11 +329,11 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, return ap_pass_brigade(f->next, bsend); } - if (ctx->num_ranges > 1) { + if (boundary) { 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); @@ -379,17 +408,15 @@ static int ap_set_byterange(request_rec *r) } } - if (!ap_strchr_c(range, ',')) { - /* a single range */ - num_ranges = 1; - } - else { - /* a multiple range */ - num_ranges = 2; - } - r->status = HTTP_PARTIAL_CONTENT; r->range = range + 6; + num_ranges = 1; + range = ap_strchr_c(r->range, ','); + + while (range) { + num_ranges++; + range = ap_strchr_c(range + 1, ','); + } return num_ranges; }