diff --git a/src/base.h b/src/base.h index 6b0b267..84d810f 100644 --- a/src/base.h +++ b/src/base.h @@ -7,6 +7,14 @@ #define GSTR_LEN(x) (x) ? (x)->str : "", (x) ? (x)->len : 0 +/* we don't use ev_init for now (stupid alias warnings), as ev_init + * just does set some values to zero and calls ev_set_cb. + * But every structure we allacote is initialized with zero, so we don't care + * about that. + * If this ever changes, we can easily use ev_init again. + */ +#define my_ev_init(ev, cb) ev_set_cb(ev, cb) + typedef enum { HTTP_TRANSFER_ENCODING_IDENTITY, HTTP_TRANSFER_ENCODING_CHUNKED diff --git a/src/chunk.c b/src/chunk.c index 84ff199..e1f1460 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -10,7 +10,11 @@ static chunkfile *chunkfile_new(GString *name, int fd, gboolean is_temp) { chunkfile *cf = g_slice_new(chunkfile); cf->refcount = 1; - cf->name = name; + if (name) { + cf->name = g_string_new_len(GSTR_LEN(name)); + } else { + cf->name = NULL; + } cf->fd = fd; cf->is_temp = is_temp; return cf; @@ -29,7 +33,7 @@ static void chunkfile_release(chunkfile *cf) { cf->fd = -1; if (cf->is_temp) unlink(cf->name->str); cf->is_temp = FALSE; - g_string_free(cf->name, TRUE); + if (cf->name) g_string_free(cf->name, TRUE); cf->name = NULL; g_slice_free(chunkfile, cf); } @@ -95,7 +99,7 @@ handler_t chunkiter_read(server *srv, connection *con, chunkiter iter, off_t sta if ( !(c->file.mmap.data != MAP_FAILED || c->mem) /* no data present */ || !( /* or in the wrong range */ (start + c->offset >= c->file.mmap.offset) - && (start + c->offset + length <= c->file.mmap.offset + c->file.mmap.length)) ) { + && (start + c->offset + length <= c->file.mmap.offset + (ssize_t) c->file.mmap.length)) ) { /* then find new range */ our_offset = start % MMAP_CHUNK_ALIGN; our_start = start - our_offset; @@ -278,6 +282,7 @@ void chunkqueue_append_mem(chunkqueue *cq, void *mem, gssize len) { static void __chunkqueue_append_file(chunkqueue *cq, GString *filename, off_t start, off_t length, int fd, gboolean is_temp) { chunk *c = chunk_new(); + c->type = FILE_CHUNK; c->file.file = chunkfile_new(filename, fd, is_temp); c->file.start = start; c->file.length = length; diff --git a/src/condition.c b/src/condition.c index c6f095c..fd7c0bc 100644 --- a/src/condition.c +++ b/src/condition.c @@ -47,6 +47,7 @@ static gboolean condition_ip_from_socket(condition_rvalue *val, sock_addr *addr) condition_lvalue* condition_lvalue_new(cond_lvalue_t type, GString *key) { condition_lvalue *lvalue = g_slice_new0(condition_lvalue); + if (type == COMP_REQUEST_HEADER) g_string_ascii_down(key); lvalue->type = type; lvalue->key = key; lvalue->refcount = 1; @@ -89,7 +90,7 @@ static condition* cond_new_string(comp_operator_t op, condition_lvalue *lvalue, static condition* cond_new_match(server *srv, comp_operator_t op, condition_lvalue *lvalue, GString *str) { UNUSED(op); UNUSED(lvalue); UNUSED(str); ERROR(srv, "%s", "pcre not supported for now"); - /* TODO */ + /* TODO: pcre */ return NULL; } #endif @@ -237,13 +238,11 @@ const char* cond_lvalue_to_string(cond_lvalue_t t) { /* COND_VALUE_STRING and COND_VALUE_REGEXP only */ static gboolean condition_check_eval_string(server *srv, connection *con, condition *cond) { const char *value = ""; - GString *tmp = NULL; gboolean result = FALSE; UNUSED(srv); UNUSED(con); switch (cond->lvalue->type) { - /* TODO: get values */ case COMP_REQUEST_LOCALIP: value = con->local_addr_str->str; break; @@ -254,7 +253,7 @@ static gboolean condition_check_eval_string(server *srv, connection *con, condit value = con->request.uri.path->str; break; case COMP_REQUEST_HOST: - value = con->request.host->str; + value = con->request.uri.host->str; break; case COMP_REQUEST_SCHEME: value = con->is_ssl ? "https" : "http"; @@ -266,21 +265,23 @@ static gboolean condition_check_eval_string(server *srv, connection *con, condit value = con->request.http_method_str->str; break; case COMP_PHYSICAL_PATH: + value = con->physical.path->str; + break; case COMP_PHYSICAL_PATH_EXISTS: - /* TODO */ + /* TODO: physical path exists */ break; case COMP_REQUEST_HEADER: - /* TODO */ + http_header_get_fast(srv->tmp_str, con->request.headers, GSTR_LEN(cond->lvalue->key)); + value = srv->tmp_str->str; break; case COMP_PHYSICAL_SIZE: - /* TODO */ - g_string_printf((tmp = g_string_sized_new(0)), "%"L_GOFFSET_FORMAT, (goffset) 0); - value = tmp->str; + /* TODO: physical size */ + g_string_printf(srv->tmp_str, "%"L_GOFFSET_FORMAT, (goffset) 0); + value = srv->tmp_str->str; break; case COMP_REQUEST_CONTENT_LENGTH: - /* TODO */ - g_string_printf((tmp = g_string_sized_new(0)), "%"L_GOFFSET_FORMAT, (goffset) 0); - value = tmp->str; + g_string_printf(srv->tmp_str, "%"L_GOFFSET_FORMAT, con->request.content_length); + value = srv->tmp_str->str; break; } @@ -310,7 +311,6 @@ static gboolean condition_check_eval_string(server *srv, connection *con, condit break; } - if (tmp) g_string_free(tmp, TRUE); return result; } @@ -327,6 +327,7 @@ static gboolean condition_check_eval_int(server *srv, connection *con, condition value = con->physical.size; break; default: + CON_ERROR(srv, con, "couldn't get int value for '%s', using -1", cond_lvalue_to_string(cond->lvalue->type)); value = -1; } @@ -349,8 +350,6 @@ static gboolean condition_check_eval_int(server *srv, connection *con, condition case CONFIG_COND_NOTIP: ERROR(srv, "cannot compare int with '%s'", comp_op_to_string(cond->op)); return FALSE; - } else { - ERROR(srv, "couldn't get int value for '%s'", cond_lvalue_to_string(cond->lvalue->type)); } return FALSE; @@ -408,7 +407,6 @@ static gboolean condition_check_eval_ip(server *srv, connection *con, condition ipval.type = COND_VALUE_INT; switch (cond->lvalue->type) { - /* TODO: get values */ case COMP_REQUEST_LOCALIP: if (!condition_ip_from_socket(&ipval, &con->local_addr)) return (cond->op == CONFIG_COND_NOTIP); @@ -418,10 +416,11 @@ static gboolean condition_check_eval_ip(server *srv, connection *con, condition return (cond->op == CONFIG_COND_NOTIP); break; case COMP_REQUEST_PATH: - value = con->request.uri.path->str; + ERROR(srv, "%s", "Cannot parse request.path as ip"); + return (cond->op == CONFIG_COND_NOTIP); break; case COMP_REQUEST_HOST: - value = con->request.host->str; + value = con->request.uri.host->str; break; case COMP_REQUEST_SCHEME: ERROR(srv, "%s", "Cannot parse request.scheme as ip"); @@ -430,14 +429,17 @@ static gboolean condition_check_eval_ip(server *srv, connection *con, condition value = con->request.uri.query->str; break; case COMP_REQUEST_METHOD: - value = con->request.http_method_str->str; + ERROR(srv, "%s", "Cannot request.method as ip"); + return (cond->op == CONFIG_COND_NOTIP); break; case COMP_PHYSICAL_PATH: case COMP_PHYSICAL_PATH_EXISTS: - /* TODO */ + ERROR(srv, "%s", "Cannot physical.path(-exists) as ip"); + return (cond->op == CONFIG_COND_NOTIP); break; case COMP_REQUEST_HEADER: - /* TODO */ + http_header_get_fast(srv->tmp_str, con->request.headers, GSTR_LEN(cond->lvalue->key)); + value = srv->tmp_str->str; break; case COMP_PHYSICAL_SIZE: case COMP_REQUEST_CONTENT_LENGTH: diff --git a/src/condition.h b/src/condition.h index da16118..faaf622 100644 --- a/src/condition.h +++ b/src/condition.h @@ -51,7 +51,7 @@ typedef enum { COMP_PHYSICAL_SIZE, /* needs a key */ - COMP_REQUEST_HEADER + COMP_REQUEST_HEADER /**< needs lowercase key, enforced by condition_lvalue_new */ } cond_lvalue_t; #define COND_LVALUE_FIRST_WITH_KEY COMP_REQUEST_HEADER diff --git a/src/condition_parsers.rl b/src/condition_parsers.rl index 2cf3369..4caf2e1 100644 --- a/src/condition_parsers.rl +++ b/src/condition_parsers.rl @@ -28,9 +28,9 @@ gboolean parse_ipv4(const char *str, guint32 *ip, guint32 *netmask) { guint8 *data = (guint8*) ip; - const char *p = str, *mark; + const char *p = str, *mark = NULL; gboolean res = FALSE; - int cs, tmpval, i = 0; + int cs, tmpval = 0, i = 0; %% write init nocs; @@ -92,9 +92,9 @@ gboolean parse_ipv6(const char *str, guint8 *ip, guint *network) { guint8 data[4] = {0,0,0,0}; guint16 *predata = (guint16*) ip, postdata[8]; size_t prec = 0, postc = 0; - const char *p = str, *mark; + const char *p = str, *mark = NULL; gboolean res = FALSE, compressed = FALSE; - int cs, tmpval, i = 0; + int cs, tmpval = 0, i = 0; %% write init nocs; diff --git a/src/connection.c b/src/connection.c index 054195a..252eba7 100644 --- a/src/connection.c +++ b/src/connection.c @@ -2,6 +2,7 @@ #include "connection.h" #include "network.h" #include "utils.h" +#include "plugin_core.h" void con_put(server *srv, connection *con); /* server.c */ @@ -68,21 +69,24 @@ static void connection_cb(struct ev_loop *loop, ev_io *w, int revents) { dojoblist = TRUE; break; case NETWORK_STATUS_FATAL_ERROR: + CON_ERROR(srv, con, "%s", "network read fatal error"); connection_set_state(srv, con, CON_STATE_ERROR); dojoblist = TRUE; break; case NETWORK_STATUS_CONNECTION_CLOSE: + con->raw_in->is_closed = TRUE; + shutdown(w->fd, SHUT_RD); connection_set_state(srv, con, CON_STATE_CLOSE); dojoblist = TRUE; break; case NETWORK_STATUS_WAIT_FOR_EVENT: break; case NETWORK_STATUS_WAIT_FOR_AIO_EVENT: - /* TODO ? */ + /* TODO: aio */ ev_io_rem_events(loop, w, EV_READ); break; case NETWORK_STATUS_WAIT_FOR_FD: - /* TODO */ + /* TODO: wait for fd */ ev_io_rem_events(loop, w, EV_READ); break; } @@ -96,6 +100,7 @@ static void connection_cb(struct ev_loop *loop, ev_io *w, int revents) { dojoblist = TRUE; break; case NETWORK_STATUS_FATAL_ERROR: + CON_ERROR(srv, con, "%s", "network write fatal error"); connection_set_state(srv, con, CON_STATE_ERROR); dojoblist = TRUE; break; @@ -106,18 +111,16 @@ static void connection_cb(struct ev_loop *loop, ev_io *w, int revents) { case NETWORK_STATUS_WAIT_FOR_EVENT: break; case NETWORK_STATUS_WAIT_FOR_AIO_EVENT: - /* TODO ? */ + /* TODO: aio */ ev_io_rem_events(loop, w, EV_WRITE); break; case NETWORK_STATUS_WAIT_FOR_FD: - /* TODO */ + /* TODO: wait for fd */ ev_io_rem_events(loop, w, EV_WRITE); break; } -// CON_TRACE(srv, con, "cq->len: raw_out=%i, out=%i", (int) con->raw_out->length, (int) con->out->length); } if (con->raw_out->length == 0) { -// CON_TRACE(srv, con, "%s", "stop write"); ev_io_rem_events(loop, w, EV_WRITE); dojoblist = TRUE; } @@ -135,20 +138,24 @@ connection* connection_new(server *srv) { con->response_headers_sent = FALSE; con->expect_100_cont = FALSE; - con->raw_in = chunkqueue_new(); - con->raw_out = chunkqueue_new(); - con->in = chunkqueue_new(); - con->out = chunkqueue_new(); - - ev_io_init(&con->sock.watcher, connection_cb, -1, 0); + my_ev_init(&con->sock.watcher, connection_cb); + ev_io_set(&con->sock.watcher, -1, 0); con->sock.srv = srv; con->sock.con = con; con->sock.watcher.data = &con->sock; con->remote_addr_str = g_string_sized_new(0); con->local_addr_str = g_string_sized_new(0); con->keep_alive = TRUE; + con->raw_in = chunkqueue_new(); + con->raw_out = chunkqueue_new(); + con->in = chunkqueue_new(); + con->out = chunkqueue_new(); + action_stack_init(&con->action_stack); + con->options = g_slice_copy(srv->option_count * sizeof(*srv->option_def_values), srv->option_def_values); + request_init(&con->request, con->raw_in); + physical_init(&con->physical); response_init(&con->response); return con; @@ -159,24 +166,31 @@ void connection_reset(server *srv, connection *con) { con->response_headers_sent = FALSE; con->expect_100_cont = FALSE; - chunkqueue_reset(con->raw_in); - chunkqueue_reset(con->raw_out); - chunkqueue_reset(con->in); - chunkqueue_reset(con->out); - ev_io_stop(srv->loop, &con->sock.watcher); if (con->sock.watcher.fd != -1) { - shutdown(con->sock.watcher.fd, SHUT_RDWR); - close(con->sock.watcher.fd); + if (con->raw_in->is_closed) { /* read already shutdown */ + shutdown(con->sock.watcher.fd, SHUT_WR); + close(con->sock.watcher.fd); + } else { + server_add_closing_socket(srv, con->sock.watcher.fd); + } } ev_io_set(&con->sock.watcher, -1, 0); g_string_truncate(con->remote_addr_str, 0); g_string_truncate(con->local_addr_str, 0); con->keep_alive = TRUE; + chunkqueue_reset(con->raw_in); + chunkqueue_reset(con->raw_out); + chunkqueue_reset(con->in); + chunkqueue_reset(con->out); + action_stack_reset(srv, &con->action_stack); + memcpy(con->options, srv->option_def_values, srv->option_count * sizeof(*srv->option_def_values)); + request_reset(&con->request); + physical_reset(&con->physical); response_reset(&con->response); } @@ -185,18 +199,21 @@ void connection_reset_keep_alive(server *srv, connection *con) { con->response_headers_sent = FALSE; con->expect_100_cont = FALSE; - con->raw_out->is_closed = FALSE; - chunkqueue_reset(con->in); - chunkqueue_reset(con->out); - ev_io_set_events(srv->loop, &con->sock.watcher, EV_READ); g_string_truncate(con->remote_addr_str, 0); g_string_truncate(con->local_addr_str, 0); con->keep_alive = TRUE; + con->raw_out->is_closed = FALSE; + chunkqueue_reset(con->in); + chunkqueue_reset(con->out); + action_stack_reset(srv, &con->action_stack); + memcpy(con->options, srv->option_def_values, srv->option_count * sizeof(*srv->option_def_values)); + request_reset(&con->request); + physical_reset(&con->physical); response_reset(&con->response); } @@ -205,24 +222,31 @@ void connection_free(server *srv, connection *con) { con->response_headers_sent = FALSE; con->expect_100_cont = FALSE; - chunkqueue_free(con->raw_in); - chunkqueue_free(con->raw_out); - chunkqueue_free(con->in); - chunkqueue_free(con->out); - ev_io_stop(srv->loop, &con->sock.watcher); if (con->sock.watcher.fd != -1) { - shutdown(con->sock.watcher.fd, SHUT_RDWR); - close(con->sock.watcher.fd); + if (con->raw_in->is_closed) { /* read already shutdown */ + shutdown(con->sock.watcher.fd, SHUT_WR); + close(con->sock.watcher.fd); + } else { + server_add_closing_socket(srv, con->sock.watcher.fd); + } } ev_io_set(&con->sock.watcher, -1, 0); g_string_free(con->remote_addr_str, TRUE); g_string_free(con->local_addr_str, TRUE); con->keep_alive = TRUE; + chunkqueue_free(con->raw_in); + chunkqueue_free(con->raw_out); + chunkqueue_free(con->in); + chunkqueue_free(con->out); + action_stack_clear(srv, &con->action_stack); + g_slice_free1(srv->option_count * sizeof(*srv->option_def_values), con->options); + request_clear(&con->request); + physical_clear(&con->physical); response_clear(&con->response); g_slice_free(connection, con); @@ -241,19 +265,21 @@ void connection_state_machine(server *srv, connection *con) { do { switch (con->state) { case CON_STATE_REQUEST_START: - /* TODO: reset some values after keep alive - or do it in CON_STATE_REQUEST_END */ connection_set_state(srv, con, CON_STATE_READ_REQUEST_HEADER); action_enter(con, srv->mainaction); break; + case CON_STATE_READ_REQUEST_HEADER: -// TRACE(srv, "%s", "reading request header"); + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "reading request header"); + } switch(http_request_parse(srv, con, &con->request.parser_ctx)) { case HANDLER_FINISHED: case HANDLER_GO_ON: connection_set_state(srv, con, CON_STATE_VALIDATE_REQUEST_HEADER); break; case HANDLER_WAIT_FOR_FD: - /* TODO */ + /* TODO: wait for fd */ done = TRUE; break; case HANDLER_WAIT_FOR_EVENT: @@ -266,13 +292,19 @@ void connection_state_machine(server *srv, connection *con) { break; } break; + case CON_STATE_VALIDATE_REQUEST_HEADER: -// TRACE(srv, "%s", "validating request header"); + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "validating request header"); + } connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST_HEADER); request_validate_header(srv, con); break; + case CON_STATE_HANDLE_REQUEST_HEADER: -// TRACE(srv, "%s", "handle request header"); + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "handle request header"); + } switch (action_execute(srv, con)) { case ACTION_WAIT_FOR_EVENT: done = TRUE; @@ -285,17 +317,29 @@ void connection_state_machine(server *srv, connection *con) { connection_set_state(srv, con, CON_STATE_WRITE_RESPONSE); break; case ACTION_ERROR: - /* action return error */ - /* TODO: return 500 instead ? */ - connection_set_state(srv, con, CON_STATE_ERROR); + internal_error(srv, con); break; } break; + case CON_STATE_READ_REQUEST_CONTENT: case CON_STATE_HANDLE_RESPONSE_HEADER: -// TRACE(srv, "%s", "read request/handle response header"); + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "read request/handle response header"); + } + if (con->expect_100_cont) { + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "send 100 Continue"); + } + chunkqueue_append_mem(con->raw_out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n")); + con->expect_100_cont = FALSE; + ev_io_add_events(srv->loop, &con->sock.watcher, EV_WRITE); + } parse_request_body(srv, con); - /* TODO: call plugin content_handler */ + + if (con->content_handler) + con->content_handler->handle_content(srv, con, con->content_handler); + switch (action_execute(srv, con)) { case ACTION_WAIT_FOR_EVENT: done = TRUE; @@ -305,12 +349,11 @@ void connection_state_machine(server *srv, connection *con) { connection_set_state(srv, con, CON_STATE_WRITE_RESPONSE); break; case ACTION_ERROR: - /* action return error */ - /* TODO: return 500 instead ? */ - connection_set_state(srv, con, CON_STATE_ERROR); + internal_error(srv, con); break; } break; + case CON_STATE_WRITE_RESPONSE: if (con->in->is_closed && con->raw_out->is_closed) { connection_set_state(srv, con, CON_STATE_RESPONSE_END); @@ -319,12 +362,21 @@ void connection_state_machine(server *srv, connection *con) { if (!con->response_headers_sent) { con->response_headers_sent = TRUE; -// TRACE(srv, "%s", "write response headers"); + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "write response headers"); + } response_send_headers(srv, con); } -// TRACE(srv, "%s", "write response"); + + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "write response"); + } + parse_request_body(srv, con); - /* TODO: call plugin content_handler */ + + if (con->content_handler) + con->content_handler->handle_content(srv, con, con->content_handler); + forward_response_body(srv, con); if (con->in->is_closed && con->raw_out->is_closed) { @@ -333,9 +385,14 @@ void connection_state_machine(server *srv, connection *con) { } if (con->state == CON_STATE_WRITE_RESPONSE) done = TRUE; break; + case CON_STATE_RESPONSE_END: -// TRACE(srv, "%s", "response end"); - /* TODO: call plugin callbacks */ + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "response end (keep_alive = %i)", con->keep_alive); + } + + plugins_handle_close(srv, con); + if (con->keep_alive) { connection_reset_keep_alive(srv, con); } else { @@ -343,15 +400,25 @@ void connection_state_machine(server *srv, connection *con) { done = TRUE; } break; + case CON_STATE_CLOSE: -// TRACE(srv, "%s", "connection closed"); - /* TODO: call plugin callbacks */ + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "connection closed"); + } + + plugins_handle_close(srv, con); + con_put(srv, con); done = TRUE; break; + case CON_STATE_ERROR: -// TRACE(srv, "%s", "connection closed (error)"); - /* TODO: call plugin callbacks */ + if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING)) { + TRACE(srv, "%s", "connection closed (error)"); + } + + plugins_handle_close(srv, con); + con_put(srv, con); done = TRUE; break; @@ -365,6 +432,13 @@ void connection_handle_direct(server *srv, connection *con) { } void connection_handle_indirect(server *srv, connection *con, plugin *p) { - connection_set_state(srv, con, CON_STATE_READ_REQUEST_CONTENT); - con->content_handler = p; + if (!p) { + connection_handle_direct(srv, con); + } else if (p->handle_content) { + connection_set_state(srv, con, CON_STATE_READ_REQUEST_CONTENT); + con->content_handler = p; + } else { + CON_ERROR(srv, con, "Indirect plugin '%s' handler has no handle_content callback", p->name); + internal_error(srv, con); + } } diff --git a/src/connection.h b/src/connection.h index 06dbe4d..f54282f 100644 --- a/src/connection.h +++ b/src/connection.h @@ -78,7 +78,7 @@ struct connection { action_stack action_stack; - gpointer *options; /* TODO */ + gpointer *options; request request; physical physical; diff --git a/src/http_headers.c b/src/http_headers.c index ce7df35..7c20f51 100644 --- a/src/http_headers.c +++ b/src/http_headers.c @@ -1,4 +1,5 @@ +#include "base.h" #include "http_headers.h" static void _string_free(gpointer p) { @@ -154,3 +155,15 @@ gboolean http_header_is(http_headers *headers, const gchar *key, size_t keylen, } return FALSE; } + +void http_header_get_fast(GString *dest, http_headers *headers, const gchar *key, size_t keylen) { + http_header *h = http_header_lookup_fast(headers, key, keylen); + GList *iter; + + g_string_truncate(dest, 0); + if (!h) return; + for (iter = g_queue_peek_head_link(&h->values); NULL != iter; iter = g_list_next(iter)) { + if (dest->len) g_string_append_len(dest, CONST_STR_LEN(", ")); + g_string_append_len(dest, GSTR_LEN((GString*)iter->data)); + } +} diff --git a/src/http_headers.h b/src/http_headers.h index d4c75e7..839b93c 100644 --- a/src/http_headers.h +++ b/src/http_headers.h @@ -41,4 +41,7 @@ LI_API http_header* http_header_lookup_fast(http_headers *headers, const gchar * /** Use lowercase keys! values are compared case-insensitive */ LI_API gboolean http_header_is(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen); +/** concats all headers with key with ', ' - empty if no header exists - use lowercase key*/ +LI_API void http_header_get_fast(GString *dest, http_headers *headers, const gchar *key, size_t keylen); + #endif diff --git a/src/http_request_parser.rl b/src/http_request_parser.rl index 873ecd2..9fd8fb0 100644 --- a/src/http_request_parser.rl +++ b/src/http_request_parser.rl @@ -20,7 +20,7 @@ action done { fbreak; } action method { getStringTo(fpc, ctx->request->http_method_str); } - action uri { getStringTo(fpc, ctx->request->uri.uri_raw); } + action uri { getStringTo(fpc, ctx->request->uri.raw); } action header_key { getStringTo(fpc, ctx->h_key); diff --git a/src/lighttpd.c b/src/lighttpd.c index e59b77d..e732533 100644 --- a/src/lighttpd.c +++ b/src/lighttpd.c @@ -53,8 +53,6 @@ int main(int argc, char *argv[]) { srv = server_new(); - log_init(srv); - plugin_register(srv, "core", plugin_core_init); /* if no path is specified for the config, read lighttpd.conf from current directory */ @@ -115,19 +113,9 @@ int main(int argc, char *argv[]) { log_warning(srv, NULL, "test %s", "foo1"); /* duplicate won't be logged */ log_warning(srv, NULL, "test %s", "foo2"); log_debug(srv, NULL, "test %s", "message"); - log_thread_start(srv); server_start(srv); - log_error(srv, NULL, "error %d", 23); - g_atomic_int_set(&srv->rotate_logs, TRUE); - log_warning(srv, NULL, "test %s", "foo3"); - log_warning(srv, NULL, "test %s", "foo4"); - - g_atomic_int_set(&srv->exiting, TRUE); - log_thread_wakeup(srv); - g_thread_join(srv->log_thread); - server_free(srv); return 0; diff --git a/src/log.h b/src/log.h index 6ed8582..8403d88 100644 --- a/src/log.h +++ b/src/log.h @@ -16,6 +16,9 @@ LI_API const char *remove_path(const char *path); #define ERROR(srv, fmt, ...) \ log_write(srv, NULL, "%s.%d: (error) "fmt, REMOVE_PATH(__FILE__), __LINE__, __VA_ARGS__) +#define INFO(srv, ...) \ + log_write(srv, NULL, __VA_ARGS__) + #define TRACE(srv, fmt, ...) \ log_write(srv, NULL, "%s.%d: (trace) "fmt, REMOVE_PATH(__FILE__), __LINE__, __VA_ARGS__) diff --git a/src/network.c b/src/network.c index b51bbac..6dca04e 100644 --- a/src/network.c +++ b/src/network.c @@ -2,7 +2,7 @@ #include "network.h" /** repeats write after EINTR */ -static ssize_t net_write(int fd, void *buf, ssize_t nbyte) { +ssize_t net_write(int fd, void *buf, ssize_t nbyte) { ssize_t r; while (-1 == (r = write(fd, buf, nbyte))) { switch (errno) { @@ -18,52 +18,8 @@ static ssize_t net_write(int fd, void *buf, ssize_t nbyte) { return r; } -network_status_t network_write(server *srv, connection *con, int fd, chunkqueue *cq) { - const ssize_t blocksize = 16*1024; /* 16k */ - const off_t max_write = 16 * blocksize; /* 256k */ - char *block_data; - off_t block_len; - ssize_t r; - off_t len = 0; - chunkiter ci; - - do { - ci = chunkqueue_iter(cq); - switch (chunkiter_read(srv, con, ci, 0, blocksize, &block_data, &block_len)) { - case HANDLER_GO_ON: - break; - case HANDLER_WAIT_FOR_FD: - return len ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; - case HANDLER_ERROR: - default: - return NETWORK_STATUS_FATAL_ERROR; - } - - if (-1 == (r = net_write(fd, block_data, block_len))) { - switch (errno) { - case EAGAIN: -#if EWOULDBLOCK != EAGAIN - case EWOULDBLOCK -#endif - return len ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; - case ECONNRESET: - return NETWORK_STATUS_CONNECTION_CLOSE; - default: - CON_ERROR(srv, con, "oops, read from fd=%d failed: %s (%d)", fd, strerror(errno), errno ); - return NETWORK_STATUS_FATAL_ERROR; - } - } else if (0 == r) { - return len ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; - } - chunkqueue_skip(cq, r); - len += r; - } while (r == block_len && len < max_write); - - return NETWORK_STATUS_SUCCESS; -} - /** repeats read after EINTR */ -static ssize_t net_read(int fd, void *buf, ssize_t nbyte) { +ssize_t net_read(int fd, void *buf, ssize_t nbyte) { ssize_t r; while (-1 == (r = read(fd, buf, nbyte))) { switch (errno) { @@ -79,6 +35,34 @@ static ssize_t net_read(int fd, void *buf, ssize_t nbyte) { return r; } +network_status_t network_write(server *srv, connection *con, int fd, chunkqueue *cq) { + network_status_t res; +#ifdef TCP_CORK + int corked = 0; +#endif + +#ifdef TCP_CORK + /* Linux: put a cork into the socket as we want to combine the write() calls + * but only if we really have multiple chunks + */ + if (cq->queue->length > 1) { + corked = 1; + setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); + } +#endif + + res = network_backend_writev(srv, con, fd, cq); + +#ifdef TCP_CORK + if (corked) { + corked = 0; + setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); + } +#endif + + return res; +} + network_status_t network_read(server *srv, connection *con, int fd, chunkqueue *cq) { const ssize_t blocksize = 16*1024; /* 16k */ const off_t max_read = 16 * blocksize; /* 256k */ diff --git a/src/network.h b/src/network.h index f76a820..db9526e 100644 --- a/src/network.h +++ b/src/network.h @@ -4,15 +4,35 @@ #include "base.h" typedef enum { - NETWORK_STATUS_SUCCESS, + NETWORK_STATUS_SUCCESS, /**< some IO was actually done (read/write) or cq was empty for write */ NETWORK_STATUS_FATAL_ERROR, NETWORK_STATUS_CONNECTION_CLOSE, - NETWORK_STATUS_WAIT_FOR_EVENT, - NETWORK_STATUS_WAIT_FOR_AIO_EVENT, - NETWORK_STATUS_WAIT_FOR_FD, + NETWORK_STATUS_WAIT_FOR_EVENT, /**< read/write returned -1 with errno=EAGAIN/EWOULDBLOCK; no real IO was done */ + NETWORK_STATUS_WAIT_FOR_AIO_EVENT, /**< nothing done yet, read/write will be done somewhere else */ + NETWORK_STATUS_WAIT_FOR_FD, /**< need a fd to open a file */ } network_status_t; +/** repeats write after EINTR */ +LI_API ssize_t net_write(int fd, void *buf, ssize_t nbyte); + +/** repeats read after EINTR */ +LI_API ssize_t net_read(int fd, void *buf, ssize_t nbyte); + LI_API network_status_t network_write(server *srv, connection *con, int fd, chunkqueue *cq); LI_API network_status_t network_read(server *srv, connection *con, int fd, chunkqueue *cq); +/* write backends */ +LI_API network_status_t network_backend_write(server *srv, connection *con, int fd, chunkqueue *cq); +LI_API network_status_t network_backend_writev(server *srv, connection *con, int fd, chunkqueue *cq); + +#define NETWORK_FALLBACK(f) do { \ + network_status_t res; \ + switch(res = f(srv, con, fd, cq)) { \ + case NETWORK_STATUS_SUCCESS: \ + break; \ + default: \ + return res; \ + } \ +} while(0) + #endif diff --git a/src/network_write.c b/src/network_write.c new file mode 100644 index 0000000..7ae8326 --- /dev/null +++ b/src/network_write.c @@ -0,0 +1,49 @@ + +#include "network.h" + +network_status_t network_backend_write(server *srv, connection *con, int fd, chunkqueue *cq) { + const ssize_t blocksize = 16*1024; /* 16k */ + const off_t max_write = 16 * blocksize; /* 256k */ + char *block_data; + off_t block_len; + ssize_t r; + off_t len = 0; + chunkiter ci; + + do { + if (0 == cq->length) return NETWORK_STATUS_SUCCESS; + + ci = chunkqueue_iter(cq); + switch (chunkiter_read(srv, con, ci, 0, blocksize, &block_data, &block_len)) { + case HANDLER_GO_ON: + break; + case HANDLER_WAIT_FOR_FD: + return len ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; + case HANDLER_ERROR: + default: + return NETWORK_STATUS_FATAL_ERROR; + } + + if (-1 == (r = net_write(fd, block_data, block_len))) { + switch (errno) { + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK +#endif + return len ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; + case ECONNRESET: + case EPIPE: + return NETWORK_STATUS_CONNECTION_CLOSE; + default: + CON_ERROR(srv, con, "oops, write to fd=%d failed: %s (%d)", fd, strerror(errno), errno ); + return NETWORK_STATUS_FATAL_ERROR; + } + } else if (0 == r) { + return len ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; + } + chunkqueue_skip(cq, r); + len += r; + } while (r == block_len && len < max_write); + + return NETWORK_STATUS_SUCCESS; +} diff --git a/src/network_writev.c b/src/network_writev.c new file mode 100644 index 0000000..a60737a --- /dev/null +++ b/src/network_writev.c @@ -0,0 +1,104 @@ + +#include "network.h" + +#include + +#ifndef UIO_MAXIOV +# if defined(__FreeBSD__) || defined(__APPLE__) || defined(__NetBSD__) +/* FreeBSD 4.7 defines it in sys/uio.h only if _KERNEL is specified */ +# define UIO_MAXIOV 1024 +# elif defined(__sgi) +/* IRIX 6.5 has sysconf(_SC_IOV_MAX) which might return 512 or bigger */ +# define UIO_MAXIOV 512 +# elif defined(__sun) +/* Solaris (and SunOS?) defines IOV_MAX instead */ +# ifndef IOV_MAX +# define UIO_MAXIOV 16 +# else +# define UIO_MAXIOV IOV_MAX +# endif +# elif defined(IOV_MAX) +# define UIO_MAXIOV IOV_MAX +# else +# error UIO_MAXIOV nor IOV_MAX are defined +# endif +#endif + +network_status_t network_backend_writev(server *srv, connection *con, int fd, chunkqueue *cq) { + const off_t max_write = 256 * 1024; /* 256k */ + off_t min_cq_len, max_chunks_len, we_have; + ssize_t r; + gboolean did_write_something = FALSE; + chunkiter ci; + chunk *c; + + GArray *chunks = g_array_sized_new(FALSE, TRUE, sizeof(struct iovec), UIO_MAXIOV); + + /* stop if chunkqueue length gets less or equal than min_cq_len */ + min_cq_len = cq->length - max_write; + if (min_cq_len < 0) min_cq_len = 0; + + do { + if (0 == cq->length) return NETWORK_STATUS_SUCCESS; + + ci = chunkqueue_iter(cq); + + if (MEM_CHUNK != (c = chunkiter_chunk(ci))->type) { + NETWORK_FALLBACK(network_backend_write); + did_write_something = TRUE; + continue; + } + + max_chunks_len = cq->length - min_cq_len; + we_have = 0; + do { + guint i = chunks->len; + off_t len = c->mem->len - c->offset; + struct iovec *v; + g_array_set_size(chunks, i + 1); + v = &g_array_index(chunks, struct iovec, i); + v->iov_base = c->mem->str + c->offset; + if (len > max_write) len = max_write; + v->iov_len = len; + we_have += len; + } while (we_have < max_chunks_len && + chunkiter_next(&ci) && + MEM_CHUNK == (c = chunkiter_chunk(ci))->type && + chunks->len < UIO_MAXIOV); + + while (-1 == (r = writev(fd, (struct iovec*) chunks->data, chunks->len))) { + switch (errno) { + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK +#endif + g_array_free(chunks, TRUE); + return did_write_something ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; + case ECONNRESET: + case EPIPE: + g_array_free(chunks, TRUE); + return NETWORK_STATUS_CONNECTION_CLOSE; + case EINTR: + break; /* try again */ + default: + g_array_free(chunks, TRUE); + CON_ERROR(srv, con, "oops, write to fd=%d failed: %s (%d)", fd, strerror(errno), errno ); + return NETWORK_STATUS_FATAL_ERROR; + } + } + if (0 == r) { + g_array_free(chunks, TRUE); + return did_write_something ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_WAIT_FOR_EVENT; + } + chunkqueue_skip(cq, r); + if (r != we_have) { + g_array_free(chunks, TRUE); + return NETWORK_STATUS_SUCCESS; + } + did_write_something = TRUE; + g_array_set_size(chunks, 0); + } while (cq->length > min_cq_len); + + g_array_free(chunks, TRUE); + return NETWORK_STATUS_SUCCESS; +} diff --git a/src/options.c b/src/options.c index af58331..4998c68 100644 --- a/src/options.c +++ b/src/options.c @@ -122,7 +122,7 @@ void option_list_free(GArray *optlist) { g_array_free(optlist, TRUE); } -/* Extract value from option, destroy option */ +/* Extract value from option, option set to none */ gpointer option_extract_value(option *opt) { gpointer val = NULL; if (!opt) return NULL; @@ -153,6 +153,5 @@ gpointer option_extract_value(option *opt) { break; } opt->type = OPTION_NONE; - g_slice_free(option, opt); return val; } diff --git a/src/options.h b/src/options.h index 66ffd4c..14b7537 100644 --- a/src/options.h +++ b/src/options.h @@ -62,7 +62,7 @@ LI_API const char* option_type_string(option_type type); LI_API void option_list_free(GArray *optlist); -/* Extract value from option, destroy option */ +/* Extract value from option, option set to none */ LI_API gpointer option_extract_value(option *opt); #endif diff --git a/src/plugin.c b/src/plugin.c index 8209ea0..cb92433 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -50,6 +50,11 @@ static void plugin_free_setups(server *srv, plugin *p) { void plugin_free(server *srv, plugin *p) { if (!p) return; + if (srv->state == SERVER_RUNNING) { + ERROR(srv, "Cannot free plugin '%s' while server is running", p->name); + return; + } + g_hash_table_remove(srv->plugins, p->name); plugin_free_options(srv, p); plugin_free_actions(srv, p); @@ -66,6 +71,11 @@ gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { return FALSE; } + if (srv->state != SERVER_STARTING) { + ERROR(srv, "Cannot register plugin '%s' after server was started", name); + return FALSE; + } + if (g_hash_table_lookup(srv->plugins, name)) { ERROR(srv, "Module '%s' already registered", name); return FALSE; @@ -75,6 +85,7 @@ gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { g_hash_table_insert(srv->plugins, (gchar*) p->name, p); init(srv, p); + p->opt_base_index = g_hash_table_size(srv->options); if (p->options) { size_t i; @@ -259,3 +270,23 @@ gboolean call_setup(server *srv, const char *name, option *opt) { return TRUE; } + +void plugins_prepare_callbacks(server *srv) { + GHashTableIter iter; + plugin *p; + + g_hash_table_iter_init(&iter, srv->plugins); + while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &p)) { + if (p->handle_close) + g_array_append_val(srv->plugins_handle_close, p); + } +} + +void plugins_handle_close(server *srv, connection *con) { + GArray *a = srv->plugins_handle_close; + guint i, len = a->len; + for (i = 0; i < len; i++) { + plugin *p = g_array_index(a, plugin*, i); + p->handle_close(srv, con, p); + } +} diff --git a/src/plugin.h b/src/plugin.h index 94f0203..46f16c5 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -41,13 +41,31 @@ typedef void (*PluginFreeOption) (server *srv, plugin *p, size_t ndx, gpo typedef action* (*PluginCreateAction) (server *srv, plugin *p, option *opt); typedef gboolean (*PluginSetup) (server *srv, plugin *p, option *opt); +typedef void (*PluginHandleContent) (server *srv, connection *con, plugin *p); +typedef void (*PluginHandleClose) (server *srv, connection *con, plugin *p); + struct plugin { size_t version; const gchar *name; /**< name of the plugin */ - gpointer data; /**< private plugin data */ + gpointer data; /**< private plugin data */ - PluginFree free; /**< called before plugin is unloaded */ + size_t opt_base_index; + + PluginFree free; /**< called before plugin is unloaded */ + + /** called if plugin registered as indirect handler with connection_handle_indirect(srv, con, p) + * - after response headers are created: + * connection_set_state(srv, con, CON_STATE_HANDLE_RESPONSE_HEADER) + * - after content is generated close output queue: + * con->out->is_closed = TRUE + */ + PluginHandleContent handle_content; + + /** called for every plugin after connection got closed (response end, reset by peer, error) + * the plugins code must not depend on any order of plugins loaded + */ + PluginHandleClose handle_close; const plugin_option *options; const plugin_action *actions; @@ -102,17 +120,28 @@ struct server_setup { /* Needed my modules to register their plugin(s) */ LI_API gboolean plugin_register(server *srv, const gchar *name, PluginInit init); +/* Internal needed functions */ LI_API void plugin_free(server *srv, plugin *p); LI_API gboolean parse_option(server *srv, const char *name, option *opt, option_set *mark); LI_API void release_option(server *srv, option_set *mark); /**< Does not free the option_set memory */ +LI_API void plugins_prepare_callbacks(server *srv); +LI_API void plugins_handle_close(server *srv, connection *con); + /* Needed for config frontends */ /** For parsing 'somemod.option = "somevalue"' */ LI_API action* option_action(server *srv, const gchar *name, option *value); -/** For parsing 'somemod.action value', e.g. 'rewrite "/url" => "/destination"' */ +/** For parsing 'somemod.action value', e.g. 'rewrite "/url" => "/destination"' + * You need to free the option after it (it should be of type NONE then) + */ LI_API action* create_action(server *srv, const gchar *name, option *value); /** For setup function, e.g. 'listen "127.0.0.1:8080"' */ LI_API gboolean call_setup(server *srv, const char *name, option *opt); +/* needs connection *con and plugin *p */ +#define OPTION(idx) _OPTION(con, p, idx) +#define _OPTION(con, p, idx) (con->options[p->opt_base_index + idx]) +#define _OPTION_ABS(con, idx) (con->options[idx]) + #endif diff --git a/src/plugin_core.c b/src/plugin_core.c index ad8eb4b..8597c2b 100644 --- a/src/plugin_core.c +++ b/src/plugin_core.c @@ -1,11 +1,17 @@ #include "base.h" +#include "plugin_core.h" static action* core_list(server *srv, plugin* p, option *opt) { action *a; guint i; UNUSED(p); + if (!opt) { + ERROR(srv, "%s", "need parameter"); + return NULL; + } + if (opt->type == OPTION_ACTION) { a = opt->value.opt_action.action; action_acquire(a); @@ -38,6 +44,10 @@ static action* core_when(server *srv, plugin* p, option *opt) { action *a; UNUSED(p); + if (!opt) { + ERROR(srv, "%s", "need parameter"); + return NULL; + } if (opt->type != OPTION_LIST) { ERROR(srv, "expected list, got %s", option_type_string(opt->type)); return NULL; @@ -63,11 +73,93 @@ static action* core_when(server *srv, plugin* p, option *opt) { return a; } +static action* core_set(server *srv, plugin* p, option *opt) { + option *value, *opt_name; + action *a; + UNUSED(p); + + if (!opt) { + ERROR(srv, "%s", "need parameter"); + return NULL; + } + if (opt->type != OPTION_LIST) { + ERROR(srv, "expected list, got %s", option_type_string(opt->type)); + return NULL; + } + if (opt->value.opt_list->len != 2) { + ERROR(srv, "expected list with length 2, has length %u", opt->value.opt_list->len); + return NULL; + } + opt_name = g_array_index(opt->value.opt_list, option*, 0); + value = g_array_index(opt->value.opt_list, option*, 1); + if (opt_name->type != OPTION_STRING) { + ERROR(srv, "expected string as first parameter, got %s", option_type_string(opt_name->type)); + return NULL; + } + a = option_action(srv, opt_name->value.opt_string->str, value); + option_free(opt); + return a; +} + +static action_result core_handle_physical(server *srv, connection *con, gpointer param) { + GString *docroot = (GString*) param; + UNUSED(srv); + + if (con->state != CON_STATE_HANDLE_REQUEST_HEADER) return ACTION_GO_ON; + + g_string_truncate(con->physical.path, 0); + g_string_append_len(con->physical.path, GSTR_LEN(docroot)); + g_string_append_len(con->physical.path, GSTR_LEN(con->request.uri.path)); + + return ACTION_GO_ON; +} + +static void core_physical_free(struct server *srv, gpointer param) { + UNUSED(srv); + g_string_free((GString*) param, TRUE); +} + +static action* core_physical(server *srv, plugin* p, option *opt) { + UNUSED(p); + GString *docroot; + + if (!opt) { + ERROR(srv, "%s", "need parameter"); + return NULL; + } + if (opt->type != OPTION_STRING) { + ERROR(srv, "expected string as parameter, got %s", option_type_string(opt->type)); + return NULL; + } + + docroot = (GString*) option_extract_value(opt); + + return action_new_function(core_handle_physical, core_physical_free, docroot); +} + static action_result core_handle_static(server *srv, connection *con, gpointer param) { UNUSED(param); - /* TODO */ - CON_ERROR(srv, con, "%s", "Not implemented yet"); - return ACTION_ERROR; + int fd; + + if (con->state != CON_STATE_HANDLE_REQUEST_HEADER) return ACTION_GO_ON; + + if (con->physical.path->len == 0) return ACTION_GO_ON; + + fd = open(con->physical.path->str, O_RDONLY); + if (fd == -1) { + con->response.http_status = 404; + } else { + struct stat st; + fstat(fd, &st); +#ifdef FD_CLOEXEC + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + con->response.http_status = 200; + chunkqueue_append_file_fd(con->out, NULL, 0, st.st_size, fd); + } + connection_handle_direct(srv, con); + + return ACTION_GO_ON; } static action* core_static(server *srv, plugin* p, option *opt) { @@ -86,7 +178,7 @@ static action_result core_handle_test(server *srv, connection *con, gpointer par if (con->state != CON_STATE_HANDLE_REQUEST_HEADER) return ACTION_GO_ON; con->response.http_status = 200; - chunkqueue_append_mem(con->out, GSTR_LEN(con->request.uri.uri)); + chunkqueue_append_mem(con->out, GSTR_LEN(con->request.uri.path)); chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n")); connection_handle_direct(srv, con); @@ -154,18 +246,24 @@ static gboolean core_listen(server *srv, plugin* p, option *opt) { } TRACE(srv, "will listen to '%s'", opt->value.opt_string->str); + option_free(opt); return TRUE; } static const plugin_option options[] = { - { "static-file.exclude", OPTION_LIST, NULL, NULL }, + { "debug.log-request-handling", OPTION_BOOLEAN, NULL, NULL}, { "log.level", OPTION_STRING, NULL, NULL }, + + { "static-file.exclude", OPTION_LIST, NULL, NULL }, { NULL, 0, NULL, NULL } }; static const plugin_action actions[] = { { "list", core_list }, { "when", core_when }, + { "set", core_set }, + + { "physical", core_physical }, { "static", core_static }, { "test", core_test }, { NULL, NULL } diff --git a/src/plugin_core.h b/src/plugin_core.h new file mode 100644 index 0000000..859c942 --- /dev/null +++ b/src/plugin_core.h @@ -0,0 +1,15 @@ +#ifndef _LIGHTTPD_PLUGIN_CORE_H_ +#define _LIGHTTPD_PLUGIN_CORE_H_ + +enum core_options_t { + CORE_OPTION_DEBUG_REQUEST_HANDLING = 0, + CORE_OPTION_LOG_LEVEL = 1, + + CORE_OPTION_STATIC_FILE_EXCLUDE = 2 +}; + +/* the core plugin always has base index 0, as it is the first plugin loaded */ +#define CORE_OPTION(idx) _CORE_OPTION(con, idx) +#define _CORE_OPTION(con, idx) _OPTION_ABS(con, idx) + +#endif diff --git a/src/request.c b/src/request.c index aa11862..e368eb7 100644 --- a/src/request.c +++ b/src/request.c @@ -1,21 +1,22 @@ #include "base.h" +#include "url_parser.h" +#include "utils.h" void request_init(request *req, chunkqueue *in) { req->http_method = HTTP_METHOD_UNSET; req->http_method_str = g_string_sized_new(0); req->http_version = HTTP_VERSION_UNSET; - req->uri.uri = g_string_sized_new(0); - req->uri.orig_uri = g_string_sized_new(0); - req->uri.uri_raw = g_string_sized_new(0); + req->uri.raw = g_string_sized_new(0); req->uri.scheme = g_string_sized_new(0); + req->uri.authority = g_string_sized_new(0); req->uri.path = g_string_sized_new(0); req->uri.query = g_string_sized_new(0); + req->uri.host = g_string_sized_new(0); req->headers = http_headers_new(); - req->host = g_string_sized_new(0); req->content_length = -1; http_request_parser_init(&req->parser_ctx, req, in); @@ -26,16 +27,15 @@ void request_reset(request *req) { g_string_truncate(req->http_method_str, 0); req->http_version = HTTP_VERSION_UNSET; - g_string_truncate(req->uri.uri, 0); - g_string_truncate(req->uri.orig_uri, 0); - g_string_truncate(req->uri.uri_raw, 0); + g_string_truncate(req->uri.raw, 0); g_string_truncate(req->uri.scheme, 0); + g_string_truncate(req->uri.authority, 0); g_string_truncate(req->uri.path, 0); g_string_truncate(req->uri.query, 0); + g_string_truncate(req->uri.host, 0); http_headers_reset(req->headers); - g_string_truncate(req->host, 0); req->content_length = -1; http_request_parser_reset(&req->parser_ctx); @@ -46,16 +46,15 @@ void request_clear(request *req) { g_string_free(req->http_method_str, TRUE); req->http_version = HTTP_VERSION_UNSET; - g_string_free(req->uri.uri, TRUE); - g_string_free(req->uri.orig_uri, TRUE); - g_string_free(req->uri.uri_raw, TRUE); + g_string_free(req->uri.raw, TRUE); g_string_free(req->uri.scheme, TRUE); + g_string_free(req->uri.authority, TRUE); g_string_free(req->uri.path, TRUE); g_string_free(req->uri.query, TRUE); + g_string_free(req->uri.host, TRUE); http_headers_free(req->headers); - g_string_free(req->host, TRUE); req->content_length = -1; http_request_parser_clear(&req->parser_ctx); @@ -68,40 +67,74 @@ static void bad_request(server *srv, connection *con, int status) { connection_handle_direct(srv, con); } +gboolean request_parse_url(server *srv, connection *con) { + request *req = &con->request; + UNUSED(srv); UNUSED(req); + + g_string_truncate(req->uri.query, 0); + g_string_truncate(req->uri.path, 0); + + if (!parse_raw_url(&req->uri)) + return FALSE; + + /* "*" only allowed for method OPTIONS */ + if (0 == strcmp(req->uri.path->str, "*") && req->http_method != HTTP_METHOD_OPTIONS) + return FALSE; + + url_decode(req->uri.path); + path_simplify(req->uri.path); + + return TRUE; +} + void request_validate_header(server *srv, connection *con) { request *req = &con->request; - response *resp = &con->response; http_header *hh; switch (req->http_version) { case HTTP_VERSION_1_0: if (!http_header_is(req->headers, CONST_STR_LEN("connection"), CONST_STR_LEN("keep-alive"))) - con->keep_alive = 0; + con->keep_alive = FALSE; break; case HTTP_VERSION_1_1: if (http_header_is(req->headers, CONST_STR_LEN("connection"), CONST_STR_LEN("close"))) - con->keep_alive = 0; + con->keep_alive = FALSE; break; case HTTP_VERSION_UNSET: bad_request(srv, con, 505); /* Version not Supported */ return; } - if (req->uri.uri_raw->len == 0) { + if (req->uri.raw->len == 0) { bad_request(srv, con, 400); /* bad request */ return; } /* get hostname */ hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("host")); - if ((!hh && req->http_version == HTTP_VERSION_1_1) || (hh && hh->values.length != 1)) { + if (hh && hh->values.length != 1) { bad_request(srv, con, 400); /* bad request */ return; } else if (hh) { - g_string_append_len(req->host, GSTR_LEN((GString*) g_queue_peek_head(&hh->values))); -// CON_TRACE(srv, con, "hostname: '%s'", req->host->str); + g_string_append_len(req->uri.host, GSTR_LEN((GString*) g_queue_peek_head(&hh->values))); + if (parse_authority(&req->uri)) { + bad_request(srv, con, 400); /* bad request */ + return; + } } + /* may override hostname */ + if (!request_parse_url(srv, con)) { + bad_request(srv, con, 400); /* bad request */ + return; + } + + /* Need hostname in HTTP/1.1 */ + if (req->uri.host->len == 0 && req->http_version == HTTP_VERSION_1_1) { + bad_request(srv, con, 400); /* bad request */ + return; + } + /* content-length */ hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("content-length")); if (hh) { @@ -195,4 +228,33 @@ void request_validate_header(server *srv, connection *con) { /* the may have a content-length */ break; } + + /* TODO: check hostname */ +} + +void physical_init(physical *phys) { + phys->path = g_string_sized_new(512); + phys->basedir = g_string_sized_new(256); + phys->doc_root = g_string_sized_new(256); + phys->rel_path = g_string_sized_new(256); + phys->pathinfo = g_string_sized_new(256); + phys->size = -1; +} + +void physical_reset(physical *phys) { + g_string_truncate(phys->path, 0); + g_string_truncate(phys->basedir, 0); + g_string_truncate(phys->doc_root, 0); + g_string_truncate(phys->rel_path, 0); + g_string_truncate(phys->pathinfo, 0); + phys->size = -1; +} + +void physical_clear(physical *phys) { + g_string_free(phys->path, TRUE); + g_string_free(phys->basedir, TRUE); + g_string_free(phys->doc_root, TRUE); + g_string_free(phys->rel_path, TRUE); + g_string_free(phys->pathinfo, TRUE); + phys->size = -1; } diff --git a/src/request.h b/src/request.h index aaeffdf..f1590ee 100644 --- a/src/request.h +++ b/src/request.h @@ -47,11 +47,14 @@ typedef struct physical physical; #include "http_request_parser.h" struct request_uri { - GString *uri, *orig_uri, *uri_raw; + GString *raw; GString *scheme; + GString *authority; GString *path; GString *query; + + GString *host; /* without userinfo and port */ }; struct physical { @@ -63,7 +66,7 @@ struct physical { GString *pathinfo; - guint64 size; + gint64 size; }; struct request { @@ -75,16 +78,22 @@ struct request { http_headers *headers; /* Parsed headers: */ - GString *host; goffset content_length; http_request_ctx parser_ctx; }; + +#include "base.h" + LI_API void request_init(request *req, chunkqueue *in); LI_API void request_reset(request *req); LI_API void request_clear(request *req); LI_API void request_validate_header(server *srv, connection *con); +LI_API void physical_init(physical *phys); +LI_API void physical_reset(physical *phys); +LI_API void physical_clear(physical *phys); + #endif diff --git a/src/server.c b/src/server.c index 43bcd99..916f079 100644 --- a/src/server.c +++ b/src/server.c @@ -2,6 +2,46 @@ #include "base.h" #include "utils.h" +struct server_closing_socket; +typedef struct server_closing_socket server_closing_socket; + +struct server_closing_socket { + server *srv; + GList *link; + int fd; +}; + +static void server_closing_socket_cb(int revents, void* arg) { + server_closing_socket *scs = (server_closing_socket*) arg; + UNUSED(revents); + + /* Whatever happend: we just close the socket */ + shutdown(scs->fd, SHUT_RD); + close(scs->fd); + g_queue_delete_link(&scs->srv->closing_sockets, scs->link); + g_slice_free(server_closing_socket, scs); +} + +void server_add_closing_socket(server *srv, int fd) { + server_closing_socket *scs = g_slice_new0(server_closing_socket); + + shutdown(fd, SHUT_WR); + + scs->srv = srv; + scs->fd = fd; + g_queue_push_tail(&srv->closing_sockets, scs); + scs->link = g_queue_peek_tail_link(&srv->closing_sockets); + + ev_once(srv->loop, fd, EV_READ, 10.0, server_closing_socket_cb, scs); +} + +/* Kill it - frees fd */ +static void server_rem_closing_socket(server *srv, server_closing_socket *scs) { + ev_feed_fd_event(srv->loop, scs->fd, EV_READ); +} + +void con_put(server *srv, connection *con); + static void server_option_free(gpointer _so) { g_slice_free(server_option, _so); } @@ -14,6 +54,37 @@ static void server_setup_free(gpointer _ss) { g_slice_free(server_setup, _ss); } +static struct ev_signal + sig_w_INT, + sig_w_TERM, + sig_w_PIPE; + +static void sigint_cb(struct ev_loop *loop, struct ev_signal *w, int revents) { + server *srv = (server*) w->data; + UNUSED(revents); + + if (!srv->exiting) { + INFO(srv, "Got signal, shutdown"); + server_exit(srv); + } else { + INFO(srv, "Got second signal, force shutdown"); + ev_unloop (loop, EVUNLOOP_ALL); + } +} + +static void sigpipe_cb(struct ev_loop *loop, struct ev_signal *w, int revents) { + /* ignore */ + UNUSED(loop); UNUSED(w); UNUSED(revents); +} + +#define CATCH_SIGNAL(loop, cb, n) do {\ + my_ev_init(&sig_w_##n, cb); \ + ev_signal_set(&sig_w_##n, SIG##n); \ + ev_signal_start(loop, &sig_w_##n); \ + sig_w_##n.data = srv; \ + ev_unref(loop); /* Signal watchers shouldn't keep loop alive */ \ +} while (0) + server* server_new() { server* srv = g_slice_new0(server); @@ -25,35 +96,78 @@ server* server_new() { fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?"); } + CATCH_SIGNAL(srv->loop, sigint_cb, INT); + CATCH_SIGNAL(srv->loop, sigint_cb, TERM); + CATCH_SIGNAL(srv->loop, sigpipe_cb, PIPE); + srv->connections_active = 0; srv->connections = g_array_new(FALSE, TRUE, sizeof(connection*)); srv->sockets = g_array_new(FALSE, TRUE, sizeof(server_socket*)); + g_queue_init(&srv->closing_sockets); srv->plugins = g_hash_table_new(g_str_hash, g_str_equal); srv->options = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_option_free); srv->actions = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_action_free); srv->setups = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_setup_free); + srv->plugins_handle_close = g_array_new(FALSE, TRUE, sizeof(plugin*)); + srv->mainaction = NULL; srv->exiting = FALSE; srv->tmp_str = g_string_sized_new(255); - srv->cur_ts = time(0); srv->last_generated_date_ts = 0; srv->ts_date_str = g_string_sized_new(255); + log_init(srv); + return srv; } void server_free(server* srv) { if (!srv) return; - /* TODO */ + srv->exiting = TRUE; + server_stop(srv); + + { /* close connections */ + guint i; + if (srv->connections_active > 0) { + ERROR(srv, "Server shutdown with unclosed connections: %u", srv->connections_active); + for (i = srv->connections_active; i-- > 0;) { + connection *con = g_array_index(srv->connections, connection*, i); + connection_set_state(srv, con, CON_STATE_ERROR); + connection_state_machine(srv, con); /* cleanup plugins */ + con_put(srv, con); + } + } + for (i = 0; i < srv->connections->len; i++) { + connection_free(srv, g_array_index(srv->connections, connection*, i)); + } + g_array_free(srv->connections, TRUE); + } + + { guint i; for (i = 0; i < srv->sockets->len; i++) { + server_socket *sock = g_array_index(srv->sockets, server_socket*, i); + close(sock->watcher.fd); + g_slice_free(server_socket, sock); + g_array_free(srv->sockets, TRUE); + }} + { /* force closing sockets */ + GList *iter; + for (iter = g_queue_peek_head_link(&srv->closing_sockets); iter; iter = g_list_next(iter)) { + server_closing_socket_cb(EV_TIMEOUT, (server_closing_socket*) iter->data); + } + g_queue_clear(&srv->closing_sockets); + } + + g_hash_table_destroy(srv->plugins); g_hash_table_destroy(srv->options); g_hash_table_destroy(srv->actions); g_hash_table_destroy(srv->setups); - g_hash_table_destroy(srv->plugins); + + g_array_free(srv->plugins_handle_close, TRUE); action_release(srv, srv->mainaction); @@ -61,13 +175,16 @@ void server_free(server* srv) { g_string_free(srv->ts_date_str, TRUE); /* free logs */ - GHashTableIter iter; - gpointer k, v; - g_hash_table_iter_init(&iter, srv->logs); - while (g_hash_table_iter_next(&iter, &k, &v)) { - log_free(srv, v); + g_thread_join(srv->log_thread); + { + GHashTableIter iter; + gpointer k, v; + g_hash_table_iter_init(&iter, srv->logs); + while (g_hash_table_iter_next(&iter, &k, &v)) { + log_free(srv, v); + } + g_hash_table_destroy(srv->logs); } - g_hash_table_destroy(srv->logs); g_mutex_free(srv->log_mutex); g_async_queue_unref(srv->log_queue); @@ -149,33 +266,13 @@ void server_listen(server *srv, int fd) { sock->srv = srv; sock->watcher.data = sock; fd_init(fd); - ev_io_init(&sock->watcher, server_listen_cb, fd, EV_READ); + my_ev_init(&sock->watcher, server_listen_cb); + ev_io_set(&sock->watcher, fd, EV_READ); if (srv->state == SERVER_RUNNING) ev_io_start(srv->loop, &sock->watcher); g_array_append_val(srv->sockets, sock); } - -static void sigint_cb(struct ev_loop *loop, struct ev_signal *w, int revents) { - UNUSED(w); UNUSED(revents); - ev_unloop (loop, EVUNLOOP_ALL); -} - -static void sigpipe_cb(struct ev_loop *loop, struct ev_signal *w, int revents) { - /* ignore */ - UNUSED(loop); UNUSED(w); UNUSED(revents); -} - -static struct ev_signal - sig_w_INT, - sig_w_TERM, - sig_w_PIPE; - -#define CATCH_SIGNAL(loop, cb, n) do {\ - ev_signal_init(&sig_w_##n, cb, SIG##n); \ - ev_signal_start(loop, &sig_w_##n); \ -} while (0) - void server_start(server *srv) { guint i; if (srv->state == SERVER_STOPPING || srv->state == SERVER_RUNNING) return; /* no restart after stop */ @@ -187,14 +284,18 @@ void server_start(server *srv) { return; } + /* TODO: get default values for options */ + srv->option_count = g_hash_table_size(srv->options); + srv->option_def_values = g_slice_alloc0(srv->option_count * sizeof(*srv->option_def_values)); + + plugins_prepare_callbacks(srv); + for (i = 0; i < srv->sockets->len; i++) { server_socket *sock = g_array_index(srv->sockets, server_socket*, i); ev_io_start(srv->loop, &sock->watcher); } - CATCH_SIGNAL(srv->loop, sigint_cb, INT); - CATCH_SIGNAL(srv->loop, sigint_cb, TERM); - CATCH_SIGNAL(srv->loop, sigpipe_cb, PIPE); + log_thread_start(srv); ev_loop(srv->loop, 0); } @@ -210,20 +311,35 @@ void server_stop(server *srv) { } } +void server_exit(server *srv) { + g_atomic_int_set(&srv->exiting, TRUE); + server_stop(srv); + + { /* force closing sockets */ + GList *iter; + for (iter = g_queue_peek_head_link(&srv->closing_sockets); iter; iter = g_list_next(iter)) { + server_rem_closing_socket(srv, (server_closing_socket*) iter->data); + } + } + + log_thread_wakeup(srv); +} + + void joblist_append(server *srv, connection *con) { connection_state_machine(srv, con); } GString *server_current_timestamp(server *srv) { - srv->cur_ts = time(0); /* TODO: update cur_ts somewhere else */ - if (srv->cur_ts != srv->last_generated_date_ts) { + time_t cur_ts = CUR_TS(srv); + if (cur_ts != srv->last_generated_date_ts) { g_string_set_size(srv->ts_date_str, 255); strftime(srv->ts_date_str->str, srv->ts_date_str->allocated_len, - "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts))); + "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(cur_ts))); g_string_set_size(srv->ts_date_str, strlen(srv->ts_date_str->str)); - srv->last_generated_date_ts = srv->cur_ts; + srv->last_generated_date_ts = cur_ts; } return srv->ts_date_str; } diff --git a/src/server.h b/src/server.h index aa74106..dda6c6f 100644 --- a/src/server.h +++ b/src/server.h @@ -5,6 +5,8 @@ #define LIGHTTPD_SERVER_MAGIC ((guint)0x12AB34CD) #endif +#define CUR_TS(srv) ((time_t)ev_now((srv)->loop)) + typedef enum { SERVER_STARTING, /** start up: don't write log files, don't accept connections */ SERVER_RUNNING, /** running: write logs, accept connections */ @@ -28,6 +30,7 @@ struct server { guint connections_active; /** 0..con_act-1: active connections, con_act..used-1: free connections */ GArray *connections; /** array of (connection*) */ GArray *sockets; /** array of (server_socket*) */ + GQueue closing_sockets; /** wait for EOF before shutdown(SHUT_RD) and close() */ GHashTable *plugins; /**< const gchar* => (plugin*) */ @@ -36,14 +39,17 @@ struct server { GHashTable *actions; /**< const gchar* => (server_action*) */ GHashTable *setups; /**< const gchar* => (server_setup*) */ - gpointer *option_def_values; /* TODO */ + GArray *plugins_handle_close; /** list of handle_close callbacks */ + + size_t option_count; /**< set to size of option hash table */ + gpointer *option_def_values; struct action *mainaction; gboolean exiting; GString *tmp_str; /**< can be used everywhere for local temporary needed strings */ - time_t cur_ts, last_generated_date_ts; + time_t last_generated_date_ts; GString *ts_date_str; /**< use server_current_timestamp(srv) */ /* logs */ @@ -62,11 +68,18 @@ LI_API void server_free(server* srv); LI_API void server_listen(server *srv, int fd); +/* Start accepting connection, use log files, no new plugins after that */ LI_API void server_start(server *srv); +/* stop accepting connections, turn keep-alive off */ LI_API void server_stop(server *srv); +/* close connections, close logs, stop log-thread */ +LI_API void server_exit(server *srv); LI_API void joblist_append(server *srv, connection *con); LI_API GString *server_current_timestamp(server *srv); +/* shutdown write and wait for eof before shutdown read and close */ +LI_API void server_add_closing_socket(server *srv, int fd); + #endif diff --git a/src/url_parser.h b/src/url_parser.h new file mode 100644 index 0000000..849c945 --- /dev/null +++ b/src/url_parser.h @@ -0,0 +1,10 @@ +#ifndef _LIGHTTPD_URL_PARSER_H_ +#define _LIGHTTPD_URL_PARSER_H_ + +#include "settings.h" +#include "request.h" + +LI_API gboolean parse_raw_url(request_uri *uri); +LI_API gboolean parse_authority(request_uri *uri); + +#endif diff --git a/src/url_parser.rl b/src/url_parser.rl new file mode 100644 index 0000000..3592a0b --- /dev/null +++ b/src/url_parser.rl @@ -0,0 +1,107 @@ + +#include "url_parser.h" + +#include + +%%{ + machine url_parser; + + action mark { mark = fpc; } + action mark_host { host_mark = fpc; } + + action save_host { + g_string_truncate(uri->host, 0); + g_string_append_len(uri->host, host_mark, fpc - host_mark); + g_string_ascii_down(uri->host); + } + action save_authority { + g_string_truncate(uri->authority, 0); + g_string_append_len(uri->authority, mark, fpc - mark); + g_string_ascii_down(uri->authority); + } + action save_path { + g_string_append_len(uri->path, mark, fpc - mark); + } + action save_query { + g_string_append_len(uri->query, mark, fpc - mark); + } + + pct_encoded = "%" xdigit xdigit; + + gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@"; + sub_delims = "!" | "$" | "&" | "'" | "(" | ")" + | "*" | "+" | "," | ";" | "="; + + reserved = gen_delims | sub_delims; + unreserved = alpha | digit | "-" | "." | "_" | "~"; + + + pchar = unreserved | pct_encoded | sub_delims | ":" | "@"; + path = ("/" ( "/" | pchar)*) >mark %save_path; + +# scheme = alpha *( alpha | digit | "+" | "-" | "." ); + scheme = "http" | "https"; + +#simple ipv4 address + dec_octet = digit{1,3}; + IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet; + + IPvFuture = "v" xdigit+ "." ( unreserved | sub_delims | ":" )+; + +# simple ipv6 address + IPv6address = (":" | xdigit)+ IPv4address?; + + IP_literal = "[" ( IPv6address | IPvFuture ) "]"; + + reg_name = ( unreserved | pct_encoded | sub_delims )+; + + userinfo = ( unreserved | pct_encoded | sub_delims | ":" )*; + host = IP_literal | IPv4address | reg_name; + port = digit+; + authority = ( userinfo "@" )? (host >mark_host %save_host) ( ":" port )?; + + query = ( pchar | "/" | "?" )* >mark %save_query; + fragment = ( pchar | "/" | "?" )*; + + URI_path = path ( "?" query )? ( "#" fragment )?; + + URI = scheme "://" (authority >mark %save_authority) URI_path; + + parse_URI := URI | ("*" >mark %save_path) | URI_path; + parse_Authority := authority; + + write data; +}%% + +gboolean parse_raw_url(request_uri *uri) { + const char *p, *pe, *eof; + const char *mark = NULL, *host_mark = NULL; + int cs; + + p = uri->raw->str; + eof = pe = uri->raw->str + uri->raw->len; + + %% write init nocs; + cs = url_parser_en_parse_URI; + + %% write exec; + + return (cs >= url_parser_first_final); +} + +gboolean parse_authority(request_uri *uri) { + const char *p, *pe, *eof; + const char *mark = NULL, *host_mark = NULL; + int cs; + + g_string_ascii_down(uri->authority); + p = uri->authority->str; + eof = pe = uri->authority->str + uri->authority->len; + + %% write init nocs; + cs = url_parser_en_parse_Authority; + + %% write exec; + + return (cs >= url_parser_first_final); +} diff --git a/src/utils.c b/src/utils.c index d9695c8..3a11ff4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -44,3 +44,127 @@ void ev_io_set_events(struct ev_loop *loop, ev_io *watcher, int events) { ev_io_set(watcher, watcher->fd, (watcher->events & ~(EV_READ | EV_WRITE)) | events); ev_io_start(loop, watcher); } + + +/* converts hex char (0-9, A-Z, a-z) to decimal. + * returns -1 on invalid input. + */ +static int hex2int(unsigned char hex) { + int res; + if (hex >= 'A') { /* 'A' < 'a': hex >= 'A' --> hex >= 'a' */ + if (hex >= 'a') { + res = hex - 'a' + 10; + } else { + res = hex - 'A' + 10; + } + } else { + res = hex - '0'; + } + if (res > 15) + res = -1; + + return res; +} + +void url_decode(GString *path) { + char *src, *dst, c; + src = dst = path->str; + for ( ; *src; src++) { + c = *src; + if (c == '%') { + if (src[1] && src[2]) { + int a = hex2int(src[1]), b = hex2int(src[2]); + if (a != -1 && b != -1) { + c = (a << 4) | b; + if (c < 32 || c == 127) c = '_'; + *(dst++) = c; + } + src += 2; + } else { + /* end of string */ + return; + } + } else { + if (c < 32 || c == 127) c = '_'; + *(dst++) = c; + } + } + g_string_set_size(path, dst - path->str); +} + +/* Remove "/../", "//", "/./" parts from path. + * + * /blah/.. gets / + * /blah/../foo gets /foo + * /abc/./xyz gets /abc/xyz + * /abc//xyz gets /abc/xyz + * + * NOTE: src and dest can point to the same buffer, in which case + * the operation is performed in-place. + */ + +void path_simplify(GString *path) { + int toklen; + char c, pre1; + char *start, *slash, *walk, *out; + unsigned short pre; + + if (path == NULL) + return; + + walk = start = out = slash = path->str; + while (*walk == ' ') { + walk++; + } + + pre1 = *(walk++); + c = *(walk++); + pre = pre1; + if (pre1 != '/') { + pre = ('/' << 8) | pre1; + *(out++) = '/'; + } + *(out++) = pre1; + + if (pre1 == '\0') { + g_string_set_size(path, out - start); + return; + } + + while (1) { + if (c == '/' || c == '\0') { + toklen = out - slash; + if (toklen == 3 && pre == (('.' << 8) | '.')) { + out = slash; + if (out > start) { + out--; + while (out > start && *out != '/') { + out--; + } + } + + if (c == '\0') + out++; + } else if (toklen == 1 || pre == (('/' << 8) | '.')) { + out = slash; + if (c == '\0') + out++; + } + + slash = out; + } + + if (c == '\0') + break; + + pre1 = c; + pre = (pre << 8) | pre1; + c = *walk; + *out = pre1; + + out++; + walk++; + } + + g_string_set_size(path, out - start); +} diff --git a/src/utils.h b/src/utils.h index 02dc049..2f6ca61 100644 --- a/src/utils.h +++ b/src/utils.h @@ -11,4 +11,10 @@ LI_API void ev_io_add_events(struct ev_loop *loop, ev_io *watcher, int events); LI_API void ev_io_rem_events(struct ev_loop *loop, ev_io *watcher, int events); LI_API void ev_io_set_events(struct ev_loop *loop, ev_io *watcher, int events); + +/* URL inplace decode: replace %XX with character \xXX; replace control characters with '_' (< 32 || == 127) */ +LI_API void url_decode(GString *path); + +LI_API void path_simplify(GString *path); + #endif diff --git a/src/wscript b/src/wscript index 03b2419..f456a84 100644 --- a/src/wscript +++ b/src/wscript @@ -19,6 +19,7 @@ common_source=''' http_request_parser.rl log.c network.c + network_write.c network_writev.c options.c plugin.c request.c @@ -26,6 +27,7 @@ common_source=''' server.c sys-files.c sys-socket.c + url_parser.rl utils.c plugin_core.c @@ -139,16 +141,16 @@ def build(bld): #lighty_mod(bld, 'mod_deflate', 'mod_deflate.c', uselib = 'bzip zlib') #lighty_mod(bld, 'mod_webdav', 'mod_webdav.c', uselib = 'sqlite3 xml uuid') - tests = bld.new_task_gen('cc', 'program') - tests.inst_var = 0 - tests.unit_test = 1 - tests.name = 'tests' - tests.source = 'tests.c ' + common_source - if env['LIB_lua']: - tests.source += common_source_lua - tests.target = 'tests' - tests.uselib += 'lighty dl ev openssl pcre lua ' + common_uselib + ' lightylast' - tests.includes = '.' + #tests = bld.new_task_gen('cc', 'program') + #tests.inst_var = 0 + #tests.unit_test = 1 + #tests.name = 'tests' + #tests.source = 'tests.c ' + common_source + #if env['LIB_lua']: + #tests.source += common_source_lua + #tests.target = 'tests' + #tests.uselib += 'lighty dl ev openssl pcre lua ' + common_uselib + ' lightylast' + #tests.includes = '.' def configure(conf): env = conf.env diff --git a/wscript b/wscript index 8373756..a4ef5b7 100644 --- a/wscript +++ b/wscript @@ -219,18 +219,18 @@ def configure(conf): conf.define("LIBRARY_DIR", opts.libdir) common_ccflags = [ - '-std=gnu99', '-Wall', '-g', '-Wshadow', '-W', '-pedantic', '-pg', + '-std=gnu99', '-Wall', '-g', '-Wshadow', '-W', '-pedantic', ] lighty_common_ccflags = [ '-fPIC', '-DHAVE_CONFIG_H', '-D_GNU_SOURCE', '-D_FILE_OFFSET_BITS=64', '-D_LARGEFILE_SOURCE', '-D_LARGE_FILES', ] - conf.env['CCFLAGS'] += common_ccflags + conf.env['CCFLAGS'] = tolist(conf.env['CCFLAGS']) + common_ccflags conf.env['CCFLAGS_lighty'] += lighty_common_ccflags + [ '-DLI_DECLARE_EXPORTS' ] conf.env['CCFLAGS_lightymod'] += lighty_common_ccflags conf.env['plugin_PREFIX'] = '' - conf.env['LINKFLAGS_lighty'] += [ '-export-dynamic', '-pg' ] + conf.env['LINKFLAGS_lighty'] += [ '-export-dynamic' ] conf.env['LINKFLAGS_lightymod'] += [ '-module', '-export-dynamic', '-avoid-version', '-W,l-no-undefined' ] conf.env['LINKFLAGS_thread'] += [ '-pthread' ]