#include "apr.h"
-#if APR_HAVE_PROCESS_H
-#include <process.h> /* for getpid() on Win32 */
-#endif
-
#include "apr_strings.h"
#include "apr_buckets.h"
#include "apr_lib.h"
#include <unistd.h>
#endif
-#ifndef DEFAULT_MAX_RANGES
-#define DEFAULT_MAX_RANGES 200
+#ifndef AP_DEFAULT_MAX_RANGES
+#define AP_DEFAULT_MAX_RANGES 200
+#endif
+#ifndef AP_DEFAULT_MAX_OVERLAPS
+#define AP_DEFAULT_MAX_OVERLAPS 20
#endif
+#ifndef AP_DEFAULT_MAX_REVERSALS
+#define AP_DEFAULT_MAX_REVERSALS 20
+#endif
+
+#define MAX_PREALLOC_RANGES 100
APLOG_USE_MODULE(http);
-static int ap_set_byterange(request_rec *r, apr_off_t clength,
- apr_array_header_t **indexes);
+typedef struct indexes_t {
+ apr_off_t start;
+ apr_off_t end;
+} indexes_t;
/*
- * 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
- * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
- * that the browser supports an older protocol. We also check User-Agent
- * for Microsoft Internet Explorer 3, which needs this as well.
+ * Returns: number of ranges (merged) or -1 for no-good
*/
-static int use_range_x(request_rec *r)
+static int ap_set_byterange(request_rec *r, apr_off_t clength,
+ apr_array_header_t **indexes,
+ int *overlaps, int *reversals)
{
- const char *ua;
- return (apr_table_get(r->headers_in, "Request-Range")
- || ((ua = apr_table_get(r->headers_in, "User-Agent"))
- && ap_strstr_c(ua, "MSIE 3")));
+ const char *range;
+ const char *ct;
+ char *cur;
+ apr_array_header_t *merged;
+ int num_ranges = 0, unsatisfiable = 0;
+ apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
+ int in_merge = 0;
+ indexes_t *idx;
+ int ranges = 1;
+ int i;
+ const char *it;
+
+ *overlaps = 0;
+ *reversals = 0;
+
+ if (r->assbackwards) {
+ return 0;
+ }
+
+ range = apr_table_get(r->headers_in, "Range");
+ if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
+ return 0;
+ }
+
+ /* is content already a single range? */
+ if (apr_table_get(r->headers_out, "Content-Range")) {
+ return 0;
+ }
+
+ /* is content already a multiple range? */
+ if ((ct = apr_table_get(r->headers_out, "Content-Type"))
+ && strncasecmp(ct, "multipart/byteranges", 20) == 0) {
+ return 0;
+ }
+
+ /*
+ * Check the If-Range header for Etag or Date.
+ */
+ if (AP_CONDITION_NOMATCH == ap_condition_if_range(r, r->headers_out)) {
+ return 0;
+ }
+
+ range += 6;
+ it = range;
+ while (*it) {
+ if (*it++ == ',') {
+ ranges++;
+ }
+ }
+ it = range;
+ if (ranges > MAX_PREALLOC_RANGES) {
+ ranges = MAX_PREALLOC_RANGES;
+ }
+ *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;
+ /*
+ * special case: 0-
+ * ignore all other ranges provided
+ * return as a single range: 0-
+ */
+ if (start == 0) {
+ num_ranges = 0;
+ sum_lengths = 0;
+ in_merge = 1;
+ oend = end;
+ ostart = start;
+ apr_array_clear(*indexes);
+ break;
+ }
+ }
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (start >= clength) {
+ unsatisfiable = 1;
+ continue;
+ }
+ if (end >= clength) {
+ end = clength - 1;
+ }
+
+ if (!in_merge) {
+ /* new set */
+ ostart = start;
+ oend = end;
+ in_merge = 1;
+ continue;
+ }
+ in_merge = 0;
+
+ if (start >= ostart && end <= oend) {
+ in_merge = 1;
+ }
+
+ if (start < ostart && end >= ostart-1) {
+ ostart = start;
+ ++*reversals;
+ in_merge = 1;
+ }
+ if (end >= oend && start <= oend+1 ) {
+ oend = end;
+ in_merge = 1;
+ }
+
+ if (in_merge) {
+ ++*overlaps;
+ continue;
+ } else {
+ idx = (indexes_t *)apr_array_push(*indexes);
+ idx->start = ostart;
+ idx->end = oend;
+ sum_lengths += oend - ostart + 1;
+ /* new set again */
+ in_merge = 1;
+ ostart = start;
+ oend = end;
+ num_ranges++;
+ }
+ }
+
+ if (in_merge) {
+ idx = (indexes_t *)apr_array_push(*indexes);
+ idx->start = ostart;
+ idx->end = oend;
+ sum_lengths += oend - ostart + 1;
+ num_ranges++;
+ }
+ else 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_TRACE1, 0, r,
+ "Sum of ranges larger than file, ignoring.");
+ return 0;
+ }
+
+ /*
+ * create the merged table now, now that we know we need it
+ */
+ merged = apr_array_make(r->pool, num_ranges, sizeof(char *));
+ idx = (indexes_t *)(*indexes)->elts;
+ for (i = 0; i < (*indexes)->nelts; i++, idx++) {
+ char **new = (char **)apr_array_push(merged);
+ *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
+ idx->start, idx->end);
+ }
+
+ r->status = HTTP_PARTIAL_CONTENT;
+ r->range = apr_array_pstrcat(r->pool, merged, ',');
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01583)
+ "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
+ it, r->range, *overlaps, *reversals, clength);
+
+ return num_ranges;
}
#define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
-#define MAX_PREALLOC_RANGES 100
static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
apr_bucket_brigade *bbout,
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;
- const char *s;
- apr_size_t len;
apr_uint64_t start64, end64;
apr_off_t pofft = 0;
if (e == first) {
if (off_first != start64) {
rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
- if (rv == APR_ENOTIMPL) {
- rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
- }
- /*
- * The read above might have morphed copy in a bucket
- * of shorter length. So read and delete until we reached
- * the correct bucket for splitting.
- */
- while (start64 - off_first > (apr_uint64_t)copy->length) {
- apr_bucket *tmp = APR_BUCKET_NEXT(copy);
- off_first += (apr_uint64_t)copy->length;
- APR_BUCKET_REMOVE(copy);
- apr_bucket_destroy(copy);
- copy = tmp;
- rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
- }
- }
- if (start64 > off_first) {
- rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
- if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
- }
- }
- else {
- copy = APR_BUCKET_PREV(copy);
- }
- }
- else if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
}
out_first = APR_BUCKET_NEXT(copy);
- APR_BUCKET_REMOVE(copy);
- apr_bucket_destroy(copy);
+ apr_bucket_delete(copy);
}
else {
out_first = copy;
}
if (end64 - off_last != (apr_uint64_t)e->length) {
rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
- if (rv == APR_ENOTIMPL) {
- rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
- }
- /*
- * The read above might have morphed copy in a bucket
- * of shorter length. So read until we reached
- * the correct bucket for splitting.
- */
- while (end64 + 1 - off_last > (apr_uint64_t)copy->length) {
- off_last += (apr_uint64_t)copy->length;
- copy = APR_BUCKET_NEXT(copy);
- rv = apr_bucket_read(copy, &s, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
- }
- }
- if (end64 < off_last + (apr_uint64_t)copy->length - 1) {
- rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
- if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
- }
- }
- }
- else if (rv != APR_SUCCESS) {
- apr_brigade_cleanup(bbout);
- return rv;
+ 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);
+ apr_bucket_delete(copy);
}
}
break;
return APR_SUCCESS;
}
-typedef struct indexes_t {
- apr_off_t start;
- apr_off_t end;
-} indexes_t;
-
-static int get_max_ranges(request_rec *r) {
- core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
- return core_conf->max_ranges == -1 ? DEFAULT_MAX_RANGES : core_conf->max_ranges;
-}
-
static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb)
{
apr_bucket *e;
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 i;
int original_status;
- int max_ranges = get_max_ranges(r);
+ int max_ranges, max_overlaps, max_reversals;
+ int overlaps = 0, reversals = 0;
+ core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
+ max_ranges = ( (core_conf->max_ranges >= 0 || core_conf->max_ranges == AP_MAXRANGES_UNLIMITED)
+ ? core_conf->max_ranges
+ : AP_DEFAULT_MAX_RANGES );
+ max_overlaps = ( (core_conf->max_overlaps >= 0 || core_conf->max_overlaps == AP_MAXRANGES_UNLIMITED)
+ ? core_conf->max_overlaps
+ : AP_DEFAULT_MAX_OVERLAPS );
+ max_reversals = ( (core_conf->max_reversals >= 0 || core_conf->max_reversals == AP_MAXRANGES_UNLIMITED)
+ ? core_conf->max_reversals
+ : AP_DEFAULT_MAX_REVERSALS );
/*
* Iterate through the brigade until reaching EOS or a bucket with
* unknown length.
}
original_status = r->status;
- num_ranges = ap_set_byterange(r, clength, &indexes);
+ num_ranges = ap_set_byterange(r, clength, &indexes, &overlaps, &reversals);
- /* We have nothing to do, get out of the way. */
- if (num_ranges == 0 || (max_ranges > 0 && num_ranges > max_ranges)) {
+ /* No Ranges or we hit a limit? We have nothing to do, get out of the way. */
+ if (num_ranges == 0 ||
+ (max_ranges >= 0 && num_ranges > max_ranges) ||
+ (max_overlaps >= 0 && overlaps > max_overlaps) ||
+ (max_reversals >= 0 && reversals > max_reversals)) {
r->status = original_status;
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
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, (long) getpid());
- ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
- use_range_x(r) ? "/x-" : "/",
- "byteranges; boundary=",
- boundary, NULL));
+ ap_set_content_type(r, apr_pstrcat(r->pool,
+ "multipart/byteranges; boundary=",
+ ap_multipart_boundary, NULL));
if (orig_ct) {
bound_head = apr_pstrcat(r->pool,
- CRLF "--", boundary,
+ CRLF "--", ap_multipart_boundary,
CRLF "Content-type: ",
orig_ct,
CRLF "Content-range: bytes ",
else {
/* if we have no type for the content, do our best */
bound_head = apr_pstrcat(r->pool,
- CRLF "--", boundary,
+ CRLF "--", ap_multipart_boundary,
CRLF "Content-range: bytes ",
NULL);
}
rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
if (rv != APR_SUCCESS ) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01584)
"copy_brigade_range() failed [%" APR_OFF_T_FMT
"-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
range_start, range_end, clength);
char *end;
/* add the final boundary */
- end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL);
+ end = apr_pstrcat(r->pool, CRLF "--", ap_multipart_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);
/* send our multipart output */
return ap_pass_brigade(f->next, bsend);
}
-
-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;
- char *cur, **new;
- apr_array_header_t *merged;
- int num_ranges = 0, unsatisfiable = 0;
- apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
- int in_merge = 0;
- indexes_t *idx;
- int overlaps = 0, reversals = 0;
- int ranges = 1;
- const char *it;
-
- if (r->assbackwards) {
- return 0;
- }
-
- /*
- * 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).
- *
- * We support this form, with Request-Range, and (farther down) we
- * send multipart/x-byteranges instead of multipart/byteranges for
- * Request-Range based requests to work around a bug in Netscape
- * Navigator 2-3 and MSIE 3.
- */
-
- if (!(range = apr_table_get(r->headers_in, "Range"))) {
- range = apr_table_get(r->headers_in, "Request-Range");
- }
-
- if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
- return 0;
- }
-
- /* is content already a single range? */
- if (apr_table_get(r->headers_out, "Content-Range")) {
- return 0;
- }
-
- /* is content already a multiple range? */
- if ((ct = apr_table_get(r->headers_out, "Content-Type"))
- && (!strncasecmp(ct, "multipart/byteranges", 20)
- || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
- return 0;
- }
-
- /*
- * 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.
- */
- if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
- if (if_range[0] == '"') {
- if (!(match = apr_table_get(r->headers_out, "Etag"))
- || (strcmp(if_range, match) != 0)) {
- return 0;
- }
- }
- else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
- || (strcmp(if_range, match) != 0)) {
- return 0;
- }
- }
-
- range += 6;
- it = range;
- while (*it) {
- if (*it++ == ',') {
- ranges++;
- }
- }
- it = range;
- if (ranges > MAX_PREALLOC_RANGES) {
- ranges = MAX_PREALLOC_RANGES;
- }
- *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
- merged = apr_array_make(r->pool, ranges, sizeof(char *));
- 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 == range) {
- /* 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;
- }
-
- if (!in_merge) {
- /* new set */
- ostart = start;
- oend = end;
- in_merge = 1;
- continue;
- }
- in_merge = 0;
-
- if (start >= ostart && end <= oend) {
- in_merge = 1;
- }
-
- if (start < ostart && end >= ostart-1) {
- ostart = start;
- reversals++;
- in_merge = 1;
- }
- if (end >= oend && start <= oend+1 ) {
- oend = end;
- in_merge = 1;
- }
-
- if (in_merge) {
- overlaps++;
- continue;
- } else {
- new = (char **)apr_array_push(merged);
- *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
- ostart, oend);
- idx = (indexes_t *)apr_array_push(*indexes);
- idx->start = ostart;
- idx->end = oend;
- sum_lengths += oend - ostart + 1;
- /* new set again */
- in_merge = 1;
- ostart = start;
- oend = end;
- num_ranges++;
- }
- }
-
- if (in_merge) {
- new = (char **)apr_array_push(merged);
- *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
- ostart, oend);
- idx = (indexes_t *)apr_array_push(*indexes);
- idx->start = ostart;
- idx->end = oend;
- sum_lengths += oend - ostart + 1;
- num_ranges++;
- }
- else if (num_ranges == 0 && unsatisfiable) {
- /* If all ranges are unsatisfiable, we should return 416 */
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "0U");
- return -1;
- }
- if (sum_lengths > clength) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "Sum of ranges larger than file, ignoring.");
- return 0;
- }
-
- r->status = HTTP_PARTIAL_CONTENT;
- r->range = apr_array_pstrcat(r->pool, merged, ',');
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
- "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
- it, r->range, overlaps, reversals, clength);
-
- return num_ranges;
-}