src/server/safs/service.c

changeset 104
a8acbb12f27c
parent 102
136a76e293b5
child 116
d7a186cf87f6
equal deleted inserted replaced
103:d3b514e2ddbd 104:a8acbb12f27c
31 #include <sys/stat.h> 31 #include <sys/stat.h>
32 32
33 #include "service.h" 33 #include "service.h"
34 #include "../util/io.h" 34 #include "../util/io.h"
35 #include "../util/pblock.h" 35 #include "../util/pblock.h"
36 #include "../util/util.h"
36 #include "../daemon/protocol.h" 37 #include "../daemon/protocol.h"
37 #include "../daemon/vfs.h" 38 #include "../daemon/vfs.h"
38 39
39 //include <sys/sendfile.h> 40 //include <sys/sendfile.h>
40 #include "../util/strbuf.h" 41 #include "../util/strbuf.h"
42 #include <ucx/string.h>
43 #include <ucx/utils.h>
41 44
42 #include <errno.h> 45 #include <errno.h>
43 46
44 47
45 /* 48 /*
46 * prepares for servicing a file 49 * prepares servicing a file
47 * 50 *
48 * adds content-length header and starts the response 51 * adds content-length header
49 * 52 *
50 * return the opened file 53 * return the opened file
51 */ 54 */
52 SYS_FILE prepare_service_file(Session *sn, Request *rq, struct stat *s) { 55 SYS_FILE prepare_service_file(Session *sn, Request *rq, VFSContext *vfs, struct stat *s) {
53 char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars); 56 char *ppath = pblock_findkeyval(pb_key_ppath, rq->vars);
54 57
55 // open the file 58 // open the file
56 VFSContext *vfs = vfs_request_context(sn, rq);
57 SYS_FILE fd = vfs_open(vfs, ppath, O_RDONLY); 59 SYS_FILE fd = vfs_open(vfs, ppath, O_RDONLY);
58 if(!fd) { 60 if(!fd) {
59 // vfs_open sets http status code 61 // vfs_open sets http status code
60 return NULL; 62 return NULL;
61 } 63 }
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);

mercurial