Sat, 22 Jun 2013 13:08:36 +0200
some fixes
/* * 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 <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 "httplistener.h" #include "error.h" void http_request_init(HTTPRequest *req) { req->connection = NULL; req->uri.ptr = NULL; HeaderArray *hd = malloc(sizeof(HeaderArray)); hd->next = NULL; hd->len = 0; hd->headers = calloc(16, sizeof(Header)); hd->alloc = 16; req->headers = hd; } int handle_request(HTTPRequest *request, threadpool_t *thrpool) { // handle nsapi request // create pool pool_handle_t *pool = pool_create(); // create nsapi data structures NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); if(sn == NULL) { /* TODO: error */ } NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); if(rq == NULL) { /* TODO: error */ } request->rq = rq; rq->phase = NSAPIAuthTrans; // fill session structure sn->connection = request->connection; sn->netbuf = request->netbuf; sn->sn.pool = pool; //sn->sn.csd = stream_new_from_fd(pool, request->connection->fd); sn->sn.csd = net_stream_from_fd(pool, request->connection->fd); sn->sn.client = pblock_create_pool(sn->sn.pool, 8); sn->sn.next = NULL; sn->sn.fill = 1; sn->sn.subject = NULL; // the session needs the current server configuration sn->config = request->connection->listener->cfg; // TODO: ref // add ip to sn->client pblock char ip_str[INET_ADDRSTRLEN]; if(inet_ntop( AF_INET, &request->connection->address.sin_addr, ip_str, INET_ADDRSTRLEN) != NULL) { pblock_kvinsert(pb_key_ip, ip_str, INET_ADDRSTRLEN, sn->sn.client); } // init NSAPI request structure if(request_initialize(pool, request, rq) != 0) { log_ereport(LOG_FAILURE, "Cannot initialize request structure"); pool_destroy(pool); return 1; } // set default virtual server rq->vs = request->connection->listener->default_vs.vs; // Pass request line as "clf-request" // remove \r\n sstr_t clfreq = request->request_line; while(clfreq.ptr[clfreq.length - 1] < 33) { clfreq.length--; } request->request_line = clfreq; pblock_kvinsert( pb_key_clf_request, request->request_line.ptr, request->request_line.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); // TODO: protocol num /* * get absolute path and query of the request uri */ // TODO: check for '#' sstr_t absPath = request->uri; sstr_t query; query.length = 0; for(int i=0;i<request->uri.length;i++) { if(request->uri.ptr[i] == '?') { /* 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 pblock_kvinsert( pb_key_query, query.ptr, query.length, rq->rq.reqpb); break; } } // Get abs_path part of request URI, and canonicalize the path absPath.ptr = util_canonicalize_uri( pool, absPath.ptr, absPath.length, (int*)&absPath.length); // 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 { // TODO: log error printf("unescape failed\n"); 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; } if(ha->headers[i].name[0] < 90) { ha->headers[i].name[0] += 32; } // change to lower case char *header_name = ha->headers[i].name; for(int i=0;header_name[i]!=0;i++) { if(header_name[i] > 64 && header_name[i] < 97) { header_name[i] += 32; } } pblock_nvinsert( ha->headers[i].name, ha->headers[i].value, rq->rq.headers); } // check for request body and prepare input buffer char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers); if(ctlen_str) { int ctlen = atoi(ctlen_str); printf("request body length: %d\n", ctlen); netbuf *nb = request->netbuf; // create new netbuf NetIOStream *net_io = (NetIOStream*)net_stream_from_fd( pool, request->connection->fd); net_io->max_read = ctlen; sn->sn.inbuf = pool_malloc(pool, sizeof(netbuf)); sn->sn.inbuf->sd = net_io; sn->sn.inbuf->pos = 0; // prepare buffer int cur_input_len = nb->cursize - nb->pos; if(cur_input_len >= ctlen) { /* * all data is already in the primary input buffer * just link the new netbuf to the primary buffer */ sn->sn.inbuf->maxsize = ctlen; sn->sn.inbuf->cursize = ctlen; sn->sn.inbuf->inbuf = nb->inbuf + nb->pos; } else { sn->sn.inbuf->maxsize = (ctlen > 2048) ? (2048) : (ctlen); sn->sn.inbuf->inbuf = pool_malloc(pool, sn->sn.inbuf->maxsize); if(cur_input_len > 0) { // we have read a part of the request body -> copy to netbuf memcpy(sn->sn.inbuf->inbuf, nb->inbuf+nb->pos, cur_input_len); } sn->sn.inbuf->cursize = cur_input_len; } } else { sn->sn.inbuf = NULL; } // // 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, char *name, char *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 * TODO: add this to new file */ int nsapi_handle_request(NSAPISession *sn, NSAPIRequest *rq) { 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++; nsapi_context_next_stage(&rq->context); } case NSAPIAddLog: { //printf(">>> AddLog\n"); r = nsapi_addlog(sn, rq); if(r != REQ_PROCESSING && r != REQ_ABORTED) { break; } rq->phase++; nsapi_context_next_stage(&rq->context); } default: // should not happen, but when it does, finish the req case REQ_FINISH: { //printf(">>> Finish\n"); r = nsapi_finish_request(sn, rq); } } if(r == REQ_ABORTED) { // if an error occurred, we send an error page nsapi_error_request((Session*)sn, (Request*)rq); break; } } while (r == REQ_RESTART); if(r != REQ_PROCESSING) { r = nsapi_finish_request(sn, rq); } return r; } int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq) { // TODO: keep alive close(sn->connection->fd); cfg_unref(sn->config); // free all memory free(sn->connection); free(sn->netbuf->inbuf); free(sn->netbuf); pool_destroy(sn->sn.pool); return 0; } 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) { fprintf(stderr, "add_objects failed\n"); 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; } return ret; } } /* if no function has set the ppath var, translate it to docroot */ if(ret == REQ_NOACTION && ppath == NULL) { sstr_t docroot = rq->vs->document_root; if(docroot.length < 1) { printf("docroot too short\n"); return REQ_ABORTED; /* docroot too short */ } /* if there is a trailing '/', remove it */ if(docroot.ptr[docroot.length - 1] == '/') { //docroot.ptr[docroot.length - 1] = 0; // TODO: can I do this? No!! docroot.length--; } sstr_t uri = sstr(pblock_findkeyval(pb_key_uri, rq->rq.reqpb)); sstr_t translated; translated.length = docroot.length + uri.length; translated.ptr = alloca(translated.length + 1); translated = sstrncat(2, translated, docroot, uri); pblock_kvinsert( pb_key_ppath, translated.ptr, translated.length, 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) { 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; } 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 */ sstr_t path = sstr(pblock_findkeyval(pb_key_ppath, rq->rq.vars)); sstr_t ct; if(path.ptr[path.length - 1] == '/') { /* directory */ ct = sstrn("internal/directory", 18); } else { ct = sstrn("text/plain", 10); } 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(strcmp(dtp, content_type) != 0) { 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_PROCEED) { fprintf(stderr, "saf not proceed\n"); } 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 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 char *poolname = pblock_findkeyval(pb_key_pool, d->param); if(poolname) { threadpool_t *pool = get_threadpool(sstr(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 d->func->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_function_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_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 = data->directive->func->func( data->directive->param, (Session*)data->sn, (Request*)data->rq); free(data); nsapi_function_return((Session*)data->sn, (Request*)data->rq, r); 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 */ 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; } /* * 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; }