From edff7c59d6f6aabcc600070339c6967453651bf7 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Mon, 1 Nov 2010 08:08:15 +0000 Subject: [PATCH] implemented proper handling of duplicate http header fields --- qse/include/qse/utl/http.h | 23 ++---- qse/lib/utl/http.c | 141 ++++++++++++++++++++++++++++++++++--- 2 files changed, 139 insertions(+), 25 deletions(-) diff --git a/qse/include/qse/utl/http.h b/qse/include/qse/utl/http.h index 5efce8ec..1df4aed7 100644 --- a/qse/include/qse/utl/http.h +++ b/qse/include/qse/utl/http.h @@ -30,20 +30,6 @@ enum qse_http_errnum_t typedef enum qse_http_errnum_t qse_http_errnum_t; -/* -struct qse_http_req_t -{ - enum - { - QSE_HTTP_REQ_HEAD, - QSE_HTTP_REQ_GET, - QSE_HTTP_REQ_POST - } method; - - qse_char_t path[]; -}; -*/ - typedef struct qse_http_t qse_http_t; struct qse_http_t @@ -53,7 +39,7 @@ struct qse_http_t struct { - //qse_size_t pending; + /*qse_size_t pending;*/ int crlf; /* crlf status */ qse_size_t plen; /* raw request length excluding crlf */ @@ -88,9 +74,12 @@ struct qse_http_t short minor; } version; - qse_htb_t hdr; + struct + { + qse_htb_t tab; + void* combined; + } hdr; } req; - }; /* returns the type of http method */ diff --git a/qse/lib/utl/http.c b/qse/lib/utl/http.c index 1d69f16c..eda95c2c 100644 --- a/qse/lib/utl/http.c +++ b/qse/lib/utl/http.c @@ -301,7 +301,9 @@ static QSE_INLINE int push_to_buffer ( do { - void* tmp = QSE_MMGR_REALLOC ((http)->mmgr, (octb)->data, ncapa * QSE_SIZEOF(*ptr)); + void* tmp = QSE_MMGR_REALLOC ( + (http)->mmgr, (octb)->data, ncapa * QSE_SIZEOF(*ptr) + ); if (tmp) { (octb)->capa = ncapa; @@ -326,6 +328,25 @@ static QSE_INLINE int push_to_buffer ( return 0; } +struct hdr_cmb_t +{ + struct hdr_cmb_t* next; +}; + +static QSE_INLINE void clear_combined_headers (qse_http_t* http) +{ + struct hdr_cmb_t* cmb = (struct hdr_cmb_t*)http->req.hdr.combined; + + while (cmb) + { + struct hdr_cmb_t* next = cmb->next; + QSE_MMGR_FREE (http->mmgr, cmb); + cmb = next; + } + + http->req.hdr.combined = QSE_NULL; +} + #define QSE_HTTP_STATE_REQ 1 #define QSE_HTTP_STATE_HDR 2 #define QSE_HTTP_STATE_POST 3 @@ -376,7 +397,7 @@ qse_http_t* qse_http_init (qse_http_t* http, qse_mmgr_t* mmgr) http->state.plen = 0; init_buffer (http, &http->req.raw); - if (qse_htb_init (&http->req.hdr, mmgr, 60, 70, 1, 1) == QSE_NULL) + if (qse_htb_init (&http->req.hdr.tab, mmgr, 60, 70, 1, 1) == QSE_NULL) { fini_buffer (http, &http->req.raw); return QSE_NULL; @@ -387,7 +408,8 @@ qse_http_t* qse_http_init (qse_http_t* http, qse_mmgr_t* mmgr) void qse_http_fini (qse_http_t* http) { - qse_htb_fini (&http->req.hdr); + qse_htb_fini (&http->req.hdr.tab); + clear_combined_headers (http); fini_buffer (http, &http->req.raw); } @@ -521,6 +543,99 @@ qse_printf (QSE_T("BADREQ\n")); return QSE_NULL; } +struct cbserter_ctx_t +{ + qse_http_t* http; + void* vptr; + qse_size_t vlen; +}; + +static qse_htb_pair_t* cbserter ( + qse_htb_t* htb, qse_htb_pair_t* pair, + void* kptr, qse_size_t klen, void* ctx) +{ + struct cbserter_ctx_t* tx = (struct cbserter_ctx_t*)ctx; + + if (pair == QSE_NULL) + { + /* the key is new. create a new pair with the key and the value */ + qse_htb_pair_t* p; + p = qse_htb_allocpair (htb, kptr, klen, tx->vptr, tx->vlen); + if (p == QSE_NULL) tx->http->errnum = QSE_HTTP_ENOMEM; + return p; + } + else + { + /* the key exists. let's combine values, each separated by a comma */ + struct hdr_cmb_t* cmb; + qse_byte_t* ptr; + qse_size_t len; + + /* TODO: reduce waste in case the same key appears again and again. + * + * the current implementation is not space nor performance efficient. + * it allocates a new buffer again whenever it encounters the + * same key. memory is wasted and performance is sacrificed. + * + * hopefully, a http header does not include a lot of duplicate + * fields and this implmentation can afford wastage. + */ + + /* allocate a block to combine the existing value and the new value */ + cmb = (struct hdr_cmb_t*) QSE_MMGR_ALLOC ( + tx->http->mmgr, + QSE_SIZEOF(*cmb) + + QSE_SIZEOF(qse_byte_t) * (pair->vlen + 1 + tx->vlen + 1) + ); + if (cmb == QSE_NULL) + { + tx->http->errnum = QSE_HTTP_ENOMEM; + return QSE_NULL; + } + + /* let 'ptr' point to the actual space for the combined value */ + ptr = (qse_byte_t*)(cmb + 1); + len = 0; + + /* fill the space with the value */ + QSE_MEMCPY (&ptr[len], pair->vptr, pair->vlen); + len += pair->vlen; + ptr[len++] = ','; + QSE_MEMCPY (&ptr[len], tx->vptr, tx->vlen); + len += tx->vlen; + ptr[len] = '\0'; + +#if 0 +TODO: +Not easy to unlink when using a singly linked list... +Change it to doubly linked for this? + + /* let's destroy the old buffer at least */ + if (!(ptr >= tx->http->req.raw.data && ptr < + &tx->http->req.raw.data[tx->http->req.raw.size])) + { + /* NOTE the range check in 'if' assumes that raw.data is never + * relocated for resizing */ + + QSE_MMGR_FREE ( + tx->http->mmgr, + ((struct hdr_cmb_t*)pair->vptr) - 1 + ); + } +#endif + + /* update the value pointer and length */ + pair->vptr = ptr; + pair->vlen = len; + + /* link the new combined value block */ + cmb->next = tx->http->req.hdr.combined; + tx->http->req.hdr.combined = cmb; + + return pair; + } +} + qse_byte_t* parse_http_header (qse_http_t* http, qse_byte_t* line) { qse_byte_t* p = line, * last; @@ -587,13 +702,21 @@ qse_byte_t* parse_http_header (qse_http_t* http, qse_byte_t* line) } *last = '\0'; - /* add the field name and value into a hash table */ - if (qse_htb_insert (&http->req.hdr, - name.ptr, name.len, value.ptr, value.len) == QSE_NULL) +{ + struct cbserter_ctx_t ctx; + + ctx.http = http; + ctx.vptr = value.ptr; + ctx.vlen = value.len; + + http->errnum = QSE_HTTP_ENOERR; + if (qse_htb_cbsert ( + &http->req.hdr.tab, name.ptr, name.len, cbserter, &ctx) == QSE_NULL) { - http->errnum = QSE_HTTP_ENOMEM; + if (http->errnum == QSE_HTTP_ENOERR) http->errnum = QSE_HTTP_ENOMEM; return QSE_NULL; } +} return p; @@ -665,9 +788,11 @@ int qse_http_feed (qse_http_t* http, const qse_byte_t* ptr, qse_size_t len) } while (1); -qse_htb_walk (&http->req.hdr, walk, QSE_NULL); +qse_htb_walk (&http->req.hdr.tab, walk, QSE_NULL); /* TODO: do the main job here... before the raw buffer is cleared out... */ + qse_htb_clear (&http->req.hdr.tab); + clear_combined_headers (http); clear_buffer (http, &http->req.raw); req = ptr; /* let ptr point to the next character to '\n' */ }