1d68e639bSGlenn Strauss /*
2d68e639bSGlenn Strauss * http_range - HTTP Range (RFC 7233)
3d68e639bSGlenn Strauss *
4d68e639bSGlenn Strauss * Copyright(c) 2015,2021 Glenn Strauss gstrauss()gluelogic.com All rights reserved
5d68e639bSGlenn Strauss * License: BSD 3-clause (same as lighttpd)
6d68e639bSGlenn Strauss */
7d68e639bSGlenn Strauss #include "http_range.h"
8d68e639bSGlenn Strauss
9d68e639bSGlenn Strauss #include <limits.h>
10d68e639bSGlenn Strauss #include <stdlib.h> /* strtol(), strtoll() */
11d68e639bSGlenn Strauss #include <string.h> /* memmove() */
12d68e639bSGlenn Strauss
13d68e639bSGlenn Strauss #include "buffer.h"
14d68e639bSGlenn Strauss #include "chunk.h"
15d68e639bSGlenn Strauss #include "http_header.h"
16d68e639bSGlenn Strauss #include "request.h"
17d68e639bSGlenn Strauss
18d68e639bSGlenn Strauss /* arbitrary limit for max num ranges (additional ranges are ignored) */
19d68e639bSGlenn Strauss #undef RMAX
20d68e639bSGlenn Strauss #define RMAX 10
21d68e639bSGlenn Strauss
22d68e639bSGlenn Strauss /* RFC 7233 Hypertext Transfer Protocol (HTTP/1.1): Range Requests
23d68e639bSGlenn Strauss * https://tools.ietf.org/html/rfc7233
24d68e639bSGlenn Strauss * Range requests are an OPTIONAL feature of HTTP, designed so that recipients
25d68e639bSGlenn Strauss * not implementing this feature (or not supporting it for the target resource)
26d68e639bSGlenn Strauss * can respond as if it is a normal GET request without impacting
27d68e639bSGlenn Strauss * interoperability. */
28d68e639bSGlenn Strauss
29d68e639bSGlenn Strauss
3025f83b84SGlenn Strauss /* default: ignore Range with HTTP/1.0 requests */
3125f83b84SGlenn Strauss static int http_range_allow_http10;
http_range_config_allow_http10(int flag)3225f83b84SGlenn Strauss void http_range_config_allow_http10 (int flag)
3325f83b84SGlenn Strauss {
3425f83b84SGlenn Strauss http_range_allow_http10 = flag;
3525f83b84SGlenn Strauss }
3625f83b84SGlenn Strauss
3725f83b84SGlenn Strauss
38d68e639bSGlenn Strauss __attribute_noinline__
39d68e639bSGlenn Strauss static int
http_range_coalesce(off_t * const restrict ranges,int n)40d68e639bSGlenn Strauss http_range_coalesce (off_t * const restrict ranges, int n)
41d68e639bSGlenn Strauss {
42d68e639bSGlenn Strauss /* coalesce/combine overlapping ranges and ranges separated by a
43d68e639bSGlenn Strauss * gap which is smaller than the overhead of sending multiple parts
44d68e639bSGlenn Strauss * (typically around 80 bytes) ([RFC7233] 4.1 206 Partial Content)
45d68e639bSGlenn Strauss * (ranges are known to be positive, so subtract 80 instead of add 80
46d68e639bSGlenn Strauss * to avoid any chance of integer overflow)
47d68e639bSGlenn Strauss * (n is limited to RMAX ranges (RMAX pairs of off_t) since a malicious set
48d68e639bSGlenn Strauss * of ranges has n^2 cost for this simplistic algorithm)
49d68e639bSGlenn Strauss * (sorting the ranges and then combining would lower the cost, but the
50d68e639bSGlenn Strauss * cost should not be an issue since client should not send many ranges
51d68e639bSGlenn Strauss * and we restrict the max number of ranges to limit abuse)
52d68e639bSGlenn Strauss * [RFC7233] 4.1 206 Partial Content recommends:
53d68e639bSGlenn Strauss * When a multipart response payload is generated, the server SHOULD send
54d68e639bSGlenn Strauss * the parts in the same order that the corresponding byte-range-spec
55d68e639bSGlenn Strauss * appeared in the received Range header field, excluding those ranges
56d68e639bSGlenn Strauss * that were deemed unsatisfiable or that were coalesced into other ranges
57d68e639bSGlenn Strauss */
58d68e639bSGlenn Strauss for (int i = 0; i+2 < n; i += 2) {
59d68e639bSGlenn Strauss const off_t b = ranges[i];
60d68e639bSGlenn Strauss const off_t e = ranges[i+1];
61d68e639bSGlenn Strauss for (int j = i+2; j < n; j += 2) {
62d68e639bSGlenn Strauss /* common case: ranges do not overlap */
63d68e639bSGlenn Strauss if (b <= ranges[j] ? e < ranges[j]-80 : ranges[j+1] < b-80)
64d68e639bSGlenn Strauss continue;
65d68e639bSGlenn Strauss /* else ranges do overlap, so combine into first range */
66d68e639bSGlenn Strauss ranges[i] = b <= ranges[j] ? b : ranges[j];
67d68e639bSGlenn Strauss ranges[i+1] = e >= ranges[j+1] ? e : ranges[j+1];
68d68e639bSGlenn Strauss memmove(ranges+j, ranges+j+2, (n-j-2)*sizeof(off_t));
69d68e639bSGlenn Strauss /* restart outer loop from beginning */
70d68e639bSGlenn Strauss n -= 2;
71d68e639bSGlenn Strauss i = -2;
72d68e639bSGlenn Strauss break;
73d68e639bSGlenn Strauss }
74d68e639bSGlenn Strauss }
75d68e639bSGlenn Strauss
76d68e639bSGlenn Strauss return n;
77d68e639bSGlenn Strauss }
78d68e639bSGlenn Strauss
79d68e639bSGlenn Strauss
80d68e639bSGlenn Strauss static const char *
http_range_parse_next(const char * restrict s,const off_t len,off_t * const restrict ranges)81d68e639bSGlenn Strauss http_range_parse_next (const char * restrict s, const off_t len,
82d68e639bSGlenn Strauss off_t * const restrict ranges)
83d68e639bSGlenn Strauss {
84e5f9e94dSGlenn Strauss /*(caller must check returned ranges[1] != -1, or else range was invalid)*/
85d68e639bSGlenn Strauss
86d68e639bSGlenn Strauss /*assert(len > 0);*//*(caller must ensure len > 0)*/
87d68e639bSGlenn Strauss char *e;
88d68e639bSGlenn Strauss off_t n = strtoll(s, &e, 10);
89d68e639bSGlenn Strauss ranges[1] = -1; /* invalid */
90d68e639bSGlenn Strauss if (n >= 0) {
91d68e639bSGlenn Strauss if (n != LLONG_MAX && n < len && s != e) {
92d68e639bSGlenn Strauss ranges[0] = n;
93d68e639bSGlenn Strauss while (*e == ' ' || *e == '\t') ++e;
94d68e639bSGlenn Strauss if (*e == '-') {
95d68e639bSGlenn Strauss n = strtoll((s = e+1), &e, 10);
96d68e639bSGlenn Strauss if (s == e || (n == 0 && e[-1] != '0'))
97d68e639bSGlenn Strauss ranges[1] = len-1;
98d68e639bSGlenn Strauss else if (ranges[0] <= n && n != LLONG_MAX)
99d68e639bSGlenn Strauss ranges[1] = n < len ? n : len-1;
100d68e639bSGlenn Strauss }
101d68e639bSGlenn Strauss }
102d68e639bSGlenn Strauss }
103d68e639bSGlenn Strauss else if (n != LLONG_MIN) {
104d68e639bSGlenn Strauss ranges[0] = len > -n ? len + n : 0;/*('n' is negative here)*/
105d68e639bSGlenn Strauss ranges[1] = len-1;
106d68e639bSGlenn Strauss }
107d68e639bSGlenn Strauss while (*e == ' ' || *e == '\t') ++e;
108d68e639bSGlenn Strauss return e; /* ',' or '\0' or else invalid char in range request */
109d68e639bSGlenn Strauss }
110d68e639bSGlenn Strauss
111d68e639bSGlenn Strauss
112d68e639bSGlenn Strauss static int
http_range_parse(const char * restrict s,const off_t content_length,off_t ranges[RMAX * 2])113d68e639bSGlenn Strauss http_range_parse (const char * restrict s, const off_t content_length,
114d68e639bSGlenn Strauss off_t ranges[RMAX*2])
115d68e639bSGlenn Strauss {
116d68e639bSGlenn Strauss /* [RFC7233] 2.1 Byte Ranges
117d68e639bSGlenn Strauss * If a valid byte-range-set includes at least one byte-range-spec
118d68e639bSGlenn Strauss * with a first-byte-pos that is less than the current length of
119d68e639bSGlenn Strauss * the representation, or at least one suffix-byte-range-spec with
120d68e639bSGlenn Strauss * a non-zero suffix-length, then the byte-range-set is satisfiable.
121d68e639bSGlenn Strauss * Otherwise, the byte-range-set is unsatisfiable.
122d68e639bSGlenn Strauss *
123d68e639bSGlenn Strauss * [RFC7233] 3.1 Range
124d68e639bSGlenn Strauss * A server that supports range requests MAY ignore or reject a Range
125d68e639bSGlenn Strauss * header field that consists of more than two overlapping ranges, or a
126d68e639bSGlenn Strauss * set of many small ranges that are not listed in ascending order,
127d68e639bSGlenn Strauss * since both are indications of either a broken client or a deliberate
128d68e639bSGlenn Strauss * denial-of-service attack (Section 6.1). A client SHOULD NOT request
129d68e639bSGlenn Strauss * multiple ranges that are inherently less efficient to process and
130d68e639bSGlenn Strauss * transfer than a single range that encompasses the same data.
131d68e639bSGlenn Strauss */
132d68e639bSGlenn Strauss int n = 0;
133d68e639bSGlenn Strauss do {
134d68e639bSGlenn Strauss s = http_range_parse_next(s, content_length, ranges+n);
135d68e639bSGlenn Strauss if ((*s == '\0' || *s == ',') && ranges[n+1] != -1)
136d68e639bSGlenn Strauss n += 2;
137d68e639bSGlenn Strauss else
138d68e639bSGlenn Strauss while (*s != '\0' && *s != ',') ++s; /*ignore invalid ranges*/
139d68e639bSGlenn Strauss } while (*s++ != '\0' && n < RMAX*2);
140d68e639bSGlenn Strauss /*(*s++ for multipart, increment to char after ',')*/
141d68e639bSGlenn Strauss
142d68e639bSGlenn Strauss if (n <= 2)
143d68e639bSGlenn Strauss return n;
144d68e639bSGlenn Strauss
145d68e639bSGlenn Strauss /* error if n == 0 (no valid ranges)
146d68e639bSGlenn Strauss * (if n == RMAX*2 (additional ranges > RMAX limit, if any, were ignored))*/
147d68e639bSGlenn Strauss return http_range_coalesce(ranges, n);
148d68e639bSGlenn Strauss }
149d68e639bSGlenn Strauss
150d68e639bSGlenn Strauss
151d68e639bSGlenn Strauss static void
http_range_single(request_st * const r,const off_t ranges[2])152d68e639bSGlenn Strauss http_range_single (request_st * const r, const off_t ranges[2])
153d68e639bSGlenn Strauss {
154d68e639bSGlenn Strauss /* caller already checked: n == 2, content_length > 0, ranges valid */
155d68e639bSGlenn Strauss chunkqueue * const restrict cq = &r->write_queue;
156d68e639bSGlenn Strauss const off_t complete_length = chunkqueue_length(cq);
157d68e639bSGlenn Strauss /* add Content-Range header */
158d68e639bSGlenn Strauss /*(large enough for "bytes X-X/X" with 3 huge numbers)*/
159d68e639bSGlenn Strauss uint32_t len = sizeof("bytes ")-1;
160d68e639bSGlenn Strauss char cr[72] = "bytes ";
161d68e639bSGlenn Strauss len += (uint32_t)li_itostrn(cr+len, sizeof(cr)-len, ranges[0]);
162d68e639bSGlenn Strauss cr[len++] = '-';
163d68e639bSGlenn Strauss len += (uint32_t)li_itostrn(cr+len, sizeof(cr)-len, ranges[1]);
164d68e639bSGlenn Strauss cr[len++] = '/';
165d68e639bSGlenn Strauss len += (uint32_t)li_itostrn(cr+len, sizeof(cr)-len, complete_length);
166d68e639bSGlenn Strauss http_header_response_set(r, HTTP_HEADER_CONTENT_RANGE,
167d68e639bSGlenn Strauss CONST_STR_LEN("Content-Range"), cr, len);
168d68e639bSGlenn Strauss if (cq->first == cq->last) { /* single chunk in cq */
169d68e639bSGlenn Strauss /* consume from cq to start of range, truncate after end of range */
170d68e639bSGlenn Strauss if (ranges[0]) {
171d68e639bSGlenn Strauss chunkqueue_mark_written(cq, ranges[0]);
172d68e639bSGlenn Strauss cq->bytes_out -= ranges[0];
173d68e639bSGlenn Strauss cq->bytes_in -= ranges[0];
174d68e639bSGlenn Strauss }
175d68e639bSGlenn Strauss cq->bytes_in -= complete_length - (ranges[1] + 1);
176d68e639bSGlenn Strauss chunk * const c = cq->first;
177d68e639bSGlenn Strauss if (c->type == FILE_CHUNK)
178d68e639bSGlenn Strauss c->file.length = c->offset + ranges[1] - ranges[0] + 1;
179d68e639bSGlenn Strauss else /*(c->type == MEM_CHUNK)*/
180d68e639bSGlenn Strauss c->mem->used = c->offset + ranges[1] - ranges[0] + 1 + 1;
181d68e639bSGlenn Strauss }
182d68e639bSGlenn Strauss else {
183d68e639bSGlenn Strauss /* transfer contents to temporary cq, then transfer range back */
184d68e639bSGlenn Strauss chunkqueue tq;
185d68e639bSGlenn Strauss memset(&tq, 0, sizeof(tq));
186d68e639bSGlenn Strauss chunkqueue_steal(&tq, cq, complete_length);
187d68e639bSGlenn Strauss cq->bytes_out -= complete_length;
188d68e639bSGlenn Strauss cq->bytes_in -= complete_length;
189d68e639bSGlenn Strauss chunkqueue_mark_written(&tq, ranges[0]);
190d68e639bSGlenn Strauss chunkqueue_steal(cq, &tq, ranges[1] - ranges[0] + 1);
191d68e639bSGlenn Strauss chunkqueue_reset(&tq);
192d68e639bSGlenn Strauss }
193d68e639bSGlenn Strauss }
194d68e639bSGlenn Strauss
195d68e639bSGlenn Strauss
196d68e639bSGlenn Strauss __attribute_cold__
197d68e639bSGlenn Strauss static void
http_range_multi(request_st * const r,const off_t ranges[RMAX * 2],const int n)198d68e639bSGlenn Strauss http_range_multi (request_st * const r,
199d68e639bSGlenn Strauss const off_t ranges[RMAX*2], const int n)
200d68e639bSGlenn Strauss {
201d68e639bSGlenn Strauss /* multiple ranges that are not ordered are not expected to be common,
202d68e639bSGlenn Strauss * so those scenarios is not optimized here */
203d68e639bSGlenn Strauss #define HTTP_MULTIPART_BOUNDARY "fkj49sn38dcn3"
204d68e639bSGlenn Strauss static const char boundary_prefix[] =
205d68e639bSGlenn Strauss "\r\n--" HTTP_MULTIPART_BOUNDARY;
206d68e639bSGlenn Strauss static const char boundary_end[] =
207d68e639bSGlenn Strauss "\r\n--" HTTP_MULTIPART_BOUNDARY "--\r\n";
208d68e639bSGlenn Strauss static const char multipart_type[] =
209d68e639bSGlenn Strauss "multipart/byteranges; boundary=" HTTP_MULTIPART_BOUNDARY;
210d68e639bSGlenn Strauss
211d68e639bSGlenn Strauss buffer * const tb = r->tmp_buf;
212d68e639bSGlenn Strauss buffer_copy_string_len(tb, CONST_STR_LEN(boundary_prefix));
213d68e639bSGlenn Strauss const buffer * const content_type =
214d68e639bSGlenn Strauss http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE,
215d68e639bSGlenn Strauss CONST_STR_LEN("Content-Type"));
216d68e639bSGlenn Strauss if (content_type) {
217dc01487eSGlenn Strauss buffer_append_str2(tb, CONST_STR_LEN("\r\nContent-Type: "),
218af3df29aSGlenn Strauss BUF_PTR_LEN(content_type));
219d68e639bSGlenn Strauss }
220d68e639bSGlenn Strauss buffer_append_string_len(tb,CONST_STR_LEN("\r\nContent-Range: bytes "));
221af3df29aSGlenn Strauss const uint32_t prefix_len = buffer_clen(tb);
222d68e639bSGlenn Strauss
223d68e639bSGlenn Strauss http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE,
224d68e639bSGlenn Strauss CONST_STR_LEN("Content-Type"),
225d68e639bSGlenn Strauss CONST_STR_LEN(multipart_type));
226d68e639bSGlenn Strauss
227d68e639bSGlenn Strauss /* caller already checked: n > 2, content_length > 0, ranges valid */
228d68e639bSGlenn Strauss chunkqueue * const restrict cq = &r->write_queue;
229d68e639bSGlenn Strauss const off_t complete_length = chunkqueue_length(cq);
230d68e639bSGlenn Strauss
231d68e639bSGlenn Strauss /* copy chunks for ranges to end of cq, then consume original chunks
232d68e639bSGlenn Strauss *
233d68e639bSGlenn Strauss * future: if ranges ordered, could use technique in http_range_single(),
234d68e639bSGlenn Strauss * but [RFC7233] 4.1 206 Partial Content recommends:
235d68e639bSGlenn Strauss * When a multipart response payload is generated, the server SHOULD send
236d68e639bSGlenn Strauss * the parts in the same order that the corresponding byte-range-spec
237d68e639bSGlenn Strauss * appeared in the received Range header field, excluding those ranges
238d68e639bSGlenn Strauss * that were deemed unsatisfiable or that were coalesced into other ranges
239d68e639bSGlenn Strauss * and this code path is not expected to be hot, and so not optimized.
240d68e639bSGlenn Strauss */
241d68e639bSGlenn Strauss
242d68e639bSGlenn Strauss chunk * const c = (cq->first == cq->last && cq->first->type == MEM_CHUNK)
243d68e639bSGlenn Strauss ? cq->first
244d68e639bSGlenn Strauss : NULL;
245d68e639bSGlenn Strauss for (int i = 0; i < n; i += 2) {
246d68e639bSGlenn Strauss /* generate boundary-header including Content-Type and Content-Range */
247af3df29aSGlenn Strauss buffer_truncate(tb, prefix_len);
248d68e639bSGlenn Strauss buffer_append_int(tb, ranges[i]);
249f2610d23SGlenn Strauss buffer_append_char(tb, '-');
250d68e639bSGlenn Strauss buffer_append_int(tb, ranges[i+1]);
251f2610d23SGlenn Strauss buffer_append_char(tb, '/');
252d68e639bSGlenn Strauss buffer_append_int(tb, complete_length);
253d68e639bSGlenn Strauss buffer_append_string_len(tb, CONST_STR_LEN("\r\n\r\n"));
254d68e639bSGlenn Strauss if (c) /* single MEM_CHUNK in original cq; not using mem_min */
255af3df29aSGlenn Strauss chunkqueue_append_mem(cq, BUF_PTR_LEN(tb));
256d68e639bSGlenn Strauss else
257af3df29aSGlenn Strauss chunkqueue_append_mem_min(cq, BUF_PTR_LEN(tb));
258d68e639bSGlenn Strauss
259d68e639bSGlenn Strauss chunkqueue_append_cq_range(cq, cq, ranges[i],
260d68e639bSGlenn Strauss ranges[i+1] - ranges[i] + 1);
261d68e639bSGlenn Strauss }
262d68e639bSGlenn Strauss
263d68e639bSGlenn Strauss /* add boundary end */
264d68e639bSGlenn Strauss chunkqueue_append_mem_min(cq, CONST_STR_LEN(boundary_end));
265d68e639bSGlenn Strauss
266d68e639bSGlenn Strauss /* remove initial chunk(s), since duplicated into multipart ranges */
267d68e639bSGlenn Strauss /* remove initial "\r\n" in front of first boundary string */
268d68e639bSGlenn Strauss chunkqueue_mark_written(cq, complete_length+2);
269d68e639bSGlenn Strauss cq->bytes_out -= complete_length+2;
270d68e639bSGlenn Strauss cq->bytes_in -= complete_length+2;
271d68e639bSGlenn Strauss }
272d68e639bSGlenn Strauss
273d68e639bSGlenn Strauss
274d68e639bSGlenn Strauss __attribute_cold__
275d68e639bSGlenn Strauss static int
http_range_not_satisfiable(request_st * const r,const off_t content_length)276d68e639bSGlenn Strauss http_range_not_satisfiable (request_st * const r, const off_t content_length)
277d68e639bSGlenn Strauss {
278d68e639bSGlenn Strauss /*(large enough for "bytes '*'/X" with 1 huge number)*/
279d68e639bSGlenn Strauss uint32_t len = sizeof("bytes */")-1;
280d68e639bSGlenn Strauss char cr[32] = "bytes */";
281d68e639bSGlenn Strauss len += (uint32_t)li_itostrn(cr+len, sizeof(cr)-len, content_length);
282d68e639bSGlenn Strauss http_header_response_set(r, HTTP_HEADER_CONTENT_RANGE,
283d68e639bSGlenn Strauss CONST_STR_LEN("Content-Range"), cr, len);
284d68e639bSGlenn Strauss r->handler_module = NULL;
285d68e639bSGlenn Strauss return (r->http_status = 416); /* Range Not Satisfiable */
286d68e639bSGlenn Strauss }
287d68e639bSGlenn Strauss
288d68e639bSGlenn Strauss
289d68e639bSGlenn Strauss __attribute_noinline__
290d68e639bSGlenn Strauss static int
http_range_process(request_st * const r,const buffer * const http_range)291d68e639bSGlenn Strauss http_range_process (request_st * const r, const buffer * const http_range)
292d68e639bSGlenn Strauss {
293d68e639bSGlenn Strauss const off_t content_length = chunkqueue_length(&r->write_queue);
294d68e639bSGlenn Strauss if (0 == content_length) /*(implementation detail; see comment at top)*/
295d68e639bSGlenn Strauss return r->http_status; /* skip Range handling if empty payload */
296d68e639bSGlenn Strauss /* future: might skip Range if content_length is below threshold, e.g. 1k */
297d68e639bSGlenn Strauss
298d68e639bSGlenn Strauss /* An origin server MUST ignore a Range header field that contains a
299d68e639bSGlenn Strauss * range unit it does not understand. */
300361b9647SGlenn Strauss if (buffer_clen(http_range) < sizeof("bytes=")-1
301361b9647SGlenn Strauss || !buffer_eq_icase_ssn(http_range->ptr, "bytes=", sizeof("bytes=")-1))
302d68e639bSGlenn Strauss return r->http_status; /* 200 OK */
303d68e639bSGlenn Strauss
304d68e639bSGlenn Strauss /* arbitrary limit: support up to RMAX ranges in request Range field
305d68e639bSGlenn Strauss * (validating and coalescing overlapping ranges is not a linear algorithm)
306d68e639bSGlenn Strauss * (use RMAX pairs of off_t to indicate too many ranges ( >= RMAX*2)) */
307d68e639bSGlenn Strauss off_t ranges[RMAX*2];
308d68e639bSGlenn Strauss const int n = http_range_parse(http_range->ptr+sizeof("bytes=")-1,
309d68e639bSGlenn Strauss content_length, ranges);
310d68e639bSGlenn Strauss
311d68e639bSGlenn Strauss /* checked above: content_length > 0, ranges valid */
312d68e639bSGlenn Strauss if (2 == n) /* single range */
313d68e639bSGlenn Strauss http_range_single(r, ranges);
314d68e639bSGlenn Strauss else if (0 == n) /* 416 Range Not Satisfiable */
315d68e639bSGlenn Strauss return http_range_not_satisfiable(r, content_length);
316d68e639bSGlenn Strauss else /* multipart ranges */
317d68e639bSGlenn Strauss http_range_multi(r, ranges, n);
318d68e639bSGlenn Strauss
319d68e639bSGlenn Strauss /*(must either set Content-Length or unset prior value, if any)*/
32026f354cbSGlenn Strauss buffer_append_int(
32126f354cbSGlenn Strauss http_header_response_set_ptr(r, HTTP_HEADER_CONTENT_LENGTH,
32226f354cbSGlenn Strauss CONST_STR_LEN("Content-Length")),
32326f354cbSGlenn Strauss chunkqueue_length(&r->write_queue));
324d68e639bSGlenn Strauss
325d68e639bSGlenn Strauss return (r->http_status = 206); /* 206 Partial Content */
326d68e639bSGlenn Strauss }
327d68e639bSGlenn Strauss
328d68e639bSGlenn Strauss
329d68e639bSGlenn Strauss int
http_range_rfc7233(request_st * const r)330d68e639bSGlenn Strauss http_range_rfc7233 (request_st * const r)
331d68e639bSGlenn Strauss {
332d68e639bSGlenn Strauss const int http_status = r->http_status;
333d68e639bSGlenn Strauss
334d68e639bSGlenn Strauss /* implementation limitation:
335d68e639bSGlenn Strauss * limit range handling to when we have complete response
336d68e639bSGlenn Strauss * (future: might extend this to streamed files if Content-Length known)
337d68e639bSGlenn Strauss * (otherwise, might be unable to validate Range before send response header
338d68e639bSGlenn Strauss * e.g. unable to handle suffix-byte-range-spec without entity length)*/
339d68e639bSGlenn Strauss if (!r->resp_body_finished)
340d68e639bSGlenn Strauss return http_status;
341d68e639bSGlenn Strauss
342d68e639bSGlenn Strauss /* limit range handling to 200 responses
343d68e639bSGlenn Strauss * [RFC7233] 3.1 Range
344d68e639bSGlenn Strauss * The Range header field is evaluated after evaluating the precondition
345d68e639bSGlenn Strauss * header fields defined in [RFC7232], and only if the result in absence
346d68e639bSGlenn Strauss * of the Range header field would be a 200 (OK) response. */
347d68e639bSGlenn Strauss if (200 != http_status)
348d68e639bSGlenn Strauss return http_status;
349d68e639bSGlenn Strauss /* limit range handling to GET and HEAD (further limited below to GET)
350d68e639bSGlenn Strauss * [RFC7233] 3.1 Range
351d68e639bSGlenn Strauss * A server MUST ignore a Range header field received with a request
352d68e639bSGlenn Strauss * method other than GET.
353d68e639bSGlenn Strauss */
354d68e639bSGlenn Strauss if (!http_method_get_or_head(r->http_method))
355d68e639bSGlenn Strauss return http_status;
356d68e639bSGlenn Strauss /* no "Range" in HTTP/1.0 */
357d68e639bSGlenn Strauss if (r->http_version < HTTP_VERSION_1_1)
35825f83b84SGlenn Strauss if (!http_range_allow_http10)
359d68e639bSGlenn Strauss return http_status;
360d68e639bSGlenn Strauss /* do not attempt to handle range if Transfer-Encoding already applied.
361d68e639bSGlenn Strauss * skip Range processing if Content-Encoding has already been applied,
362d68e639bSGlenn Strauss * since Range is on the unencoded content length and Content-Encoding
363d68e639bSGlenn Strauss * might change content. This includes if mod_deflate has applied
364d68e639bSGlenn Strauss * Content-Encoding. If Transfer-Encoding: gzip, chunked were used
365d68e639bSGlenn Strauss * instead (not done), then Range processing could safely take place, too,
366d68e639bSGlenn Strauss * (but mod_deflate would need to run from new hook to handle TE: gzip).
367d68e639bSGlenn Strauss * Alternatively, lighttpd.conf could be configured to disable mod_deflate
368d68e639bSGlenn Strauss * for Range requests:
369d68e639bSGlenn Strauss * $REQUEST_HEADER["Range"] != "" { deflate.mimetypes = () }
370d68e639bSGlenn Strauss */
371d68e639bSGlenn Strauss if ((r->resp_htags
372d68e639bSGlenn Strauss & (light_bshift(HTTP_HEADER_TRANSFER_ENCODING)
373d68e639bSGlenn Strauss |light_bshift(HTTP_HEADER_CONTENT_ENCODING))))
374d68e639bSGlenn Strauss return http_status;
375d68e639bSGlenn Strauss #if 0 /*(if Range already handled, HTTP status expected to be 206, not 200)*/
376d68e639bSGlenn Strauss /* check if Range request already handled (single range or multipart)*/
377d68e639bSGlenn Strauss if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_RANGE))
378d68e639bSGlenn Strauss return http_status;
379d68e639bSGlenn Strauss const buffer * const content_type =
380d68e639bSGlenn Strauss http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE,
381d68e639bSGlenn Strauss CONST_STR_LEN("Content-Type"));
382d68e639bSGlenn Strauss if (content_type
383361b9647SGlenn Strauss && buffer_clen(content_type) >= sizeof("multipart/byteranges")-1
384361b9647SGlenn Strauss && buffer_eq_icase_ssn(content_type->ptr, "multipart/byteranges",
385d68e639bSGlenn Strauss sizeof("multipart/byteranges")-1))
386d68e639bSGlenn Strauss return http_status;
387d68e639bSGlenn Strauss #endif
388d68e639bSGlenn Strauss
389d68e639bSGlenn Strauss /* optional: advertise Accept-Ranges: bytes
390d68e639bSGlenn Strauss * Even if "Accept-Ranges: bytes" is not given,
391d68e639bSGlenn Strauss * [RFC7233] 2.3 Accept-Ranges
392d68e639bSGlenn Strauss * A client MAY generate range requests without having received this
393d68e639bSGlenn Strauss * header for the resource involved.
394d68e639bSGlenn Strauss */
395d68e639bSGlenn Strauss if (!light_btst(r->resp_htags, HTTP_HEADER_ACCEPT_RANGES))
396d68e639bSGlenn Strauss http_header_response_set(r, HTTP_HEADER_ACCEPT_RANGES,
397d68e639bSGlenn Strauss CONST_STR_LEN("Accept-Ranges"),
398d68e639bSGlenn Strauss CONST_STR_LEN("bytes"));
399d68e639bSGlenn Strauss else {
400d68e639bSGlenn Strauss const buffer * const accept_ranges =
401d68e639bSGlenn Strauss http_header_response_get(r, HTTP_HEADER_ACCEPT_RANGES,
402d68e639bSGlenn Strauss CONST_STR_LEN("Accept-Ranges"));
4037138de92SGlenn Strauss #ifdef __COVERITY__
4047138de92SGlenn Strauss force_assert(accept_ranges); /*(r->resp_htags checked above)*/
4057138de92SGlenn Strauss #endif
406d68e639bSGlenn Strauss if (buffer_eq_slen(accept_ranges, CONST_STR_LEN("none")))
407d68e639bSGlenn Strauss return http_status;
408d68e639bSGlenn Strauss }
409d68e639bSGlenn Strauss
410d68e639bSGlenn Strauss /* limit range handling to GET
411d68e639bSGlenn Strauss * [RFC7233] 3.1 Range
412d68e639bSGlenn Strauss * A server MUST ignore a Range header field received with a request
413d68e639bSGlenn Strauss * method other than GET.
414d68e639bSGlenn Strauss */
415d68e639bSGlenn Strauss if (r->http_method != HTTP_METHOD_GET)
416d68e639bSGlenn Strauss return http_status;
417d68e639bSGlenn Strauss
418d68e639bSGlenn Strauss /* check for Range request */
419d68e639bSGlenn Strauss const buffer * const http_range =
420d68e639bSGlenn Strauss http_header_request_get(r, HTTP_HEADER_RANGE, CONST_STR_LEN("Range"));
421d68e639bSGlenn Strauss if (!http_range)
422d68e639bSGlenn Strauss return http_status;
423d68e639bSGlenn Strauss
424d68e639bSGlenn Strauss /* [RFC7233] 3.2 If-Range
425d68e639bSGlenn Strauss * If-Range = entity-tag / HTTP-date
426d68e639bSGlenn Strauss * A client MUST NOT generate an If-Range header field containing an
427d68e639bSGlenn Strauss * entity-tag that is marked as weak.
428d68e639bSGlenn Strauss * [...]
429d68e639bSGlenn Strauss * Note that this comparison by exact match, including when the validator
430d68e639bSGlenn Strauss * is an HTTP-date, differs from the "earlier than or equal to" comparison
431d68e639bSGlenn Strauss * used when evaluating an If-Unmodified-Since conditional. */
432d68e639bSGlenn Strauss if (light_btst(r->rqst_htags, HTTP_HEADER_IF_RANGE)) {
433d68e639bSGlenn Strauss const buffer * const if_range =
434d68e639bSGlenn Strauss http_header_request_get(r, HTTP_HEADER_IF_RANGE,
435d68e639bSGlenn Strauss CONST_STR_LEN("If-Range"));
4367138de92SGlenn Strauss #ifdef __COVERITY__
4377138de92SGlenn Strauss force_assert(if_range); /*(r->rqst_htags checked above)*/
4387138de92SGlenn Strauss #endif
439d68e639bSGlenn Strauss /* (weak ETag W/"<etag>" will not match Last-Modified) */
440d68e639bSGlenn Strauss const buffer * const cmp = (if_range->ptr[0] == '"')
441d68e639bSGlenn Strauss ? http_header_response_get(r, HTTP_HEADER_ETAG,
442d68e639bSGlenn Strauss CONST_STR_LEN("ETag"))
443d68e639bSGlenn Strauss : http_header_response_get(r, HTTP_HEADER_LAST_MODIFIED,
444d68e639bSGlenn Strauss CONST_STR_LEN("Last-Modified"));
445d68e639bSGlenn Strauss if (!cmp || !buffer_is_equal(if_range, cmp))
446d68e639bSGlenn Strauss return http_status;
447d68e639bSGlenn Strauss
448d68e639bSGlenn Strauss #if 0 /*(questionable utility; not deemed worthwhile)*/
449*25f5085aSGlenn Strauss /* (In a modular server, the following RFC recommendation might be
450d68e639bSGlenn Strauss * expensive and invasive to implement perfectly, so only making an
451d68e639bSGlenn Strauss * effort here to comply with known headers added within this routine
452d68e639bSGlenn Strauss * and within the purview of Range requests)
453d68e639bSGlenn Strauss * [RFC7233] 4.1 206 Partial Content
454d68e639bSGlenn Strauss * If a 206 is generated in response to a request with an If-Range
455d68e639bSGlenn Strauss * header field, the sender SHOULD NOT generate other representation
456d68e639bSGlenn Strauss * header fields beyond those required above, because the client is
457d68e639bSGlenn Strauss * understood to already have a prior response containing those header
458d68e639bSGlenn Strauss * fields.
459d68e639bSGlenn Strauss */
460d68e639bSGlenn Strauss http_header_response_unset(r, HTTP_HEADER_ACCEPT_RANGES,
461d68e639bSGlenn Strauss CONST_STR_LEN("Accept-Ranges"));
462d68e639bSGlenn Strauss #endif
463d68e639bSGlenn Strauss }
464d68e639bSGlenn Strauss
465d68e639bSGlenn Strauss return http_range_process(r, http_range);
466d68e639bSGlenn Strauss }
467