summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2021-02-25 21:42:59 -0500
committerGlenn Strauss <gstrauss@gluelogic.com>2021-03-02 10:14:25 -0500
commitcc35c03c3cf8791d00d526eebc2fa5d5f748aec1 (patch)
tree8d46f2fc2dcdf9844e324d1e2e6f8bf2413e48c6 /src
parentd68e639b7112004cbca381692385a16a1897b224 (diff)
downloadlighttpd-git-cc35c03c3cf8791d00d526eebc2fa5d5f748aec1.tar.gz
[core] RFC 7233 Range handling for non-streaming
RFC 7233 Range handling for all non-streaming responses, including (non-streaming) dynamic responses (previously Range responses handled only for static files)
Diffstat (limited to 'src')
-rw-r--r--src/http-header-glue.c280
-rw-r--r--src/response.c5
2 files changed, 5 insertions, 280 deletions
diff --git a/src/http-header-glue.c b/src/http-header-glue.c
index c2982951..b8c3fb9f 100644
--- a/src/http-header-glue.c
+++ b/src/http-header-glue.c
@@ -305,267 +305,9 @@ handler_t http_response_reqbody_read_error (request_st * const r, int http_statu
}
-static int http_response_coalesce_ranges (off_t * const ranges, int n)
-{
- /* coalesce/combine overlapping ranges and ranges separated by a
- * gap which is smaller than the overhead of sending multiple parts
- * (typically around 80 bytes) ([RFC7233] 4.1 206 Partial Content)
- * (ranges are known to be positive, so subtract 80 instead of add 80
- * to avoid any chance of integer overflow)
- * (max n should be limited in caller since a malicious set of ranges has
- * n^2 cost for the simplistic algorithm below)
- * (sorting the ranges and then combining would lower the cost, but the
- * cost should not be an issue since client should not send many ranges
- * and caller should restrict the max number of ranges to limit abuse)
- * [RFC7233] 4.1 206 Partial Content recommends:
- * When a multipart response payload is generated, the server SHOULD send
- * the parts in the same order that the corresponding byte-range-spec
- * appeared in the received Range header field, excluding those ranges
- * that were deemed unsatisfiable or that were coalesced into other ranges
- */
- for (int i = 0; i+2 < n; i += 2) {
- const off_t b = ranges[i];
- const off_t e = ranges[i+1];
- for (int j = i+2; j < n; j += 2) {
- /* common case: ranges do not overlap */
- if (b <= ranges[j] ? e < ranges[j]-80 : ranges[j+1] < b-80)
- continue;
- /* else ranges do overlap, so combine into first range */
- ranges[i] = b <= ranges[j] ? b : ranges[j];
- ranges[i+1] = e >= ranges[j+1] ? e : ranges[j+1];
- memmove(ranges+j, ranges+j+2, (n-j-2)*sizeof(off_t));
- /* restart outer loop from beginning */
- n -= 2;
- i = -2;
- break;
- }
- }
-
- return n;
-}
-
-
-static int http_response_parse_range(request_st * const r, stat_cache_entry * const sce, const char * const range) {
- int n = 0;
- int error;
- off_t start, end;
- const off_t st_size = sce->st.st_size;
- const char *s, *minus;
- static const char boundary[] = "fkj49sn38dcn3";
- const buffer *content_type =
- http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE,
- CONST_STR_LEN("Content-Type"));
- off_t ranges[16];
-
- start = 0;
- end = st_size - 1;
-
- for (s = range, error = 0;
- !error && *s && NULL != (minus = strchr(s, '-')); ) {
- char *err;
- off_t la = 0, le;
- *((const char **)&err) = s; /*(quiet clang --analyze)*/
-
- if (s != minus) {
- la = strtoll(s, &err, 10);
- if (err != minus) {
- /* should not have multiple range-unit in Range, but
- * handle just in case multiple Range headers merged */
- while (*s == ' ' || *s == '\t') ++s;
- if (0 != strncmp(s, "bytes=", 6)) return -1;
- s += 6;
- if (s != minus) {
- la = strtoll(s, &err, 10);
- if (err != minus) return -1;
- }
- }
- }
-
- if (s == minus) {
- /* -<stop> */
-
- le = strtoll(s, &err, 10);
-
- if (le == 0) {
- /* RFC 2616 - 14.35.1 */
-
- r->http_status = 416;
- error = 1;
- } else if (*err == '\0') {
- /* end */
- s = err;
-
- end = st_size - 1;
- start = st_size + le;
- } else if (*err == ',') {
- s = err + 1;
-
- end = st_size - 1;
- start = st_size + le;
- } else {
- error = 1;
- }
-
- } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
- /* <start>- */
-
- /* ok */
-
- if (*(err + 1) == '\0') {
- s = err + 1;
-
- end = st_size - 1;
- start = la;
-
- } else if (*(err + 1) == ',') {
- s = err + 2;
-
- end = st_size - 1;
- start = la;
- } else {
- error = 1;
- }
- } else {
- /* <start>-<stop> */
-
- le = strtoll(minus+1, &err, 10);
-
- /* RFC 2616 - 14.35.1 */
- if (la > le) {
- error = 1;
- }
-
- if (*err == '\0') {
- /* ok, end*/
- s = err;
-
- end = le;
- start = la;
- } else if (*err == ',') {
- s = err + 1;
-
- end = le;
- start = la;
- } else {
- /* error */
-
- error = 1;
- }
- }
-
- if (!error) {
- if (start < 0) start = 0;
-
- /* RFC 2616 - 14.35.1 */
- if (end > st_size - 1) end = st_size - 1;
-
- if (start > st_size - 1) {
- error = 1;
-
- r->http_status = 416;
- }
- }
-
- if (!error) {
- if (n < (int)(sizeof(ranges)/sizeof(*ranges))) {
- ranges[n] = start;
- ranges[n+1] = end;
- n += 2;
- }
- else { /* excessive num ranges in request */
- error = 1;
- r->http_status = 416;
- }
- }
- }
-
- /* something went wrong */
- if (error) return -1;
-
- if (n > 2) n = http_response_coalesce_ranges(ranges, n);
-
- for (int i = 0; i < n; i += 2) {
- start = ranges[i];
- end = ranges[i+1];
- if (n > 2) {
- /* write boundary-header */
- buffer *b = r->tmp_buf;
- buffer_copy_string_len(b, CONST_STR_LEN("\r\n--"));
- buffer_append_string_len(b, boundary, sizeof(boundary)-1);
-
- /* write Content-Range */
- buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes "));
- buffer_append_int(b, start);
- buffer_append_string_len(b, CONST_STR_LEN("-"));
- buffer_append_int(b, end);
- buffer_append_string_len(b, CONST_STR_LEN("/"));
- buffer_append_int(b, st_size);
-
- if (content_type) {
- buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: "));
- buffer_append_string_buffer(b, content_type);
- }
-
- /* write END-OF-HEADER */
- buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
- http_chunk_append_mem(r, CONST_BUF_LEN(b));
- }
-
- http_chunk_append_file_ref_range(r, sce, start, end - start + 1);
- }
-
- buffer * const tb = r->tmp_buf;
-
- if (n > 2) {
- /* add boundary end */
- buffer_copy_string_len(tb, "\r\n--", 4);
- buffer_append_string_len(tb, boundary, sizeof(boundary)-1);
- buffer_append_string_len(tb, "--\r\n", 4);
- http_chunk_append_mem(r, CONST_BUF_LEN(tb));
-
- /* set header-fields */
-
- buffer_copy_string_len(tb, CONST_STR_LEN("multipart/byteranges; boundary="));
- buffer_append_string_len(tb, boundary, sizeof(boundary)-1);
-
- /* overwrite content-type */
- http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE,
- CONST_STR_LEN("Content-Type"),
- CONST_BUF_LEN(tb));
- } else {
- /* add Content-Range-header */
-
- buffer_copy_string_len(tb, CONST_STR_LEN("bytes "));
- buffer_append_int(tb, start);
- buffer_append_string_len(tb, CONST_STR_LEN("-"));
- buffer_append_int(tb, end);
- buffer_append_string_len(tb, CONST_STR_LEN("/"));
- buffer_append_int(tb, st_size);
-
- http_header_response_set(r, HTTP_HEADER_CONTENT_RANGE,
- CONST_STR_LEN("Content-Range"),
- CONST_BUF_LEN(tb));
- }
-
- /* ok, the file is set-up */
- return 0;
-}
-
-
-__attribute_pure__
-static int http_response_match_if_range(request_st * const r, const buffer * const mtime) {
- const buffer *vb = http_header_request_get(r, HTTP_HEADER_IF_RANGE,
- CONST_STR_LEN("If-Range"));
- return NULL == vb
- || ((vb->ptr[0] == '"')
- ? buffer_is_equal(vb, &r->physical.etag) /*compare ETag ("...") */
- : mtime && buffer_is_equal(vb, mtime)); /*compare Last-Modified*/
-}
-
void http_response_send_file (request_st * const r, buffer * const path) {
stat_cache_entry * const sce = stat_cache_get_entry_open(path, r->conf.follow_symlink);
const buffer *mtime = NULL;
- const buffer *vb;
int allow_caching = (0 == r->http_status || 200 == r->http_status);
if (NULL == sce) {
@@ -630,15 +372,6 @@ void http_response_send_file (request_st * const r, buffer * const path) {
}
}
- if (!http_method_get_or_head(r->http_method)
- || r->http_version < HTTP_VERSION_1_1)
- r->conf.range_requests = 0;
- if (r->conf.range_requests) {
- http_header_response_append(r, HTTP_HEADER_ACCEPT_RANGES,
- CONST_STR_LEN("Accept-Ranges"),
- CONST_STR_LEN("bytes"));
- }
-
if (allow_caching) {
if (!light_btst(r->resp_htags, HTTP_HEADER_ETAG)
&& 0 != r->conf.etag_flags) {
@@ -674,19 +407,6 @@ void http_response_send_file (request_st * const r, buffer * const path) {
return;
}
- if (r->conf.range_requests
- && (200 == r->http_status || 0 == r->http_status)
- && NULL != (vb = http_header_request_get(r, HTTP_HEADER_RANGE,
- CONST_STR_LEN("Range")))
- && !light_btst(r->resp_htags, HTTP_HEADER_CONTENT_ENCODING)
- && http_response_match_if_range(r, mtime) /* "If-Range" */
- && !buffer_string_is_empty(vb) && 0 == strncmp(vb->ptr, "bytes=", 6)) {
- r->resp_body_finished = 1;
- if (0 == http_response_parse_range(r, sce, vb->ptr+6))
- r->http_status = 206;
- return;
- }
-
/* if we are still here, prepare body */
/* we add it here for all requests
diff --git a/src/response.c b/src/response.c
index d243915d..7da19b11 100644
--- a/src/response.c
+++ b/src/response.c
@@ -11,6 +11,7 @@
#include "chunk.h"
#include "http_chunk.h"
#include "http_date.h"
+#include "http_range.h"
#include "plugin.h"
@@ -868,6 +869,10 @@ http_response_write_prepare(request_st * const r)
}
if (r->resp_body_finished) {
+ /* check for Range request (current impl requires resp_body_finished) */
+ if (r->conf.range_requests && http_range_rfc7233(r) >= 400)
+ http_response_static_errdoc(r); /* 416 Range Not Satisfiable */
+
/* set content-length if length is known and not already set */
if (!(r->resp_htags
& (light_bshift(HTTP_HEADER_CONTENT_LENGTH)