87 // sets last-modified, content-length and checks conditions |
89 // sets last-modified, content-length and checks conditions |
88 if(http_set_finfo(sn, rq, s) != REQ_PROCEED) { |
90 if(http_set_finfo(sn, rq, s) != REQ_PROCEED) { |
89 vfs_close(fd); |
91 vfs_close(fd); |
90 return NULL; |
92 return NULL; |
91 } |
93 } |
|
94 |
|
95 // TODO: check if vfs can seek |
|
96 pblock_kvinsert(pb_key_accept_ranges, "bytes", 5, rq->srvhdrs); |
92 |
97 |
93 // start response |
98 // start response |
94 protocol_status(sn, rq, 200, NULL); |
99 protocol_status(sn, rq, 200, NULL); |
|
100 |
|
101 return fd; |
|
102 } |
|
103 |
|
104 static void free_range(Session *sn, HttpRange *range) { |
|
105 HttpRange *elm = range; |
|
106 while(elm) { |
|
107 HttpRange *next = elm->next; |
|
108 pool_free(sn->pool, elm); |
|
109 elm = next; |
|
110 } |
|
111 } |
|
112 |
|
113 static HttpRange* parse_range(Session *sn, char *header, int *status) { |
|
114 *status = PROTOCOL_OK; |
|
115 |
|
116 sstr_t range = sstrtrim(sstr(header)); |
|
117 if(!sstrprefix(range, S("bytes="))) { |
|
118 // unknown range unit - ignore range header |
|
119 return NULL; |
|
120 } |
|
121 |
|
122 // get byte-range-set |
|
123 range = sstrsubs(range, 6); |
|
124 if(range.length < 1) { |
|
125 return NULL; |
|
126 } |
|
127 |
|
128 HttpRange *range_list = NULL; |
|
129 HttpRange *last = NULL; |
|
130 off_t begin = -1; |
|
131 int start = 0; |
|
132 int hasbegin = 0; |
|
133 for(int i=0;i<=range.length;i++) { |
|
134 char c = range.ptr[i]; |
|
135 if(c == '-') { |
|
136 sstr_t num = sstrsubsl(range, start, i-start); |
|
137 if(num.length == 0) { |
|
138 // empty string before '-' is legal |
|
139 hasbegin = 1; |
|
140 begin = -1; |
|
141 start = i+1; |
|
142 continue; |
|
143 } |
|
144 char *end; |
|
145 long long n = strtoll(num.ptr, &end, 10); |
|
146 if(errno == 0 && end == range.ptr + i && n >= 0) { |
|
147 begin = n; |
|
148 hasbegin = 1; |
|
149 start = i+1; |
|
150 } else { |
|
151 // syntax error |
|
152 free_range(sn, range_list); |
|
153 return NULL; |
|
154 } |
|
155 } else if(c == ',' || c == '\0') { |
|
156 sstr_t num = sstrsubsl(range, start, i-start); |
|
157 if(hasbegin) { |
|
158 long long n; |
|
159 if(num.length == 0) { |
|
160 // empty string after '-' is legal |
|
161 n = -1; |
|
162 } else { |
|
163 char *end; |
|
164 n = strtoll(num.ptr, &end, 10); |
|
165 if(errno != 0 || end != range.ptr + i || n < 0) { |
|
166 // syntax error |
|
167 free_range(sn, range_list); |
|
168 return NULL; |
|
169 } |
|
170 } |
|
171 |
|
172 if(!(begin < 0 && n < 0)) { |
|
173 // range: begin - n |
|
174 HttpRange *rangeelm = pool_malloc(sn->pool, sizeof(HttpRange)); |
|
175 if(!rangeelm) { |
|
176 free_range(sn, range_list); |
|
177 *status = PROTOCOL_SERVER_ERROR; |
|
178 return NULL; |
|
179 } |
|
180 rangeelm->begin = begin; |
|
181 rangeelm->end = n; |
|
182 rangeelm->next = NULL; |
|
183 if(!last) { |
|
184 range_list = rangeelm; |
|
185 last = rangeelm; |
|
186 } else { |
|
187 last->next = rangeelm; |
|
188 last = rangeelm; |
|
189 } |
|
190 |
|
191 hasbegin = 0; |
|
192 start = i+1; |
|
193 continue; |
|
194 } |
|
195 } |
|
196 |
|
197 // syntax error |
|
198 free_range(sn, range_list); |
|
199 return NULL; |
|
200 } |
|
201 } |
|
202 |
|
203 return range_list; |
|
204 } |
|
205 |
|
206 static int validate_range(HttpRange *range, struct stat *finfo, int *status) { |
|
207 off_t max_len = finfo->st_size; |
|
208 while(range) { |
|
209 if(range->begin > 0 && range->end > 0) { |
|
210 if(range->end < range->begin) { |
|
211 *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; |
|
212 return 0; |
|
213 } |
|
214 } |
|
215 if(range->begin >= max_len) { |
|
216 *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; |
|
217 return 0; |
|
218 } |
|
219 if(range->end >= max_len) { |
|
220 *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; |
|
221 return 0; |
|
222 } |
|
223 |
|
224 range = range->next; |
|
225 } |
|
226 |
|
227 // TODO: check for Denial-of-Service Attacks |
|
228 |
|
229 return 1; |
|
230 } |
|
231 |
|
232 /* |
|
233 * translates a HttpRange element to a begin offset and a length |
|
234 * the HttpRange must be validated |
|
235 */ |
|
236 static void range2off(HttpRange *range, off_t filelen, off_t *begin, off_t *length) { |
|
237 if(range->begin < 0) { |
|
238 // bytes=-a |
|
239 *begin = filelen - range->end; |
|
240 *length = range->end; |
|
241 } else if(range->end < 0) { |
|
242 // bytes=a- |
|
243 *begin = range->begin; |
|
244 *length = filelen - range->begin; |
|
245 } else { |
|
246 // bytes=a-b |
|
247 *begin = range->begin; |
|
248 *length = range->end + 1 - range->begin; |
|
249 } |
|
250 } |
|
251 |
|
252 #define SF_MAX_LEN 0x8000000 |
|
253 |
|
254 static int send_range(Session *sn, SYS_FILE fd, off_t offset, off_t length, char *header, int headerlen) { |
|
255 off_t remaining = length; |
|
256 |
|
257 sendfiledata sfd; |
|
258 sfd.fd = fd; |
|
259 sfd.header = header; |
|
260 sfd.hlen = headerlen; |
|
261 sfd.trailer = NULL; |
|
262 |
|
263 while(remaining > 0) { |
|
264 size_t sflen = remaining < SF_MAX_LEN ? remaining : SF_MAX_LEN; |
|
265 sfd.offset = offset; |
|
266 sfd.len = sflen; |
|
267 |
|
268 ssize_t r = net_sendfile(sn->csd, &sfd); |
|
269 if(r < 0) { |
|
270 return -1; |
|
271 } |
|
272 |
|
273 sfd.header = NULL; // make sure the header is only sent once |
|
274 offset += r; |
|
275 remaining -= r; |
|
276 } |
|
277 |
|
278 return 0; |
|
279 } |
|
280 |
|
281 struct multi_range_elm { |
|
282 sstr_t header; |
|
283 off_t offset; |
|
284 off_t length; |
|
285 }; |
|
286 |
|
287 static int send_multi_range(Session *sn, Request *rq, SYS_FILE fd, off_t filelen, HttpRange *range) { |
|
288 pb_param *content_type = pblock_remove("content-type", rq->srvhdrs); |
|
289 |
|
290 char sep[64]; |
|
291 int seplen = util_mime_separator(sep); |
|
292 |
|
293 sstr_t newct = ucx_sprintf("multipart/byteranges; boundary=%s", sep+4); |
|
294 pblock_kvinsert( |
|
295 pb_key_content_type, |
|
296 newct.ptr, |
|
297 newct.length, |
|
298 rq->srvhdrs); |
|
299 free(newct.ptr); |
|
300 |
|
301 // calculate content-length |
|
302 off_t response_len = 0; |
|
303 |
|
304 int nrange = 0; |
|
305 HttpRange *rangeelm = range; |
|
306 while(rangeelm) { |
|
307 nrange++; |
|
308 rangeelm = rangeelm->next; |
|
309 } |
|
310 |
|
311 struct multi_range_elm *r = calloc(nrange, sizeof(struct multi_range_elm)); |
|
312 rangeelm = range; |
|
313 int i=0; |
|
314 while(rangeelm) { |
|
315 range2off(rangeelm, filelen, &(r[i].offset), &(r[i].length)); |
|
316 r[i].header = ucx_sprintf( |
|
317 "%s\r\nContent-Type: %s\r\nContent-Range: bytes %lld-%lld/%lld\r\n\r\n", |
|
318 sep, |
|
319 content_type->value, |
|
320 (long long)r[i].offset, |
|
321 (long long)r[i].offset+r[i].length - 1, |
|
322 filelen); |
|
323 |
|
324 response_len += r[i].header.length + r[i].length; |
|
325 |
|
326 rangeelm = rangeelm->next; |
|
327 i++; |
|
328 } |
|
329 |
|
330 response_len += seplen + 4; // trailer: sep + '--' + CRLF |
|
331 |
|
332 // finally, set the content-length header |
|
333 pblock_kllinsert( |
|
334 pb_key_content_length, |
|
335 (long long)response_len, |
|
336 rq->srvhdrs); |
|
337 |
|
338 // and start the response |
95 http_start_response(sn, rq); |
339 http_start_response(sn, rq); |
96 |
340 |
97 return fd; |
341 rangeelm = range; |
|
342 i = 0; |
|
343 while(rangeelm) { |
|
344 if(send_range(sn, fd, r[i].offset, r[i].length, r[i].header.ptr, r[i].header.length)) { |
|
345 // TODO: error |
|
346 } |
|
347 rangeelm = rangeelm->next; |
|
348 i++; |
|
349 } |
|
350 net_printf(sn->csd, "%s--\r\n", sep); |
|
351 |
|
352 return 0; |
98 } |
353 } |
99 |
354 |
100 int send_file(pblock *pb, Session *sn, Request *rq) { |
355 int send_file(pblock *pb, Session *sn, Request *rq) { |
101 struct stat s; |
356 struct stat s; |
102 SYS_FILE fd = prepare_service_file(sn, rq, &s); |
357 VFSContext *vfs = vfs_request_context(sn, rq); |
|
358 SYS_FILE fd = prepare_service_file(sn, rq, vfs, &s); |
103 if(!fd) { |
359 if(!fd) { |
104 // if an error occurs, prepare_service_file sets the http status code |
360 // if an error occurs, prepare_service_file sets the http status code |
105 // we can just return REQ_ABORTED |
361 // we can just return REQ_ABORTED |
106 return REQ_ABORTED; |
362 return REQ_ABORTED; |
107 } |
363 } |
108 |
364 |
109 if(!S_ISDIR(s.st_mode)) { |
365 // get and validate range header |
110 // send file |
366 char *range_header = pblock_findkeyval(pb_key_range, rq->headers); |
111 sendfiledata sfd; |
367 HttpRange *range = NULL; |
112 sfd.fd = fd; |
368 if(range_header) { |
113 sfd.len = s.st_size; |
369 int status; |
114 sfd.offset = 0; |
370 range = parse_range(sn, range_header, &status); |
115 sfd.header = NULL; |
371 if(status != PROTOCOL_OK) { |
116 sfd.trailer = NULL; |
372 protocol_status(sn, rq, status, NULL); |
117 net_sendfile(sn->csd, &sfd); |
373 vfs_close(fd); |
118 } // else: status 302 set by prepare_service_file |
374 return REQ_ABORTED; |
119 |
375 } |
|
376 |
|
377 if(!validate_range(range, &s, &status)) { |
|
378 protocol_status(sn, rq, status, NULL); |
|
379 free_range(sn, range); |
|
380 vfs_close(fd); |
|
381 return REQ_ABORTED; |
|
382 } |
|
383 } |
|
384 |
|
385 int single_range = 1; |
|
386 off_t offset; |
|
387 off_t length; |
|
388 if(range) { |
|
389 protocol_status(sn, rq, 206, NULL); |
|
390 pblock_removekey(pb_key_content_length, rq->srvhdrs); |
|
391 |
|
392 if(range->next) { |
|
393 single_range = 0; |
|
394 } else { |
|
395 range2off(range, s.st_size, &offset, &length); |
|
396 |
|
397 pblock_kllinsert( |
|
398 pb_key_content_length, |
|
399 (long long)length, |
|
400 rq->srvhdrs); |
|
401 |
|
402 sstr_t content_range = ucx_sprintf( |
|
403 "%lld-%lld/%lld", |
|
404 (long long)offset, |
|
405 (long long)offset+length - 1, |
|
406 s.st_size); |
|
407 pblock_kvinsert( |
|
408 pb_key_content_range, |
|
409 content_range.ptr, |
|
410 content_range.length, |
|
411 rq->srvhdrs); |
|
412 free(content_range.ptr); |
|
413 } |
|
414 } else { |
|
415 offset = 0; |
|
416 length = s.st_size; |
|
417 } |
|
418 |
|
419 if(single_range) { |
|
420 // send response header |
|
421 http_start_response(sn, rq); |
|
422 // send content |
|
423 if(send_range(sn, fd, offset, length, NULL, 0)) { |
|
424 // TODO: error |
|
425 } |
|
426 } else { |
|
427 if(send_multi_range(sn, rq, fd, s.st_size, range)) { |
|
428 // TODO: error |
|
429 } |
|
430 } |
|
431 |
|
432 // cleanup |
120 vfs_close(fd); |
433 vfs_close(fd); |
|
434 free_range(sn, range); |
121 |
435 |
122 return REQ_PROCEED; |
436 return REQ_PROCEED; |
123 } |
437 } |
|
438 |
|
439 |
124 |
440 |
125 int service_hello(pblock *pb, Session *sn, Request *rq) { |
441 int service_hello(pblock *pb, Session *sn, Request *rq) { |
126 pblock_removekey(pb_key_content_type, rq->srvhdrs); |
442 pblock_removekey(pb_key_content_type, rq->srvhdrs); |
127 pblock_nvinsert("content-type", "text/plain", rq->srvhdrs); |
443 pblock_nvinsert("content-type", "text/plain", rq->srvhdrs); |
128 pblock_nninsert("content-length", 13, rq->srvhdrs); |
444 pblock_nninsert("content-length", 13, rq->srvhdrs); |