UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2013 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <sys/file.h> 31 #include <sys/stat.h> 32 33 #include "service.h" 34 #include "objecttype.h" 35 #include "../util/io.h" 36 #include "../util/pblock.h" 37 #include "../util/util.h" 38 #include "../daemon/protocol.h" 39 #include "../daemon/vfs.h" 40 41 #include "../util/strbuf.h" 42 #include <cx/string.h> 43 #include <cx/utils.h> 44 #include <cx/printf.h> 45 46 #include <errno.h> 47 48 /* 49 * prepares servicing a file 50 * 51 * adds content-length header 52 * 53 * return the opened file 54 */ 55 SYS_FILE prepare_service_file(Session *sn, Request *rq, VFSContext *vfs, struct stat *s, int *ret) { 56 char *path = pblock_findkeyval(pb_key_path, rq->vars); 57 58 // open the file 59 SYS_FILE fd = vfs_open(vfs, path, O_RDONLY); 60 if(!fd) { 61 // vfs_open sets http status code 62 *ret = REQ_ABORTED; 63 return NULL; 64 } 65 66 // get stat 67 if(vfs_fstat(vfs, fd, s) != 0) { 68 //perror("prepare_service_file: stat"); 69 protocol_status(sn, rq, 500, NULL); 70 *ret = REQ_ABORTED; 71 return NULL; 72 } 73 74 // check if the file is a directory 75 if(S_ISDIR(s->st_mode)) { 76 pblock_removekey(pb_key_content_type, rq->srvhdrs); 77 char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); 78 size_t urilen = strlen(uri); 79 if(urilen > 0 && uri[urilen-1] != '/') { 80 pblock_nvinsert("content-length", "0", rq->srvhdrs); 81 char *location = pool_malloc(sn->pool, urilen + 2); 82 memcpy(location, uri, urilen); 83 location[urilen] = '/'; 84 location[urilen+1] = '\0'; 85 pblock_kvinsert(pb_key_location, location, urilen + 1, rq->srvhdrs); 86 protocol_status(sn, rq, 302, NULL); 87 http_start_response(sn, rq); 88 *ret = REQ_PROCEED; 89 } else { 90 // set content-type to "internal/directory" and ret to REQ_NOACTION 91 // maybe a SAF will respond to that 92 pblock_kvinsert( 93 pb_key_content_type, 94 OBJTYPE_INTERNAL_DIRECTORY, 95 sizeof(OBJTYPE_INTERNAL_DIRECTORY)-1, 96 rq->srvhdrs); 97 *ret = REQ_NOACTION; 98 } 99 vfs_close(fd); 100 return NULL; 101 } 102 103 // sets last-modified, content-length and checks conditions 104 const char *etag = vfs_getetag(fd); // optionally, get etag from file 105 if(http_set_finfo_etag(sn, rq, s, etag) != REQ_PROCEED) { 106 vfs_close(fd); 107 *ret = REQ_ABORTED; 108 return NULL; 109 } 110 111 // TODO: check if vfs can seek 112 pblock_kvinsert(pb_key_accept_ranges, "bytes", 5, rq->srvhdrs); 113 114 // start response 115 protocol_status(sn, rq, 200, NULL); 116 117 return fd; 118 } 119 120 static void free_range(Session *sn, HttpRange *range) { 121 HttpRange *elm = range; 122 while(elm) { 123 HttpRange *next = elm->next; 124 pool_free(sn->pool, elm); 125 elm = next; 126 } 127 } 128 129 static HttpRange* parse_range(Session *sn, char *header, int *status) { 130 *status = PROTOCOL_OK; 131 132 cxstring range = cx_strtrim(cx_str(header)); 133 if(!cx_strprefix(range, (cxstring)CX_STR("bytes="))) { 134 // unknown range unit - ignore range header 135 return NULL; 136 } 137 138 // get byte-range-set 139 range = cx_strsubs(range, 6); 140 if(range.length < 1) { 141 return NULL; 142 } 143 144 HttpRange *range_list = NULL; 145 HttpRange *last = NULL; 146 off_t begin = -1; 147 int start = 0; 148 int hasbegin = 0; 149 for(int i=0;i<=range.length;i++) { 150 char c = range.ptr[i]; 151 if(c == '-') { 152 cxstring num = cx_strsubsl(range, start, i-start); 153 if(num.length == 0) { 154 // empty string before '-' is legal 155 hasbegin = 1; 156 begin = -1; 157 start = i+1; 158 continue; 159 } 160 char *end; 161 errno = 0; 162 long long n = strtoll(num.ptr, &end, 10); 163 if(errno == 0 && end == range.ptr + i && n >= 0) { 164 begin = n; 165 hasbegin = 1; 166 start = i+1; 167 } else { 168 // syntax error 169 free_range(sn, range_list); 170 return NULL; 171 } 172 } else if(c == ',' || c == '\0') { 173 cxstring num = cx_strsubsl(range, start, i-start); 174 if(hasbegin) { 175 long long n; 176 if(num.length == 0) { 177 // empty string after '-' is legal 178 n = -1; 179 } else { 180 char *end; 181 errno = 0; 182 n = strtoll(num.ptr, &end, 10); 183 if(errno != 0 || end != range.ptr + i || n < 0) { 184 // syntax error 185 free_range(sn, range_list); 186 return NULL; 187 } 188 } 189 190 if(!(begin < 0 && n < 0)) { 191 // range: begin - n 192 HttpRange *rangeelm = pool_malloc(sn->pool, sizeof(HttpRange)); 193 if(!rangeelm) { 194 free_range(sn, range_list); 195 *status = PROTOCOL_SERVER_ERROR; 196 return NULL; 197 } 198 rangeelm->begin = begin; 199 rangeelm->end = n; 200 rangeelm->next = NULL; 201 if(!last) { 202 range_list = rangeelm; 203 last = rangeelm; 204 } else { 205 last->next = rangeelm; 206 last = rangeelm; 207 } 208 209 hasbegin = 0; 210 start = i+1; 211 continue; 212 } 213 } 214 215 // syntax error 216 free_range(sn, range_list); 217 return NULL; 218 } 219 } 220 221 return range_list; 222 } 223 224 static int validate_range(HttpRange *range, struct stat *finfo, int *status) { 225 off_t max_len = finfo->st_size; 226 while(range) { 227 if(range->begin > 0 && range->end > 0) { 228 if(range->end < range->begin) { 229 *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; 230 return 0; 231 } 232 } 233 if(range->begin >= max_len) { 234 *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; 235 return 0; 236 } 237 if(range->end >= max_len) { 238 *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; 239 return 0; 240 } 241 242 range = range->next; 243 } 244 245 // TODO: check for Denial-of-Service Attacks 246 247 return 1; 248 } 249 250 /* 251 * translates a HttpRange element to a begin offset and a length 252 * the HttpRange must be validated 253 */ 254 static void range2off(HttpRange *range, off_t filelen, off_t *begin, off_t *length) { 255 if(range->begin < 0) { 256 // bytes=-a 257 *begin = filelen - range->end; 258 *length = range->end; 259 } else if(range->end < 0) { 260 // bytes=a- 261 *begin = range->begin; 262 *length = filelen - range->begin; 263 } else { 264 // bytes=a-b 265 *begin = range->begin; 266 *length = range->end + 1 - range->begin; 267 } 268 } 269 270 #define SF_MAX_LEN 0x8000000 271 272 static int send_range(Session *sn, SYS_FILE fd, off_t offset, off_t length, char *header, int headerlen) { 273 off_t remaining = length; 274 275 sendfiledata sfd; 276 sfd.fd = fd; 277 sfd.header = header; 278 sfd.hlen = headerlen; 279 sfd.trailer = NULL; 280 sfd.tlen = 0; 281 282 while(remaining > 0) { 283 size_t sflen = remaining < SF_MAX_LEN ? remaining : SF_MAX_LEN; 284 sfd.offset = offset; 285 sfd.len = sflen; 286 287 ssize_t r = net_sendfile(sn->csd, &sfd); 288 if(r < 0) { 289 return -1; 290 } 291 292 sfd.header = NULL; // make sure the header is only sent once 293 offset += r; 294 remaining -= r; 295 } 296 297 return 0; 298 } 299 300 301 static void send_range_cleanup(AsyncSendRange *asr) { 302 WSBool error = asr->error; 303 Session *sn = asr->sn; 304 Request *rq = asr->rq; 305 306 pool_handle_t *pool = asr->sn->pool; 307 vfs_close(asr->in); 308 pool_free(pool, asr->aio->buf); 309 pool_free(pool, asr->aio); 310 pool_free(pool, asr->readev); 311 pool_free(pool, asr->writeev); 312 pool_free(pool, asr); 313 314 int ret = REQ_PROCEED; 315 if(error) { 316 rq->rq_attr.keep_alive = 0; 317 ret = REQ_ABORTED; 318 } 319 // return to nsapi loop 320 nsapi_function_return(sn, rq, ret); 321 } 322 323 static int send_buf( 324 SYS_NETFD out, 325 char *restrict buf, 326 size_t len, 327 size_t *restrict pos) 328 { 329 while(*pos < len) { 330 ssize_t w = net_write(out, buf + *pos, len - *pos); 331 if(w <= 0) { 332 return -1; 333 } 334 *pos += w; 335 } 336 return 0; 337 } 338 339 static int send_bytes(AsyncSendRange *asr, WSBool *completed) { 340 *completed = FALSE; 341 if(asr->header) { 342 if(send_buf(asr->out, asr->header, asr->headerlen, &asr->headerpos)) { 343 if(net_errno(asr->out) == EAGAIN) { 344 return 0; 345 } else { 346 asr->error = TRUE; 347 return 1; 348 } 349 } 350 if(asr->headerpos >= asr->headerlen) { 351 asr->header = NULL; 352 } 353 } 354 355 if(send_buf(asr->out, asr->aio->buf, asr->aio->result, &asr->wpos)) { 356 if(net_errno(asr->out) == EAGAIN) { 357 return 0; 358 } else { 359 asr->error = TRUE; 360 return 1; 361 } 362 } 363 364 if(!asr->read_complete) { 365 // write completed => new asynchronous read 366 asr->aio->offset += asr->aio->result; 367 size_t length = asr->end - asr->offset; 368 asr->aio->nbytes = AIO_BUF_SIZE < length ? AIO_BUF_SIZE : length; 369 asr->read_inprogress = TRUE; 370 if(system_aio_read(asr->aio)) { 371 asr->error = TRUE; 372 return 1; 373 } 374 } 375 *completed = TRUE; 376 return 0; 377 } 378 379 static int send_range_readevent(EventHandler *ev, Event *event) { 380 AsyncSendRange *asr = event->cookie; 381 asr->read_inprogress = FALSE; 382 asr->wpos = 0; 383 asr->offset += asr->aio->result; 384 if(asr->error || asr->aio->result < 0) { 385 return 0; 386 } 387 388 int ret = 1; 389 if(asr->aio->result == 0 || asr->offset >= asr->end) { 390 asr->read_complete = TRUE; 391 ret = 0; 392 } 393 394 WSBool completed; 395 if(send_bytes(asr, &completed)) { 396 return 0; 397 } 398 if(!completed && !asr->write_inprogress) { 399 asr->write_inprogress = TRUE; 400 if(event_pollout(ev, asr->out, asr->writeev)) { 401 asr->error = TRUE; 402 return 0; 403 } 404 } 405 406 return ret; 407 } 408 409 static int send_range_writeevent(EventHandler *ev, Event *event) { 410 AsyncSendRange *asr = event->cookie; 411 if(asr->error) { 412 return 1; 413 } 414 415 WSBool completed; 416 if(send_bytes(asr, &completed)) { 417 return 1; 418 } 419 420 if(completed) { 421 return 0; 422 } 423 424 return 1; 425 } 426 427 static int send_range_aio_finish(EventHandler *ev, Event *event) { 428 AsyncSendRange *asr = event->cookie; 429 if(!asr->write_inprogress) { 430 send_range_cleanup(asr); 431 } 432 asr->read_inprogress = FALSE; 433 return 0; 434 } 435 436 static int send_range_poll_finish(EventHandler *ev, Event *event) { 437 AsyncSendRange *asr = event->cookie; 438 if(!asr->read_inprogress) { 439 send_range_cleanup(asr); 440 } 441 asr->write_inprogress = FALSE; 442 return 0; 443 } 444 445 static int send_range_aio(Session *sn, Request *rq, SYS_FILE fd, off_t offset, off_t length, char *header, int headerlen) { 446 net_setnonblock(sn->csd, TRUE); 447 448 // try to send the header 449 ssize_t hw = net_write(sn->csd, header, headerlen); 450 if(hw < 0) { 451 if(net_errno(sn->csd) == EAGAIN) { 452 hw = 0; 453 } else { 454 return REQ_ABORTED; 455 } 456 } 457 458 AsyncSendRange *asr = pool_malloc(sn->pool, sizeof(AsyncSendRange)); 459 asr->sn = sn; 460 asr->rq = rq; 461 asr->in = fd; 462 asr->out = sn->csd; 463 asr->offset = offset; 464 asr->end = offset + length; 465 //asr->length = length; 466 asr->pos = offset; 467 asr->read_complete = FALSE; 468 asr->read_inprogress = FALSE; 469 asr->write_inprogress = FALSE; 470 asr->error = FALSE; 471 if(hw == headerlen) { 472 asr->header = NULL; 473 asr->headerlen = 0; 474 asr->headerpos = 0; 475 } else { 476 asr->header = header; 477 asr->headerlen = headerlen; 478 asr->headerpos = hw; 479 } 480 481 Event *readev = pool_malloc(sn->pool, sizeof(Event)); 482 ZERO(readev, sizeof(Event)); 483 readev->cookie = asr; 484 readev->fn = send_range_readevent; 485 readev->finish = send_range_aio_finish; 486 487 Event *writeev = pool_malloc(sn->pool, sizeof(Event)); 488 ZERO(writeev, sizeof(Event)); 489 writeev->cookie = asr; 490 writeev->fn = send_range_writeevent; 491 writeev->finish = send_range_poll_finish; 492 493 asr->readev = readev; 494 asr->writeev = writeev; 495 496 aiocb_s *aio = pool_malloc(sn->pool, sizeof(aiocb_s)); 497 aio->buf = pool_malloc(sn->pool, AIO_BUF_SIZE); 498 aio->nbytes = AIO_BUF_SIZE < length ? AIO_BUF_SIZE : length; 499 aio->filedes = fd; 500 aio->offset = offset; 501 aio->evhandler = sn->ev; 502 aio->event = readev; 503 504 asr->aio = aio; 505 asr->wpos = 0; 506 507 asr->read_inprogress = TRUE; 508 if(system_aio_read(aio)) { 509 send_range_cleanup(asr); 510 return REQ_ABORTED; 511 } 512 asr->read_inprogress = TRUE; 513 514 return REQ_PROCESSING; 515 } 516 517 struct multi_range_elm { 518 cxmutstr header; 519 off_t offset; 520 off_t length; 521 }; 522 523 static int send_multi_range(Session *sn, Request *rq, SYS_FILE fd, off_t filelen, HttpRange *range) { 524 CxAllocator *a = pool_allocator(sn->pool); 525 526 pb_param *content_type = pblock_remove("content-type", rq->srvhdrs); 527 528 char sep[64]; 529 int seplen = util_mime_separator(sep); 530 531 cxmutstr newct = cx_asprintf_a(a, "multipart/byteranges; boundary=%s", sep+4); 532 pblock_kvinsert( 533 pb_key_content_type, 534 newct.ptr, 535 newct.length, 536 rq->srvhdrs); 537 cxFree(a, newct.ptr); 538 539 // calculate content-length 540 off_t response_len = 0; 541 542 int nrange = 0; 543 HttpRange *rangeelm = range; 544 while(rangeelm) { 545 nrange++; 546 rangeelm = rangeelm->next; 547 } 548 549 struct multi_range_elm *r = pool_calloc(sn->pool, nrange, sizeof(struct multi_range_elm)); 550 rangeelm = range; 551 int i=0; 552 while(rangeelm) { 553 range2off(rangeelm, filelen, &(r[i].offset), &(r[i].length)); 554 r[i].header = cx_asprintf_a( 555 a, 556 "%s\r\nContent-Type: %s\r\nContent-Range: bytes %lld-%lld/%lld\r\n\r\n", 557 sep, 558 content_type->value, 559 (long long)r[i].offset, 560 (long long)r[i].offset+r[i].length - 1, 561 (long long)filelen); 562 563 response_len += r[i].header.length + r[i].length; 564 565 rangeelm = rangeelm->next; 566 i++; 567 } 568 569 response_len += seplen + 4; // trailer: sep + '--' + CRLF 570 571 // finally, set the content-length header 572 pblock_kllinsert( 573 pb_key_content_length, 574 (long long)response_len, 575 rq->srvhdrs); 576 577 // and start the response 578 http_start_response(sn, rq); 579 580 rangeelm = range; 581 i = 0; 582 while(rangeelm) { 583 if(send_range(sn, fd, r[i].offset, r[i].length, r[i].header.ptr, r[i].header.length)) { 584 // TODO: error 585 } 586 rangeelm = rangeelm->next; 587 i++; 588 } 589 net_printf(sn->csd, "%s--\r\n", sep); 590 591 pool_free(sn->pool, r); 592 return 0; 593 } 594 595 int send_file(pblock *pb, Session *sn, Request *rq) { 596 int ret = REQ_NOACTION; 597 struct stat s; 598 VFSContext *vfs = vfs_request_context(sn, rq); 599 SYS_FILE fd = prepare_service_file(sn, rq, vfs, &s, &ret); 600 if(!fd) { 601 // if an error occurs, prepare_service_file sets the http status code 602 // in case fd is a directory and the uri already ends with an trailing 603 // '/', ret is set to REQ_NOACTION 604 return ret; 605 } 606 607 // get and validate range header 608 char *range_header = pblock_findkeyval(pb_key_range, rq->headers); 609 HttpRange *range = NULL; 610 if(range_header) { 611 log_ereport(LOG_DEBUG, "send_file: range: %s", range_header); 612 613 int status; 614 range = parse_range(sn, range_header, &status); 615 if(status != PROTOCOL_OK) { 616 protocol_status(sn, rq, status, NULL); 617 vfs_close(fd); 618 return REQ_ABORTED; 619 } 620 621 if(!validate_range(range, &s, &status)) { 622 protocol_status(sn, rq, status, NULL); 623 free_range(sn, range); 624 vfs_close(fd); 625 return REQ_ABORTED; 626 } 627 } 628 629 int single_range = 1; 630 off_t offset; 631 off_t length; 632 if(range) { 633 protocol_status(sn, rq, 206, NULL); 634 pblock_removekey(pb_key_content_length, rq->srvhdrs); 635 636 if(range->next) { 637 single_range = 0; 638 } else { 639 range2off(range, s.st_size, &offset, &length); 640 641 pblock_kllinsert( 642 pb_key_content_length, 643 (long long)length, 644 rq->srvhdrs); 645 646 cxmutstr content_range = cx_asprintf( 647 "%lld-%lld/%lld", 648 (long long)offset, 649 (long long)offset+length - 1, 650 (long long)s.st_size); 651 pblock_kvinsert( 652 pb_key_content_range, 653 content_range.ptr, 654 content_range.length, 655 rq->srvhdrs); 656 free(content_range.ptr); 657 } 658 } else { 659 offset = 0; 660 length = s.st_size; 661 } 662 663 if(single_range) { 664 // send response header 665 http_start_response(sn, rq); 666 // send content 667 // TODO: fix: send_range_aio is unstable #96 668 //ret = send_range_aio(sn, rq, fd, offset, length, NULL, 0); 669 //if(ret == REQ_PROCESSING) { 670 // return ret; 671 //} 672 673 if(send_range(sn, fd, offset, length, NULL, 0)) { 674 // TODO: error 675 } 676 } else { 677 ret = send_multi_range(sn, rq, fd, s.st_size, range); 678 // TODO: error 679 } 680 681 // cleanup 682 vfs_close(fd); 683 free_range(sn, range); 684 685 return ret; 686 } 687 688 689 690 int service_hello(pblock *pb, Session *sn, Request *rq) { 691 pblock_removekey(pb_key_content_type, rq->srvhdrs); 692 pblock_nvinsert("content-type", "text/plain", rq->srvhdrs); 693 pblock_nninsert("content-length", 13, rq->srvhdrs); 694 protocol_status(sn, rq, 200, NULL); 695 http_start_response(sn, rq); 696 net_write(sn->csd, "Hello World!\n", 13); 697 return REQ_PROCEED; 698 } 699 700 static int ws_msghandler(WebSocket *ws, WSMessage *msg) { 701 if(msg->type == 1) { 702 printf("Message(text): %.*s\n", (int)msg->length, msg->data); 703 websocket_send_text(ws->userdata, "hello", 5); 704 } else { 705 printf("Message: opcode: %d | length: %d\n", msg->type, (int)msg->length); 706 } 707 return 0; 708 } 709 710 int service_ws_hello(pblock *pb, Session *sn, Request *rq) { 711 WebSocket ws; 712 ZERO(&ws, sizeof(WebSocket)); 713 ws.userdata = sn->csd; 714 715 ws.on_message = ws_msghandler; 716 return http_handle_websocket(sn, rq, &ws); 717 } 718 719 int service_index(pblock *pb, Session *sn, Request *rq) { 720 //printf("service_index\n"); 721 722 char *path = pblock_findkeyval(pb_key_path, rq->vars); 723 char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); 724 725 cxstring r_uri = cx_str(uri); 726 727 // open the file 728 VFSContext *vfs = vfs_request_context(sn, rq); 729 VFS_DIR dir = vfs_opendir(vfs, path); 730 if(!dir) { 731 return REQ_ABORTED; 732 } 733 734 sbuf_t *out = sbuf_new(1024); // output buffer 735 736 // write html header 737 sbuf_puts(out, "<html>\n<head>\n<title>Index of "); 738 sbuf_puts(out, uri); 739 sbuf_puts(out, "</title>\n</head><body>\n<h1>Index of "); 740 sbuf_puts(out, uri); 741 sbuf_puts(out, "</h1><hr>\n\n"); 742 743 // list directory 744 VFS_ENTRY f; 745 while(vfs_readdir(dir, &f)) { 746 cxstring filename = cx_str(f.name); 747 748 sbuf_puts(out, "<a href=\""); 749 sbuf_append(out, r_uri); 750 sbuf_append(out, filename); 751 sbuf_puts(out, "\">"); 752 sbuf_append(out, filename); 753 sbuf_puts(out, "</a><br>\n"); 754 } 755 756 sbuf_puts(out, "\n</body>\n</html>\n"); 757 758 // send stuff to client 759 pblock_removekey(pb_key_content_type, rq->srvhdrs); 760 pblock_kvinsert(pb_key_content_type, "text/html", 9, rq->srvhdrs); 761 pblock_nninsert("content-length", out->length, rq->srvhdrs); 762 protocol_status(sn, rq, 200, NULL); 763 http_start_response(sn, rq); 764 765 net_write(sn->csd, out->ptr, out->length); 766 767 // close 768 vfs_closedir(dir); 769 sbuf_free(out); 770 771 return REQ_PROCEED; 772 } 773 774 int send_options(pblock *pb, Session *sn, Request *rq) { 775 char *allow = "HEAD, GET, PUT, DELETE, TRACE, OPTIONS, MOVE, COPY, " 776 "PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, ACL, REPORT"; 777 char *dav = "1,2,access-control"; 778 779 pblock_removekey(pb_key_content_type, rq->srvhdrs); 780 pblock_nvinsert("allow", allow, rq->srvhdrs); 781 pblock_nvinsert("dav", dav, rq->srvhdrs); 782 pblock_nninsert("content-length", 0, rq->srvhdrs); 783 protocol_status(sn, rq, 204, NULL); 784 http_start_response(sn, rq); 785 786 return REQ_PROCEED; 787 } 788