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 <stdlib.h> 31 #include <limits.h> 32 33 #include <arpa/inet.h> 34 35 #include "../public/nsapi.h" 36 #include "../util/pool.h" 37 #include "../util/pblock.h" 38 #include "../util/io.h" 39 #include "../util/util.h" 40 #include "httprequest.h" 41 #include "config.h" 42 #include "vserver.h" 43 #include "event.h" 44 #include "httplistener.h" 45 #include "func.h" 46 #include "error.h" 47 48 void http_request_init(HTTPRequest *req) { 49 memset(req, 0, sizeof(HTTPRequest)); 50 51 HeaderArray *hd = malloc(sizeof(HeaderArray)); 52 hd->next = NULL; 53 hd->len = 0; 54 hd->headers = calloc(16, sizeof(Header)); 55 hd->alloc = 16; 56 57 req->headers = hd; 58 59 req->req_start = time(NULL); 60 } 61 62 void http_request_cleanup(HTTPRequest *req) { 63 header_array_free(req->headers); 64 free(req); 65 } 66 67 cxmutstr http_request_get_abspath(HTTPRequest *req) { 68 cxmutstr uri = req->uri; 69 70 int i = 0; 71 if(uri.ptr[0] == '/') { 72 return uri; 73 } else if(cx_strprefix(cx_strcast(uri), (cxstring)CX_STR("http://"))) { 74 i = 7; 75 } else if(cx_strprefix(cx_strcast(uri), (cxstring)CX_STR("https://"))) { 76 i = 8; 77 } else if(!cx_strcmp(cx_strcast(uri), (cxstring)CX_STR("*"))) { 78 return uri; 79 } 80 81 for(;i<uri.length;i++) { 82 if(uri.ptr[i] == '/') { 83 return cx_strsubs_m(uri, i); 84 } 85 } 86 87 return (cxmutstr){ "/", 1 }; 88 } 89 90 NSAPISession* nsapisession_create(pool_handle_t *pool) { 91 NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); 92 if(!sn) { 93 return NULL; 94 } 95 96 ZERO(sn, sizeof(NSAPISession)); 97 98 sn->sn.pool = pool; 99 100 sn->sn.client = pblock_create_pool(sn->sn.pool, 8); 101 if(!sn->sn.client) { 102 pool_free(pool, sn); 103 return NULL; 104 } 105 sn->sn.fill = 1; 106 107 return sn; 108 } 109 110 int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io) { 111 SessionHandler *sh = conn->session_handler; 112 WSBool ssl; 113 IOStream *sio = sh->create_iostream(sh, conn, sn->sn.pool, &ssl); 114 if(!sio) { 115 return 1; 116 } 117 *io = sio; 118 IOStream *http = httpstream_new(sn->sn.pool, sio); 119 if(!http) { 120 return 1; 121 } 122 sn->connection = conn; 123 sn->netbuf = inbuf; 124 sn->sn.csd = http; 125 sn->sn.ssl = ssl; 126 sn->sn.inbuf = inbuf; 127 sn->sn.inbuf->sd = http; 128 return 0; 129 } 130 131 int handle_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev) { 132 // handle nsapi request 133 134 // create pool 135 pool_handle_t *pool = pool_create(); 136 if(!pool) { 137 log_ereport(LOG_FAILURE, "cannot create new memory pool for http request"); 138 return 1; 139 } 140 141 int ret = nsapi_start_request(request, thrpool, ev, pool); 142 if(ret) { 143 pool_destroy(pool); 144 } 145 return ret; 146 } 147 148 int nsapi_start_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev, pool_handle_t *pool) { 149 log_ereport(LOG_DEBUG, "trace reqid: %016llx nsapi_start_request", (unsigned long long int)request->connection->id); 150 151 // create nsapi data structures 152 NSAPISession *sn = nsapisession_create(pool); 153 if(sn == NULL) { 154 /* TODO: error */ 155 return 1; 156 } 157 158 NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); 159 if(rq == NULL) { 160 /* TODO: error */ 161 return 1; 162 } 163 ZERO(rq, sizeof(NSAPIRequest)); 164 rq->rq.req_start = request->req_start; 165 rq->phase = NSAPIAuthTrans; 166 167 // fill session structure 168 IOStream *io = NULL; 169 if(nsapisession_setconnection(sn, request->connection, request->netbuf, &io)) { 170 // TODO: error 171 return 1; 172 } 173 174 if(!ev) { 175 ev = ev_instance(get_default_event_handler()); 176 } 177 sn->sn.ev = ev; 178 179 // the session needs the current server configuration 180 sn->config = request->connection->listener->cfg; 181 182 // add ip to sn->client pblock 183 if(request->connection->addr_type == CONN_ADDR_IPV4) { 184 char ip_str[INET_ADDRSTRLEN]; 185 if(inet_ntop( 186 AF_INET, 187 &request->connection->address.address_v4.sin_addr, 188 ip_str, 189 INET_ADDRSTRLEN) != NULL) 190 { 191 pblock_kvinsert(pb_key_ip, ip_str, INET_ADDRSTRLEN, sn->sn.client); 192 } 193 } else if(request->connection->addr_type == CONN_ADDR_IPV6) { 194 char ip_str[INET6_ADDRSTRLEN]; 195 if(inet_ntop( 196 AF_INET6, 197 &request->connection->address.address_v6.sin6_addr, 198 ip_str, 199 INET6_ADDRSTRLEN) != NULL) 200 { 201 pblock_kvinsert(pb_key_ip, ip_str, INET6_ADDRSTRLEN, sn->sn.client); 202 } 203 } 204 205 // init NSAPI request structure 206 if(request_initialize(pool, request, rq) != 0) { 207 log_ereport(LOG_FAILURE, "Cannot initialize request structure"); 208 return 1; 209 } 210 211 // set default virtual server 212 rq->vs = request->connection->listener->default_vs.vs; 213 214 // Pass request line as "clf-request" 215 // remove \r\n 216 cxmutstr clfreq = request->request_line; 217 while(clfreq.length > 0 && clfreq.ptr[clfreq.length - 1] < 33) { 218 clfreq.length--; 219 } 220 pblock_kvinsert( 221 pb_key_clf_request, 222 clfreq.ptr, 223 clfreq.length, 224 rq->rq.reqpb); 225 226 // Pass method as "method" in reqpb, and also as method_num 227 pblock_kvinsert( 228 pb_key_method, 229 request->method.ptr, 230 request->method.length, 231 rq->rq.reqpb); 232 // TODO: method num 233 //rqRq.rq.method_num = rqHdr->GetMethodNumber(); 234 //PR_ASSERT(rqRq.rq.method_num != -1 || iStatus); 235 236 // Pass protocol as "protocol" in reqpb, and also in protv_num 237 pblock_kvinsert( 238 pb_key_protocol, 239 request->httpv.ptr, 240 request->httpv.length, 241 rq->rq.reqpb); 242 243 if(!cx_strcmp(cx_strcast(request->httpv), (cxstring)CX_STR("HTTP/1.1"))) { 244 rq->rq.protv_num = PROTOCOL_VERSION_HTTP11; 245 } else if(!cx_strcmp(cx_strcast(request->httpv), (cxstring)CX_STR("HTTP/1.0"))) { 246 rq->rq.protv_num = PROTOCOL_VERSION_HTTP10; 247 } else { 248 // invalid protocol version - abort 249 log_ereport( 250 LOG_FAILURE, 251 "invalid protocol version: %.*s", 252 (int)request->httpv.length, 253 request->httpv.ptr); 254 return 1; 255 } 256 257 /* 258 * get absolute path and query of the request uri 259 */ 260 // TODO: check for '#' #72 261 cxmutstr absPath = http_request_get_abspath(request); 262 if(!absPath.ptr) { 263 // TODO: error msg 264 return 1; 265 } else if(absPath.ptr[0] == '*') { 266 // TODO: implement global OPTIONS #71 267 return 1; 268 } 269 270 for(int i=0;i<request->uri.length;i++) { 271 if(request->uri.ptr[i] == '?') { 272 cxmutstr query; 273 query.ptr = NULL; 274 query.length = 0; 275 276 /* split uri in path and query */ 277 if(absPath.length > i + 2) { 278 query.length = absPath.length - i - 1; 279 query.ptr = absPath.ptr + i + 1; 280 } 281 absPath.length = i; 282 283 // Pass any query as 'query' in reqpb 284 if(query.length > 0) { 285 pblock_kvinsert( 286 pb_key_query, 287 query.ptr, 288 query.length, 289 rq->rq.reqpb); 290 } 291 292 break; 293 } 294 } 295 296 // Get abs_path part of request URI, and canonicalize the path 297 cxmutstr orig_path = absPath; 298 absPath.ptr = util_canonicalize_uri( 299 pool, 300 absPath.ptr, 301 absPath.length, 302 (int*)&absPath.length); 303 if(!absPath.ptr) { 304 log_ereport( 305 LOG_WARN, 306 "invalid request path: {%.*s}", 307 (int)orig_path.length, 308 orig_path.ptr); 309 // TODO: 400 bad request 310 return 1; 311 } 312 313 // Decode the abs_path 314 if(util_uri_unescape_strict(absPath.ptr)) { 315 // Pass the abs_path as 'uri' in reqpb 316 pblock_kvinsert( 317 pb_key_uri, 318 absPath.ptr, 319 absPath.length, 320 rq->rq.reqpb); 321 } else { 322 log_ereport( 323 LOG_WARN, 324 "uri unescape failed: {%.*s}", 325 (int)absPath.length, 326 absPath.ptr); 327 // TODO: 400 bad request 328 pblock_kvinsert(pb_key_uri, "/", 1, rq->rq.reqpb); 329 } 330 331 // pass http header to the NSAPI request structure 332 int hlen = request->headers->len; 333 HeaderArray *ha = request->headers; 334 for(int i=0;i<=hlen;i++) { 335 if(i == hlen) { 336 ha = ha->next; 337 if(ha == NULL) { 338 break; 339 } 340 i = 0; 341 hlen = ha->len; 342 } 343 344 Header header = ha->headers[i]; 345 346 if(header.name.ptr[0] < 90) { 347 header.name.ptr[0] += 32; 348 } 349 350 // change to lower case 351 for(int j=0;j<header.name.length;j++) { 352 if(header.name.ptr[j] > 64 && header.name.ptr[j] < 97) { 353 header.name.ptr[j] += 32; 354 } 355 } 356 357 pblock_nvlinsert( 358 header.name.ptr, 359 header.name.length, 360 header.value.ptr, 361 header.value.length, 362 rq->rq.headers); 363 } 364 365 // get host and port 366 char *hosthdr = pblock_findkeyval(pb_key_host, rq->rq.headers); 367 if(hosthdr) { 368 char *host = pool_strdup(pool, hosthdr); 369 char *portstr = NULL; 370 if(host[0] != '[') { 371 portstr = strchr(host, ':'); 372 } else { 373 char *v6end = strchr(host, ']'); 374 if(v6end) { 375 portstr = strchr(v6end, ':'); 376 } 377 } 378 379 if(portstr) { 380 *portstr = '\0'; 381 } 382 rq->host = host; 383 } else { 384 rq->host = NULL; // TODO: value from listener 385 } 386 rq->port = request->connection->listener->port; 387 388 if(rq->host) { 389 VirtualServer *vs = cxMapGet(sn->config->host_vs, cx_hash_key_str(rq->host)); 390 if(vs) { 391 rq->vs = vs; 392 } else { 393 log_ereport( 394 LOG_VERBOSE, 395 "Unkown host ''%s'': using default virtual server", 396 rq->host); 397 } 398 } 399 400 // parse connection header 401 rq->rq.rq_attr.keep_alive = (rq->rq.protv_num >= PROTOCOL_VERSION_HTTP11); 402 char *conn_str = pblock_findkeyval(pb_key_connection, rq->rq.headers); 403 if(conn_str) { 404 if(!strcasecmp(conn_str, "keep-alive")) { 405 rq->rq.rq_attr.keep_alive = 1; 406 } else if(!strcasecmp(conn_str, "close")) { 407 rq->rq.rq_attr.keep_alive = 0; 408 } 409 } 410 411 // check for request body and prepare input buffer 412 char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers); 413 if(ctlen_str) { 414 int64_t ctlen; 415 if(util_strtoint(ctlen_str, &ctlen)) { 416 netbuf *nb = sn->netbuf; 417 HttpStream *net_io = (HttpStream*)sn->sn.csd; 418 net_io->read_eof = WS_FALSE; 419 420 // how many bytes are already read and in the buffer 421 int cur_input_available = nb->cursize - nb->pos; 422 423 if(cur_input_available >= ctlen) { 424 // we have the whole request body in the buffer and 425 // maybe even more 426 // no more read from the socket is necessary to get the body, 427 // therefore disable it 428 net_io->max_read = 0; 429 } else { 430 // read still required to get the complete request body 431 net_io->max_read = ctlen - cur_input_available; 432 } 433 //printf("request body length: %d\n", ctlen); 434 } // else: should we abort? 435 } 436 char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers); 437 if(transfer_encoding) { 438 if(!strcmp(transfer_encoding, "chunked")) { 439 netbuf *nb = sn->netbuf; 440 // a separate buffer is required for reading chunked transfer enc 441 sn->buffer = pool_malloc(pool, nb->maxsize); 442 if(!sn->buffer) { 443 // TODO: error 500 444 return 1; 445 } 446 447 // copy remaining bytes from inbuf to the additional buffer 448 if(nb->cursize - nb->pos > 0) { 449 memcpy(sn->buffer, nb->inbuf, nb->cursize); 450 } 451 452 sn->pos = nb->pos; 453 sn->cursize = nb->cursize; 454 455 // clear inbuf 456 nb->pos = 0; 457 nb->cursize = 0; 458 459 if(httpstream_enable_chunked_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) { 460 // TODO: error 500 461 return 1; 462 } 463 } // else: TODO: unknown transfer encoding error 464 } 465 466 // 467 // Send the request to the NSAPI system 468 // 469 470 // compare current and listener thread pool 471 threadpool_t *lstp = request->connection->listener->threadpool; 472 sn->defaultpool = lstp; 473 if(lstp == thrpool) { 474 sn->currentpool = thrpool; 475 nsapi_handle_request(sn, rq); 476 } else { 477 // execute nsapi functions on a different thread pool 478 nsapi_change_threadpool(sn, rq, lstp); 479 } 480 481 return 0; 482 } 483 484 485 486 void header_add(HeaderArray *hd, cxmutstr name, cxmutstr value) { 487 while(hd->len >= hd->alloc) { 488 if(hd->next == NULL) { 489 HeaderArray *block = malloc(sizeof(HeaderArray)); 490 block->next = NULL; 491 block->len = 0; 492 block->headers = calloc(16, sizeof(Header)); 493 block->alloc = 16; 494 hd->next = block; 495 } 496 hd = hd->next; 497 } 498 hd->headers[hd->len].name = name; 499 hd->headers[hd->len].value = value; 500 hd->len++; 501 } 502 503 void header_array_free(HeaderArray *hd) { 504 HeaderArray *next; 505 while(hd) { 506 next = hd->next; 507 free(hd->headers); 508 free(hd); 509 hd = next; 510 } 511 } 512 513 514 /* 515 * NSAPI Processing 516 */ 517 518 int nsapi_handle_request(NSAPISession *sn, NSAPIRequest *rq) { 519 log_ereport(LOG_DEBUG, "trace reqid: %016llx nsapi_handle_request %d", (unsigned long long int)sn->connection->id, rq->phase); 520 521 int r = REQ_NOACTION; 522 do { 523 switch(rq->phase) { 524 case NSAPIAuthTrans: { 525 r = nsapi_authtrans(sn, rq); 526 if(r != REQ_PROCEED) { 527 break; 528 } 529 rq->phase++; 530 nsapi_context_next_stage(&rq->context); 531 } 532 case NSAPINameTrans: { 533 //printf(">>> NameTrans\n"); 534 r = nsapi_nametrans(sn, rq); 535 if(r != REQ_PROCEED) { 536 break; 537 } 538 rq->phase++; 539 nsapi_context_next_stage(&rq->context); 540 } 541 case NSAPIPathCheck: { 542 //printf(">>> PathCheck\n"); 543 r = nsapi_pathcheck(sn, rq); 544 if(r != REQ_PROCEED) { 545 break; 546 } 547 rq->phase++; 548 nsapi_context_next_stage(&rq->context); 549 } 550 case NSAPIObjectType: { 551 //printf(">>> ObjectType\n"); 552 r = nsapi_objecttype(sn, rq); 553 if(r != REQ_PROCEED) { 554 break; 555 } 556 rq->phase++; 557 nsapi_context_next_stage(&rq->context); 558 } 559 case NSAPIService: { 560 //printf(">>> Service\n"); 561 r = nsapi_service(sn, rq); 562 if(r != REQ_PROCEED) { 563 break; 564 } 565 rq->phase = NSAPIAddLog; // skip NSAPIError 566 nsapi_context_next_stage(&rq->context); 567 } 568 case NSAPIAddLog: { 569 //printf(">>> AddLog\n"); 570 r = nsapi_addlog(sn, rq); 571 if(r == REQ_PROCESSING) { 572 break; 573 } 574 // finish request 575 r = REQ_PROCEED; 576 break; 577 } 578 default: // should not happen, but when it does, finish the req 579 case REQ_FINISH: { 580 //printf(">>> Finish\n"); 581 //r = nsapi_finish_request(sn, rq); 582 r = REQ_PROCEED; 583 break; 584 } 585 case NSAPIError: { 586 //printf(">>> Error\n"); 587 r = nsapi_error(sn, rq); 588 if(r == REQ_PROCEED) { 589 // restart the loop to switch to AddLog directive 590 r = REQ_RESTART; 591 rq->phase = NSAPIAddLog; 592 nsapi_context_next_stage(&rq->context); 593 } else { 594 /* 595 * an error occured while handling an error 596 * leave loop to finish the request (without AddLog) 597 */ 598 r = REQ_PROCEED; 599 break; 600 } 601 } 602 } 603 604 if(r == REQ_ABORTED) { 605 // switch to NSAPIError 606 rq->phase = NSAPIError; 607 nsapi_context_next_stage(&rq->context); 608 r = REQ_RESTART; 609 } 610 611 } while (r == REQ_RESTART); 612 613 if(r != REQ_PROCESSING) { 614 r = nsapi_finish_request(sn, rq); 615 } 616 617 return r; 618 } 619 620 int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq) { 621 rq->finished = TRUE; 622 request_free_resources(sn, rq); 623 624 log_ereport(LOG_DEBUG, "trace reqid: %016llx nsapi_finish_request", (unsigned long long int)sn->connection->id); 625 626 WSBool read_stream_eof = httpstream_eof(sn->sn.csd); 627 if(!read_stream_eof) { 628 log_ereport(LOG_WARN, "request input stream not closed"); 629 // TODO: clean stream 630 rq->rq.rq_attr.keep_alive = 0; // workaround 631 } 632 if(sn->pos < sn->cursize) { 633 log_ereport(LOG_WARN, "nsapi_finish_request: TODO: remaining bytes in buffer"); 634 // TODO: reuse buffer in next request 635 rq->rq.rq_attr.keep_alive = 0; // workaround 636 } 637 638 if(rq->rq.senthdrs) { 639 // flush buffer and add termination if chunked encoding 640 // is enabled 641 net_finish(sn->sn.csd); 642 } else { 643 // why was no response sent? 644 // something must have gone wrong 645 // terminate the session 646 char *clf_req = pblock_findkeyval(pb_key_clf_request, rq->rq.reqpb); 647 log_ereport(LOG_WARN, "nsapi_finish_request: no response header: request: %s", clf_req); 648 rq->rq.rq_attr.keep_alive = 0; 649 } 650 651 char *response_content_length = pblock_findkeyval(pb_key_content_length, rq->rq.srvhdrs); 652 int64_t response_ctlen; 653 if(response_content_length && util_strtoint(response_content_length, &response_ctlen)) { 654 int64_t w = httpstream_written(sn->sn.csd); 655 if(w != response_ctlen) { 656 log_ereport(LOG_WARN, "nsapi_finish_request: content-length != number of bytes written"); 657 rq->rq.rq_attr.keep_alive = 0; // fatal io error, we can not safely reuse the connection 658 } 659 } 660 661 if(rq->rq.rq_attr.keep_alive) { 662 SessionHandler *sh = sn->connection->session_handler; 663 sh->keep_alive(sh, sn->connection); 664 /* 665 * keep the connection object 666 * the sn->config is referenced by the connection, so we don't 667 * unref it 668 */ 669 } else { 670 log_ereport(LOG_DEBUG, "trace reqid: %016llx connection: close", (unsigned long long int)sn->connection->id); 671 connection_destroy(sn->connection); 672 } 673 674 // free all memory 675 free(sn->netbuf->inbuf); 676 free(sn->netbuf); 677 678 pool_destroy(sn->sn.pool); 679 680 return 0; 681 } 682 683 void request_free_resources(NSAPISession *sn, NSAPIRequest *rq) { 684 if(!rq->resources) return; 685 686 CxIterator i = cxMapIteratorValues(rq->resources); 687 cx_foreach(ResourceData *, resource, i) { 688 resourcepool_free(&sn->sn, &rq->rq, resource); 689 } 690 } 691 692 int nsapi_authtrans(NSAPISession *sn, NSAPIRequest *rq) { 693 HTTPObjectConfig *objconf = rq->vs->objects; 694 httpd_object *obj = objconf->objects[0]; 695 dtable *dt = object_get_dtable(obj, NSAPIAuthTrans); 696 697 int ret = rq->context.last_req_code; 698 for(int i=NCX_DI(rq);i<dt->ndir;i++) { 699 if(ret == REQ_NOACTION) { 700 directive *d = dt->dirs[i]; 701 ret = nsapi_exec(d, sn, rq); 702 } 703 704 if(ret != REQ_NOACTION) { 705 /* 706 * if a saf is still processing, we need to save the context, to 707 * process this object at a later time 708 */ 709 if(ret == REQ_PROCESSING) { 710 // save nsapi context 711 // add +1 to start next round with next function 712 rq->context.dtable_index = i + 1; 713 } 714 715 return ret; 716 } 717 } 718 719 720 return REQ_PROCEED; 721 } 722 723 int nsapi_nametrans(NSAPISession *sn, NSAPIRequest *rq) { 724 HTTPObjectConfig *objconf = rq->vs->objects; 725 //printf("nsapi_nametrans\n"); 726 httpd_objset *objset = objset_create(sn->sn.pool); 727 rq->rq.os = objset; 728 // first object in objconf is the default object TODO: make sure it is 729 objset_add_object(sn->sn.pool, objset, objconf->objects[0]); 730 731 httpd_object *obj = objset->obj[0]; // nametrans only in default object 732 dtable *dt = object_get_dtable(obj, NSAPINameTrans); 733 734 // execute directives 735 int ret = rq->context.last_req_code; 736 char *name = NULL; 737 char *ppath = NULL; 738 for(int i=NCX_DI(rq);i<dt->ndir;i++) { 739 if(ret == REQ_NOACTION) { 740 directive *d = dt->dirs[i]; 741 ret = nsapi_exec(d, sn, rq); 742 } 743 744 // check for name or ppath 745 name = pblock_findkeyval(pb_key_name, rq->rq.vars); 746 ppath = pblock_findkeyval(pb_key_ppath, rq->rq.vars); 747 748 // add additional objects to the objset 749 if(add_objects(objconf, objset, sn, rq, name, ppath) == REQ_ABORTED) { 750 log_ereport(LOG_FAILURE, "add_objects failed"); 751 return REQ_ABORTED; 752 } 753 754 if(ret != REQ_NOACTION) { 755 /* 756 * if a saf is still processing, we need to save the context, to 757 * process this object at a later time 758 */ 759 if(ret == REQ_PROCESSING) { 760 // save nsapi context 761 // add +1 to start next round with next function 762 rq->context.dtable_index = i + 1; 763 } else if(ret == REQ_PROCEED) { 764 char *pp = pblock_findkeyval(pb_key_ppath, rq->rq.vars); 765 pblock_kvinsert(pb_key_path, pp, strlen(pp), rq->rq.vars); 766 } 767 768 return ret; 769 } 770 } 771 772 // if no function has set the ppath var, translate it to docroot 773 if(ret == REQ_NOACTION && ppath == NULL) { 774 cxmutstr docroot = rq->vs->document_root; 775 if(docroot.length < 1) { 776 log_ereport( 777 LOG_WARN, 778 "VirtualServer(%.*s) docroot too short", 779 (int)rq->vs->name.length, 780 rq->vs->name.ptr); 781 return REQ_ABORTED; // docroot too short 782 } 783 784 // if there is a trailing '/', remove it 785 /* 786 if(docroot.ptr[docroot.length - 1] == '/') { 787 docroot.length--; 788 } 789 790 cxmutstr uri = cx_str(pblock_findkeyval(pb_key_uri, rq->rq.reqpb)); 791 792 cxmutstr translated; 793 translated.length = docroot.length + uri.length; 794 translated.ptr = alloca(translated.length + 1); 795 translated = cx_strncat(translated, 2, docroot, uri); 796 797 pblock_kvinsert( 798 pb_key_ppath, 799 translated.ptr, 800 translated.length, 801 rq->rq.vars); 802 */ 803 cxstring uri = cx_str(pblock_findkeyval(pb_key_uri, rq->rq.reqpb)); 804 request_set_path(cx_strcast(docroot), uri, rq->rq.vars); 805 } 806 807 // TODO: remove ppath 808 char *pp = pblock_findkeyval(pb_key_ppath, rq->rq.vars); 809 pblock_kvinsert(pb_key_path, pp, strlen(pp), rq->rq.vars); 810 811 return REQ_PROCEED; 812 } 813 814 int nsapi_pathcheck(NSAPISession *sn, NSAPIRequest *rq) { 815 //printf("nsapi_pathcheck\n"); 816 httpd_objset *objset = rq->rq.os; 817 818 if(NCX_OI(rq) == -1) { 819 NCX_OI(rq) = objset->pos - 1; 820 } 821 822 int ret = rq->context.last_req_code; 823 for(int i=NCX_OI(rq);i>=0;i--) { 824 httpd_object *obj = objset->obj[i]; 825 dtable *dt = object_get_dtable(obj, NSAPIPathCheck); 826 827 // execute directives 828 for(int j=NCX_DI(rq);j<dt->ndir;j++) { 829 if(ret == REQ_NOACTION || ret == REQ_PROCEED) { 830 directive *d = dt->dirs[j]; 831 ret = nsapi_exec(d, sn, rq); 832 } else { 833 if(ret == REQ_PROCESSING) { 834 // save nsapi context 835 rq->context.objset_index = i; 836 837 // add +1 to start next round with next function 838 rq->context.dtable_index = j + 1; 839 } 840 841 return ret; 842 } 843 } 844 } 845 846 return REQ_PROCEED; 847 } 848 849 int nsapi_objecttype(NSAPISession *sn, NSAPIRequest *rq) { 850 //printf("nsapi_objecttype\n"); 851 httpd_objset *objset = rq->rq.os; 852 853 if(NCX_OI(rq) == -1) { 854 // object index is undefined -> define correct object index 855 NCX_OI(rq) = objset->pos - 1; 856 } 857 858 int ret = rq->context.last_req_code; 859 for(int i=NCX_OI(rq);i>=0;i--) { 860 httpd_object *obj = objset->obj[i]; 861 dtable *dt = object_get_dtable(obj, NSAPIObjectType); 862 863 // execute directives 864 for(int j=NCX_DI(rq);j<dt->ndir;j++) { 865 if(ret == REQ_NOACTION) { 866 directive *d = dt->dirs[j]; 867 ret = nsapi_exec(d, sn, rq); 868 } 869 870 switch(ret) { 871 case REQ_PROCEED: { 872 char *type = pblock_findkeyval( 873 pb_key_content_type, 874 rq->rq.srvhdrs); 875 if(type == NULL) { 876 ret = REQ_NOACTION; 877 break; 878 } 879 return ret; 880 } 881 case REQ_PROCESSING: { 882 // save nsapi context 883 rq->context.objset_index = i; 884 885 // add +1 to start next round with next function 886 rq->context.dtable_index = j + 1; 887 return ret; 888 } 889 case REQ_NOACTION: { 890 break; 891 } 892 default: { 893 return ret; 894 } 895 } 896 } 897 } 898 899 /* 900 * No function returned with REQ_PROCEED, but we need a content type. 901 * If the path ends with a '/', we set the content type to 902 * 'internal/directory' so that 'index-common' can serve the content. 903 * Otherwise we set the content type to text/plain 904 */ 905 cxstring path = cx_str(pblock_findkeyval(pb_key_ppath, rq->rq.vars)); 906 cxstring ct; 907 if(path.ptr[path.length - 1] == '/') { 908 // directory 909 ct = (cxstring)CX_STR("internal/directory"); 910 } else { 911 ct = (cxstring)CX_STR("text/plain"); 912 } 913 pblock_kvinsert(pb_key_content_type, ct.ptr, ct.length, rq->rq.srvhdrs); 914 915 return REQ_PROCEED; 916 } 917 918 int nsapi_service(NSAPISession *sn, NSAPIRequest *rq) { 919 //printf("nsapi_service\n"); 920 httpd_objset *objset = rq->rq.os; 921 922 if(NCX_OI(rq) == -1) { 923 NCX_OI(rq) = objset->pos - 1; 924 } 925 926 int ret = rq->context.last_req_code; 927 char *content_type = NULL; 928 char *method = NULL; 929 for(int i=NCX_OI(rq);i>=0;i--) { 930 httpd_object *obj = objset->obj[i]; 931 dtable *dt = object_get_dtable(obj, NSAPIService); 932 933 // execute directives 934 for(int j=NCX_DI(rq);j<dt->ndir;j++) { 935 if(ret == REQ_NOACTION) { 936 directive *d = dt->dirs[j]; 937 938 // check type parameter 939 char *dtp = pblock_findkeyval(pb_key_type, d->param); 940 if(dtp) { 941 // type parameter for directive 942 if(!content_type) { 943 content_type = pblock_findkeyval( 944 pb_key_content_type, 945 rq->rq.srvhdrs); 946 } 947 // compare types 948 if(!contenttype_match(cx_str(dtp), cx_str(content_type))) { 949 continue; 950 } 951 } 952 953 // check method parameter 954 char *dmt = pblock_findkeyval(pb_key_method, d->param); 955 if(dmt) { 956 if(!method) { 957 method = pblock_findkeyval(pb_key_method, rq->rq.reqpb); 958 } 959 960 if(!method_match(dmt, method)) { 961 continue; 962 } 963 } 964 965 // execute the saf 966 ret = nsapi_exec(d, sn, rq); 967 } 968 969 if(ret != REQ_NOACTION) { 970 if(ret == REQ_PROCEED && !rq->rq.senthdrs) { 971 // a service SAF must send a response 972 // senthdrs == 0 indicators something has gone 973 // wrong 974 protocol_status(&sn->sn, &rq->rq, 500, NULL); 975 ret = REQ_ABORTED; 976 } else if(ret == REQ_PROCESSING) { 977 // save nsapi context 978 rq->context.objset_index = i; 979 980 // add +1 to start next round with next function 981 rq->context.dtable_index = j + 1; 982 } 983 984 return ret; 985 } 986 } 987 } 988 989 return ret; 990 } 991 992 int nsapi_error(NSAPISession *sn, NSAPIRequest *rq) { 993 //printf("nsapi_error\n"); 994 httpd_objset *objset = rq->rq.os; 995 996 if(NCX_OI(rq) == -1) { 997 NCX_OI(rq) = objset->pos - 1; 998 } 999 1000 int ret = rq->context.last_req_code; 1001 for(int i=NCX_OI(rq);i>=0;i--) { 1002 httpd_object *obj = objset->obj[i]; 1003 dtable *dt = object_get_dtable(obj, NSAPIError); 1004 1005 // execute directives 1006 for(int j=NCX_DI(rq);j<dt->ndir;j++) { 1007 if(ret == REQ_NOACTION) { 1008 directive *d = dt->dirs[j]; 1009 1010 // check status code parameter 1011 // Error SAFs can specify, for which status code they should 1012 // be executed 1013 char *status = pblock_findkeyval(pb_key_type, d->param); 1014 if(status) { 1015 int64_t statuscode = -1; 1016 if(!util_strtoint(status, &statuscode)) { 1017 log_ereport( 1018 LOG_WARN, 1019 "nsapi_error: directive ''%s'' ignored: invalid type parameter: integer status code expected", 1020 d->func->name); 1021 } else if(statuscode != rq->rq.status_num) { 1022 continue; 1023 } 1024 } 1025 1026 // execute the saf 1027 ret = nsapi_exec(d, sn, rq); 1028 } 1029 1030 if(ret == REQ_ABORTED) { 1031 // if an error directive fails, we use the default 1032 // error handler 1033 break; 1034 } 1035 if(ret != REQ_NOACTION) { 1036 if(ret == REQ_PROCEED) { 1037 // flush buffer and add termination if chunked encoding 1038 // is enabled 1039 net_finish(sn->sn.csd); 1040 } else if(ret == REQ_PROCESSING) { 1041 // save nsapi context 1042 rq->context.objset_index = i; 1043 1044 // add +1 to start next round with next function 1045 rq->context.dtable_index = j + 1; 1046 } 1047 return ret; 1048 } 1049 } 1050 } 1051 1052 if(ret != REQ_PROCEED) { 1053 // default error handler 1054 if(!rq->rq.senthdrs) { 1055 nsapi_error_request((Session*)sn, (Request*)rq); 1056 } 1057 } 1058 1059 return ret; 1060 } 1061 1062 int nsapi_addlog(NSAPISession *sn, NSAPIRequest *rq) { 1063 //printf("nsapi_addlog\n"); 1064 httpd_objset *objset = rq->rq.os; 1065 1066 if(NCX_OI(rq) == -1) { 1067 NCX_OI(rq) = objset->pos - 1; 1068 } 1069 1070 int ret = rq->context.last_req_code; 1071 for(int i=NCX_OI(rq);i>=0;i--) { 1072 httpd_object *obj = objset->obj[i]; 1073 dtable *dt = object_get_dtable(obj, NSAPIAddLog); 1074 1075 // execute directives 1076 for(int j=NCX_DI(rq);j<dt->ndir;j++) { 1077 if(ret == REQ_NOACTION) { 1078 directive *d = dt->dirs[j]; 1079 ret = nsapi_exec(d, sn, rq); 1080 } 1081 1082 if(ret != REQ_NOACTION) { 1083 if(ret == REQ_PROCESSING) { 1084 // save nsapi context 1085 rq->context.objset_index = i; 1086 1087 // add +1 to start next round with next function 1088 rq->context.dtable_index = j + 1; 1089 } 1090 1091 return ret; 1092 } 1093 } 1094 } 1095 1096 return REQ_PROCEED; 1097 } 1098 1099 struct _tpd_data { 1100 NSAPISession *sn; 1101 NSAPIRequest *rq; 1102 directive *directive; 1103 threadpool_t *threadpool; 1104 }; 1105 1106 int nsapi_exec(directive *d, NSAPISession *sn, NSAPIRequest *rq) { 1107 // TODO: condition 1108 if(d->cond) { 1109 if(!condition_evaluate(d->cond, (Session*)sn, (Request*)rq)) { 1110 return REQ_NOACTION; 1111 } 1112 } 1113 1114 char *poolname = pblock_findkeyval(pb_key_pool, d->param); 1115 if(poolname) { 1116 threadpool_t *pool = get_threadpool(cx_str(poolname)); 1117 if(pool && pool != sn->currentpool) { 1118 // execute directive in different thread pool 1119 return nsapi_exec_tp(d, sn, rq, pool); 1120 } 1121 } else if(sn->currentpool != sn->defaultpool) { 1122 // if no pool is set, use always the default thread pool 1123 return nsapi_exec_tp(d, sn, rq, sn->defaultpool); 1124 } 1125 1126 return SAF_EXEC(d->func, d->param, (Session*)sn, (Request*)rq); 1127 } 1128 1129 int nsapi_exec_tp( 1130 directive *d, 1131 NSAPISession *sn, 1132 NSAPIRequest *rq, 1133 threadpool_t *pool) 1134 { 1135 struct _tpd_data *data = malloc(sizeof(struct _tpd_data)); 1136 if(data == NULL) { 1137 // TODO: set error 1138 return REQ_ABORTED; 1139 } 1140 data->sn = sn; 1141 data->rq = rq; 1142 data->directive = d; 1143 data->threadpool = pool; 1144 1145 sn->currentpool = pool; 1146 threadpool_run(pool, thrpool_exec, data); 1147 1148 return REQ_PROCESSING; 1149 } 1150 1151 void nsapi_saf_return(Session *session, Request *request, int ret) { 1152 NSAPISession *sn = (NSAPISession*)session; 1153 NSAPIRequest *rq = (NSAPIRequest*)request; 1154 1155 rq->context.last_req_code = ret; 1156 1157 if(sn->currentpool != sn->defaultpool) { 1158 nsapi_change_threadpool(sn, rq, sn->defaultpool); 1159 } else { 1160 nsapi_handle_request(sn, rq); 1161 } 1162 } 1163 1164 void nsapi_function_return(Session *session, Request *request, int ret) { 1165 ev_saf_return(session->ev, session, request, ret); 1166 } 1167 1168 void nsapi_change_threadpool( 1169 NSAPISession *sn, 1170 NSAPIRequest *rq, 1171 threadpool_t *thrpool) 1172 { 1173 struct _tpd_data *data = malloc(sizeof(struct _tpd_data)); 1174 data->sn = sn; 1175 data->rq = rq; 1176 data->threadpool = thrpool; 1177 1178 threadpool_run(thrpool, thrpool_change, data); 1179 } 1180 1181 void* thrpool_exec(void *d) { 1182 struct _tpd_data *data = d; 1183 1184 data->sn->currentpool = data->threadpool; 1185 int r = SAF_EXEC( 1186 data->directive->func, 1187 data->directive->param, 1188 (Session*)data->sn, 1189 (Request*)data->rq); 1190 1191 nsapi_function_return((Session*)data->sn, (Request*)data->rq, r); 1192 free(data); 1193 1194 return NULL; 1195 } 1196 1197 void* thrpool_change(void *d) { 1198 struct _tpd_data *data = d; 1199 1200 data->sn->currentpool = data->threadpool; 1201 nsapi_handle_request(data->sn, data->rq); 1202 1203 free(data); 1204 return NULL; 1205 } 1206 1207 1208 /* 1209 * checks if the method matches the cmp string 1210 * the format of cmp is a single method string or a list of methods 1211 * (method1|method2|method3|...) 1212 */ 1213 int method_match(char *cmp, char *method) { 1214 if(cmp[0] != '(') { 1215 /* not a list of methods, so just compare the 2 strings */ 1216 if(!strcmp(cmp, method)) { 1217 return 1; 1218 } 1219 } else if(cmp[0] == 0) { 1220 // empty string 1221 log_ereport( 1222 LOG_WARN, 1223 "Skipped service saf with empty method parameter"); 1224 return 0; 1225 } 1226 1227 size_t method_len = strlen(method); 1228 size_t last_pos = 0; 1229 cmp++; /* skip '(' */ 1230 for(int i=0;cmp[i]!=0;i++) { 1231 char c = cmp[i]; 1232 if(c == '|' || c == ')') { 1233 size_t len = i - last_pos; 1234 if(len == method_len) { 1235 char *cp = cmp + last_pos; 1236 if(!memcmp(cp, method, len)) { 1237 return 1; 1238 } 1239 } 1240 last_pos = i + 1; 1241 } 1242 } 1243 1244 return 0; 1245 } 1246 1247 /* 1248 * checks if the content type matches the cmp string 1249 * the format of cmp is a single string with wildcards or a list 1250 * of types (also with wildcard support) 1251 * (type1|type2*) 1252 */ 1253 int contenttype_match(cxstring cmp, cxstring ctype) { 1254 if(cmp.ptr[0] != '(') { 1255 if(cmp.ptr[0] == '*') { 1256 cmp.ptr++; 1257 cmp.length--; 1258 return cx_strsuffix(ctype, cmp); 1259 } else if(cmp.ptr[cmp.length-1] == '*') { 1260 cmp.length--; 1261 return cx_strprefix(ctype, cmp); 1262 } else { 1263 return !cx_strcmp(cmp, ctype); 1264 } 1265 } else if(cmp.ptr[0] == 0) { 1266 log_ereport(LOG_WARN, "Skipped service saf with empty type parameter"); 1267 return 0; 1268 } 1269 1270 cmp = cx_strsubsl(cmp, 1, cmp.length - 2); 1271 1272 int begin = 0; 1273 for(int i=0;i<cmp.length;i++) { 1274 if(cmp.ptr[i] == '|') { 1275 if(contenttype_match(cx_strsubsl(cmp, begin, i-begin), ctype)) { 1276 return 1; 1277 } 1278 begin = i + 1; 1279 } 1280 } 1281 return contenttype_match(cx_strsubs(cmp, begin), ctype); 1282 } 1283 1284 /* 1285 * adds objects with specific name or path to the httpd_objset 1286 */ 1287 int add_objects( 1288 HTTPObjectConfig *objs, 1289 httpd_objset *os, 1290 NSAPISession *sn, 1291 NSAPIRequest *rq, 1292 char *name, 1293 char *path) 1294 { 1295 /* first, add all objects with a matching path */ 1296 /* TODO */ 1297 1298 1299 /* add object with object with matching name */ 1300 if(name == NULL) { 1301 return REQ_PROCEED; 1302 } 1303 1304 for(int i=0;i<objs->nobj;i++) { 1305 httpd_object *obj = objs->objects[i]; 1306 1307 if(obj->name && !strcmp(name, obj->name)) { 1308 objset_add_object(sn->sn.pool, os, obj); 1309 } 1310 } 1311 1312 return REQ_PROCEED; 1313 } 1314