Author: rhuijben Date: Mon Oct 26 19:04:19 2015 New Revision: 1710678 URL: http://svn.apache.org/viewvc?rev=1710678&view=rev Log: Implement a hpack decoder button to allow processing the results of a http2 request.
* buckets/hpack_buckets.c (serf_hpack_item_t): Rename to... (serf_hpack_entry_t): ... this, to match naming in RFC. Add a few values. (hpack_free_entry): New function. (HPACK_ENTRY_SIZE): New macro. (serf_hpack_table_t): New struct. (hpack_static_table, hpack_static_table_count): New static variable. (serf_hpack_table_create, hpack_table_get): New function. (serf_hpack_context_t): Add reference to tbl. (hpack_copy_from_headers): Add comment. (serf_bucket_hpack_create): Update init. (serf_bucket_hpack_setx): Tweak names. Lowercase headers if needed. (serf_bucket_hpack_getc, serf_bucket_hpack_do): Tweak variables. (serialize): Tweak release strategy. (serf_hpack_read, serf_hpack_read_iovec, serf_hpack_read_peek): Use private api to allow bucket debugging. (serf_hpack_destroy_and_data): Extract a bit of code to hpack_free_entry. (serf_hpack_decode_ctx_t): New struct. (hpack_decode_databuf_reader): New prototype. (serf_bucket_hpack_decode_create, read_hpack_int, handle_read_entry_and_clear, hpack_process, hpack_decode_databuf_reader, serf_hpack_decode_read, serf_hpack_decode_readline, serf_hpack_decode_peek, serf_hpack_decode_destroy): New function. (serf_bucket_type_hpack_decode): New bucket type. * serf_bucket_types.h (serf_hpack_table_create): New function. (serf_bucket_type_hpack_decode): New variable. (serf_bucket_hpack_decode_create): New function. Modified: serf/trunk/buckets/hpack_buckets.c serf/trunk/serf_bucket_types.h Modified: serf/trunk/buckets/hpack_buckets.c URL: http://svn.apache.org/viewvc/serf/trunk/buckets/hpack_buckets.c?rev=1710678&r1=1710677&r2=1710678&view=diff ============================================================================== --- serf/trunk/buckets/hpack_buckets.c (original) +++ serf/trunk/buckets/hpack_buckets.c Mon Oct 26 19:04:19 2015 @@ -242,23 +242,217 @@ serf__hpack_huffman_encode(const char *t /* ==================================================================== */ -typedef struct serf_hpack_item_t +typedef struct serf_hpack_entry_t { const char *key; apr_size_t key_len; const char *value; apr_size_t value_len; - struct serf_hpack_item_t *next; - struct serf_hpack_item_t *prev; -} serf_hpack_item_t; + struct serf_hpack_entry_t *next; + struct serf_hpack_entry_t *prev; + + char free_key; /* Key must be freed */ + char free_val; /* Value must be freed */ +} serf_hpack_entry_t; + +static void hpack_free_entry(serf_hpack_entry_t *entry, + serf_bucket_alloc_t *alloc) +{ + if (entry->free_key) + serf_bucket_mem_free(alloc, (char*)entry->key); + if (entry->free_val) + serf_bucket_mem_free(alloc, (char*)entry->value); + serf_bucket_mem_free(alloc, entry); +} + +/* https://tools.ietf.org/html/rfc7541#section-4.1 + + The size of an entry is the sum of its name's length in octets (as + defined in Section 5.2), its value's length in octets, and 32. + + The size of an entry is calculated using the length of its name and + value without any Huffman encoding applied. +*/ +#define HPACK_ENTRY_SIZE(hi) (hi->key_len + hi->value_len + 32) + +struct serf_hpack_table_t +{ + apr_pool_t *pool; + serf_bucket_alloc_t *alloc; + + char lowercase_keys; + char send_tablesize_update; + + /* The local -> remote 'encoder' list */ + serf_hpack_entry_t *lr_first, *lr_last, *lr_start; + unsigned int lr_count; /* Number of items (first..last) */ + unsigned int lr_indexable; /* Number of items (start..last) */ + unsigned int lr_size; /* 'Bytes' in list, calculated by HPACK_ENTRY_SIZE() */ + unsigned int lr_max_table_size; + + serf_hpack_entry_t *rl_first, *rl_last, *rl_start; + unsigned int rl_count; /* Number of items (first..last) */ + unsigned int rl_indexable; /* Number of items (start..last) */ + unsigned int rl_size; /* 'Bytes' in list, calculated by HPACK_ENTRY_SIZE() */ + unsigned int rl_max_table_size; +}; + +/* The staticly defined list of pre-encoded entries. All numbers above + this list are dynamically defined, so some new standard is needed to + extend this list */ +static const serf_hpack_entry_t hpack_static_table[] = +{ +#define HPACK_STR(x) x, (sizeof(x)-1) + {/* 1*/ HPACK_STR(":authority"), HPACK_STR("")}, + {/* 2*/ HPACK_STR(":method"), HPACK_STR("GET")}, + {/* 3*/ HPACK_STR(":method"), HPACK_STR("POST")}, + {/* 4*/ HPACK_STR(":path"), HPACK_STR("/")}, + {/* 5*/ HPACK_STR(":path"), HPACK_STR("/index.html") }, + {/* 6*/ HPACK_STR(":scheme"), HPACK_STR("http")}, + {/* 7*/ HPACK_STR(":scheme"), HPACK_STR("https")}, + {/* 8*/ HPACK_STR(":status"), HPACK_STR("200")}, + {/* 9*/ HPACK_STR(":status"), HPACK_STR("204")}, + {/*10*/ HPACK_STR(":status"), HPACK_STR("206")}, + {/*11*/ HPACK_STR(":status"), HPACK_STR("304")}, + {/*12*/ HPACK_STR(":status"), HPACK_STR("400")}, + {/*13*/ HPACK_STR(":status"), HPACK_STR("404")}, + {/*14*/ HPACK_STR(":status"), HPACK_STR("500")}, + {/*15*/ HPACK_STR("accept-charset"), HPACK_STR("")}, + {/*16*/ HPACK_STR("accept-encoding"), HPACK_STR("gzip, deflate")}, + {/*17*/ HPACK_STR("accept-language"), HPACK_STR("")}, + {/*18*/ HPACK_STR("accept-ranges"), HPACK_STR("")}, + {/*19*/ HPACK_STR("accept"), HPACK_STR("")}, + {/*20*/ HPACK_STR("access-control-allow-origin"),HPACK_STR("")}, + {/*21*/ HPACK_STR("age"), HPACK_STR("")}, + {/*22*/ HPACK_STR("allow"), HPACK_STR("")}, + {/*23*/ HPACK_STR("authorization"), HPACK_STR("")}, + {/*24*/ HPACK_STR("cache-control"), HPACK_STR("")}, + {/*25*/ HPACK_STR("content-disposition"), HPACK_STR("")}, + {/*26*/ HPACK_STR("content-encoding"), HPACK_STR("")}, + {/*27*/ HPACK_STR("content-language"), HPACK_STR("")}, + {/*28*/ HPACK_STR("content-length"), HPACK_STR("")}, + {/*29*/ HPACK_STR("content-location"), HPACK_STR("")}, + {/*30*/ HPACK_STR("content-range"), HPACK_STR("")}, + {/*31*/ HPACK_STR("content-type"), HPACK_STR("")}, + {/*32*/ HPACK_STR("cookie"), HPACK_STR("")}, + {/*33*/ HPACK_STR("date"), HPACK_STR("")}, + {/*34*/ HPACK_STR("etag"), HPACK_STR("")}, + {/*35*/ HPACK_STR("expect"), HPACK_STR("")}, + {/*36*/ HPACK_STR("expires"), HPACK_STR("")}, + {/*37*/ HPACK_STR("from"), HPACK_STR("")}, + {/*38*/ HPACK_STR("host"), HPACK_STR("")}, + {/*39*/ HPACK_STR("if-match"), HPACK_STR("")}, + {/*40*/ HPACK_STR("if-modified-since"), HPACK_STR("")}, + {/*41*/ HPACK_STR("if-none-match"), HPACK_STR("")}, + {/*42*/ HPACK_STR("if-range"), HPACK_STR("")}, + {/*43*/ HPACK_STR("if-unmodified-since"), HPACK_STR("")}, + {/*44*/ HPACK_STR("last-modified"), HPACK_STR("")}, + {/*45*/ HPACK_STR("link"), HPACK_STR("")}, + {/*46*/ HPACK_STR("location"), HPACK_STR("")}, + {/*47*/ HPACK_STR("max-forwards"), HPACK_STR("")}, + {/*48*/ HPACK_STR("proxy-authenticate"), HPACK_STR("")}, + {/*49*/ HPACK_STR("proxy-authorization"), HPACK_STR("")}, + {/*50*/ HPACK_STR("range"), HPACK_STR("")}, + {/*51*/ HPACK_STR("referer"), HPACK_STR("")}, + {/*52*/ HPACK_STR("refresh"), HPACK_STR("")}, + {/*53*/ HPACK_STR("retry-after"), HPACK_STR("")}, + {/*54*/ HPACK_STR("server"), HPACK_STR("")}, + {/*55*/ HPACK_STR("set-cookie"), HPACK_STR("")}, + {/*56*/ HPACK_STR("strict-transport-security"), HPACK_STR("")}, + {/*57*/ HPACK_STR("transfer-encoding"), HPACK_STR("")}, + {/*58*/ HPACK_STR("user-agent"), HPACK_STR("")}, + {/*59*/ HPACK_STR("vary"), HPACK_STR("")}, + {/*60*/ HPACK_STR("via"), HPACK_STR("")}, + {/*61*/ HPACK_STR("www-authenticate"), HPACK_STR("")} +#undef HPACK_STR +}; +static const apr_uint64_t hpack_static_table_count = + (sizeof(hpack_static_table) / sizeof(hpack_static_table[0])); + + +serf_hpack_table_t * +serf_hpack_table_create(int for_http2, + apr_size_t default_max_table_size, + apr_pool_t *result_pool) +{ + serf_hpack_table_t *tbl = apr_pcalloc(result_pool, sizeof(*tbl)); + + tbl->pool = result_pool; + tbl->alloc = serf_bucket_allocator_create(result_pool, NULL, NULL); + + tbl->lr_max_table_size = default_max_table_size; + tbl->rl_max_table_size = default_max_table_size; + + tbl->lowercase_keys = FALSE; + tbl->send_tablesize_update = FALSE; + + if (for_http2) + { + /* HTTP2 (aka RFC7540) has some additional rules on how it uses HPACK + (aka RFC7541), most notably that all header keys *MUST* be lowercase. + + Let's keep this thing generic and keep this as a configuration knob. + */ + tbl->lowercase_keys = TRUE; + } + + return tbl; +} + +static apr_status_t +hpack_table_get(apr_uint64_t v, + serf_hpack_table_t *tbl, + const char **key, + apr_size_t *key_size, + const char **value, + apr_size_t *value_size) +{ + const serf_hpack_entry_t *entry = NULL; + if (v == 0) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + v--; + if (v < hpack_static_table_count) + entry = &hpack_static_table[v]; + else + { + serf_hpack_entry_t *i; + v -= sizeof(hpack_static_table) / sizeof(hpack_static_table[0]); + + for (i = tbl->rl_start; i; i = i->next) + { + if (!v) + { + entry = i; + break; + } + v--; + } + } + + if (!entry) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + if (key) + *key = entry->key; + if (key_size) + *key_size = entry->key_len; + if (value) + *value = entry->value; + if (value_size) + *value_size = entry->value_len; + + return APR_SUCCESS; +} typedef struct serf_hpack_context_t { serf_bucket_alloc_t *alloc; + serf_hpack_table_t *tbl; - serf_hpack_item_t *first; - serf_hpack_item_t *last; + serf_hpack_entry_t *first; + serf_hpack_entry_t *last; } serf_hpack_context_t; static apr_status_t @@ -268,6 +462,7 @@ hpack_copy_from_headers(void *baton, { serf_bucket_t *hpack = baton; + /* TODO: Others? */ if (!strcasecmp(key, "Host") || !strcasecmp(key, "Connection") || !strncasecmp(key, "Connection-", 11)) @@ -314,11 +509,11 @@ serf__bucket_hpack_create_from_request(s serf_bucket_t * serf_bucket_hpack_create(serf_hpack_table_t *hpack_table, serf_bucket_alloc_t *allocator) - { serf_hpack_context_t *ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); ctx->alloc = allocator; + ctx->tbl = hpack_table; ctx->first = ctx->last = NULL; return serf_bucket_create(&serf_bucket_type_hpack, allocator, ctx); @@ -336,18 +531,19 @@ void serf_bucket_hpack_setc(serf_bucket_ void serf_bucket_hpack_setx(serf_bucket_t *hpack_bucket, const char *key, apr_size_t key_size, - int header_copy, + int key_copy, const char *value, apr_size_t value_size, int value_copy) { serf_hpack_context_t *ctx = hpack_bucket->data; - serf_hpack_item_t *hi; + serf_hpack_entry_t *entry; + apr_size_t i; - for (hi = ctx->first; hi; hi = hi->next) + for (entry = ctx->first; entry; entry = entry->next) { - if (key_size == hi->key_len - && !strncasecmp(key, hi->key, key_size)) + if (key_size == entry->key_len + && !strncasecmp(key, entry->key, key_size)) { break; } @@ -355,53 +551,90 @@ void serf_bucket_hpack_setx(serf_bucket_ /* TODO: Handle *_copy by keeping some flags */ - if (hi && value[0] == ':') + if (entry && value[0] == ':') { - serf_bucket_mem_free(ctx->alloc, (void*)hi->value); - hi->value = serf_bstrmemdup(ctx->alloc, value, value_size); - hi->value_len = value_size; + if (entry->free_val) + serf_bucket_mem_free(ctx->alloc, (void*)entry->value); + + entry->value = serf_bstrmemdup(ctx->alloc, value, value_size); + entry->value_len = value_size; + entry->free_val = TRUE; return; } - else if (hi) + else if (entry) { /* We probably want to allow duplicate *and* join behavior? */ } - hi = serf_bucket_mem_alloc(ctx->alloc, sizeof(*hi)); + entry = serf_bucket_mem_calloc(ctx->alloc, sizeof(*entry)); + + if (ctx->tbl && ctx->tbl->lowercase_keys) + { + /* https://tools.ietf.org/html/rfc7540#section-8.1.2 + Just as in HTTP/1.x, header field names are strings of ASCII + characters that are compared in a case-insensitive fashion. However, + header field names MUST be converted to lowercase prior to their + encoding in HTTP/2. A request or response containing uppercase + header field names MUST be treated as malformed (Section 8.1.2.6). */ - /* Convert keys to lower case as in RFC? Or keep case for - 1.1 like compatibility */ + char *ckey = serf_bstrmemdup(ctx->alloc, key, key_size); + for (i = 0; i < key_size; i++) + { + if (ckey[i] >= 'A' && key[i] <= 'Z') + ckey[i] += ('a' - 'A'); + } + entry->key = ckey; + entry->free_key = TRUE; + } + else if (!key_copy) + { + entry->key = key; + entry->free_key = FALSE; + } + else + { + entry->key = serf_bstrmemdup(ctx->alloc, key, key_size); + entry->free_key = TRUE; + } - hi->key = serf_bstrmemdup(ctx->alloc, key, key_size); - hi->key_len = key_size; - hi->value = serf_bstrmemdup(ctx->alloc, value, value_size); - hi->value_len = value_size; + entry->key_len = key_size; + if (value_copy) + { + entry->value = serf_bstrmemdup(ctx->alloc, value, value_size); + entry->free_val = TRUE; + } + else + { + entry->value = value; + entry->free_val = FALSE; + } + entry->value_len = value_size; - hi->prev = ctx->last; - hi->next = NULL; + entry->prev = ctx->last; + entry->next = NULL; if (ctx->last) { - ctx->last->next = hi; - ctx->last = hi; + ctx->last->next = entry; + ctx->last = entry; } else - ctx->first = ctx->last = hi; + ctx->first = ctx->last = entry; } const char *serf_bucket_hpack_getc(serf_bucket_t *hpack_bucket, const char *key) { serf_hpack_context_t *ctx = hpack_bucket->data; - serf_hpack_item_t *hi; + serf_hpack_entry_t *entry; apr_size_t key_len = strlen(key); - for (hi = ctx->first; hi; hi = hi->next) + for (entry = ctx->first; entry; entry = entry->next) { - if (key_len == hi->key_len - && !strncasecmp(key, hi->key, key_len)) + if (key_len == entry->key_len + && !strncasecmp(key, entry->key, key_len)) { - return hi->value; + return entry->value; } } @@ -413,11 +646,11 @@ void serf_bucket_hpack_do(serf_bucket_t void *baton) { serf_hpack_context_t *ctx = hpack_bucket->data; - serf_hpack_item_t *hi; + serf_hpack_entry_t *entry; - for (hi = ctx->first; hi; hi = hi->next) + for (entry = ctx->first; entry; entry = entry->next) { - if (func(baton, hi->key, hi->key_len, hi->value, hi->value_len)) + if (func(baton, entry->key, entry->key_len, entry->value, entry->value_len)) break; } } @@ -465,18 +698,19 @@ serialize(serf_bucket_t *bucket) serf_hpack_context_t *ctx = bucket->data; serf_bucket_alloc_t *alloc = ctx->alloc; - serf_hpack_item_t *hi; - serf_hpack_item_t *next; + serf_hpack_entry_t *entry; + serf_hpack_entry_t *next; /* Put on our aggregate bucket cloak */ serf_bucket_aggregate_become(bucket); - for (hi = ctx->first; hi; hi = next) + for (entry = ctx->first; entry; entry = next) { char intbuf[10]; apr_size_t intbuf_len; + serf_bucket_t *v; - next = hi->next; + next = entry->next; /* Literal header, no indexing (=has a name) */ hpack_int(0x40, 6, 0, intbuf, &intbuf_len); @@ -485,23 +719,35 @@ serialize(serf_bucket_t *bucket) serf_bucket_simple_copy_create(intbuf, intbuf_len, alloc)); /* Name is literal, no huffman encoding */ - hpack_int(0, 7, hi->key_len, intbuf, &intbuf_len); + hpack_int(0, 7, entry->key_len, intbuf, &intbuf_len); serf_bucket_aggregate_append(bucket, serf_bucket_simple_copy_create(intbuf, intbuf_len, alloc)); - serf_bucket_aggregate_append(bucket, - serf_bucket_simple_own_create(hi->key, hi->key_len, alloc)); + if (entry->free_key) + v = serf_bucket_simple_own_create(entry->key, entry->key_len, + alloc); + else + v = serf_bucket_simple_create(entry->key, entry->key_len, NULL, NULL, + alloc); + + serf_bucket_aggregate_append(bucket, v); /* Value is literal, no huffman encoding */ - hpack_int(0, 7, hi->value_len, intbuf, &intbuf_len); + hpack_int(0, 7, entry->value_len, intbuf, &intbuf_len); serf_bucket_aggregate_append(bucket, serf_bucket_simple_copy_create(intbuf, intbuf_len, alloc)); - serf_bucket_aggregate_append(bucket, - serf_bucket_simple_own_create(hi->value, hi->value_len, alloc)); + if (entry->free_key) + v = serf_bucket_simple_own_create(entry->value, entry->value_len, + alloc); + else + v = serf_bucket_simple_create(entry->value, entry->value_len, NULL, + NULL, alloc); + + serf_bucket_aggregate_append(bucket, v); /* We handed ownership of key and value, so we only have to free item */ - serf_bucket_mem_free(alloc, hi); + serf_bucket_mem_free(alloc, entry); } ctx->first = ctx->last = NULL; @@ -521,7 +767,7 @@ serf_hpack_read(serf_bucket_t *bucket, if (status) return status; - return serf_bucket_read(bucket, requested, data, len); + return bucket->type->read(bucket, requested, data, len); } static apr_status_t @@ -536,7 +782,8 @@ serf_hpack_read_iovec(serf_bucket_t *buc if (status) return status; - return serf_bucket_read_iovec(bucket, requested, vecs_size, vecs, vecs_used); + return bucket->type->read_iovec(bucket, requested, vecs_size, vecs, + vecs_used); } static apr_status_t @@ -549,25 +796,21 @@ serf_hpack_peek(serf_bucket_t *bucket, if (status) return status; - return serf_bucket_peek(bucket, data, len); + return bucket->type->peek(bucket, data, len); } static void serf_hpack_destroy_and_data(serf_bucket_t *bucket) { serf_hpack_context_t *ctx = bucket->data; - serf_hpack_item_t *hi; - serf_hpack_item_t *next; + serf_hpack_entry_t *hi; + serf_hpack_entry_t *next; for (hi = ctx->first; hi; hi = next) { next = hi->next; - /* TODO: Implement conditional free */ - - serf_bucket_mem_free(ctx->alloc, (char*)hi->key); - serf_bucket_mem_free(ctx->alloc, (char*)hi->value); - serf_bucket_mem_free(ctx->alloc, hi); + hpack_free_entry(hi, ctx->alloc); } serf_default_destroy_and_data(bucket); @@ -587,3 +830,741 @@ const serf_bucket_type_t serf_bucket_typ serf_hpack_peek, serf_hpack_destroy_and_data, }; + +/* ==================================================================== */ + +typedef struct serf_hpack_decode_ctx_t +{ + serf_bucket_alloc_t *alloc; + serf_hpack_table_t *tbl; + + serf_bucket_t *stream; + apr_size_t max_entry_size; + + apr_status_t(*item_callback)(void *baton, + const char *key, + apr_size_t key_size, + const char *value, + apr_size_t value_size); + void *item_baton; + + char *buffer; + apr_size_t buffer_size; + apr_size_t buffer_used; + + const char *key; /* Allocated in tbl->alloc */ + apr_size_t key_size; + const char *val; /* Allocated in tbl->alloc */ + apr_size_t val_size; + char index_item; + char key_hm; + char val_hm; + apr_uint64_t reuse_item; + + enum + { + HPACK_DECODE_STATE_KIND = 0, + HPACK_DECODE_STATE_INDEX, + HPACK_DECODE_STATE_KEYINDEX, + HPACK_DECODE_STATE_KEY_LEN, + HPACK_DECODE_STATE_KEY, + HPACK_DECODE_STATE_VALUE_LEN, + HPACK_DECODE_STATE_VALUE, + HPACK_DECODE_TABLESIZE_UPDATE + } state; + + /* When producing HTTP/1.1 style output */ + serf_bucket_t *agg; + serf_databuf_t databuf; + + char wrote_header; + char hit_eof; +} serf_hpack_decode_ctx_t; + +/* Forward definition */ +static apr_status_t +hpack_decode_databuf_reader(void *baton, + apr_size_t bufsize, + char *buf, + apr_size_t *len); + +serf_bucket_t * +serf_bucket_hpack_decode_create(serf_bucket_t *stream, + apr_status_t (*item_callback)( + void *baton, + const char *key, + apr_size_t key_size, + const char *value, + apr_size_t value_size), + void *item_baton, + apr_size_t max_entry_size, + serf_hpack_table_t *hpack_table, + serf_bucket_alloc_t *alloc) +{ + serf_hpack_decode_ctx_t *ctx = serf_bucket_mem_calloc(alloc, sizeof(*ctx)); + + ctx->alloc = alloc; + ctx->tbl = hpack_table; + ctx->stream = stream; + ctx->max_entry_size = max_entry_size; + + ctx->buffer_size = max_entry_size + 16; + ctx->buffer_used = 0; + ctx->buffer = serf_bucket_mem_alloc(alloc, ctx->buffer_size); + + if (item_callback) + { + ctx->item_callback = item_callback; + ctx->item_baton = item_baton; + ctx->agg = NULL; + } + else + { + ctx->item_callback = NULL; + ctx->item_baton = NULL; + ctx->agg = serf_bucket_aggregate_create(alloc); + + serf_databuf_init(&ctx->databuf); + ctx->databuf.read = hpack_decode_databuf_reader; + ctx->databuf.read_baton = ctx; + } + + /* Prepare TBL for decoding */ + ctx->tbl->rl_start = ctx->tbl->rl_first; + ctx->tbl->rl_indexable = ctx->tbl->rl_count; + + return serf_bucket_create(&serf_bucket_type_hpack_decode, alloc, ctx); +} + +static apr_status_t +read_hpack_int(apr_uint64_t *v, + unsigned char *flags, + serf_bucket_t *bucket, + int bits) +{ + serf_hpack_decode_ctx_t *ctx = bucket->data; + apr_status_t status; + apr_uint16_t value_mask; + apr_uint64_t vv; + + if (!ctx->buffer_used) + { + const char *data; + apr_size_t len; + + status = serf_bucket_read(ctx->stream, 1, &data, &len); + if (status || len == 0) + return status ? status : APR_EAGAIN; + + ctx->buffer[0] = *data; + ctx->buffer_used++; + } + + value_mask = (1 << bits) - 1; + + if (((unsigned char)ctx->buffer[0] & value_mask) != value_mask) + { + /* Everything fits in the initial byte :-) */ + vv = ((unsigned char)ctx->buffer[0] & value_mask); + } + else + { + apr_size_t i; + do + { + const char *data; + apr_size_t len; + + /* We already have all the bits we can store */ + if ((7 * (ctx->buffer_used - 1) + bits) >= 64) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + status = serf_bucket_read(ctx->stream, 1, &data, &len); + if (status || len == 0) + return status ? status : APR_EAGAIN; + + ctx->buffer[ctx->buffer_used] = *data; + ctx->buffer_used++; + } + while (ctx->buffer[ctx->buffer_used - 1] & 0x80); + + /* Check if the value could have been stored more efficiently */ + if (ctx->buffer[ctx->buffer_used - 1] == 0) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + vv = value_mask; + + for (i = 1; i < ctx->buffer_used; i++) + vv += (apr_uint64_t)((unsigned char)ctx->buffer[i] & 0x7F) << (7 * (i - 1)); + } + + *v = vv; + + if (flags) + *flags = (((unsigned char)ctx->buffer[0]) & ~value_mask); + + ctx->buffer_used = 0; + + return APR_SUCCESS; +} + +static apr_status_t +handle_read_entry_and_clear(serf_hpack_decode_ctx_t *ctx) +{ + serf_hpack_table_t *tbl = ctx->tbl; + const char *keep_key = NULL; + const char *keep_val = NULL; + apr_status_t status; + char own_key; + char own_val; + + if (ctx->item_callback) + { + status = ctx->item_callback(ctx->item_baton, + ctx->key, ctx->key_size, + ctx->val, ctx->val_size); + + if (status) + return status; + } + else if (!ctx->wrote_header) + { + serf_bucket_t *b; + + if (ctx->key_size == 7 && !strcmp(ctx->key, ":status")) + { + ctx->wrote_header = TRUE; + + b = SERF_BUCKET_SIMPLE_STRING("HTTP/2.0 ", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = serf_bucket_simple_copy_create(ctx->val, ctx->val_size, ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = SERF_BUCKET_SIMPLE_STRING(" <http2>\r\n", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + } + else if (ctx->key_size && ctx->key[0] == ':') + { + /* Ignore all magic headers */ + } + else + { + /* Write some header with some status code first */ + ctx->wrote_header = TRUE; + + b = SERF_BUCKET_SIMPLE_STRING("HTTP/2.0 505 Missing ':status' header\r\n", + ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + /* And now the actual header */ + b = serf_bucket_simple_copy_create(ctx->key, ctx->key_size, ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = SERF_BUCKET_SIMPLE_STRING(": ", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = serf_bucket_simple_copy_create(ctx->val, ctx->val_size, ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = SERF_BUCKET_SIMPLE_STRING("\r\n", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + } + } + else if (ctx->key_size && ctx->key[0] != ':') + { + serf_bucket_t *b; + + /* Write header */ + b = serf_bucket_simple_copy_create(ctx->key, ctx->key_size, ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = SERF_BUCKET_SIMPLE_STRING(": ", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = serf_bucket_simple_copy_create(ctx->val, ctx->val_size, ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + + b = SERF_BUCKET_SIMPLE_STRING("\r\n", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + } + + if (ctx->reuse_item) + { + status = hpack_table_get(ctx->reuse_item, tbl, + &keep_key, NULL, + &keep_val, NULL); + } + + own_key = (ctx->key && ctx->key != keep_key); + own_val = (ctx->val && ctx->val != keep_val); + + if (ctx->index_item) + { + serf_hpack_entry_t *entry = serf_bucket_mem_calloc(tbl->alloc, + sizeof(*entry)); + + entry->key = own_key ? ctx->key : serf_bstrmemdup(tbl->alloc, ctx->key, + ctx->key_size); + entry->value = own_val ? ctx->val : serf_bstrmemdup(tbl->alloc, + ctx->val, + ctx->val_size); + entry->free_key = entry->free_val = TRUE; + entry->next = tbl->rl_first; + tbl->lr_first = entry; + tbl->lr_count++; + tbl->lr_size += HPACK_ENTRY_SIZE(entry); + if (entry->next) + entry->next->prev = entry; + + /* We don't update lr_start... that is the idea */ + } + else + { + if (own_key) + serf_bucket_mem_free(tbl->alloc, (void*)ctx->key); + if (own_val) + serf_bucket_mem_free(tbl->alloc, (void*)ctx->val); + } + return APR_SUCCESS; +} + +static apr_status_t +hpack_process(serf_bucket_t *bucket) +{ + serf_hpack_decode_ctx_t *ctx = bucket->data; + apr_status_t status = APR_SUCCESS; + + while (status == APR_SUCCESS) + { + switch (ctx->state) + { + case HPACK_DECODE_STATE_KIND: + { + unsigned char uc; + const char *data; + apr_size_t len; + + status = serf_bucket_read(ctx->stream, 1, &data, &len); + if (status || !len) + continue; + + ctx->key_hm = ctx->val_hm = FALSE; + ctx->reuse_item = 0; + + uc = *data; + if (uc & 0x80) + { + /* 6.1. Indexed Header Field Representation + https://tools.ietf.org/html/rfc7541#section-6.1 */ + + ctx->state = HPACK_DECODE_STATE_INDEX; + ctx->buffer[0] = *data; + ctx->buffer_used = 1; + ctx->index_item = FALSE; + } + else if (uc == 0x40 || uc == 0x00) + { + /* 0x40: Literal Header Field with Incremental Indexing + -- New Name + https://tools.ietf.org/html/rfc7541#section-6.2.1 + 0x00: Literal Header Field without Indexing + https://tools.ietf.org/html/rfc7541#section-6.2.2 */ + + ctx->state = HPACK_DECODE_STATE_KEY_LEN; + ctx->buffer_used = 0; + ctx->index_item = (uc == 0x40); + } + else if ((uc & 0x60) == 0x20) + { + /* 6.3. Dynamic Table Size Update + https://tools.ietf.org/html/rfc7541#section-6.3 */ + ctx->state = HPACK_DECODE_TABLESIZE_UPDATE; + ctx->buffer[0] = *data; + ctx->buffer_used = 1; + } + else + { + /* 6.2.1 Literal Header Field with Incremental Indexing + -- Indexed Name + https://tools.ietf.org/html/rfc7541#section-6.2.1 + 6.2.3. Literal Header Field Never Indexed + https://tools.ietf.org/html/rfc7541#section-6.2.3 */ + + ctx->state = HPACK_DECODE_STATE_KEYINDEX; + ctx->buffer[0] = *data; + ctx->buffer_used = 1; + ctx->index_item = (uc & 0x40) != 0; + } + continue; + } + case HPACK_DECODE_STATE_INDEX: + { + apr_uint64_t v; + status = read_hpack_int(&v, NULL, bucket, 7); + if (status) + continue; + if (v == 0) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + ctx->reuse_item = v; + status = hpack_table_get(v, ctx->tbl, + &ctx->key, &ctx->key_size, + &ctx->val, &ctx->val_size); + if (status) + continue; + + status = handle_read_entry_and_clear(ctx); + if (status) + return status; + + /* Get key and value from table and handle result */ + ctx->state = HPACK_DECODE_STATE_KIND; + continue; + } + case HPACK_DECODE_STATE_KEYINDEX: + { + apr_uint64_t v; + status = read_hpack_int(&v, NULL, bucket, + ctx->index_item ? 6 : 4); + if (status) + continue; + + ctx->reuse_item = v; + status = hpack_table_get(v, ctx->tbl, + &ctx->key, &ctx->key_size, + NULL, NULL); + if (status) + continue; + + /* Get key from table */ + ctx->state = HPACK_DECODE_STATE_VALUE_LEN; + continue; + } + case HPACK_DECODE_STATE_KEY_LEN: + { + apr_uint64_t v; + unsigned char flags; + status = read_hpack_int(&v, &flags, bucket, 7); + if (status) + continue; + + ctx->key_hm = (flags & 0x80) != 0; + + if (v > ctx->max_entry_size) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + ctx->key_size = (apr_size_t)v; + ctx->state = HPACK_DECODE_STATE_KEY; + /* Fall through */ + } + case HPACK_DECODE_STATE_KEY: + { + const char *data; + apr_size_t len; + + if (ctx->key_size > 0) + { + status = serf_bucket_read(ctx->stream, + ctx->key_size - ctx->buffer_used, + &data, &len); + + if (!SERF_BUCKET_READ_ERROR(status)) + { + memcpy(&ctx->buffer[ctx->buffer_used], + data, len); + ctx->buffer_used += len; + } + else + continue; + } + else + status = APR_SUCCESS; + + if (ctx->buffer_used < ctx->key_size) + return status ? status : APR_EAGAIN; + + if (ctx->key_hm) + { + apr_size_t ks; + apr_status_t status2; + char *key; + + status2 = serf__hpack_huffman_decode((void*)ctx->buffer, + ctx->key_size, + 0, NULL, &ks); + + if (status2) + return status2; + + if (ks > ctx->max_entry_size) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + key = serf_bucket_mem_alloc(ctx->tbl->alloc, ks + 1); + + status2 = serf__hpack_huffman_decode((void*)ctx->buffer, + ctx->key_size, + ks + 1, key, + &ctx->key_size); + if (status2) + return status2; + + ctx->key = key; + } + else + ctx->key = serf_bstrmemdup(ctx->tbl->alloc, ctx->buffer, + ctx->key_size); + + ctx->buffer_used = 0; + ctx->state = HPACK_DECODE_STATE_VALUE_LEN; + /* Fall through */ + } + case HPACK_DECODE_STATE_VALUE_LEN: + { + apr_uint64_t v; + unsigned char flags; + status = read_hpack_int(&v, &flags, bucket, 7); + if (status) + continue; + + ctx->val_hm = (flags & 0x80) != 0; + + if (v > ctx->max_entry_size + || (v + ctx->key_size) > ctx->max_entry_size) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + ctx->val_size = (apr_size_t)v; + ctx->state = HPACK_DECODE_STATE_VALUE; + /* Fall through */ + } + case HPACK_DECODE_STATE_VALUE: + { + const char *data; + apr_size_t len; + + if (ctx->val_size > 0) + { + status = serf_bucket_read(ctx->stream, + ctx->val_size - ctx->buffer_used, + &data, &len); + + if (!SERF_BUCKET_READ_ERROR(status)) + { + memcpy(&ctx->buffer[ctx->buffer_used], + data, len); + ctx->buffer_used += len; + } + else + continue; + } + else + status = APR_SUCCESS; + + if (ctx->buffer_used < ctx->val_size) + return status ? status : APR_EAGAIN; + + if (ctx->val_hm) + { + apr_size_t ks; + apr_status_t status2; + char *val; + + status2 = serf__hpack_huffman_decode((void*)ctx->buffer, + ctx->val_size, + 0, NULL, &ks); + + if (status2) + return status2; + + if (ks > ctx->max_entry_size) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + val = serf_bucket_mem_alloc(ctx->tbl->alloc, ks + 1); + + status2 = serf__hpack_huffman_decode((void*)ctx->buffer, + ctx->val_size, + ks + 1, val, + &ctx->val_size); + if (status2) + return status2; + + ctx->val = val; + } + else + ctx->val = serf_bstrmemdup(ctx->tbl->alloc, ctx->buffer, + ctx->val_size); + + + status = handle_read_entry_and_clear(ctx); + if (status) + continue; + + ctx->buffer_used = 0; + ctx->state = HPACK_DECODE_STATE_KIND; + continue; + } + case HPACK_DECODE_TABLESIZE_UPDATE: + { + apr_uint64_t v; + + status = read_hpack_int(&v, NULL, bucket, 5); + if (status) + continue; + + /* TODO: Store max size + Verify if it is in allowed range, etc. + The current code works, until we announce that we support + a bigger table size. We might store too much data though + */ + ctx->state = HPACK_DECODE_STATE_KIND; + continue; + } + default: + abort(); + } + } + + if (APR_STATUS_IS_EOF(status)) + { + serf_bucket_t *b; + if (ctx->state != HPACK_DECODE_STATE_KIND) + return SERF_ERROR_HTTP2_COMPRESSION_ERROR; + + if (!ctx->hit_eof) + { + ctx->hit_eof = TRUE; + + if (!ctx->item_callback) + { + /* Write the final "\r\n" for http/1.1 compatibility */ + b = SERF_BUCKET_SIMPLE_STRING("\r\n", ctx->alloc); + serf_bucket_aggregate_append(ctx->agg, b); + } + } + } + + return status; +} + +static apr_status_t +hpack_decode_databuf_reader(void *baton, + apr_size_t bufsize, + char *buf, + apr_size_t *len) +{ + serf_hpack_decode_ctx_t *ctx = baton; + apr_status_t status; + const char *data; + + status = serf_bucket_read(ctx->agg, bufsize, &data, len); + + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + if (*len) + memcpy(buf, data, *len); + + if (APR_STATUS_IS_EOF(status) && ctx->hit_eof) + return APR_EOF; + else + return APR_EAGAIN; +} + +static apr_status_t +serf_hpack_decode_read(serf_bucket_t *bucket, + apr_size_t requested, + const char **data, + apr_size_t *len) +{ + serf_hpack_decode_ctx_t *ctx = bucket->data; + apr_status_t status; + + status = hpack_process(bucket); + if (SERF_BUCKET_READ_ERROR(status)) + { + *len = 0; + return status; + } + + if (ctx->agg) + status = serf_databuf_read(&ctx->databuf, + requested, data, len); + else + *len = 0; + + return status; +} + +static apr_status_t +serf_hpack_decode_readline(serf_bucket_t *bucket, + int acceptable, + int *found, + const char **data, + apr_size_t *len) +{ + serf_hpack_decode_ctx_t *ctx = bucket->data; + apr_status_t status; + + status = hpack_process(bucket); + if (SERF_BUCKET_READ_ERROR(status)) + { + *len = 0; + return status; + } + + if (ctx->agg) + status = serf_databuf_readline(&ctx->databuf, acceptable, + found, data, len); + else + *len = 0; + + return status; +} + +static apr_status_t +serf_hpack_decode_peek(serf_bucket_t *bucket, + const char **data, + apr_size_t *len) +{ + serf_hpack_decode_ctx_t *ctx = bucket->data; + apr_status_t status; + + status = hpack_process(bucket); + if (SERF_BUCKET_READ_ERROR(status)) + { + *len = 0; + return status; + } + + if (ctx->agg) + status = serf_databuf_peek(&ctx->databuf, data, len); + + return status; +} + +static void +serf_hpack_decode_destroy(serf_bucket_t *bucket) +{ + serf_hpack_decode_ctx_t *ctx = bucket->data; + serf_bucket_destroy(ctx->stream); + + if (ctx->agg) + serf_bucket_destroy(ctx->agg); + + /* Key and value are handled by table. If we fail reading + table can't be used anyway, so the allocator cleanup will + handle the leak */ + + serf_default_destroy_and_data(bucket); +} + +const serf_bucket_type_t serf_bucket_type_hpack_decode = { + "HPACK-DECODE", + serf_hpack_decode_read, + serf_hpack_decode_readline, + serf_default_read_iovec, + serf_default_read_for_sendfile, + serf_default_read_bucket, + serf_hpack_decode_peek, + serf_hpack_decode_destroy +}; \ No newline at end of file Modified: serf/trunk/serf_bucket_types.h URL: http://svn.apache.org/viewvc/serf/trunk/serf_bucket_types.h?rev=1710678&r1=1710677&r2=1710678&view=diff ============================================================================== --- serf/trunk/serf_bucket_types.h (original) +++ serf/trunk/serf_bucket_types.h Mon Oct 26 19:04:19 2015 @@ -860,6 +860,35 @@ void serf_bucket_hpack_do(serf_bucket_t serf_bucket_hpack_do_callback_fn_t func, void *baton); +serf_hpack_table_t * +serf_hpack_table_create(int for_http2, + apr_size_t default_max_table_size, + apr_pool_t *result_pool); + +/* ==================================================================== */ +extern const serf_bucket_type_t serf_bucket_type_hpack_decode; +#define SERF_BUCKET_IS_HPACK_DECODE(b) SERF_BUCKET_CHECK((b), hpack_decode) + +/* If ITEM_CALLBACK is not null calls it for every item while reading, and + the bucket will just return no data and APR_EAGAIN until done. + + If ITEM_CALLBACK is NULL, the bucket will read as a HTTP/1 like header block, + starting with a status line and ending with "\r\n\r\n", which allows using + the result as the start of the result for a response_bucket. + */ +serf_bucket_t * +serf_bucket_hpack_decode_create(serf_bucket_t *stream, + apr_status_t(*item_callback)( + void *baton, + const char *key, + apr_size_t key_size, + const char *value, + apr_size_t value_size), + void *item_baton, + apr_size_t max_entry_size, + serf_hpack_table_t *hpack_table, + serf_bucket_alloc_t *alloc); + /* ==================================================================== */ extern const serf_bucket_type_t serf_bucket_type_http2_frame;