Wed, 27 Nov 2024 23:00:07 +0100
add TODO to use a future ucx feature
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2013 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <arpa/inet.h> #include "../public/nsapi.h" #include "../util/pool.h" #include "../util/pblock.h" #include "../util/io.h" #include "../util/util.h" #include "httprequest.h" #include "config.h" #include "vserver.h" #include "event.h" #include "httplistener.h" #include "func.h" #include "error.h" void http_request_init(HTTPRequest *req) { memset(req, 0, sizeof(HTTPRequest)); HeaderArray *hd = malloc(sizeof(HeaderArray)); hd->next = NULL; hd->len = 0; hd->headers = calloc(16, sizeof(Header)); hd->alloc = 16; req->headers = hd; req->req_start = time(NULL); } void http_request_cleanup(HTTPRequest *req) { header_array_free(req->headers); free(req); } cxmutstr http_request_get_abspath(HTTPRequest *req) { cxmutstr uri = req->uri; int i = 0; if(uri.ptr[0] == '/') { return uri; } else if(cx_strprefix(cx_strcast(uri), (cxstring)CX_STR("http://"))) { i = 7; } else if(cx_strprefix(cx_strcast(uri), (cxstring)CX_STR("https://"))) { i = 8; } else if(!cx_strcmp(cx_strcast(uri), (cxstring)CX_STR("*"))) { return uri; } for(;i<uri.length;i++) { if(uri.ptr[i] == '/') { return cx_strsubs_m(uri, i); } } return (cxmutstr){ "/", 1 }; } NSAPISession* nsapisession_create(pool_handle_t *pool) { NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); if(!sn) { return NULL; } ZERO(sn, sizeof(NSAPISession)); sn->sn.pool = pool; sn->sn.client = pblock_create_pool(sn->sn.pool, 8); if(!sn->sn.client) { pool_free(pool, sn); return NULL; } sn->sn.fill = 1; return sn; } int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io) { SessionHandler *sh = conn->session_handler; WSBool ssl; IOStream *sio = sh->create_iostream(sh, conn, sn->sn.pool, &ssl); if(!sio) { return 1; } *io = sio; IOStream *http = httpstream_new(sn->sn.pool, sio); if(!http) { return 1; } sn->connection = conn; sn->netbuf = inbuf; sn->sn.csd = http; sn->sn.ssl = ssl; sn->sn.inbuf = inbuf; sn->sn.inbuf->sd = http; return 0; } int handle_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev) { // handle nsapi request // create pool pool_handle_t *pool = pool_create(); if(!pool) { log_ereport(LOG_FAILURE, "cannot create new memory pool for http request"); return 1; } int ret = nsapi_start_request(request, thrpool, ev, pool); if(ret) { pool_destroy(pool); } return ret; } int nsapi_start_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev, pool_handle_t *pool) { log_ereport(LOG_DEBUG, "trace reqid: %016llx nsapi_start_request", (unsigned long long int)request->connection->id); // create nsapi data structures NSAPISession *sn = nsapisession_create(pool); if(sn == NULL) { /* TODO: error */ return 1; } NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); if(rq == NULL) { /* TODO: error */ return 1; } ZERO(rq, sizeof(NSAPIRequest)); rq->rq.req_start = request->req_start; rq->phase = NSAPIAuthTrans; // fill session structure IOStream *io = NULL; if(nsapisession_setconnection(sn, request->connection, request->netbuf, &io)) { // TODO: error return 1; } if(!ev) { ev = ev_instance(get_default_event_handler()); } sn->sn.ev = ev; // the session needs the current server configuration sn->config = request->connection->listener->cfg; // add ip to sn->client pblock if(request->connection->addr_type == CONN_ADDR_IPV4) { char ip_str[INET_ADDRSTRLEN]; if(inet_ntop( AF_INET, &request->connection->address.address_v4.sin_addr, ip_str, INET_ADDRSTRLEN) != NULL) { pblock_kvinsert(pb_key_ip, ip_str, INET_ADDRSTRLEN, sn->sn.client); } } else if(request->connection->addr_type == CONN_ADDR_IPV6) { char ip_str[INET6_ADDRSTRLEN]; if(inet_ntop( AF_INET6, &request->connection->address.address_v6.sin6_addr, ip_str, INET6_ADDRSTRLEN) != NULL) { pblock_kvinsert(pb_key_ip, ip_str, INET6_ADDRSTRLEN, sn->sn.client); } } // init NSAPI request structure if(request_initialize(pool, request, rq) != 0) { log_ereport(LOG_FAILURE, "Cannot initialize request structure"); return 1; } // set default virtual server rq->vs = request->connection->listener->default_vs.vs; // Pass request line as "clf-request" // remove \r\n cxmutstr clfreq = request->request_line; while(clfreq.length > 0 && clfreq.ptr[clfreq.length - 1] < 33) { clfreq.length--; } pblock_kvinsert( pb_key_clf_request, clfreq.ptr, clfreq.length, rq->rq.reqpb); // Pass method as "method" in reqpb, and also as method_num pblock_kvinsert( pb_key_method, request->method.ptr, request->method.length, rq->rq.reqpb); // TODO: method num //rqRq.rq.method_num = rqHdr->GetMethodNumber(); //PR_ASSERT(rqRq.rq.method_num != -1 || iStatus); // Pass protocol as "protocol" in reqpb, and also in protv_num pblock_kvinsert( pb_key_protocol, request->httpv.ptr, request->httpv.length, rq->rq.reqpb); if(!cx_strcmp(cx_strcast(request->httpv), (cxstring)CX_STR("HTTP/1.1"))) { rq->rq.protv_num = PROTOCOL_VERSION_HTTP11; } else if(!cx_strcmp(cx_strcast(request->httpv), (cxstring)CX_STR("HTTP/1.0"))) { rq->rq.protv_num = PROTOCOL_VERSION_HTTP10; } else { // invalid protocol version - abort log_ereport( LOG_FAILURE, "invalid protocol version: %.*s", (int)request->httpv.length, request->httpv.ptr); return 1; } /* * get absolute path and query of the request uri */ // TODO: check for '#' #72 cxmutstr absPath = http_request_get_abspath(request); if(!absPath.ptr) { // TODO: error msg return 1; } else if(absPath.ptr[0] == '*') { // TODO: implement global OPTIONS #71 return 1; } for(int i=0;i<request->uri.length;i++) { if(request->uri.ptr[i] == '?') { cxmutstr query; query.ptr = NULL; query.length = 0; /* split uri in path and query */ if(absPath.length > i + 2) { query.length = absPath.length - i - 1; query.ptr = absPath.ptr + i + 1; } absPath.length = i; // Pass any query as 'query' in reqpb if(query.length > 0) { pblock_kvinsert( pb_key_query, query.ptr, query.length, rq->rq.reqpb); } break; } } // Get abs_path part of request URI, and canonicalize the path cxmutstr orig_path = absPath; absPath.ptr = util_canonicalize_uri( pool, absPath.ptr, absPath.length, (int*)&absPath.length); if(!absPath.ptr) { log_ereport( LOG_WARN, "invalid request path: {%.*s}", (int)orig_path.length, orig_path.ptr); // TODO: 400 bad request return 1; } // Decode the abs_path if(util_uri_unescape_strict(absPath.ptr)) { // Pass the abs_path as 'uri' in reqpb pblock_kvinsert( pb_key_uri, absPath.ptr, absPath.length, rq->rq.reqpb); } else { log_ereport( LOG_WARN, "uri unescape failed: {%.*s}", (int)absPath.length, absPath.ptr); // TODO: 400 bad request pblock_kvinsert(pb_key_uri, "/", 1, rq->rq.reqpb); } // pass http header to the NSAPI request structure int hlen = request->headers->len; HeaderArray *ha = request->headers; for(int i=0;i<=hlen;i++) { if(i == hlen) { ha = ha->next; if(ha == NULL) { break; } i = 0; hlen = ha->len; } Header header = ha->headers[i]; if(header.name.ptr[0] < 90) { header.name.ptr[0] += 32; } // change to lower case for(int j=0;j<header.name.length;j++) { if(header.name.ptr[j] > 64 && header.name.ptr[j] < 97) { header.name.ptr[j] += 32; } } pblock_nvlinsert( header.name.ptr, header.name.length, header.value.ptr, header.value.length, rq->rq.headers); } // get host and port char *hosthdr = pblock_findkeyval(pb_key_host, rq->rq.headers); if(hosthdr) { char *host = pool_strdup(pool, hosthdr); char *portstr = NULL; if(host[0] != '[') { portstr = strchr(host, ':'); } else { char *v6end = strchr(host, ']'); if(v6end) { portstr = strchr(v6end, ':'); } } if(portstr) { *portstr = '\0'; } rq->host = host; } else { rq->host = NULL; // TODO: value from listener } rq->port = request->connection->listener->port; if(rq->host) { VirtualServer *vs = cxMapGet(sn->config->host_vs, cx_hash_key_str(rq->host)); if(vs) { rq->vs = vs; } else { log_ereport( LOG_VERBOSE, "Unkown host '%s': using default virtual server", rq->host); } } // parse connection header rq->rq.rq_attr.keep_alive = (rq->rq.protv_num >= PROTOCOL_VERSION_HTTP11); char *conn_str = pblock_findkeyval(pb_key_connection, rq->rq.headers); if(conn_str) { if(!strcasecmp(conn_str, "keep-alive")) { rq->rq.rq_attr.keep_alive = 1; } else if(!strcasecmp(conn_str, "close")) { rq->rq.rq_attr.keep_alive = 0; } } // check for request body and prepare input buffer char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers); if(ctlen_str) { int64_t ctlen; if(util_strtoint(ctlen_str, &ctlen)) { netbuf *nb = sn->netbuf; HttpStream *net_io = (HttpStream*)sn->sn.csd; net_io->read_eof = WS_FALSE; // how many bytes are already read and in the buffer int cur_input_available = nb->cursize - nb->pos; if(cur_input_available >= ctlen) { // we have the whole request body in the buffer and // maybe even more // no more read from the socket is necessary to get the body, // therefore disable it net_io->max_read = 0; } else { // read still required to get the complete request body net_io->max_read = ctlen - cur_input_available; } //printf("request body length: %d\n", ctlen); } // else: should we abort? } char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers); if(transfer_encoding) { if(!strcmp(transfer_encoding, "chunked")) { netbuf *nb = sn->netbuf; // a separate buffer is required for reading chunked transfer enc sn->buffer = pool_malloc(pool, nb->maxsize); if(!sn->buffer) { // TODO: error 500 return 1; } // copy remaining bytes from inbuf to the additional buffer if(nb->cursize - nb->pos > 0) { memcpy(sn->buffer, nb->inbuf, nb->cursize); } sn->pos = nb->pos; sn->cursize = nb->cursize; // clear inbuf nb->pos = 0; nb->cursize = 0; if(httpstream_enable_chunked_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) { // TODO: error 500 return 1; } } // else: TODO: unknown transfer encoding error } // // Send the request to the NSAPI system // // compare current and listener thread pool threadpool_t *lstp = request->connection->listener->threadpool; sn->defaultpool = lstp; if(lstp == thrpool) { sn->currentpool = thrpool; nsapi_handle_request(sn, rq); } else { // execute nsapi functions on a different thread pool nsapi_change_threadpool(sn, rq, lstp); } return 0; } void header_add(HeaderArray *hd, cxmutstr name, cxmutstr value) { while(hd->len >= hd->alloc) { if(hd->next == NULL) { HeaderArray *block = malloc(sizeof(HeaderArray)); block->next = NULL; block->len = 0; block->headers = calloc(16, sizeof(Header)); block->alloc = 16; hd->next = block; } hd = hd->next; } hd->headers[hd->len].name = name; hd->headers[hd->len].value = value; hd->len++; } void header_array_free(HeaderArray *hd) { HeaderArray *next; while(hd) { next = hd->next; free(hd->headers); free(hd); hd = next; } } /* * NSAPI Processing */ int nsapi_handle_request(NSAPISession *sn, NSAPIRequest *rq) { log_ereport(LOG_DEBUG, "trace reqid: %016llx nsapi_handle_request %d", (unsigned long long int)sn->connection->id, rq->phase); int r = REQ_NOACTION; do { switch(rq->phase) { case NSAPIAuthTrans: { r = nsapi_authtrans(sn, rq); if(r != REQ_PROCEED) { break; } rq->phase++; nsapi_context_next_stage(&rq->context); } case NSAPINameTrans: { //printf(">>> NameTrans\n"); r = nsapi_nametrans(sn, rq); if(r != REQ_PROCEED) { break; } rq->phase++; nsapi_context_next_stage(&rq->context); } case NSAPIPathCheck: { //printf(">>> PathCheck\n"); r = nsapi_pathcheck(sn, rq); if(r != REQ_PROCEED) { break; } rq->phase++; nsapi_context_next_stage(&rq->context); } case NSAPIObjectType: { //printf(">>> ObjectType\n"); r = nsapi_objecttype(sn, rq); if(r != REQ_PROCEED) { break; } rq->phase++; nsapi_context_next_stage(&rq->context); } case NSAPIService: { //printf(">>> Service\n"); r = nsapi_service(sn, rq); if(r != REQ_PROCEED) { break; } rq->phase = NSAPIAddLog; // skip NSAPIError nsapi_context_next_stage(&rq->context); } case NSAPIAddLog: { //printf(">>> AddLog\n"); r = nsapi_addlog(sn, rq); if(r == REQ_PROCESSING) { break; } // finish request r = REQ_PROCEED; break; } default: // should not happen, but when it does, finish the req case REQ_FINISH: { //printf(">>> Finish\n"); //r = nsapi_finish_request(sn, rq); r = REQ_PROCEED; break; } case NSAPIError: { //printf(">>> Error\n"); r = nsapi_error(sn, rq); if(r == REQ_PROCEED) { // restart the loop to switch to AddLog directive r = REQ_RESTART; rq->phase = NSAPIAddLog; nsapi_context_next_stage(&rq->context); } else { /* * an error occured while handling an error * leave loop to finish the request (without AddLog) */ r = REQ_PROCEED; break; } } } if(r == REQ_ABORTED) { // switch to NSAPIError rq->phase = NSAPIError; nsapi_context_next_stage(&rq->context); r = REQ_RESTART; } } while (r == REQ_RESTART); if(r != REQ_PROCESSING) { r = nsapi_finish_request(sn, rq); } return r; } int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq) { rq->finished = TRUE; request_free_resources(sn, rq); log_ereport(LOG_DEBUG, "trace reqid: %016llx nsapi_finish_request", (unsigned long long int)sn->connection->id); WSBool read_stream_eof = httpstream_eof(sn->sn.csd); if(!read_stream_eof) { log_ereport(LOG_WARN, "request input stream not closed"); // TODO: clean stream rq->rq.rq_attr.keep_alive = 0; // workaround } if(sn->pos < sn->cursize) { log_ereport(LOG_WARN, "nsapi_finish_request: TODO: remaining bytes in buffer"); // TODO: reuse buffer in next request rq->rq.rq_attr.keep_alive = 0; // workaround } if(rq->rq.senthdrs) { // flush buffer and add termination if chunked encoding // is enabled net_finish(sn->sn.csd); } else { // why was no response sent? // something must have gone wrong // terminate the session char *clf_req = pblock_findkeyval(pb_key_clf_request, rq->rq.reqpb); log_ereport(LOG_WARN, "nsapi_finish_request: no response header: request: %s", clf_req); rq->rq.rq_attr.keep_alive = 0; } char *response_content_length = pblock_findkeyval(pb_key_content_length, rq->rq.srvhdrs); int64_t response_ctlen; if(response_content_length && util_strtoint(response_content_length, &response_ctlen)) { int64_t w = httpstream_written(sn->sn.csd); if(w != response_ctlen) { log_ereport(LOG_WARN, "nsapi_finish_request: content-length != number of bytes written"); rq->rq.rq_attr.keep_alive = 0; // fatal io error, we can not safely reuse the connection } } if(rq->rq.rq_attr.keep_alive) { SessionHandler *sh = sn->connection->session_handler; sh->keep_alive(sh, sn->connection); /* * keep the connection object * the sn->config is referenced by the connection, so we don't * unref it */ } else { log_ereport(LOG_DEBUG, "trace reqid: %016llx connection: close", (unsigned long long int)sn->connection->id); connection_destroy(sn->connection); } // free all memory free(sn->netbuf->inbuf); free(sn->netbuf); pool_destroy(sn->sn.pool); return 0; } void request_free_resources(NSAPISession *sn, NSAPIRequest *rq) { if(!rq->resources) return; CxIterator i = cxMapIteratorValues(rq->resources); cx_foreach(ResourceData *, resource, i) { resourcepool_free(&sn->sn, &rq->rq, resource); } } int nsapi_authtrans(NSAPISession *sn, NSAPIRequest *rq) { HTTPObjectConfig *objconf = rq->vs->objects; httpd_object *obj = objconf->objects[0]; dtable *dt = object_get_dtable(obj, NSAPIAuthTrans); int ret = rq->context.last_req_code; for(int i=NCX_DI(rq);i<dt->ndir;i++) { if(ret == REQ_NOACTION) { directive *d = dt->dirs[i]; ret = nsapi_exec(d, sn, rq); } if(ret != REQ_NOACTION) { /* * if a saf is still processing, we need to save the context, to * process this object at a later time */ if(ret == REQ_PROCESSING) { // save nsapi context // add +1 to start next round with next function rq->context.dtable_index = i + 1; } return ret; } } return REQ_PROCEED; } int nsapi_nametrans(NSAPISession *sn, NSAPIRequest *rq) { HTTPObjectConfig *objconf = rq->vs->objects; //printf("nsapi_nametrans\n"); httpd_objset *objset = objset_create(sn->sn.pool); rq->rq.os = objset; // first object in objconf is the default object TODO: make sure it is objset_add_object(sn->sn.pool, objset, objconf->objects[0]); httpd_object *obj = objset->obj[0]; // nametrans only in default object dtable *dt = object_get_dtable(obj, NSAPINameTrans); // execute directives int ret = rq->context.last_req_code; char *name = NULL; char *ppath = NULL; for(int i=NCX_DI(rq);i<dt->ndir;i++) { if(ret == REQ_NOACTION) { directive *d = dt->dirs[i]; ret = nsapi_exec(d, sn, rq); } // check for name or ppath name = pblock_findkeyval(pb_key_name, rq->rq.vars); ppath = pblock_findkeyval(pb_key_ppath, rq->rq.vars); // add additional objects to the objset if(add_objects(objconf, objset, sn, rq, name, ppath) == REQ_ABORTED) { log_ereport(LOG_FAILURE, "add_objects failed"); return REQ_ABORTED; } if(ret != REQ_NOACTION) { /* * if a saf is still processing, we need to save the context, to * process this object at a later time */ if(ret == REQ_PROCESSING) { // save nsapi context // add +1 to start next round with next function rq->context.dtable_index = i + 1; } else if(ret == REQ_PROCEED) { char *pp = pblock_findkeyval(pb_key_ppath, rq->rq.vars); pblock_kvinsert(pb_key_path, pp, strlen(pp), rq->rq.vars); } return ret; } } // if no function has set the ppath var, translate it to docroot if(ret == REQ_NOACTION && ppath == NULL) { cxmutstr docroot = rq->vs->document_root; if(docroot.length < 1) { log_ereport( LOG_WARN, "VirtualServer(%.*s) docroot too short", (int)rq->vs->name.length, rq->vs->name.ptr); return REQ_ABORTED; // docroot too short } // if there is a trailing '/', remove it /* if(docroot.ptr[docroot.length - 1] == '/') { docroot.length--; } cxmutstr uri = cx_str(pblock_findkeyval(pb_key_uri, rq->rq.reqpb)); cxmutstr translated; translated.length = docroot.length + uri.length; translated.ptr = alloca(translated.length + 1); translated = cx_strncat(translated, 2, docroot, uri); pblock_kvinsert( pb_key_ppath, translated.ptr, translated.length, rq->rq.vars); */ cxstring uri = cx_str(pblock_findkeyval(pb_key_uri, rq->rq.reqpb)); request_set_path(cx_strcast(docroot), uri, rq->rq.vars); } // TODO: remove ppath char *pp = pblock_findkeyval(pb_key_ppath, rq->rq.vars); pblock_kvinsert(pb_key_path, pp, strlen(pp), rq->rq.vars); return REQ_PROCEED; } int nsapi_pathcheck(NSAPISession *sn, NSAPIRequest *rq) { //printf("nsapi_pathcheck\n"); httpd_objset *objset = rq->rq.os; if(NCX_OI(rq) == -1) { NCX_OI(rq) = objset->pos - 1; } int ret = rq->context.last_req_code; for(int i=NCX_OI(rq);i>=0;i--) { httpd_object *obj = objset->obj[i]; dtable *dt = object_get_dtable(obj, NSAPIPathCheck); // execute directives for(int j=NCX_DI(rq);j<dt->ndir;j++) { if(ret == REQ_NOACTION || ret == REQ_PROCEED) { directive *d = dt->dirs[j]; ret = nsapi_exec(d, sn, rq); } else { if(ret == REQ_PROCESSING) { // save nsapi context rq->context.objset_index = i; // add +1 to start next round with next function rq->context.dtable_index = j + 1; } return ret; } } } return REQ_PROCEED; } int nsapi_objecttype(NSAPISession *sn, NSAPIRequest *rq) { //printf("nsapi_objecttype\n"); httpd_objset *objset = rq->rq.os; if(NCX_OI(rq) == -1) { // object index is undefined -> define correct object index NCX_OI(rq) = objset->pos - 1; } int ret = rq->context.last_req_code; for(int i=NCX_OI(rq);i>=0;i--) { httpd_object *obj = objset->obj[i]; dtable *dt = object_get_dtable(obj, NSAPIObjectType); // execute directives for(int j=NCX_DI(rq);j<dt->ndir;j++) { if(ret == REQ_NOACTION) { directive *d = dt->dirs[j]; ret = nsapi_exec(d, sn, rq); } switch(ret) { case REQ_PROCEED: { char *type = pblock_findkeyval( pb_key_content_type, rq->rq.srvhdrs); if(type == NULL) { ret = REQ_NOACTION; break; } return ret; } case REQ_PROCESSING: { // save nsapi context rq->context.objset_index = i; // add +1 to start next round with next function rq->context.dtable_index = j + 1; return ret; } case REQ_NOACTION: { break; } default: { return ret; } } } } /* * No function returned with REQ_PROCEED, but we need a content type. * If the path ends with a '/', we set the content type to * 'internal/directory' so that 'index-common' can serve the content. * Otherwise we set the content type to text/plain */ cxstring path = cx_str(pblock_findkeyval(pb_key_ppath, rq->rq.vars)); cxstring ct; if(path.ptr[path.length - 1] == '/') { // directory ct = (cxstring)CX_STR("internal/directory"); } else { ct = (cxstring)CX_STR("text/plain"); } pblock_kvinsert(pb_key_content_type, ct.ptr, ct.length, rq->rq.srvhdrs); return REQ_PROCEED; } int nsapi_service(NSAPISession *sn, NSAPIRequest *rq) { //printf("nsapi_service\n"); httpd_objset *objset = rq->rq.os; if(NCX_OI(rq) == -1) { NCX_OI(rq) = objset->pos - 1; } int ret = rq->context.last_req_code; char *content_type = NULL; char *method = NULL; for(int i=NCX_OI(rq);i>=0;i--) { httpd_object *obj = objset->obj[i]; dtable *dt = object_get_dtable(obj, NSAPIService); // execute directives for(int j=NCX_DI(rq);j<dt->ndir;j++) { if(ret == REQ_NOACTION) { directive *d = dt->dirs[j]; // check type parameter char *dtp = pblock_findkeyval(pb_key_type, d->param); if(dtp) { // type parameter for directive if(!content_type) { content_type = pblock_findkeyval( pb_key_content_type, rq->rq.srvhdrs); } // compare types if(!contenttype_match(cx_str(dtp), cx_str(content_type))) { continue; } } // check method parameter char *dmt = pblock_findkeyval(pb_key_method, d->param); if(dmt) { if(!method) { method = pblock_findkeyval(pb_key_method, rq->rq.reqpb); } if(!method_match(dmt, method)) { continue; } } // execute the saf ret = nsapi_exec(d, sn, rq); } if(ret != REQ_NOACTION) { if(ret == REQ_PROCEED && !rq->rq.senthdrs) { // a service SAF must send a response // senthdrs == 0 indicators something has gone // wrong protocol_status(&sn->sn, &rq->rq, 500, NULL); ret = REQ_ABORTED; } else if(ret == REQ_PROCESSING) { // save nsapi context rq->context.objset_index = i; // add +1 to start next round with next function rq->context.dtable_index = j + 1; } return ret; } } } return ret; } int nsapi_error(NSAPISession *sn, NSAPIRequest *rq) { //printf("nsapi_error\n"); httpd_objset *objset = rq->rq.os; if(NCX_OI(rq) == -1) { NCX_OI(rq) = objset->pos - 1; } int ret = rq->context.last_req_code; for(int i=NCX_OI(rq);i>=0;i--) { httpd_object *obj = objset->obj[i]; dtable *dt = object_get_dtable(obj, NSAPIError); // execute directives for(int j=NCX_DI(rq);j<dt->ndir;j++) { if(ret == REQ_NOACTION) { directive *d = dt->dirs[j]; // check status code parameter // Error SAFs can specify, for which status code they should // be executed char *status = pblock_findkeyval(pb_key_type, d->param); if(status) { int64_t statuscode = -1; if(!util_strtoint(status, &statuscode)) { log_ereport( LOG_WARN, "nsapi_error: directive '%s' ignored: invalid type parameter: integer status code expected", d->func->name); } else if(statuscode != rq->rq.status_num) { continue; } } // execute the saf ret = nsapi_exec(d, sn, rq); } if(ret == REQ_ABORTED) { // if an error directive fails, we use the default // error handler break; } if(ret != REQ_NOACTION) { if(ret == REQ_PROCEED) { // flush buffer and add termination if chunked encoding // is enabled net_finish(sn->sn.csd); } else if(ret == REQ_PROCESSING) { // save nsapi context rq->context.objset_index = i; // add +1 to start next round with next function rq->context.dtable_index = j + 1; } return ret; } } } if(ret != REQ_PROCEED) { // default error handler if(!rq->rq.senthdrs) { nsapi_error_request((Session*)sn, (Request*)rq); } } return ret; } int nsapi_addlog(NSAPISession *sn, NSAPIRequest *rq) { //printf("nsapi_addlog\n"); httpd_objset *objset = rq->rq.os; if(NCX_OI(rq) == -1) { NCX_OI(rq) = objset->pos - 1; } int ret = rq->context.last_req_code; for(int i=NCX_OI(rq);i>=0;i--) { httpd_object *obj = objset->obj[i]; dtable *dt = object_get_dtable(obj, NSAPIAddLog); // execute directives for(int j=NCX_DI(rq);j<dt->ndir;j++) { if(ret == REQ_NOACTION) { directive *d = dt->dirs[j]; ret = nsapi_exec(d, sn, rq); } if(ret != REQ_NOACTION) { if(ret == REQ_PROCESSING) { // save nsapi context rq->context.objset_index = i; // add +1 to start next round with next function rq->context.dtable_index = j + 1; } return ret; } } } return REQ_PROCEED; } struct _tpd_data { NSAPISession *sn; NSAPIRequest *rq; directive *directive; threadpool_t *threadpool; }; int nsapi_exec(directive *d, NSAPISession *sn, NSAPIRequest *rq) { // TODO: condition if(d->cond) { if(!condition_evaluate(d->cond, (Session*)sn, (Request*)rq)) { return REQ_NOACTION; } } char *poolname = pblock_findkeyval(pb_key_pool, d->param); if(poolname) { threadpool_t *pool = get_threadpool(cx_str(poolname)); if(pool && pool != sn->currentpool) { // execute directive in different thread pool return nsapi_exec_tp(d, sn, rq, pool); } } else if(sn->currentpool != sn->defaultpool) { // if no pool is set, use always the default thread pool return nsapi_exec_tp(d, sn, rq, sn->defaultpool); } return SAF_EXEC(d->func, d->param, (Session*)sn, (Request*)rq); } int nsapi_exec_tp( directive *d, NSAPISession *sn, NSAPIRequest *rq, threadpool_t *pool) { struct _tpd_data *data = malloc(sizeof(struct _tpd_data)); if(data == NULL) { // TODO: set error return REQ_ABORTED; } data->sn = sn; data->rq = rq; data->directive = d; data->threadpool = pool; sn->currentpool = pool; threadpool_run(pool, thrpool_exec, data); return REQ_PROCESSING; } void nsapi_saf_return(Session *session, Request *request, int ret) { NSAPISession *sn = (NSAPISession*)session; NSAPIRequest *rq = (NSAPIRequest*)request; rq->context.last_req_code = ret; if(sn->currentpool != sn->defaultpool) { nsapi_change_threadpool(sn, rq, sn->defaultpool); } else { nsapi_handle_request(sn, rq); } } void nsapi_function_return(Session *session, Request *request, int ret) { ev_saf_return(session->ev, session, request, ret); } void nsapi_change_threadpool( NSAPISession *sn, NSAPIRequest *rq, threadpool_t *thrpool) { struct _tpd_data *data = malloc(sizeof(struct _tpd_data)); data->sn = sn; data->rq = rq; data->threadpool = thrpool; threadpool_run(thrpool, thrpool_change, data); } void* thrpool_exec(void *d) { struct _tpd_data *data = d; data->sn->currentpool = data->threadpool; int r = SAF_EXEC( data->directive->func, data->directive->param, (Session*)data->sn, (Request*)data->rq); nsapi_function_return((Session*)data->sn, (Request*)data->rq, r); free(data); return NULL; } void* thrpool_change(void *d) { struct _tpd_data *data = d; data->sn->currentpool = data->threadpool; nsapi_handle_request(data->sn, data->rq); free(data); return NULL; } /* * checks if the method matches the cmp string * the format of cmp is a single method string or a list of methods * (method1|method2|method3|...) */ int method_match(char *cmp, char *method) { if(cmp[0] != '(') { /* not a list of methods, so just compare the 2 strings */ if(!strcmp(cmp, method)) { return 1; } } else if(cmp[0] == 0) { // empty string log_ereport( LOG_WARN, "Skipped service saf with empty method parameter"); return 0; } size_t method_len = strlen(method); size_t last_pos = 0; cmp++; /* skip '(' */ for(int i=0;cmp[i]!=0;i++) { char c = cmp[i]; if(c == '|' || c == ')') { size_t len = i - last_pos; if(len == method_len) { char *cp = cmp + last_pos; if(!memcmp(cp, method, len)) { return 1; } } last_pos = i + 1; } } return 0; } /* * checks if the content type matches the cmp string * the format of cmp is a single string with wildcards or a list * of types (also with wildcard support) * (type1|type2*) */ int contenttype_match(cxstring cmp, cxstring ctype) { if(cmp.ptr[0] != '(') { if(cmp.ptr[0] == '*') { cmp.ptr++; cmp.length--; return cx_strsuffix(ctype, cmp); } else if(cmp.ptr[cmp.length-1] == '*') { cmp.length--; return cx_strprefix(ctype, cmp); } else { return !cx_strcmp(cmp, ctype); } } else if(cmp.ptr[0] == 0) { log_ereport(LOG_WARN, "Skipped service saf with empty type parameter"); return 0; } cmp = cx_strsubsl(cmp, 1, cmp.length - 2); int begin = 0; for(int i=0;i<cmp.length;i++) { if(cmp.ptr[i] == '|') { if(contenttype_match(cx_strsubsl(cmp, begin, i-begin), ctype)) { return 1; } begin = i + 1; } } return contenttype_match(cx_strsubs(cmp, begin), ctype); } /* * adds objects with specific name or path to the httpd_objset */ int add_objects( HTTPObjectConfig *objs, httpd_objset *os, NSAPISession *sn, NSAPIRequest *rq, char *name, char *path) { /* first, add all objects with a matching path */ /* TODO */ /* add object with object with matching name */ if(name == NULL) { return REQ_PROCEED; } for(int i=0;i<objs->nobj;i++) { httpd_object *obj = objs->objects[i]; if(obj->name && !strcmp(name, obj->name)) { objset_add_object(sn->sn.pool, os, obj); } } return REQ_PROCEED; }