From ff0d137fb33e4302c6be010ec10030218a743efb Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 12 Mar 2016 13:23:12 +0900 Subject: [PATCH] Reference counted HPACK name/value pair --- genlibtokenlookup.py | 2 +- lib/Makefile.am | 6 +- lib/includes/nghttp2/nghttp2.h | 47 +++ lib/nghttp2_hd.c | 518 ++++++++++++++------------------- lib/nghttp2_hd.h | 106 ++++--- lib/nghttp2_http.c | 87 +++--- lib/nghttp2_http.h | 5 +- lib/nghttp2_mem.c | 4 + lib/nghttp2_mem.h | 1 + lib/nghttp2_rcbuf.c | 98 +++++++ lib/nghttp2_rcbuf.h | 80 +++++ lib/nghttp2_session.c | 21 +- tests/nghttp2_hd_test.c | 12 +- 13 files changed, 557 insertions(+), 430 deletions(-) create mode 100644 lib/nghttp2_rcbuf.c create mode 100644 lib/nghttp2_rcbuf.h diff --git a/genlibtokenlookup.py b/genlibtokenlookup.py index 625e62d1..360d2198 100755 --- a/genlibtokenlookup.py +++ b/genlibtokenlookup.py @@ -162,7 +162,7 @@ def gen_enum(): def gen_index_header(): print '''\ -static inline int lookup_token(const uint8_t *name, size_t namelen) { +static inline int32_t lookup_token(const uint8_t *name, size_t namelen) { switch (namelen) {''' b = build_header(HEADERS) for size in sorted(b.keys()): diff --git a/lib/Makefile.am b/lib/Makefile.am index 3ea5bed2..17e86bfc 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -47,7 +47,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ nghttp2_option.c \ nghttp2_callbacks.c \ nghttp2_mem.c \ - nghttp2_http.c + nghttp2_http.c \ + nghttp2_rcbuf.c HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_frame.h \ @@ -61,7 +62,8 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_option.h \ nghttp2_callbacks.h \ nghttp2_mem.h \ - nghttp2_http.h + nghttp2_http.h \ + nghttp2_rcbuf.h libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) libnghttp2_la_LDFLAGS = -no-undefined \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index f6133055..2a3674fb 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -419,6 +419,53 @@ typedef enum { NGHTTP2_ERR_FLOODED = -904 } nghttp2_error; +/** + * @struct + * + * The object representing single contagious buffer. + */ +typedef struct { + /** + * The pointer to the buffer. + */ + uint8_t *base; + /** + * The length of the buffer. + */ + size_t len; +} nghttp2_vec; + +struct nghttp2_rcbuf; + +/** + * @struct + * + * The object representing reference counted buffer. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_rcbuf nghttp2_rcbuf; + +/** + * @function + * + * Increments the reference count of |rcbuf| by 1. + */ +void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf); + +/** + * @function + * + * Decrements the reference count of |rcbuf| by 1. If the reference + * count becomes zero, the object pointed by |rcbuf| will be freed. + * In this case, application must not use |rcbuf| again. + */ +void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf); + +/** + * Returns the underlying buffer managed by |rcbuf|. + */ +nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf); + /** * @enum * diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index c6c5b5f8..8da40064 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -34,15 +34,17 @@ /* Make scalar initialization form of nghttp2_hd_entry */ #define MAKE_STATIC_ENT(N, V, T, H) \ { \ - { (uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0 } \ - , NULL, 0, (H), (T), 1, NGHTTP2_HD_FLAG_NONE \ + { NULL, NULL, (uint8_t *)(N), sizeof((N)) - 1, -1 } \ + , {NULL, NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, \ + {(uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0}, \ + T, H \ } /* Generated by mkstatictbl.py */ /* 3rd parameter is nghttp2_token value for header field name. We use first enum value if same header names are repeated (e.g., :status). */ -static nghttp2_hd_entry static_table[] = { +static nghttp2_hd_static_entry static_table[] = { MAKE_STATIC_ENT(":authority", "", 0, 3153725150u), MAKE_STATIC_ENT(":method", "GET", 1, 695666056u), MAKE_STATIC_ENT(":method", "POST", 1, 695666056u), @@ -114,7 +116,7 @@ static int memeq(const void *s1, const void *s2, size_t n) { * This function was generated by genlibtokenlookup.py. Inspired by * h2o header lookup. https://github.com/h2o/h2o */ -static int lookup_token(const uint8_t *name, size_t namelen) { +static int32_t lookup_token(const uint8_t *name, size_t namelen) { switch (namelen) { case 2: switch (name[1]) { @@ -790,86 +792,33 @@ static int lookup_token(const uint8_t *name, size_t namelen) { return -1; } -int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name, - size_t namelen, uint8_t *value, size_t valuelen, - int token, nghttp2_mem *mem) { - int rv = 0; - - /* Since nghttp2_hd_entry is used for indexing, ent->nv.flags always - NGHTTP2_NV_FLAG_NONE */ - ent->nv.flags = NGHTTP2_NV_FLAG_NONE; - - if ((flags & NGHTTP2_HD_FLAG_NAME_ALLOC) && - (flags & NGHTTP2_HD_FLAG_NAME_GIFT) == 0) { - if (namelen == 0) { - flags = (uint8_t)(flags & ~NGHTTP2_HD_FLAG_NAME_ALLOC); - ent->nv.name = (uint8_t *)""; - } else { - /* name may not be NULL terminated on compression. */ - ent->nv.name = nghttp2_mem_malloc(mem, namelen + 1); - if (ent->nv.name == NULL) { - rv = NGHTTP2_ERR_NOMEM; - goto fail; - } - memcpy(ent->nv.name, name, namelen); - ent->nv.name[namelen] = '\0'; - } - } else { - ent->nv.name = name; - } - if ((flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) && - (flags & NGHTTP2_HD_FLAG_VALUE_GIFT) == 0) { - if (valuelen == 0) { - flags = (uint8_t)(flags & ~NGHTTP2_HD_FLAG_VALUE_ALLOC); - ent->nv.value = (uint8_t *)""; - } else { - /* value may not be NULL terminated on compression. */ - ent->nv.value = nghttp2_mem_malloc(mem, valuelen + 1); - if (ent->nv.value == NULL) { - rv = NGHTTP2_ERR_NOMEM; - goto fail2; - } - memcpy(ent->nv.value, value, valuelen); - ent->nv.value[valuelen] = '\0'; - } - } else { - ent->nv.value = value; - } - ent->nv.namelen = namelen; - ent->nv.valuelen = valuelen; - ent->token = token; - ent->ref = 1; - ent->flags = flags; +void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv) { + ent->nv = *nv; + ent->cnv.name = nv->name->base; + ent->cnv.namelen = nv->name->len; + ent->cnv.value = nv->value->base; + ent->cnv.valuelen = nv->value->len; + ent->cnv.flags = nv->flags; ent->next = NULL; ent->hash = 0; - return 0; - -fail2: - if ((flags & NGHTTP2_HD_FLAG_NAME_ALLOC) && - (flags & NGHTTP2_HD_FLAG_NAME_GIFT) == 0) { - nghttp2_mem_free(mem, ent->nv.name); - } -fail: - return rv; + nghttp2_rcbuf_incref(ent->nv.name); + nghttp2_rcbuf_incref(ent->nv.value); } -void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem) { - assert(ent->ref == 0); - if (ent->flags & NGHTTP2_HD_FLAG_NAME_ALLOC) { - nghttp2_mem_free(mem, ent->nv.name); - } - if (ent->flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) { - nghttp2_mem_free(mem, ent->nv.value); - } +void nghttp2_hd_entry_free(nghttp2_hd_entry *ent) { + nghttp2_rcbuf_decref(ent->nv.value); + nghttp2_rcbuf_decref(ent->nv.name); } -static int name_eq(const nghttp2_nv *a, const nghttp2_nv *b) { - return a->namelen == b->namelen && memeq(a->name, b->name, a->namelen); +static int name_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) { + return a->name->len == b->namelen && + memeq(a->name->base, b->name, b->namelen); } -static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) { - return a->valuelen == b->valuelen && memeq(a->value, b->value, a->valuelen); +static int value_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) { + return a->value->len == b->valuelen && + memeq(a->value->base, b->value, b->valuelen); } static uint32_t name_hash(const nghttp2_nv *nv) { @@ -905,7 +854,7 @@ static void hd_map_insert(nghttp2_hd_map *map, nghttp2_hd_entry *ent) { } static nghttp2_hd_entry *hd_map_find(nghttp2_hd_map *map, int *exact_match, - const nghttp2_nv *nv, int token, + const nghttp2_nv *nv, int32_t token, uint32_t hash) { nghttp2_hd_entry *p; nghttp2_hd_entry *res = NULL; @@ -913,7 +862,7 @@ static nghttp2_hd_entry *hd_map_find(nghttp2_hd_map *map, int *exact_match, *exact_match = 0; for (p = map->table[hash & (HD_MAP_SIZE - 1)]; p; p = p->next) { - if (token != p->token || + if (token != p->nv.token || (token == -1 && (hash != p->hash || !name_eq(&p->nv, nv)))) { continue; } @@ -1008,8 +957,8 @@ static void hd_ringbuf_free(nghttp2_hd_ringbuf *ringbuf, nghttp2_mem *mem) { } for (i = 0; i < ringbuf->len; ++i) { nghttp2_hd_entry *ent = hd_ringbuf_get(ringbuf, i); - --ent->ref; - nghttp2_hd_entry_free(ent, mem); + + nghttp2_hd_entry_free(ent); nghttp2_mem_free(mem, ent); } nghttp2_mem_free(mem, ringbuf->buffer); @@ -1098,7 +1047,6 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) { inflater->settings_hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; inflater->min_hd_table_bufsize_max = UINT32_MAX; - inflater->ent_keep = NULL; inflater->nv_name_keep = NULL; inflater->nv_value_keep = NULL; @@ -1108,6 +1056,9 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) { nghttp2_buf_init(&inflater->namebuf); nghttp2_buf_init(&inflater->valuebuf); + inflater->namercbuf = NULL; + inflater->valuercbuf = NULL; + inflater->huffman_encoded = 0; inflater->index = 0; inflater->left = 0; @@ -1122,21 +1073,10 @@ fail: } static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater) { - nghttp2_mem *mem; + nghttp2_rcbuf_decref(inflater->nv_value_keep); + nghttp2_rcbuf_decref(inflater->nv_name_keep); - mem = inflater->ctx.mem; - if (inflater->ent_keep) { - if (inflater->ent_keep->ref == 0) { - nghttp2_hd_entry_free(inflater->ent_keep, mem); - nghttp2_mem_free(mem, inflater->ent_keep); - } - inflater->ent_keep = NULL; - } - - nghttp2_mem_free(mem, inflater->nv_value_keep); inflater->nv_value_keep = NULL; - - nghttp2_mem_free(mem, inflater->nv_name_keep); inflater->nv_name_keep = NULL; } @@ -1145,13 +1085,11 @@ void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater) { } void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) { - nghttp2_mem *mem; - - mem = inflater->ctx.mem; - hd_inflate_keep_free(inflater); - nghttp2_buf_free(&inflater->valuebuf, mem); - nghttp2_buf_free(&inflater->namebuf, mem); + + nghttp2_rcbuf_decref(inflater->valuercbuf); + nghttp2_rcbuf_decref(inflater->namercbuf); + hd_context_free(&inflater->ctx); } @@ -1159,23 +1097,13 @@ static size_t entry_room(size_t namelen, size_t valuelen) { return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen; } -static int emit_indexed_header(nghttp2_nv *nv_out, int *token_out, - nghttp2_hd_entry *ent) { - DEBUGF(fprintf(stderr, "inflatehd: header emission: %s: %s\n", ent->nv.name, - ent->nv.value)); +static int emit_header(nghttp2_hd_nv *nv_out, nghttp2_hd_nv *nv) { + DEBUGF(fprintf(stderr, "inflatehd: header emission: %s: %s\n", nv->name->base, + nv->value->base)); /* ent->ref may be 0. This happens if the encoder emits literal block larger than header table capacity with indexing. */ - *nv_out = ent->nv; - *token_out = ent->token; - return 0; -} - -static int emit_literal_header(nghttp2_nv *nv_out, int *token_out, - nghttp2_nv *nv) { - DEBUGF(fprintf(stderr, "inflatehd: header emission: %s: %s\n", nv->name, - nv->value)); *nv_out = *nv; - *token_out = lookup_token(nv->name, nv->namelen); + return 0; } @@ -1483,17 +1411,16 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, return 0; } -static nghttp2_hd_entry * -add_hd_table_incremental(nghttp2_hd_context *context, const nghttp2_nv *nv, - int token, uint8_t entry_flags, nghttp2_hd_map *map, - uint32_t hash) { +static int add_hd_table_incremental(nghttp2_hd_context *context, + nghttp2_hd_nv *nv, nghttp2_hd_map *map, + uint32_t hash) { int rv; nghttp2_hd_entry *new_ent; size_t room; nghttp2_mem *mem; mem = context->mem; - room = entry_room(nv->namelen, nv->valuelen); + room = entry_room(nv->name->len, nv->value->len); while (context->hd_table_bufsize + room > context->hd_table_bufsize_max && context->hd_table.len > 0) { @@ -1501,72 +1428,53 @@ add_hd_table_incremental(nghttp2_hd_context *context, const nghttp2_nv *nv, size_t idx = context->hd_table.len - 1; nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); - context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); + context->hd_table_bufsize -= + entry_room(ent->nv.name->len, ent->nv.value->len); DEBUGF(fprintf(stderr, "hpack: remove item from header table: %s: %s\n", - ent->nv.name, ent->nv.value)); + (char *)ent->nv.name->base, (char *)ent->nv.value->base)); hd_ringbuf_pop_back(&context->hd_table); if (map) { hd_map_remove(map, ent); } - if (--ent->ref == 0) { - nghttp2_hd_entry_free(ent, mem); - nghttp2_mem_free(mem, ent); - } - } - new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry)); - if (new_ent == NULL) { - return NULL; - } - - rv = nghttp2_hd_entry_init(new_ent, entry_flags, nv->name, nv->namelen, - nv->value, nv->valuelen, token, mem); - if (rv != 0) { - nghttp2_mem_free(mem, new_ent); - return NULL; + nghttp2_hd_entry_free(ent); + nghttp2_mem_free(mem, ent); } if (room > context->hd_table_bufsize_max) { /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is - immediately evicted. */ - --new_ent->ref; - } else { - rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem); - - if (rv != 0) { - --new_ent->ref; - - if ((entry_flags & NGHTTP2_HD_FLAG_NAME_ALLOC) && - (entry_flags & NGHTTP2_HD_FLAG_NAME_GIFT)) { - /* nv->name are managed by caller. */ - new_ent->nv.name = NULL; - new_ent->nv.namelen = 0; - } - if ((entry_flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) && - (entry_flags & NGHTTP2_HD_FLAG_VALUE_GIFT)) { - /* nv->value are managed by caller. */ - new_ent->nv.value = NULL; - new_ent->nv.valuelen = 0; - } - - nghttp2_hd_entry_free(new_ent, mem); - nghttp2_mem_free(mem, new_ent); - - return NULL; - } - - new_ent->seq = context->next_seq++; - new_ent->hash = hash; - - if (map) { - hd_map_insert(map, new_ent); - } - - context->hd_table_bufsize += room; + immediately evicted. So we don't allocate memory for it. */ + return 0; } - return new_ent; + + new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry)); + if (new_ent == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_hd_entry_init(new_ent, nv); + + rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem); + + if (rv != 0) { + nghttp2_hd_entry_free(new_ent); + nghttp2_mem_free(mem, new_ent); + + return rv; + } + + new_ent->seq = context->next_seq++; + new_ent->hash = hash; + + if (map) { + hd_map_insert(map, new_ent); + } + + context->hd_table_bufsize += room; + + return 0; } typedef struct { @@ -1575,10 +1483,11 @@ typedef struct { uint8_t name_value_match; } search_result; -static search_result search_static_table(const nghttp2_nv *nv, int token, +static search_result search_static_table(const nghttp2_nv *nv, int32_t token, int indexing_mode) { search_result res = {token, 0}; int i; + nghttp2_hd_static_entry *ent; if (indexing_mode == NGHTTP2_HD_NEVER_INDEXING) { return res; @@ -1587,7 +1496,9 @@ static search_result search_static_table(const nghttp2_nv *nv, int token, for (i = token; i <= NGHTTP2_TOKEN_WWW_AUTHENTICATE && static_table[i].token == token; ++i) { - if (value_eq(&static_table[i].nv, nv)) { + ent = &static_table[i]; + if (ent->value.len == nv->valuelen && + memcmp(ent->value.base, nv->value, nv->valuelen) == 0) { res.index = i; res.name_value_match = 1; return res; @@ -1597,7 +1508,7 @@ static search_result search_static_table(const nghttp2_nv *nv, int token, } static search_result search_hd_table(nghttp2_hd_context *context, - const nghttp2_nv *nv, int token, + const nghttp2_nv *nv, int32_t token, int indexing_mode, nghttp2_hd_map *map, uint32_t hash) { search_result res = {-1, 0}; @@ -1641,15 +1552,15 @@ static void hd_context_shrink_table_size(nghttp2_hd_context *context, context->hd_table.len > 0) { size_t idx = context->hd_table.len - 1; nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); - context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); + context->hd_table_bufsize -= + entry_room(ent->nv.name->len, ent->nv.value->len); hd_ringbuf_pop_back(&context->hd_table); if (map) { hd_map_remove(map, ent); } - if (--ent->ref == 0) { - nghttp2_hd_entry_free(ent, mem); - nghttp2_mem_free(mem, ent); - } + + nghttp2_hd_entry_free(ent); + nghttp2_mem_free(mem, ent); } } @@ -1708,19 +1619,33 @@ static size_t get_max_index(nghttp2_hd_context *context) { return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH - 1; } -nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context, - size_t idx) { +nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t idx) { assert(INDEX_RANGE_VALID(context, idx)); if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { - return hd_ringbuf_get(&context->hd_table, - idx - NGHTTP2_STATIC_TABLE_LENGTH); + return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH) + ->nv; } else { - return &static_table[idx]; + nghttp2_hd_static_entry *ent; + + ent = &static_table[idx]; + return (nghttp2_hd_nv){&ent->name, &ent->value, ent->token, + NGHTTP2_NV_FLAG_NONE}; } } +static const nghttp2_nv *nghttp2_hd_table_get2(nghttp2_hd_context *context, + size_t idx) { + assert(INDEX_RANGE_VALID(context, idx)); + if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { + return &hd_ringbuf_get(&context->hd_table, + idx - NGHTTP2_STATIC_TABLE_LENGTH)->cnv; + } + + return &static_table[idx].cnv; +} + static int hd_deflate_decide_indexing(nghttp2_hd_deflater *deflater, - const nghttp2_nv *nv, int token) { + const nghttp2_nv *nv, int32_t token) { if (token == NGHTTP2_TOKEN__PATH || token == NGHTTP2_TOKEN_AGE || token == NGHTTP2_TOKEN_CONTENT_LENGTH || token == NGHTTP2_TOKEN_ETAG || token == NGHTTP2_TOKEN_IF_MODIFIED_SINCE || @@ -1740,7 +1665,7 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, search_result res; ssize_t idx; int indexing_mode; - int token; + int32_t token; nghttp2_mem *mem; uint32_t hash = 0; @@ -1789,28 +1714,36 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, } if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) { - nghttp2_hd_entry *new_ent; + nghttp2_hd_nv hd_nv; + if (idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) { - nghttp2_nv nv_indname; - nv_indname = *nv; - nv_indname.name = - nghttp2_hd_table_get(&deflater->ctx, (size_t)idx)->nv.name; - new_ent = add_hd_table_incremental(&deflater->ctx, &nv_indname, token, - NGHTTP2_HD_FLAG_VALUE_ALLOC, - &deflater->map, hash); + hd_nv.name = nghttp2_hd_table_get(&deflater->ctx, (size_t)idx).name; + nghttp2_rcbuf_incref(hd_nv.name); } else { - new_ent = add_hd_table_incremental(&deflater->ctx, nv, token, - NGHTTP2_HD_FLAG_NAME_ALLOC | - NGHTTP2_HD_FLAG_VALUE_ALLOC, - &deflater->map, hash); + rv = nghttp2_rcbuf_new2(&hd_nv.name, nv->name, nv->namelen, mem); + if (rv != 0) { + return rv; + } } - if (!new_ent) { + + rv = nghttp2_rcbuf_new2(&hd_nv.value, nv->value, nv->valuelen, mem); + + if (rv != 0) { + nghttp2_rcbuf_decref(hd_nv.name); + return rv; + } + + hd_nv.token = token; + hd_nv.flags = NGHTTP2_NV_FLAG_NONE; + + rv = add_hd_table_incremental(&deflater->ctx, &hd_nv, &deflater->map, hash); + + nghttp2_rcbuf_decref(hd_nv.value); + nghttp2_rcbuf_decref(hd_nv.name); + + if (rv != 0) { return NGHTTP2_ERR_HEADER_COMP; } - if (new_ent->ref == 0) { - nghttp2_hd_entry_free(new_ent, mem); - nghttp2_mem_free(mem, new_ent); - } } if (idx == -1) { rv = emit_newname_block(bufs, nv, indexing_mode); @@ -2093,10 +2026,10 @@ static ssize_t hd_inflate_read(nghttp2_hd_inflater *inflater, nghttp2_buf *buf, * Out of memory */ static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater, - nghttp2_nv *nv_out, int *token_out) { - nghttp2_hd_entry *ent = nghttp2_hd_table_get(&inflater->ctx, inflater->index); + nghttp2_hd_nv *nv_out) { + nghttp2_hd_nv nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index); - emit_indexed_header(nv_out, token_out, ent); + emit_header(nv_out, &nv); return 0; } @@ -2113,19 +2046,9 @@ static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater, * Out of memory */ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater, - nghttp2_nv *nv_out, int *token_out) { - nghttp2_nv nv; - nghttp2_mem *mem; - - mem = inflater->ctx.mem; - - nv.name = inflater->namebuf.pos; - nv.namelen = nghttp2_buf_len(&inflater->namebuf); - nv.value = inflater->valuebuf.pos; - nv.valuelen = nghttp2_buf_len(&inflater->valuebuf); - - nghttp2_buf_init(&inflater->valuebuf); - nghttp2_buf_init(&inflater->namebuf); + nghttp2_hd_nv *nv_out) { + nghttp2_hd_nv nv; + int rv; if (inflater->no_index) { nv.flags = NGHTTP2_NV_FLAG_NO_INDEX; @@ -2133,35 +2056,26 @@ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater, nv.flags = NGHTTP2_NV_FLAG_NONE; } + nv.name = inflater->namercbuf; + nv.value = inflater->valuercbuf; + nv.token = lookup_token(inflater->namercbuf->base, inflater->namercbuf->len); + if (inflater->index_required) { - nghttp2_hd_entry *new_ent; - uint8_t ent_flags; + rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0); - ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_NAME_GIFT | - NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT; - - new_ent = add_hd_table_incremental(&inflater->ctx, &nv, - lookup_token(nv.name, nv.namelen), - ent_flags, NULL, 0); - - if (new_ent) { - emit_indexed_header(nv_out, token_out, new_ent); - inflater->ent_keep = new_ent; - - return 0; + if (rv != 0) { + return rv; } - - nghttp2_mem_free(mem, nv.value); - nghttp2_mem_free(mem, nv.name); - - return NGHTTP2_ERR_NOMEM; } - emit_literal_header(nv_out, token_out, &nv); + emit_header(nv_out, &nv); inflater->nv_name_keep = nv.name; inflater->nv_value_keep = nv.value; + inflater->namercbuf = NULL; + inflater->valuercbuf = NULL; + return 0; } @@ -2177,12 +2091,11 @@ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater, * Out of memory */ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, - nghttp2_nv *nv_out, int *token_out) { - nghttp2_nv nv; - nghttp2_hd_entry *ent_name; - nghttp2_mem *mem; + nghttp2_hd_nv *nv_out) { + nghttp2_hd_nv nv; + int rv; - mem = inflater->ctx.mem; + nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index); if (inflater->no_index) { nv.flags = NGHTTP2_NV_FLAG_NO_INDEX; @@ -2190,72 +2103,57 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, nv.flags = NGHTTP2_NV_FLAG_NONE; } - ent_name = nghttp2_hd_table_get(&inflater->ctx, inflater->index); + nghttp2_rcbuf_incref(nv.name); + + nv.value = inflater->valuercbuf; if (inflater->index_required) { - nghttp2_hd_entry *new_ent; - uint8_t ent_flags; - - ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT; - - if (inflater->index >= NGHTTP2_STATIC_TABLE_LENGTH) { - /* We don't copy name in static table */ - ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC; + rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0); + if (rv != 0) { + nghttp2_rcbuf_decref(nv.name); + return NGHTTP2_ERR_NOMEM; } - - nv.name = ent_name->nv.name; - nv.namelen = ent_name->nv.namelen; - - nv.value = inflater->valuebuf.pos; - nv.valuelen = nghttp2_buf_len(&inflater->valuebuf); - - nghttp2_buf_init(&inflater->valuebuf); - - new_ent = add_hd_table_incremental(&inflater->ctx, &nv, ent_name->token, - ent_flags, NULL, 0); - - /* At this point, ent_name might be deleted. */ - - if (new_ent) { - emit_indexed_header(nv_out, token_out, new_ent); - - inflater->ent_keep = new_ent; - - return 0; - } - - nghttp2_mem_free(mem, nv.value); - - return NGHTTP2_ERR_NOMEM; } - nv.name = ent_name->nv.name; - nv.namelen = ent_name->nv.namelen; - nv.value = inflater->valuebuf.pos; - nv.valuelen = nghttp2_buf_len(&inflater->valuebuf); - - nghttp2_buf_init(&inflater->valuebuf); - - emit_literal_header(nv_out, token_out, &nv); + emit_header(nv_out, &nv); + inflater->nv_name_keep = nv.name; inflater->nv_value_keep = nv.value; + inflater->valuercbuf = NULL; + return 0; } ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, uint8_t *in, size_t inlen, int in_final) { - int token; + ssize_t rv; + nghttp2_hd_nv hd_nv; - return nghttp2_hd_inflate_hd2(inflater, nv_out, inflate_flags, &token, in, - inlen, in_final); + rv = nghttp2_hd_inflate_hd2(inflater, &hd_nv, inflate_flags, in, inlen, + in_final); + + if (rv < 0) { + return rv; + } + + if (*inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + nv_out->name = hd_nv.name->base; + nv_out->namelen = hd_nv.name->len; + + nv_out->value = hd_nv.value->base; + nv_out->valuelen = hd_nv.value->len; + + nv_out->flags = hd_nv.flags; + } + + return rv; } ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, - nghttp2_nv *nv_out, int *inflate_flags, - int *token_out, uint8_t *in, size_t inlen, - int in_final) { + nghttp2_hd_nv *nv_out, int *inflate_flags, + uint8_t *in, size_t inlen, int in_final) { ssize_t rv = 0; uint8_t *first = in; uint8_t *last = in + inlen; @@ -2271,7 +2169,6 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, DEBUGF(fprintf(stderr, "inflatehd: start state=%d\n", inflater->state)); hd_inflate_keep_free(inflater); - *token_out = -1; *inflate_flags = NGHTTP2_HD_INFLATE_NONE; for (; in != last || busy;) { busy = 0; @@ -2377,7 +2274,7 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, inflater->index = inflater->left; --inflater->index; - rv = hd_inflate_commit_indexed(inflater, nv_out, token_out); + rv = hd_inflate_commit_indexed(inflater, nv_out); if (rv < 0) { goto fail; } @@ -2422,17 +2319,21 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx); inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF; - rv = nghttp2_buf_reserve(&inflater->namebuf, inflater->left * 2 + 1, - mem); + + rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left * 2 + 1, + mem); } else { inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME; - rv = nghttp2_buf_reserve(&inflater->namebuf, inflater->left + 1, mem); + rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left + 1, mem); } if (rv != 0) { goto fail; } + nghttp2_buf_wrap_init(&inflater->namebuf, inflater->namercbuf->base, + inflater->namercbuf->len); + break; case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF: rv = hd_inflate_read_huff(inflater, &inflater->namebuf, in, last); @@ -2452,6 +2353,7 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, } *inflater->namebuf.last = '\0'; + inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf); inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; @@ -2473,6 +2375,7 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, } *inflater->namebuf.last = '\0'; + inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf); inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; @@ -2505,18 +2408,21 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF; - rv = nghttp2_buf_reserve(&inflater->valuebuf, inflater->left * 2 + 1, - mem); + rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left * 2 + 1, + mem); } else { inflater->state = NGHTTP2_HD_STATE_READ_VALUE; - rv = nghttp2_buf_reserve(&inflater->valuebuf, inflater->left + 1, mem); + rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left + 1, mem); } if (rv != 0) { goto fail; } + nghttp2_buf_wrap_init(&inflater->valuebuf, inflater->valuercbuf->base, + inflater->valuercbuf->len); + busy = 1; break; @@ -2538,11 +2444,12 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, } *inflater->valuebuf.last = '\0'; + inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf); if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { - rv = hd_inflate_commit_newname(inflater, nv_out, token_out); + rv = hd_inflate_commit_newname(inflater, nv_out); } else { - rv = hd_inflate_commit_indname(inflater, nv_out, token_out); + rv = hd_inflate_commit_indname(inflater, nv_out); } if (rv != 0) { @@ -2572,11 +2479,12 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, } *inflater->valuebuf.last = '\0'; + inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf); if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { - rv = hd_inflate_commit_newname(inflater, nv_out, token_out); + rv = hd_inflate_commit_newname(inflater, nv_out); } else { - rv = hd_inflate_commit_indname(inflater, nv_out, token_out); + rv = hd_inflate_commit_indname(inflater, nv_out); } if (rv != 0) { @@ -2710,7 +2618,7 @@ static const nghttp2_nv *hd_get_table_entry(nghttp2_hd_context *context, return NULL; } - return &nghttp2_hd_table_get(context, idx)->nv; + return nghttp2_hd_table_get2(context, idx); } size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater) { diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index fff4c5a5..cacfcf3a 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -34,6 +34,7 @@ #include "nghttp2_hd_huffman.h" #include "nghttp2_buf.h" #include "nghttp2_mem.h" +#include "nghttp2_rcbuf.h" #define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE #define NGHTTP2_HD_ENTRY_OVERHEAD 32 @@ -168,25 +169,29 @@ typedef enum { NGHTTP2_TOKEN_X_XSS_PROTECTION, } nghttp2_token; -typedef enum { - NGHTTP2_HD_FLAG_NONE = 0, - /* Indicates name was dynamically allocated and must be freed */ - NGHTTP2_HD_FLAG_NAME_ALLOC = 1, - /* Indicates value was dynamically allocated and must be freed */ - NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1, - /* Indicates that the name was gifted to the entry and no copying - necessary. */ - NGHTTP2_HD_FLAG_NAME_GIFT = 1 << 2, - /* Indicates that the value was gifted to the entry and no copying - necessary. */ - NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 3 -} nghttp2_hd_flags; - struct nghttp2_hd_entry; typedef struct nghttp2_hd_entry nghttp2_hd_entry; +typedef struct { + /* The buffer containing header field name. NULL-termination is + guaranteed. */ + nghttp2_rcbuf *name; + /* The buffer containing header field value. NULL-termination is + guaranteed. */ + nghttp2_rcbuf *value; + /* nghttp2_token value for name. It could be -1 if we have no token + for that header field name. */ + int32_t token; + /* Bitwise OR of one or more of nghttp2_nv_flag. */ + uint8_t flags; +} nghttp2_hd_nv; + struct nghttp2_hd_entry { - nghttp2_nv nv; + /* The header field name/value pair */ + nghttp2_hd_nv nv; + /* This is solely for nghttp2_hd_{deflate,inflate}_get_table_entry + APIs to keep backward compatibility. */ + nghttp2_nv cnv; /* The next entry which shares same bucket in hash table. */ nghttp2_hd_entry *next; /* The sequence number. We will increment it by one whenever we @@ -194,14 +199,17 @@ struct nghttp2_hd_entry { uint32_t seq; /* The hash value for header name (nv.name). */ uint32_t hash; - /* nghttp2_token value for nv.name. It could be -1 if we have no - token for that header field name. */ - int token; - /* Reference count */ - uint8_t ref; - uint8_t flags; }; +/* The entry used for static header table. */ +typedef struct { + nghttp2_rcbuf name; + nghttp2_rcbuf value; + nghttp2_nv cnv; + int32_t token; + uint32_t hash; +} nghttp2_hd_static_entry; + typedef struct { nghttp2_hd_entry **buffer; size_t mask; @@ -275,17 +283,14 @@ struct nghttp2_hd_deflater { struct nghttp2_hd_inflater { nghttp2_hd_context ctx; - /* header buffer */ - nghttp2_buf namebuf, valuebuf; /* Stores current state of huffman decoding */ nghttp2_hd_huff_decode_context huff_decode_ctx; - /* Pointer to the nghttp2_hd_entry which is used current header - emission. This is required because in some cases the - ent_keep->ref == 0 and we have to keep track of it. */ - nghttp2_hd_entry *ent_keep; - /* Pointer to the name/value pair buffer which is used in the - current header emission. */ - uint8_t *nv_name_keep, *nv_value_keep; + /* header buffer */ + nghttp2_buf namebuf, valuebuf; + nghttp2_rcbuf *namercbuf, *valuercbuf; + /* Pointer to the name/value pair which are used in the current + header emission. */ + nghttp2_rcbuf *nv_name_keep, *nv_value_keep; /* The number of bytes to read */ size_t left; /* The index in indexed repr or indexed name */ @@ -309,24 +314,16 @@ struct nghttp2_hd_inflater { }; /* - * Initializes the |ent| members. If NGHTTP2_HD_FLAG_NAME_ALLOC bit - * set in the |flags|, the content pointed by the |name| with length - * |namelen| is copied. Likewise, if NGHTTP2_HD_FLAG_VALUE_ALLOC bit - * set in the |flags|, the content pointed by the |value| with length - * |valuelen| is copied. The |token| is enum number looked up by - * |name|. It could be -1 if we don't have that enum value. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. + * Initializes the |ent| members. The reference counts of nv->name + * and nv->value are increased by one for each. */ -int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name, - size_t namelen, uint8_t *value, size_t valuelen, - int token, nghttp2_mem *mem); +void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv); -void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem); +/* + * This function decreases the reference counts of nv->name and + * nv->value. + */ +void nghttp2_hd_entry_free(nghttp2_hd_entry *ent); /* * Initializes |deflater| for deflating name/values pairs. @@ -407,16 +404,14 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem); void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater); /* - * Similar to nghttp2_hd_inflate_hd(), but this takes additional - * output parameter |token|. On successful header emission, it - * contains nghttp2_token value for nv_out->name. It could be -1 if - * we don't have enum value for the name. Other than that return - * values and semantics are the same as nghttp2_hd_inflate_hd(). + * Similar to nghttp2_hd_inflate_hd(), but this takes nghttp2_hd_nv + * instead of nghttp2_nv as output parameter |nv_out|. Other than + * that return values and semantics are the same as + * nghttp2_hd_inflate_hd(). */ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, - nghttp2_nv *nv_out, int *inflate_flags, - int *token, uint8_t *in, size_t inlen, - int in_final); + nghttp2_hd_nv *nv_out, int *inflate_flags, + uint8_t *in, size_t inlen, int in_final); /* For unittesting purpose */ int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index, @@ -430,8 +425,7 @@ int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv, int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size); /* For unittesting purpose */ -nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context, - size_t index); +nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index); /* For unittesting purpose */ ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final, diff --git a/lib/nghttp2_http.c b/lib/nghttp2_http.c index 6aee90f0..f993c167 100644 --- a/lib/nghttp2_http.c +++ b/lib/nghttp2_http.c @@ -82,12 +82,12 @@ static int lws(const uint8_t *s, size_t n) { return 1; } -static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_nv *nv, +static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv, int flag) { if (stream->http_flags & flag) { return 0; } - if (lws(nv->value, nv->valuelen)) { + if (lws(nv->value->base, nv->value->len)) { return 0; } stream->http_flags = (uint16_t)(stream->http_flags | flag); @@ -112,16 +112,16 @@ static int check_path(nghttp2_stream *stream) { (stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK))); } -static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, - int token, int trailer) { - if (nv->name[0] == ':') { +static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, + int trailer) { + if (nv->name->base[0] == ':') { if (trailer || (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { return NGHTTP2_ERR_HTTP_HEADER; } } - switch (token) { + switch (nv->token) { case NGHTTP2_TOKEN__AUTHORITY: if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) { return NGHTTP2_ERR_HTTP_HEADER; @@ -131,16 +131,16 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) { return NGHTTP2_ERR_HTTP_HEADER; } - switch (nv->valuelen) { + switch (nv->value->len) { case 4: - if (lstreq("HEAD", nv->value, nv->valuelen)) { + if (lstreq("HEAD", nv->value->base, nv->value->len)) { stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; } break; case 7: - switch (nv->value[6]) { + switch (nv->value->base[6]) { case 'T': - if (lstreq("CONNECT", nv->value, nv->valuelen)) { + if (lstreq("CONNECT", nv->value->base, nv->value->len)) { if (stream->stream_id % 2 == 0) { /* we won't allow CONNECT for push */ return NGHTTP2_ERR_HTTP_HEADER; @@ -153,7 +153,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, } break; case 'S': - if (lstreq("OPTIONS", nv->value, nv->valuelen)) { + if (lstreq("OPTIONS", nv->value->base, nv->value->len)) { stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS; } break; @@ -168,9 +168,9 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) { return NGHTTP2_ERR_HTTP_HEADER; } - if (nv->value[0] == '/') { + if (nv->value->base[0] == '/') { stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR; - } else if (nv->valuelen == 1 && nv->value[0] == '*') { + } else if (nv->value->len == 1 && nv->value->base[0] == '*') { stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK; } break; @@ -181,8 +181,8 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) { return NGHTTP2_ERR_HTTP_HEADER; } - if ((nv->valuelen == 4 && memieq("http", nv->value, 4)) || - (nv->valuelen == 5 && memieq("https", nv->value, 5))) { + if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) || + (nv->value->len == 5 && memieq("https", nv->value->base, 5))) { stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP; } break; @@ -195,7 +195,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, if (stream->content_length != -1) { return NGHTTP2_ERR_HTTP_HEADER; } - stream->content_length = parse_uint(nv->value, nv->valuelen); + stream->content_length = parse_uint(nv->value->base, nv->value->len); if (stream->content_length == -1) { return NGHTTP2_ERR_HTTP_HEADER; } @@ -209,41 +209,41 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, case NGHTTP2_TOKEN_UPGRADE: return NGHTTP2_ERR_HTTP_HEADER; case NGHTTP2_TOKEN_TE: - if (!lstrieq("trailers", nv->value, nv->valuelen)) { + if (!lstrieq("trailers", nv->value->base, nv->value->len)) { return NGHTTP2_ERR_HTTP_HEADER; } break; default: - if (nv->name[0] == ':') { + if (nv->name->base[0] == ':') { return NGHTTP2_ERR_HTTP_HEADER; } } - if (nv->name[0] != ':') { + if (nv->name->base[0] != ':') { stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; } return 0; } -static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv, - int token, int trailer) { - if (nv->name[0] == ':') { +static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, + int trailer) { + if (nv->name->base[0] == ':') { if (trailer || (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { return NGHTTP2_ERR_HTTP_HEADER; } } - switch (token) { + switch (nv->token) { case NGHTTP2_TOKEN__STATUS: { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) { return NGHTTP2_ERR_HTTP_HEADER; } - if (nv->valuelen != 3) { + if (nv->value->len != 3) { return NGHTTP2_ERR_HTTP_HEADER; } - stream->status_code = (int16_t)parse_uint(nv->value, nv->valuelen); + stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len); if (stream->status_code == -1) { return NGHTTP2_ERR_HTTP_HEADER; } @@ -253,7 +253,7 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv, if (stream->content_length != -1) { return NGHTTP2_ERR_HTTP_HEADER; } - stream->content_length = parse_uint(nv->value, nv->valuelen); + stream->content_length = parse_uint(nv->value->base, nv->value->len); if (stream->content_length == -1) { return NGHTTP2_ERR_HTTP_HEADER; } @@ -267,17 +267,17 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv, case NGHTTP2_TOKEN_UPGRADE: return NGHTTP2_ERR_HTTP_HEADER; case NGHTTP2_TOKEN_TE: - if (!lstrieq("trailers", nv->value, nv->valuelen)) { + if (!lstrieq("trailers", nv->value->base, nv->value->len)) { return NGHTTP2_ERR_HTTP_HEADER; } break; default: - if (nv->name[0] == ':') { + if (nv->name->base[0] == ':') { return NGHTTP2_ERR_HTTP_HEADER; } } - if (nv->name[0] != ':') { + if (nv->name->base[0] != ':') { stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; } @@ -375,7 +375,7 @@ static int check_scheme(const uint8_t *value, size_t len) { } int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, - nghttp2_frame *frame, nghttp2_nv *nv, int token, + nghttp2_frame *frame, nghttp2_hd_nv *nv, int trailer) { int rv; @@ -386,14 +386,14 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, this, we may disrupt many web sites and/or libraries. So we become conservative here, and just ignore those illegal regular headers. */ - if (!nghttp2_check_header_name(nv->name, nv->namelen)) { + if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) { size_t i; - if (nv->namelen > 0 && nv->name[0] == ':') { + if (nv->name->len > 0 && nv->name->base[0] == ':') { return NGHTTP2_ERR_HTTP_HEADER; } /* header field name must be lower-cased without exception */ - for (i = 0; i < nv->namelen; ++i) { - uint8_t c = nv->name[i]; + for (i = 0; i < nv->name->len; ++i) { + uint8_t c = nv->name->base[i]; if ('A' <= c && c <= 'Z') { return NGHTTP2_ERR_HTTP_HEADER; } @@ -405,17 +405,18 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, return NGHTTP2_ERR_IGN_HTTP_HEADER; } - if (token == NGHTTP2_TOKEN__AUTHORITY || token == NGHTTP2_TOKEN_HOST) { - rv = check_authority(nv->value, nv->valuelen); - } else if (token == NGHTTP2_TOKEN__SCHEME) { - rv = check_scheme(nv->value, nv->valuelen); + if (nv->token == NGHTTP2_TOKEN__AUTHORITY || + nv->token == NGHTTP2_TOKEN_HOST) { + rv = check_authority(nv->value->base, nv->value->len); + } else if (nv->token == NGHTTP2_TOKEN__SCHEME) { + rv = check_scheme(nv->value->base, nv->value->len); } else { - rv = nghttp2_check_header_value(nv->value, nv->valuelen); + rv = nghttp2_check_header_value(nv->value->base, nv->value->len); } if (rv == 0) { - assert(nv->namelen > 0); - if (nv->name[0] == ':') { + assert(nv->name->len > 0); + if (nv->name->base[0] == ':') { return NGHTTP2_ERR_HTTP_HEADER; } /* When ignoring regular headers, we set this flag so that we @@ -426,10 +427,10 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, } if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { - return http_request_on_header(stream, nv, token, trailer); + return http_request_on_header(stream, nv, trailer); } - return http_response_on_header(stream, nv, token, trailer); + return http_response_on_header(stream, nv, trailer); } int nghttp2_http_on_request_headers(nghttp2_stream *stream, diff --git a/lib/nghttp2_http.h b/lib/nghttp2_http.h index f782058f..ac684c4d 100644 --- a/lib/nghttp2_http.h +++ b/lib/nghttp2_http.h @@ -36,8 +36,7 @@ /* * This function is called when HTTP header field |nv| in |frame| is * received for |stream|. This function will validate |nv| against - * the current state of stream. The |token| is nghttp2_token value - * for nv->name, or -1 if we don't have enum value for the name. + * the current state of stream. * * This function returns 0 if it succeeds, or one of the following * negative error codes: @@ -49,7 +48,7 @@ * if it was not received because of compatibility reasons. */ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, - nghttp2_frame *frame, nghttp2_nv *nv, int token, + nghttp2_frame *frame, nghttp2_hd_nv *nv, int trailer); /* diff --git a/lib/nghttp2_mem.c b/lib/nghttp2_mem.c index e7d5aae3..317363a7 100644 --- a/lib/nghttp2_mem.c +++ b/lib/nghttp2_mem.c @@ -52,6 +52,10 @@ void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) { mem->free(ptr, mem->mem_user_data); } +void nghttp2_mem_free2(nghttp2_free free, void *ptr, void *mem_user_data) { + free(ptr, mem_user_data); +} + void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) { return mem->calloc(nmemb, size, mem->mem_user_data); } diff --git a/lib/nghttp2_mem.h b/lib/nghttp2_mem.h index d1fded4f..7709e1c5 100644 --- a/lib/nghttp2_mem.h +++ b/lib/nghttp2_mem.h @@ -38,6 +38,7 @@ nghttp2_mem *nghttp2_mem_default(void); |mem|. */ void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size); void nghttp2_mem_free(nghttp2_mem *mem, void *ptr); +void nghttp2_mem_free2(nghttp2_free free, void *ptr, void *mem_user_data); void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size); void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size); diff --git a/lib/nghttp2_rcbuf.c b/lib/nghttp2_rcbuf.c new file mode 100644 index 00000000..a1af1da8 --- /dev/null +++ b/lib/nghttp2_rcbuf.c @@ -0,0 +1,98 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_rcbuf.h" + +#include +#include + +#include "nghttp2_mem.h" + +int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, + nghttp2_mem *mem) { + uint8_t *p; + + p = nghttp2_mem_malloc(mem, sizeof(nghttp2_rcbuf) + size); + if (p == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + *rcbuf_ptr = (void *)p; + + (*rcbuf_ptr)->mem_user_data = mem->mem_user_data; + (*rcbuf_ptr)->free = mem->free; + (*rcbuf_ptr)->base = p + sizeof(nghttp2_rcbuf); + (*rcbuf_ptr)->len = size; + (*rcbuf_ptr)->ref = 1; + + return 0; +} + +int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src, + size_t srclen, nghttp2_mem *mem) { + int rv; + + rv = nghttp2_rcbuf_new(rcbuf_ptr, srclen + 1, mem); + if (rv != 0) { + return rv; + } + + memcpy((*rcbuf_ptr)->base, src, srclen); + + (*rcbuf_ptr)->len = srclen; + (*rcbuf_ptr)->base[srclen] = '\0'; + + return 0; +} + +/* + * Frees |rcbuf| itself, regardless of its reference cout. + */ +void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf) { + nghttp2_mem_free2(rcbuf->free, rcbuf, rcbuf->mem_user_data); +} + +void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf) { + if (rcbuf->ref == -1) { + return; + } + + ++rcbuf->ref; +} + +void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf) { + if (rcbuf == NULL || rcbuf->ref == -1) { + return; + } + + assert(rcbuf->ref > 0); + + if (--rcbuf->ref == 0) { + nghttp2_rcbuf_del(rcbuf); + } +} + +nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf) { + return (nghttp2_vec){rcbuf->base, rcbuf->len}; +} diff --git a/lib/nghttp2_rcbuf.h b/lib/nghttp2_rcbuf.h new file mode 100644 index 00000000..29d1543e --- /dev/null +++ b/lib/nghttp2_rcbuf.h @@ -0,0 +1,80 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_RCBUF_H +#define NGHTTP2_RCBUF_H + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include + +struct nghttp2_rcbuf { + /* custom memory allocator belongs to the mem parameter when + creating this object. */ + void *mem_user_data; + nghttp2_free free; + /* The pointer to the underlying buffer */ + uint8_t *base; + /* Size of buffer pointed by |base|. */ + size_t len; + /* Reference count */ + int32_t ref; +}; + +/* + * Allocates nghttp2_rcbuf object with |size| as initial buffer size. + * When the function succeeds, the reference count becomes 1. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM: + * Out of memory. + */ +int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem); + +/* + * Like nghttp2_rcbuf_new(), but initializes the buffer with |src| of + * length |srclen|. This function allocates additional byte at the + * end and puts '\0' into it, so that the resulting buffer could be + * used as NULL-terminated string. Still (*rcbuf_ptr)->len equals to + * |srclen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM: + * Out of memory. + */ +int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src, + size_t srclen, nghttp2_mem *mem); + +/* + * Frees |rcbuf| itself, regardless of its reference cout. + */ +void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf); + +#endif /* NGHTTP2_RCBUF_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index cbc1bac6..92b1c482 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -3122,12 +3122,12 @@ static int session_call_on_begin_headers(nghttp2_session *session, static int session_call_on_header(nghttp2_session *session, const nghttp2_frame *frame, - const nghttp2_nv *nv) { + const nghttp2_hd_nv *nv) { int rv; if (session->callbacks.on_header_callback) { rv = session->callbacks.on_header_callback( - session, frame, nv->name, nv->namelen, nv->value, nv->valuelen, - nv->flags, session->user_data); + session, frame, nv->name->base, nv->name->len, nv->value->base, + nv->value->len, nv->flags, session->user_data); if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { return rv; @@ -3317,11 +3317,10 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, ssize_t proclen; int rv; int inflate_flags; - nghttp2_nv nv; + nghttp2_hd_nv nv; nghttp2_stream *stream; nghttp2_stream *subject_stream; int trailer = 0; - int token; *readlen_ptr = 0; stream = nghttp2_session_get_stream(session, frame->hd.stream_id); @@ -3338,7 +3337,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, for (;;) { inflate_flags = 0; proclen = nghttp2_hd_inflate_hd2(&session->hd_inflater, &nv, &inflate_flags, - &token, in, inlen, final); + in, inlen, final); if (nghttp2_is_fatal((int)proclen)) { return (int)proclen; } @@ -3373,13 +3372,13 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { rv = 0; if (subject_stream && session_enforce_http_messaging(session)) { - rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, token, + rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, trailer); if (rv == NGHTTP2_ERR_HTTP_HEADER) { DEBUGF(fprintf( stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n", - frame->hd.type, subject_stream->stream_id, (int)nv.namelen, - nv.name, (int)nv.valuelen, nv.value)); + frame->hd.type, subject_stream->stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base)); rv = session_handle_invalid_stream2(session, subject_stream->stream_id, @@ -3394,8 +3393,8 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, /* header is ignored */ DEBUGF(fprintf( stderr, "recv: HTTP ignored: type=%d, id=%d, header %.*s: %.*s\n", - frame->hd.type, subject_stream->stream_id, (int)nv.namelen, - nv.name, (int)nv.valuelen, nv.value)); + frame->hd.type, subject_stream->stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base)); } } if (rv == 0) { diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c index ed954bde..3d8ab83b 100644 --- a/tests/nghttp2_hd_test.c +++ b/tests/nghttp2_hd_test.c @@ -295,11 +295,6 @@ void test_nghttp2_hd_inflate_indname_inc(void) { assert_nv_equal(&nv, out.nva, 1, mem); CU_ASSERT(1 == inflater.ctx.hd_table.len); CU_ASSERT(62 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); - assert_nv_equal(&nv, - &nghttp2_hd_table_get(&inflater.ctx, - NGHTTP2_STATIC_TABLE_LENGTH + - inflater.ctx.hd_table.len - 1)->nv, - 1, mem); assert_nv_equal(&nv, nghttp2_hd_inflate_get_table_entry( &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len), @@ -429,10 +424,9 @@ void test_nghttp2_hd_inflate_newname_inc(void) { CU_ASSERT(1 == out.nvlen); assert_nv_equal(&nv, out.nva, 1, mem); CU_ASSERT(1 == inflater.ctx.hd_table.len); - assert_nv_equal(&nv, - &nghttp2_hd_table_get(&inflater.ctx, - NGHTTP2_STATIC_TABLE_LENGTH + - inflater.ctx.hd_table.len - 1)->nv, + assert_nv_equal(&nv, nghttp2_hd_inflate_get_table_entry( + &inflater, NGHTTP2_STATIC_TABLE_LENGTH + + inflater.ctx.hd_table.len), 1, mem); nva_out_reset(&out, mem);