From 47c0acf4c2658764e47055419f18719a4b2f95bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Tue, 9 Sep 2008 16:38:40 +0200 Subject: [PATCH] Changed header implementation from hash-table to double linked list --- src/http_headers.c | 214 ++++++++++++++++++++++----------------------- src/http_headers.h | 23 +++-- src/plugin_core.c | 4 +- src/request.c | 27 +++--- src/response.c | 25 ++---- 5 files changed, 146 insertions(+), 147 deletions(-) diff --git a/src/http_headers.c b/src/http_headers.c index 7c20f51..526d95c 100644 --- a/src/http_headers.c +++ b/src/http_headers.c @@ -2,168 +2,166 @@ #include "base.h" #include "http_headers.h" -static void _string_free(gpointer p) { - g_string_free((GString*) p, TRUE); -} - -static void _string_queue_string_free(gpointer data, gpointer userdata) { - UNUSED(userdata); - g_string_free((GString*) data, TRUE); -} - static void _http_header_free(gpointer p) { http_header *h = (http_header*) p; - g_queue_foreach(&h->values, _string_queue_string_free, NULL); - g_queue_clear(&h->values); - g_string_free(h->key, TRUE); + g_string_free(h->data, TRUE); g_slice_free(http_header, h); } -static http_header* _http_header_new(const gchar *key, size_t keylen) { +static http_header* _http_header_new(const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { http_header *h = g_slice_new0(http_header); - g_queue_init(&h->values); - h->key = g_string_new_len(key, keylen); + h->data = g_string_sized_new(keylen + valuelen + 2); + h->keylen = keylen; + g_string_append_len(h->data, key, keylen); + g_string_append_len(h->data, CONST_STR_LEN(": ")); + g_string_append_len(h->data, value, valuelen); return h; } +static void _header_queue_free(gpointer data, gpointer userdata) { + UNUSED(userdata); + _http_header_free((http_header*) data); +} + http_headers* http_headers_new() { http_headers* headers = g_slice_new0(http_headers); - headers->table = g_hash_table_new_full( - (GHashFunc) g_string_hash, (GEqualFunc) g_string_equal, - _string_free, _http_header_free); + g_queue_init(&headers->entries); return headers; } void http_headers_reset(http_headers* headers) { - g_hash_table_remove_all(headers->table); + g_queue_foreach(&headers->entries, _header_queue_free, NULL); + g_queue_clear(&headers->entries); } void http_headers_free(http_headers* headers) { if (!headers) return; - g_hash_table_destroy(headers->table); + g_queue_foreach(&headers->entries, _header_queue_free, NULL); + g_queue_clear(&headers->entries); g_slice_free(http_headers, headers); } -/* Just insert the header (using lokey) - */ -static void header_insert(http_headers *headers, GString *lokey, - const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { - http_header *h = _http_header_new(key, keylen); - g_queue_push_tail(&h->values, g_string_new_len(value, valuelen)); - - g_hash_table_insert(headers->table, lokey, h); -} - -/** If header does not exist, just insert normal header. If it exists, append (", %s", value) */ -void http_header_append(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { - GString *lokey, *tval; - http_header *h; - - lokey = g_string_new_len(key, keylen); - g_string_ascii_down(lokey); - h = (http_header*) g_hash_table_lookup(headers->table, lokey); - if (NULL == h) { - header_insert(headers, lokey, key, keylen, value, valuelen); - } else if (NULL == (tval = g_queue_peek_tail(&h->values))) { - g_string_free(lokey, TRUE); - g_queue_push_tail(&h->values, g_string_new_len(value, valuelen)); - } else { - g_string_free(lokey, TRUE); - g_string_append_len(tval, ", ", 2); - g_string_append_len(tval, value, valuelen); - } -} - -/** If header does not exist, just insert normal header. If it exists, append ("\r\n%s: %s", key, value) */ +/** just insert normal header, allow duplicates */ void http_header_insert(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { - GString *lokey; + http_header *h = _http_header_new(key, keylen, value, valuelen); + g_queue_push_tail(&headers->entries, h); +} + +GList* http_header_find_first(http_headers *headers, const gchar *key, size_t keylen) { + http_header *h; + GList *l; + + for (l = g_queue_peek_head_link(&headers->entries); l; l = g_list_next(l)) { + h = (http_header*) l->data; + if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l; + } + return NULL; +} + +GList* http_header_find_next(GList *l, const gchar *key, size_t keylen) { http_header *h; - lokey = g_string_new_len(key, keylen); - g_string_ascii_down(lokey); - h = (http_header*) g_hash_table_lookup(headers->table, lokey); - if (NULL == h) { - header_insert(headers, lokey, key, keylen, value, valuelen); + for (l = g_list_next(l); l; l = g_list_next(l)) { + h = (http_header*) l->data; + if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l; + } + return NULL; +} + +GList* http_header_find_last(http_headers *headers, const gchar *key, size_t keylen) { + http_header *h; + GList *l; + + for (l = g_queue_peek_tail_link(&headers->entries); l; l = g_list_previous(l)) { + h = (http_header*) l->data; + if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l; + } + return NULL; +} + +/** If header does not exist, just insert normal header. If it exists, append (", %s", value) to the last inserted one */ +void http_header_append(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { + GList *l; + http_header *h; + + l = http_header_find_last(headers, key, keylen); + if (NULL == l) { + http_header_insert(headers, key, keylen, value, valuelen); } else { - g_string_free(lokey, TRUE); - g_queue_push_tail(&h->values, g_string_new_len(value, valuelen)); + h = (http_header*) l; + g_string_append_len(h->data, CONST_STR_LEN(", ")); + g_string_append_len(h->data, value, valuelen); } } -/** If header does not exist, just insert normal header. If it exists, overwrite the value */ +/** If header does not exist, just insert normal header. If it exists, overwrite the last occurrence */ void http_header_overwrite(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { - GString *lokey; + GList *l; http_header *h; - lokey = g_string_new_len(key, keylen); - g_string_ascii_down(lokey); - h = (http_header*) g_hash_table_lookup(headers->table, lokey); - if (NULL == h) { - header_insert(headers, lokey, key, keylen, value, valuelen); + l = http_header_find_last(headers, key, keylen); + if (NULL == l) { + http_header_insert(headers, key, keylen, value, valuelen); } else { - g_string_free(lokey, TRUE); - g_string_truncate(h->key, 0); - g_string_append_len(h->key, key, keylen); - /* kill old headers */ - g_queue_foreach(&h->values, _string_queue_string_free, NULL); - /* new header */ - g_queue_push_tail(&h->values, g_string_new_len(value, valuelen)); + h = (http_header*) l; + g_string_truncate(h->data, 0); + g_string_append_len(h->data, key, keylen); + g_string_append_len(h->data, CONST_STR_LEN(": ")); + g_string_append_len(h->data, value, valuelen); } } +void http_header_remove_link(http_headers *headers, GList *l) { + _http_header_free(l->data); + g_queue_delete_link(&headers->entries, l); +} + gboolean http_header_remove(http_headers *headers, const gchar *key, size_t keylen) { - GString *lokey; - gboolean res; + GList *l, *lp = NULL;; + gboolean res = FALSE; - lokey = g_string_new_len(key, keylen); - g_string_ascii_down(lokey); - res = g_hash_table_remove(headers->table, lokey); - g_string_free(lokey, TRUE); + for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) { + if (lp) { + http_header_remove_link(headers, lp); + res = TRUE; + lp = NULL; + } + lp = l; + } + if (lp) { + http_header_remove_link(headers, lp); + res = TRUE; + lp = NULL; + } return res; } http_header* http_header_lookup(http_headers *headers, const gchar *key, size_t keylen) { - GString *lokey; - http_header *h; + GList *l; - lokey = g_string_new_len(key, keylen); - g_string_ascii_down(lokey); - h = (http_header*) g_hash_table_lookup(headers->table, lokey); - g_string_free(lokey, TRUE); - return h; -} - -http_header* http_header_lookup_fast(http_headers *headers, const gchar *key, size_t keylen) { - GString lokey; - http_header *h; - - /* Fake GString */ - lokey.str = (gchar*) key; - lokey.len = lokey.allocated_len = keylen; - h = (http_header*) g_hash_table_lookup(headers->table, &lokey); - return h; + l = http_header_find_last(headers, key, keylen); + return NULL == l ? NULL : (http_header*) l->data; } gboolean http_header_is(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) { - http_header *h = http_header_lookup_fast(headers, key, keylen); - GList *iter; + GList *l; UNUSED(valuelen); - if (!h) return FALSE; - for (iter = g_queue_peek_head_link(&h->values); NULL != iter; iter = g_list_next(iter)) { - if (0 == strcasecmp( ((GString*)iter->data)->str, value )) return TRUE; + for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) { + http_header *h = (http_header*) l->data; + if (h->data->len - (h->keylen + 2) != valuelen) continue; + if (0 == g_ascii_strcasecmp( &h->data->str[h->keylen+2], value )) return TRUE; } 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; - + GList *l; g_string_truncate(dest, 0); - if (!h) return; - for (iter = g_queue_peek_head_link(&h->values); NULL != iter; iter = g_list_next(iter)) { + + for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) { + http_header *h = (http_header*) l->data; if (dest->len) g_string_append_len(dest, CONST_STR_LEN(", ")); - g_string_append_len(dest, GSTR_LEN((GString*)iter->data)); + g_string_append_len(dest, &h->data->str[h->keylen+2], h->data->len - (h->keylen + 2)); } } diff --git a/src/http_headers.h b/src/http_headers.h index 839b93c..be5a8d8 100644 --- a/src/http_headers.h +++ b/src/http_headers.h @@ -9,14 +9,19 @@ typedef struct http_headers http_headers; #include "settings.h" +#define HEADER_VALUE(h) \ + (&(h)->data->str[h->keylen + 2]) + +#define HEADER_VALUE_LEN(h) \ + (&(h)->data->str[h->keylen + 2]), ((h)->data->len - (h->keylen + 2)) + struct http_header { - GString *key; - GQueue values; /**< queue of GString* */ + guint keylen; /** length of "headername" in data */ + GString *data; /** "headername: value" */ }; struct http_headers { - /** keys are lowercase header name (GString*), values are http_header* */ - GHashTable *table; + GQueue entries; }; /* strings alweays get copied, so you should free key and value yourself */ @@ -32,11 +37,13 @@ LI_API void http_header_insert(http_headers *headers, const gchar *key, size_t k /** If header does not exist, just insert normal header. If it exists, overwrite the value */ LI_API void http_header_overwrite(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen); LI_API gboolean http_header_remove(http_headers *headers, const gchar *key, size_t keylen); +LI_API void http_header_remove_link(http_headers *headers, GList *l); LI_API http_header* http_header_lookup(http_headers *headers, const gchar *key, size_t keylen); -/** Use lowercase keys! */ -LI_API http_header* http_header_lookup_fast(http_headers *headers, const gchar *key, size_t keylen); +LI_API GList* http_header_find_first(http_headers *headers, const gchar *key, size_t keylen); +LI_API GList* http_header_find_next(GList *l, const gchar *key, size_t keylen); +LI_API GList* http_header_find_last(http_headers *headers, const gchar *key, size_t keylen); /** 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); @@ -44,4 +51,8 @@ LI_API gboolean http_header_is(http_headers *headers, const gchar *key, size_t k /** 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); +INLINE gboolean http_header_key_is(http_header *h, const gchar *key, size_t keylen) { + return (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)); +} + #endif diff --git a/src/plugin_core.c b/src/plugin_core.c index 0aef326..d3fd344 100644 --- a/src/plugin_core.c +++ b/src/plugin_core.c @@ -240,7 +240,7 @@ static action_result core_handle_test(connection *con, gpointer param) { chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\nevent handler: ")); chunkqueue_append_mem(con->out, backend, strlen(backend)); - chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n\r\n--- headers ---\r\n")); +/* chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n\r\n--- headers ---\r\n")); g_hash_table_iter_init(&iter, con->request.headers->table); while (g_hash_table_iter_next(&iter, &k, &v)) { hv = g_queue_peek_head_link(&((http_header*)v)->values); @@ -251,7 +251,7 @@ static action_result core_handle_test(connection *con, gpointer param) { chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n")); hv = hv->next; } - } + }*/ chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n")); connection_handle_direct(con); diff --git a/src/request.c b/src/request.c index aabdc75..94f5d25 100644 --- a/src/request.c +++ b/src/request.c @@ -89,6 +89,7 @@ gboolean request_parse_url(connection *con) { void request_validate_header(connection *con) { request *req = &con->request; http_header *hh; + GList *l; switch (req->http_version) { case HTTP_VERSION_1_0: @@ -110,12 +111,14 @@ void request_validate_header(connection *con) { } /* get hostname */ - hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("host")); - if (hh && hh->values.length != 1) { + l = http_header_find_first(req->headers, CONST_STR_LEN("host")); + if (NULL != l && NULL != http_header_find_next(l, CONST_STR_LEN("host"))) { + /* more than one "host" header */ bad_request(con, 400); /* bad request */ return; - } else if (hh) { - g_string_append_len(req->uri.authority, GSTR_LEN((GString*) g_queue_peek_head(&hh->values))); + } else { + hh = (http_header*) l->data; + g_string_append_len(req->uri.authority, HEADER_VALUE_LEN(hh)); if (!parse_hostname(&req->uri)) { bad_request(con, 400); /* bad request */ return; @@ -135,13 +138,13 @@ void request_validate_header(connection *con) { } /* content-length */ - hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("content-length")); + hh = http_header_lookup(req->headers, CONST_STR_LEN("content-length")); if (hh) { - GString *val = (GString*) g_queue_peek_head(&hh->values); + const gchar *val = HEADER_VALUE(hh); off_t r; char *err; - r = str_to_off_t(val->str, &err, 10); + r = str_to_off_t(val, &err, 10); if (*err != '\0') { CON_TRACE(con, "content-length is not a number: %s (Status: 400)", err); bad_request(con, 400); /* bad request */ @@ -172,13 +175,13 @@ void request_validate_header(connection *con) { } /* Expect: 100-continue */ - hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("expect")); - if (hh) { - GList *iter; + l = http_header_find_first(req->headers, CONST_STR_LEN("expect")); + if (l) { gboolean expect_100_cont = FALSE; - for (iter = g_queue_peek_head_link(&hh->values); NULL != iter; iter = g_list_next(iter)) { - if (0 == strcasecmp( ((GString*)iter->data)->str, "100-continue" )) { + for ( ; l ; l = http_header_find_next(l, CONST_STR_LEN("expect")) ) { + hh = (http_header*) l->data; + if (0 == strcasecmp( HEADER_VALUE(hh), "100-continue" )) { expect_100_cont = TRUE; } else { /* we only support 100-continue */ diff --git a/src/response.c b/src/response.c index 0b40757..0831ccb 100644 --- a/src/response.c +++ b/src/response.c @@ -80,28 +80,15 @@ void response_send_headers(connection *con) { /* Append headers */ { - GHashTableIter iter; - GString *key; http_header *header; - GList *valiter; + GList *iter; gboolean have_date = FALSE, have_server = FALSE; - g_hash_table_iter_init (&iter, con->response.headers->table); - while (g_hash_table_iter_next (&iter, (gpointer*) &key, (gpointer*) &header)) { - if (!header) continue; - - valiter = g_queue_peek_head_link(&header->values); - if (!valiter) continue; - do { - g_string_append_len(head, GSTR_LEN(header->key)); - g_string_append_len(head, CONST_STR_LEN(": ")); - g_string_append_len(head, GSTR_LEN((GString*) valiter->data)); - g_string_append_len(head, CONST_STR_LEN("\r\n")); - } while (NULL != (valiter = g_list_next(valiter))); - - /* key is lowercase */ - if (0 == strcmp(key->str, "date")) have_date = TRUE; - else if (0 == strcmp(key->str, "server")) have_server = TRUE; + for (iter = g_queue_peek_head_link(&con->response.headers->entries); iter; iter = g_list_next(iter)) { + header = (http_header*) iter->data; + g_string_append_len(head, GSTR_LEN(header->data)); + if (!have_date && http_header_key_is(header, CONST_STR_LEN("date"))) have_date = TRUE; + if (!have_server && http_header_key_is(header, CONST_STR_LEN("server"))) have_server = TRUE; } if (!have_date) {