blob: 83f61748a49e0081c085f2f9b509646c65bcb64b [file] [log] [blame]
/* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <stdlib.h>
#include <apr_pools.h>
#include "serf.h"
#include "serf_bucket_util.h"
#include "serf_private.h"
#include "protocols/http2_buckets.h"
#include "hpack_huffman.inc"
#define EOS_CHAR (256)
/* Callback for bsearch() */
static int
hpack_hm_compare(const void *k,
const void *i)
{
const apr_uint32_t *key = k;
const struct serf_hpack_huffman_item_t *it = i;
apr_uint32_t k1 = (*key & it->hmask);
apr_uint32_t k2 = it->hval;
if (k1 < k2)
return -1;
else if (k1 > k2)
return 1;
else
return 0;
}
/* Convert encoded data in ENCODED of size ENCODED_LEN. If TEXT is not
NULL, put the result in the buffer pointed to by TEXT which is of size
TEXT_AVAIL. Sets *TEXT_LEN to the resulting length.
If TEXT_AVAIL allows it a final '\0' is added at TEXT[*TEXT_LEN].
If TEXT is not large enough return APR_ENOMEM. If ENCODED isn't properly
encoded return APR_EINVAL.
*/
apr_status_t
serf__hpack_huffman_decode(const unsigned char *encoded,
apr_size_t encoded_len,
apr_size_t text_avail,
char *text,
apr_size_t *text_len)
{
apr_uint64_t stash = 0;
apr_int16_t bits_left = 0;
apr_size_t result_len = 0;
while (encoded_len || bits_left)
{
apr_uint32_t match;
struct serf_hpack_huffman_item_t *r;
while (bits_left < 30 && encoded_len)
{
stash |= (apr_uint64_t)*encoded << (64 - 8 - bits_left);
bits_left += 8;
encoded_len--;
encoded++;
}
match = stash >> 32;
r = bsearch(&match, &serf_hpack_hm_map,
sizeof(serf_hpack_hm_map) / sizeof(serf_hpack_hm_map[0]),
sizeof(serf_hpack_hm_map[0]), hpack_hm_compare);
if (!r || (r->bits > bits_left))
{
/* With a canonical huffman code we can only reach this state
at the end of the string */
break;
}
if (r->cval == EOS_CHAR)
return APR_EINVAL;
if (text)
{
if (result_len < text_avail)
text[result_len] = (char)r->cval;
else
return APR_ENOMEM;
}
result_len++;
stash <<= r->bits;
bits_left -= r->bits;
}
if (bits_left)
{
/* https://tools.ietf.org/html/rfc7541#section-5.2
Upon decoding, an incomplete code at the end of the encoded data is
to be considered as padding and discarded. A padding strictly longer
than 7 bits MUST be treated as a decoding error. A padding not
corresponding to the most significant bits of the code for the EOS
symbol MUST be treated as a decoding error. A Huffman-encoded string
literal containing the EOS symbol MUST be treated as a decoding
error. */
const struct serf_hpack_huffman_item_t *eos;
apr_uint64_t exp_stash;
eos = &serf_hpack_hm_map[serf_hpack_hm_rmap[EOS_CHAR]];
/* Trim EOS value to the bits we need */
exp_stash = ((apr_uint32_t)eos->hval >> (32 - bits_left));
/* And move it to the right position */
exp_stash <<= 64 - bits_left;
if (exp_stash != stash)
return APR_EINVAL;
}
*text_len = result_len;
if (text && result_len < text_avail)
text[result_len] = 0;
return APR_SUCCESS;
}
/* Encodes data in TEXT of size TEXT_LEN.
Sets ENCODED_LEN to the required length.
If ENCODED is not NULL, it specifies an output buffer of size
ENCODED_AVAIL into which the data will be encoded.
If ENCODE is not NULL and the data doesn't fit returns APR_ENOMEM.
*/
apr_status_t
serf__hpack_huffman_encode(const char *text,
apr_size_t text_len,
apr_size_t encoded_avail,
unsigned char *encoded,
apr_size_t *encoded_len)
{
apr_uint64_t stash = 0;
apr_int16_t bits_left = 0;
apr_size_t result_len = 0;
if (!encoded)
{
/* Just calculating size needed. Avoid bit handling */
apr_int64_t result_bits = 0;
while (text_len)
{
const struct serf_hpack_huffman_item_t *r;
r = &serf_hpack_hm_map[serf_hpack_hm_rmap[*(unsigned char*)text]];
result_bits += r->bits;
text_len--;
text++;
}
*encoded_len = (apr_size_t)((result_bits + 7) / 8);
return APR_SUCCESS;
}
while (text_len)
{
if (text_len)
{
const struct serf_hpack_huffman_item_t *r;
r = &serf_hpack_hm_map[serf_hpack_hm_rmap[*(unsigned char*)text]];
stash |= (apr_uint64_t)r->hval << (64 - 32 - bits_left);
bits_left += r->bits;
text_len--;
text++;
}
while (bits_left > 8)
{
if (!encoded_avail)
return APR_ENOMEM;
*encoded = (unsigned char)(stash >> (64 - 8));
encoded++;
stash <<= 8;
bits_left -= 8;
encoded_avail--;
result_len++;
}
}
if (bits_left)
{
/* https://tools.ietf.org/html/rfc7541#section-5.2
As the Huffman-encoded data doesn't always end at an octet boundary,
some padding is inserted after it, up to the next octet boundary. To
prevent this padding from being misinterpreted as part of the string
literal, the most significant bits of the code corresponding to the
EOS (end-of-string) symbol are used. */
const struct serf_hpack_huffman_item_t *r;
if (!encoded_avail)
return APR_ENOMEM;
r = &serf_hpack_hm_map[serf_hpack_hm_rmap[EOS_CHAR]];
stash |= (apr_uint64_t)r->hval << (64 - 32 - bits_left);
/* bits_left += r->bits; */
*encoded = (unsigned char)(stash >> (64 - 8));
/* encoded++ */
/* stash <<= 8; */
/* bits_left -= 8; */
/* encoded_avail--; */
result_len++;
}
*encoded_len = result_len;
return APR_SUCCESS;
}
/* ==================================================================== */
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_entry_t *next;
struct serf_hpack_entry_t *prev;
bool free_key; /* Key must be freed */
bool free_val; /* Value must be freed */
char dont_index; /* 0=index, 1=no-index, 2=never-index */
} 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)
/* The per key, value variant of HPACK_ENTRY_SIZE */
#define HPACK_KEY_SIZE(key_sz) ((key_sz) + 16)
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;
apr_size_t lr_size; /* 'Bytes' in list, calculated by HPACK_ENTRY_SIZE() */
apr_size_t lr_max_table_size;
apr_size_t lr_sys_table_size;
serf_hpack_entry_t *rl_first, *rl_last;
apr_size_t rl_size; /* 'Bytes' in list, calculated by HPACK_ENTRY_SIZE() */
apr_size_t rl_max_table_size;
apr_size_t rl_sys_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]));
static apr_status_t
cleanup_hpack_table(void *data)
{
#ifdef _DEBUG
serf_hpack_table_t *tbl = data;
serf_hpack_entry_t *hi, *next;
/* This is not really necessary, as we create our own allocator,
which lives in the same pool. But it helps tracking down
memory leaks in different locations */
for (hi = tbl->lr_first; hi; hi = next)
{
next = hi->next;
hpack_free_entry(hi, tbl->alloc);
}
tbl->lr_first = tbl->lr_last = NULL;
tbl->lr_size = 0;
for (hi = tbl->rl_first; hi; hi = next)
{
next = hi->next;
hpack_free_entry(hi, tbl->alloc);
}
tbl->rl_first = tbl->rl_last = NULL;
tbl->rl_size = 0;
#endif
return APR_SUCCESS;
}
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);
/* We register this this after creating the allocator, or we would touch
memory that is already freed.*/
apr_pool_cleanup_register(result_pool, tbl, cleanup_hpack_table,
apr_pool_cleanup_null);
tbl->lr_sys_table_size = tbl->lr_max_table_size = default_max_table_size;
tbl->rl_sys_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 void
hpack_shrink_table(serf_hpack_entry_t **first,
serf_hpack_entry_t **last,
apr_size_t *size,
apr_size_t max_size,
serf_bucket_alloc_t *allocator)
{
while (*last && (*size > max_size))
{
serf_hpack_entry_t *entry = *last;
*last = entry->prev;
if (first && (*first == entry))
*first = NULL;
if (entry->prev)
entry->prev->next = NULL;
*size -= HPACK_ENTRY_SIZE(entry);
hpack_free_entry(entry, allocator);
}
}
void
serf__hpack_table_set_max_table_size(serf_hpack_table_t *hpack_tbl,
apr_size_t max_decoder_size,
apr_size_t max_encoder_size)
{
if (max_decoder_size != hpack_tbl->rl_sys_table_size)
{
hpack_tbl->rl_sys_table_size = max_decoder_size;
}
if (max_encoder_size != hpack_tbl->lr_max_table_size)
{
hpack_tbl->lr_sys_table_size = max_encoder_size;
if (max_encoder_size > (128 * 1024))
max_encoder_size = (128 * 1024);
if (max_encoder_size < hpack_tbl->lr_max_table_size)
hpack_tbl->send_tablesize_update = TRUE;
hpack_shrink_table(&hpack_tbl->lr_first,
&hpack_tbl->lr_last, &hpack_tbl->lr_size,
hpack_tbl->lr_max_table_size, hpack_tbl->alloc);
}
}
static apr_status_t
hpack_table_size_update(serf_hpack_table_t *hpack_tbl,
apr_size_t size)
{
if (size <= hpack_tbl->rl_sys_table_size)
{
hpack_tbl->rl_max_table_size = size;
hpack_shrink_table(&hpack_tbl->rl_first,
&hpack_tbl->rl_last, &hpack_tbl->rl_size,
hpack_tbl->rl_max_table_size, hpack_tbl->alloc);
}
else
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
return APR_SUCCESS;
}
static apr_status_t
hpack_table_get(apr_uint32_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_first; 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_hpack_table_t *tbl;
serf_hpack_entry_t *first;
serf_hpack_entry_t *last;
} serf_hpack_context_t;
static apr_status_t
hpack_copy_from_headers(void *baton,
const char *key,
const char *value)
{
serf_bucket_t *hpack = baton;
apr_size_t key_sz = strlen(key);
/* TODO: Others? */
if ((key_sz == 4 && !strcasecmp(key, "Host"))
|| (key_sz == 7 && !strcasecmp(key, "Upgrade"))
|| (key_sz == 10 && !strcasecmp(key, "Keep-Alive"))
|| (key_sz == 10 && !strcasecmp(key, "Connection"))
|| (key_sz > 11 && !strncasecmp(key, "Connection-", 11))
|| (key_sz == 16 && !strcasecmp(key, "Proxy-Connection"))
|| (key_sz == 17 && !strcasecmp(key, "Transfer-Encoding")))
{
return APR_SUCCESS;
}
serf__bucket_hpack_setc(hpack, key, value);
return APR_SUCCESS;
}
apr_status_t
serf__bucket_hpack_create_from_request(serf_bucket_t **new_hpack_bucket,
serf_hpack_table_t *hpack_table,
serf_bucket_t *request,
const char *scheme,
serf_bucket_alloc_t *allocator)
{
const char *uri, *method, *host;
serf_bucket_t *hpack = serf__bucket_hpack_create(hpack_table, allocator);
serf_bucket_t *headers = serf_bucket_request_get_headers(request);
host = serf_bucket_headers_get(headers, "Host");
serf__bucket_request_read(request, NULL, &uri, &method);
serf__bucket_hpack_setc(hpack, ":method", method);
serf__bucket_hpack_setc(hpack, ":scheme", scheme);
serf__bucket_hpack_setc(hpack, ":authority", host);
serf__bucket_hpack_setc(hpack, ":path", uri);
serf_bucket_headers_do(headers, hpack_copy_from_headers, hpack);
*new_hpack_bucket = hpack;
return APR_SUCCESS;
}
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->tbl = hpack_table;
ctx->first = ctx->last = NULL;
return serf_bucket_create(&serf_bucket_type__hpack, allocator, ctx);
}
void
serf__bucket_hpack_setc(serf_bucket_t *hpack_bucket,
const char *key,
const char *value)
{
serf__bucket_hpack_setx(hpack_bucket,
key, strlen(key), TRUE,
value, strlen(value), TRUE,
FALSE, FALSE);
}
void
serf__bucket_hpack_setx(serf_bucket_t *bucket,
const char *key,
apr_size_t key_size,
int key_copy,
const char *value,
apr_size_t value_size,
int value_copy,
int dont_index,
int never_index)
{
serf_hpack_context_t *ctx = bucket->data;
serf_hpack_entry_t *entry;
apr_size_t i;
for (entry = ctx->first; entry; entry = entry->next)
{
if (key_size == entry->key_len
&& !strncasecmp(key, entry->key, key_size))
{
break;
}
}
/* TODO: Handle *_copy by keeping some flags */
if (entry && value[0] == ':')
{
if (entry->free_val)
serf_bucket_mem_free(bucket->allocator, (void*)entry->value);
entry->value = serf_bstrmemdup(bucket->allocator, value, value_size);
entry->value_len = value_size;
entry->free_val = true;
entry->dont_index = never_index ? 2 : (dont_index ? 1 : 0);
return;
}
else if (entry)
{
/* We probably want to allow duplicate *and* join behavior? */
}
entry = serf_bucket_mem_calloc(bucket->allocator, 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). */
char *ckey = serf_bstrmemdup(bucket->allocator, 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(bucket->allocator, key, key_size);
entry->free_key = true;
}
entry->key_len = key_size;
if (value_copy)
{
entry->value = serf_bstrmemdup(bucket->allocator, value, value_size);
entry->free_val = true;
}
else
{
entry->value = value;
entry->free_val = false;
}
entry->value_len = value_size;
entry->dont_index = never_index ? 2 : (dont_index ? 1 : 0);
entry->prev = ctx->last;
entry->next = NULL;
if (ctx->last)
{
ctx->last->next = entry;
ctx->last = entry;
}
else
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_entry_t *entry;
apr_size_t key_len = strlen(key);
for (entry = ctx->first; entry; entry = entry->next)
{
if (key_len == entry->key_len
&& !strncasecmp(key, entry->key, key_len))
{
return entry->value;
}
}
return NULL;
}
void serf__bucket_hpack_do(serf_bucket_t *hpack_bucket,
serf_bucket_hpack_do_callback_fn_t func,
void *baton)
{
serf_hpack_context_t *ctx = hpack_bucket->data;
serf_hpack_entry_t *entry;
for (entry = ctx->first; entry; entry = entry->next)
{
if (func(baton, entry->key, entry->key_len, entry->value,
entry->value_len))
{
break;
}
}
}
static void hpack_int(unsigned char flags,
int bits,
apr_uint32_t value,
char to[6],
apr_size_t *used)
{
unsigned char max_direct;
apr_size_t u;
flags = flags & ~((1 << bits) - 1);
max_direct = (unsigned char)(((apr_uint16_t)1 << bits) - 1);
if (value < max_direct)
{
to[0] = flags | (unsigned char)value;
*used = 1;
return;
}
to[0] = flags | max_direct;
value -= max_direct;
u = 1;
while (value >= 0x80)
{
to[u++] = (value & 0x7F) | 0x80;
value >>= 7;
}
to[u++] = (unsigned char)value;
*used = u;
}
static void
serialize_ensure_buffer(serf_bucket_t *bucket, apr_size_t ensure,
char **buffer, apr_size_t *offset)
{
const apr_size_t chunksize = 1024;
if (*buffer && ((*offset + ensure) < chunksize))
return;
if (*buffer) {
serf_bucket_aggregate_append(
bucket,
serf_bucket_simple_own_create(*buffer, *offset, bucket->allocator));
*buffer = NULL;
}
*offset = 0;
*buffer = serf_bucket_mem_alloc(bucket->allocator,
MAX(chunksize, ensure + 64));
*offset = 0;
}
static apr_status_t
serialize(serf_bucket_t *bucket)
{
serf_hpack_context_t *ctx = bucket->data;
serf_bucket_alloc_t *alloc = bucket->allocator;
serf_hpack_table_t *tbl = ctx->tbl;
serf_hpack_entry_t *entry;
serf_hpack_entry_t *next;
char *buffer = NULL;
apr_size_t offset = 0;
/* Put on our aggregate bucket cloak */
serf_bucket_aggregate_become(bucket);
/* Is there a tablesize update queued? */
if (tbl && tbl->send_tablesize_update)
{
apr_size_t len;
serialize_ensure_buffer(bucket, 8, &buffer, &offset);
hpack_int(0x20, 5, tbl->lr_max_table_size, buffer + offset, &len);
offset += len;
tbl->send_tablesize_update = FALSE;
}
for (entry = ctx->first; entry; entry = next)
{
apr_status_t status;
apr_size_t len;
apr_uint32_t reuse = 0;
const serf_hpack_entry_t *e;
bool reuseVal = false;
apr_uint32_t i;
next = entry->next;
serialize_ensure_buffer(bucket, 16, &buffer, &offset);
for (i = 0; i < hpack_static_table_count; i++) {
e = &hpack_static_table[i];
if (e->key_len == entry->key_len
&& !memcmp(e->key, entry->key, e->key_len))
{
if (e->value_len == entry->value_len
&& !memcmp(e->value, entry->value, e->value_len))
{
reuse = i+1;
reuseVal = true;
break;
}
if (!reuse)
reuse = i+1;
}
}
if (!reuseVal) {
for (e = tbl->lr_first; e; e = e->next) {
i++;
if (e->key_len == entry->key_len
&& !memcmp(e->key, entry->key, e->key_len))
{
if (e->value_len == entry->value_len
&& !memcmp(e->value, entry->value, e->value_len))
{
reuse = i;
reuseVal = true;
break;
}
if (!reuse)
reuse = i;
}
}
}
if (reuseVal) {
/* Nice, we have an exact match of key+value. We can
use those, but never index them. */
hpack_int(0x80, 7, reuse, buffer + offset, &len);
offset += len;
}
else if (!entry->dont_index) {
/* We reuse the header name, but will add our own value.
Or we don't reuse, but do index (value 0) */
hpack_int(0x40, 6, reuse, buffer + offset, &len);
offset += len;
}
else if (entry->dont_index == 2) {
/* Never index the value */
hpack_int(0x10, 4, reuse, buffer + offset, &len);
offset += len;
}
else {
hpack_int(0x00, 4, reuse, buffer + offset, &len);
offset += len;
}
if (!reuse) {
/* To huff or not... */
status = serf__hpack_huffman_encode(entry->key, entry->key_len,
0, NULL, &len);
if (!status && len < entry->key_len)
{
apr_size_t int_len;
/* It is more efficient to huffman encode */
serialize_ensure_buffer(bucket, 8 + len, &buffer, &offset);
hpack_int(0x80, 7, len, buffer + offset, &int_len);
offset += int_len;
status = serf__hpack_huffman_encode(entry->key, entry->key_len,
len,
(void*)(buffer + offset),
&len);
offset += len;
if (status)
return status;
}
else
{
/* It is more efficient not to encode */
serialize_ensure_buffer(bucket, 8 + entry->key_len,
&buffer, &offset);
hpack_int(0x00, 7, entry->key_len, buffer + offset, &len);
offset += len;
memcpy(buffer + offset, entry->key, entry->key_len);
offset += entry->key_len;
}
}
if (!reuseVal) {
/* To huff or not... */
status = serf__hpack_huffman_encode(entry->value, entry->value_len,
0, NULL, &len);
if (!status && len < entry->key_len)
{
apr_size_t int_len;
/* It is more efficient to huffman encode */
serialize_ensure_buffer(bucket, 8 + len, &buffer, &offset);
hpack_int(0x80, 7, len, buffer + offset, &int_len);
offset += int_len;
status = serf__hpack_huffman_encode(entry->value,
entry->value_len,
len,
(void*)(buffer + offset),
&len);
offset += len;
if (status)
return status;
}
else
{
/* It is more efficient not to encode */
serialize_ensure_buffer(bucket, 8 + entry->value_len,
&buffer, &offset);
hpack_int(0x00, 7, entry->value_len, buffer + offset, &len);
offset += len;
memcpy(buffer + offset, entry->value, entry->value_len);
offset += entry->value_len;
}
}
/* If we didn't re-use key+value and we are allowed to index the
item, let's do that. Note that we already told the other side that
we did this, as that side has to keep its table in sync */
if (!reuseVal && !entry->dont_index) {
serf_hpack_entry_t *tbl_entry;
tbl_entry = serf_bucket_mem_calloc(tbl->alloc, sizeof(*tbl_entry));
tbl_entry->key = serf_bstrmemdup(tbl->alloc,
entry->key, entry->key_len);
tbl_entry->key_len = entry->key_len;
tbl_entry->value = serf_bstrmemdup(tbl->alloc,
entry->value, entry->value_len);
tbl_entry->value_len = entry->value_len;
tbl_entry->free_key = entry->free_val = true;
tbl_entry->next = tbl->lr_first;
tbl->lr_first = tbl_entry;
tbl->lr_size += HPACK_ENTRY_SIZE(tbl_entry);
if (tbl_entry->next)
tbl_entry->next->prev = tbl_entry;
else
tbl->lr_last = tbl_entry;
if (tbl->lr_size > tbl->rl_max_table_size) {
hpack_shrink_table(&tbl->lr_first,
&tbl->lr_last, &tbl->lr_size,
tbl->lr_max_table_size, tbl->alloc);
}
}
/* And now free the item */
hpack_free_entry(entry, alloc);
}
ctx->first = ctx->last = NULL;
if (buffer)
{
if (offset)
{
serf_bucket_aggregate_append(
bucket,
serf_bucket_simple_own_create(buffer, offset, alloc));
}
else
serf_bucket_mem_free(alloc, buffer);
}
serf_bucket_mem_free(alloc, ctx);
return APR_SUCCESS;
}
static apr_status_t
serf_hpack_read(serf_bucket_t *bucket,
apr_size_t requested,
const char **data,
apr_size_t *len)
{
apr_status_t status = serialize(bucket);
if (status)
return status;
return bucket->type->read(bucket, requested, data, len);
}
static apr_status_t
serf_hpack_read_iovec(serf_bucket_t *bucket,
apr_size_t requested,
int vecs_size,
struct iovec *vecs,
int *vecs_used)
{
apr_status_t status = serialize(bucket);
if (status)
return status;
return bucket->type->read_iovec(bucket, requested, vecs_size, vecs,
vecs_used);
}
static apr_status_t
serf_hpack_peek(serf_bucket_t *bucket,
const char **data,
apr_size_t *len)
{
apr_status_t status = serialize(bucket);
if (status)
return status;
return bucket->type->peek(bucket, data, len);
}
static apr_uint64_t
serf_hpack_get_remaining(serf_bucket_t *bucket)
{
apr_status_t status = serialize(bucket);
if (status)
return SERF_LENGTH_UNKNOWN;
/* This assumes that the aggregate is a v2 bucket */
return bucket->type->get_remaining(bucket);
}
static void
serf_hpack_destroy_and_data(serf_bucket_t *bucket)
{
serf_hpack_context_t *ctx = bucket->data;
serf_hpack_entry_t *hi;
serf_hpack_entry_t *next;
for (hi = ctx->first; hi; hi = next)
{
next = hi->next;
hpack_free_entry(hi, bucket->allocator);
}
serf_default_destroy_and_data(bucket);
}
const serf_bucket_type_t serf_bucket_type__hpack = {
"HPACK",
serf_hpack_read,
serf_default_readline,
serf_hpack_read_iovec,
serf_default_read_for_sendfile,
serf_buckets_are_v2,
serf_hpack_peek,
serf_hpack_destroy_and_data,
serf_default_read_bucket,
serf_hpack_get_remaining,
serf_default_ignore_config,
};
/* ==================================================================== */
typedef struct serf_hpack_decode_ctx_t
{
serf_hpack_table_t *tbl;
serf_bucket_t *stream;
apr_size_t header_allowed;
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_uint32_t reuse_item;
enum
{
HPACK_DECODE_STATE_INITIAL = 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_bucket_t *headers; /* When not NULL added in agg */
serf_config_t *config;
bool is_request;
bool hit_eof;
const char *method;
const char *path;
const char *authority;
} serf_hpack_decode_ctx_t;
serf_bucket_t *
serf__bucket_hpack_decode_create(serf_bucket_t *stream,
apr_size_t max_header_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->tbl = hpack_table;
ctx->stream = stream;
ctx->header_allowed = max_header_size;
/* The buffer should be large enough to keep a *compressed* key
or value and will be resized if necessary.
Longer keys are more likely to use compression, so the default
should be enough for simple requests.
(It is also used for compressed integer values, but there 10 bytes
should be enough to store a uint64 with 7 bits/byte) */
ctx->buffer_size = 128;
ctx->buffer_used = 0;
ctx->buffer = serf_bucket_mem_alloc(alloc, ctx->buffer_size);
ctx->agg = serf_bucket_aggregate_create(alloc);
ctx->headers = NULL;
return serf_bucket_create(&serf_bucket_type__hpack_decode, alloc, ctx);
}
static void
hpack_decode_buffer_ensure(serf_bucket_t *bucket,
apr_size_t minsize)
{
serf_hpack_decode_ctx_t *ctx = bucket->data;
char *new_buffer;
if (minsize < ctx->buffer_size)
return;
while (minsize < ctx->buffer_size)
{
ctx->buffer_size *= 2;
}
new_buffer = serf_bucket_mem_alloc(bucket->allocator,
ctx->buffer_size);
/* In general only a small part of the old buffer is used at this point */
memcpy(new_buffer, ctx->buffer, ctx->buffer_used);
serf_bucket_mem_free(bucket->allocator, ctx->buffer);
ctx->buffer = new_buffer;
}
static apr_status_t
read_hpack_int(apr_uint32_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)
return SERF_ERROR_EMPTY_READ;
else if (SERF_BUCKET_READ_ERROR(status) || len == 0)
return status;
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;
/* Here we read the necessary bytes for the integer upto the
first byte that doesn't have the 0x80 bit set.
We could try to be smart by peeking, getting the size if
possible, etc.... but that would optimize for large ints
while the value typically fits in 1 or 2 bytes max.
My guess is that trying to be smart will be more expensive
here. */
do
{
const char *data;
apr_size_t len;
/* We already have all the bits we can store */
if ((7 * (ctx->buffer_used - 1) + bits) >= 32)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
status = serf_bucket_read(ctx->stream, 1, &data, &len);
if (!status && !len)
return SERF_ERROR_EMPTY_READ;
else if (SERF_BUCKET_READ_ERROR(status) || len == 0)
return status;
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 it
can then this is a compression error.
The value where all the bits in the first byte are 1 really
needs the next byte as 0, to encode that. */
if (ctx->buffer_used > 2 && 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));
if ((vv & APR_UINT32_MAX) != vv)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
}
*v = (apr_uint32_t)vv;
if (flags)
*flags = (((unsigned char)ctx->buffer[0]) & ~value_mask);
ctx->buffer_used = 0; /* Done with buffer */
return APR_SUCCESS;
}
static void
write_request_header(serf_hpack_decode_ctx_t *ctx)
{
serf_bucket_t *b;
serf_bucket_alloc_t *alloc = ctx->agg->allocator;
if (ctx->method)
b = serf_bucket_simple_own_create(ctx->method, strlen(ctx->method),
alloc);
else
b = SERF_BUCKET_SIMPLE_STRING("GET", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
b = SERF_BUCKET_SIMPLE_STRING(" ", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
if (ctx->path)
b = serf_bucket_simple_own_create(ctx->path, strlen(ctx->path),
alloc);
else
b = SERF_BUCKET_SIMPLE_STRING("/", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
b = SERF_BUCKET_SIMPLE_STRING(" HTTP/2.0\r\n", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
if (ctx->authority)
{
b = SERF_BUCKET_SIMPLE_STRING("Host: ", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
b = serf_bucket_simple_own_create(ctx->authority,
strlen(ctx->authority),
alloc);
serf_bucket_aggregate_append(ctx->agg, b);
b = SERF_BUCKET_SIMPLE_STRING("\r\n", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
}
/* Now owned by bucket */
ctx->method = ctx->path = ctx->authority = NULL;
ctx->headers = serf_bucket_headers_create(ctx->agg->allocator);
serf_bucket_aggregate_append(ctx->agg, ctx->headers);
}
static apr_status_t
handle_read_entry_and_clear(serf_hpack_decode_ctx_t *ctx,
serf_bucket_alloc_t *alloc)
{
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;
serf__log(LOGLVL_INFO, SERF_LOGCOMP_PROTOCOL, __FILE__, ctx->config,
"Parsed from HPACK: %.*s: %.*s\n",
ctx->key_size, ctx->key, ctx->val_size, ctx->val);
if (!ctx->headers)
{
serf_bucket_t *b;
if (ctx->key_size == 7 && !strcmp(ctx->key, ":status"))
{
b = SERF_BUCKET_SIMPLE_STRING("HTTP/2.0 ", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
b = serf_bucket_simple_copy_create(ctx->val, ctx->val_size, alloc);
serf_bucket_aggregate_append(ctx->agg, b);
b = SERF_BUCKET_SIMPLE_STRING(" <http2>\r\n", alloc);
serf_bucket_aggregate_append(ctx->agg, b);
ctx->headers = serf_bucket_headers_create(alloc);
serf_bucket_aggregate_append(ctx->agg, ctx->headers);
}
else if (ctx->key_size == 7 && !strcmp(ctx->key, ":method"))
{
ctx->is_request = true;
ctx->method = serf_bstrmemdup(ctx->agg->allocator,
ctx->val, ctx->val_size);
if (ctx->authority && ctx->method && ctx->path)
write_request_header(ctx);
}
else if (ctx->key_size == 10 && !strcmp(ctx->key, ":authority"))
{
ctx->is_request = true;
ctx->authority = serf_bstrmemdup(ctx->agg->allocator,
ctx->val, ctx->val_size);
if (ctx->authority && ctx->method && ctx->path)
write_request_header(ctx);
}
else if (ctx->key_size == 5 && !strcmp(ctx->key, ":path"))
{
ctx->is_request = true;
ctx->path = serf_bstrmemdup(ctx->agg->allocator,
ctx->val, ctx->val_size);
if (ctx->authority && ctx->method && ctx->path)
write_request_header(ctx);
}
else if (ctx->key_size && ctx->key[0] == ':')
{
/* Ignore all magic headers */
}
else
{
/* Write some header with some status code first */
if (ctx->is_request)
write_request_header(ctx);
else
{
b = SERF_BUCKET_SIMPLE_STRING(
"HTTP/2.0 505 Missing ':status' header\r\n",
alloc);
serf_bucket_aggregate_append(ctx->agg, b);
ctx->headers = serf_bucket_headers_create(alloc);
serf_bucket_aggregate_append(ctx->agg, ctx->headers);
}
serf_bucket_headers_setc(ctx->headers, ctx->key, ctx->val);
}
}
else if (ctx->key_size && ctx->key[0] != ':')
{
serf_bucket_headers_setc(ctx->headers, ctx->key, ctx->val);
}
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->key_len = ctx->key_size;
entry->value = own_val ? ctx->val : serf_bstrmemdup(tbl->alloc,
ctx->val,
ctx->val_size);
entry->value_len = ctx->val_size;
entry->free_key = entry->free_val = true;
entry->next = tbl->rl_first;
tbl->rl_first = entry;
tbl->rl_size += HPACK_ENTRY_SIZE(entry);
if (entry->next)
entry->next->prev = entry;
else
tbl->rl_last = entry;
}
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;
}
/* Reads the exact amount of bytes, buffered if necessary.
Note: APR_EOF is not returned in case we have everything
we need. The callers depend on this behavior
*/
static apr_status_t hpack_read_bytes(serf_bucket_t *bucket,
apr_size_t required,
const void **data)
{
serf_hpack_decode_ctx_t *ctx = bucket->data;
apr_status_t status = APR_SUCCESS;
apr_size_t len;
const char *some_data;
/* assert(required < ctx->buffer_used); */
if (required == 0)
{
*data = ctx->buffer;
return APR_SUCCESS;
}
if (!ctx->buffer_used)
{
status = serf_bucket_read(ctx->stream, required, &some_data, &len);
if (SERF_BUCKET_READ_ERROR(status) || (len == required))
{
if (APR_STATUS_IS_EOF(status) && len == required)
status = APR_SUCCESS;
*data = some_data;
return status;
}
hpack_decode_buffer_ensure(bucket, required);
memcpy(ctx->buffer, some_data, len);
ctx->buffer_used = len;
if (status)
return status;
/* Fall through: Try to continue reading*/
}
else
{
/* Ensure that the buffer is large enough to hold everything */
hpack_decode_buffer_ensure(bucket, required);
}
while (ctx->buffer_used < required)
{
status = serf_bucket_read(ctx->stream, required - ctx->buffer_used,
&some_data, &len);
if (SERF_BUCKET_READ_ERROR(status))
return status;
memcpy(ctx->buffer + ctx->buffer_used, some_data, len);
ctx->buffer_used += len;
if (status)
break;
else if (!status && !len)
return SERF_ERROR_EMPTY_READ;
}
if (ctx->buffer_used == required)
{
*data = ctx->buffer;
ctx->buffer_used = 0; /* Done with buffer */
status = APR_SUCCESS;
}
return status;
}
static apr_status_t
hpack_process(serf_bucket_t *bucket)
{
serf_hpack_decode_ctx_t *ctx = bucket->data;
apr_status_t status = APR_SUCCESS;
if (ctx->hit_eof)
return APR_SUCCESS;
while (status == APR_SUCCESS)
{
switch (ctx->state)
{
case HPACK_DECODE_STATE_INITIAL:
{
unsigned char uc;
const char *data;
apr_size_t len;
status = serf_bucket_read(ctx->stream, 1, &data, &len);
if (!status && !len)
return SERF_ERROR_EMPTY_READ;
else if (SERF_BUCKET_READ_ERROR(status) || len == 0)
break;
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; /* Initial state for
read_hpack_int() */
ctx->index_item = FALSE;
}
else if (uc == 0x40 || uc == 0x00 || uc == 0x10)
{
/* 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
-- New Name
https://tools.ietf.org/html/rfc7541#section-6.2.2
0x10: Literal Header Field Never Indexed
-- New Name
https://tools.ietf.org/html/rfc7541#section-6.2.3 */
ctx->state = HPACK_DECODE_STATE_KEY_LEN;
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; /* Initial state for
read_hpack_int() */
}
else
{
/* 6.2.1 Literal Header Field with Incremental Indexing
-- Indexed Name
https://tools.ietf.org/html/rfc7541#section-6.2.1
6.2.2: Literal Header Field without Indexing
-- Indexed Name
https://tools.ietf.org/html/rfc7541#section-6.2.2
6.2.3. Literal Header Field Never Indexed
-- Indexed Name
https://tools.ietf.org/html/rfc7541#section-6.2.3 */
ctx->state = HPACK_DECODE_STATE_KEYINDEX;
ctx->buffer[0] = *data;
ctx->buffer_used = 1; /* Initial state for
read_hpack_int() */
ctx->index_item = (uc & 0x40) != 0;
}
status = APR_SUCCESS; /* Or we exit the loop */
break;
}
case HPACK_DECODE_STATE_INDEX:
{
apr_uint32_t v;
status = read_hpack_int(&v, NULL, bucket, 7);
if (status)
break;
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)
return status;
if (ctx->header_allowed <= HPACK_KEY_SIZE(ctx->key_size)
+ HPACK_KEY_SIZE(ctx->val_size))
{
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
}
ctx->header_allowed -= HPACK_KEY_SIZE(ctx->key_size)
+ HPACK_KEY_SIZE(ctx->val_size);
status = handle_read_entry_and_clear(ctx,
bucket->allocator);
if (status)
return status;
/* Get key and value from table and handle result */
ctx->state = HPACK_DECODE_STATE_INITIAL;
break;
}
case HPACK_DECODE_STATE_KEYINDEX:
{
apr_uint32_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)
return status;
/* Get key from table */
ctx->state = HPACK_DECODE_STATE_VALUE_LEN;
if (HPACK_KEY_SIZE(ctx->key_size) >= ctx->header_allowed)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
ctx->header_allowed -= HPACK_KEY_SIZE(ctx->key_size);
break;
}
case HPACK_DECODE_STATE_KEY_LEN:
{
apr_uint32_t v;
unsigned char flags;
status = read_hpack_int(&v, &flags, bucket, 7);
if (status)
continue;
ctx->key_hm = (flags & 0x80) != 0;
/* Just check compressed size first. If the result is
smaller the encoder shouldn't have used compression */
if (HPACK_KEY_SIZE(v) >= ctx->header_allowed)
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 void *data;
status = hpack_read_bytes(bucket, ctx->key_size, &data);
if (status)
continue;
if (ctx->key_hm)
{
apr_size_t ks;
char *key;
status = serf__hpack_huffman_decode(data,
ctx->key_size,
0, NULL, &ks);
if (status)
return status;
if (HPACK_KEY_SIZE(ks) >= ctx->header_allowed)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
key = serf_bucket_mem_alloc(ctx->tbl->alloc, ks + 1);
status = serf__hpack_huffman_decode(data,
ctx->key_size,
ks + 1, key,
&ctx->key_size);
if (status)
return status;
ctx->key = key;
}
else
ctx->key = serf_bstrmemdup(ctx->tbl->alloc, data,
ctx->key_size);
ctx->state = HPACK_DECODE_STATE_VALUE_LEN;
ctx->header_allowed -= HPACK_KEY_SIZE(ctx->key_size);
/* Fall through */
}
case HPACK_DECODE_STATE_VALUE_LEN:
{
apr_uint32_t v;
unsigned char flags;
status = read_hpack_int(&v, &flags, bucket, 7);
if (status)
continue;
ctx->val_hm = (flags & 0x80) != 0;
/* Just check compressed size first. If the result is
smaller the encoder shouldn't have used compression */
if (HPACK_KEY_SIZE(v) >= ctx->header_allowed)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
ctx->val_size = v;
ctx->state = HPACK_DECODE_STATE_VALUE;
/* Fall through */
}
case HPACK_DECODE_STATE_VALUE:
{
const void *data;
status = hpack_read_bytes(bucket, ctx->val_size, &data);
if (status)
continue;
if (ctx->val_hm)
{
apr_size_t ks;
char *val;
status = serf__hpack_huffman_decode(data,
ctx->val_size,
0, NULL, &ks);
if (status)
return status;
if (HPACK_KEY_SIZE(ks) >= ctx->header_allowed)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
val = serf_bucket_mem_alloc(ctx->tbl->alloc, ks + 1);
status = serf__hpack_huffman_decode(data,
ctx->val_size,
ks + 1, val,
&ctx->val_size);
if (status)
return status;
ctx->val = val;
}
else
ctx->val = serf_bstrmemdup(ctx->tbl->alloc, data,
ctx->val_size);
ctx->header_allowed -= HPACK_KEY_SIZE(ctx->val_size);
status = handle_read_entry_and_clear(ctx,
bucket->allocator);
if (status)
return status;
ctx->state = HPACK_DECODE_STATE_INITIAL;
break;
}
case HPACK_DECODE_TABLESIZE_UPDATE:
{
apr_uint32_t v;
status = read_hpack_int(&v, NULL, bucket, 5);
if (status)
continue;
/* Send remote tablesize update to our table */
if (v >= APR_SIZE_MAX)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
status = hpack_table_size_update(ctx->tbl, (apr_size_t)v);
if (status)
return status;
ctx->state = HPACK_DECODE_STATE_INITIAL;
break;
}
default:
abort();
}
}
if (APR_STATUS_IS_EOF(status))
{
if (ctx->state != HPACK_DECODE_STATE_INITIAL)
return SERF_ERROR_HTTP2_COMPRESSION_ERROR;
if (!ctx->hit_eof)
{
serf_hpack_table_t *tbl = ctx->tbl;
ctx->hit_eof = TRUE;
hpack_shrink_table(&tbl->rl_first,
&tbl->rl_last, &tbl->rl_size,
tbl->rl_max_table_size, tbl->alloc);
}
return APR_SUCCESS;
}
return status;
}
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 (status)
{
*len = 0;
return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : status;
}
return serf_bucket_read(ctx->agg, requested, data, len);
}
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 (status)
{
*len = 0;
return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : status;
}
return serf_bucket_peek(ctx->agg, data, len);
}
static serf_bucket_t *
serf_hpack_decode_read_bucket(serf_bucket_t *bucket,
const serf_bucket_type_t *type)
{
serf_hpack_decode_ctx_t *ctx = bucket->data;
if (!ctx->hit_eof)
return serf_default_read_bucket(bucket, type);
else
return serf_bucket_read_bucket(ctx->agg, type);
}
static apr_status_t
serf_hpack_decode_set_config(serf_bucket_t *bucket,
serf_config_t *config)
{
serf_hpack_decode_ctx_t *ctx = bucket->data;
apr_status_t status;
ctx->config = config;
status = serf_bucket_set_config(ctx->stream, config);
if (status)
return status;
if (ctx->agg)
{
status = serf_bucket_set_config(ctx->agg, config);
if (status)
return status;
}
return APR_SUCCESS;
}
static void
serf_hpack_decode_destroy(serf_bucket_t *bucket)
{
serf_hpack_decode_ctx_t *ctx = bucket->data;
serf_bucket_destroy(ctx->stream);
serf_bucket_destroy(ctx->agg);
if (ctx->method)
serf_bucket_mem_free(bucket->allocator, (void*)ctx->method);
if (ctx->path)
serf_bucket_mem_free(bucket->allocator, (void*)ctx->method);
if (ctx->authority)
serf_bucket_mem_free(bucket->allocator, (void*)ctx->authority);
serf_bucket_mem_free(bucket->allocator, ctx->buffer);
/* 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_default_readline,
serf_default_read_iovec,
serf_default_read_for_sendfile,
serf_buckets_are_v2,
serf_hpack_decode_peek,
serf_hpack_decode_destroy,
serf_hpack_decode_read_bucket,
serf_default_get_remaining,
serf_hpack_decode_set_config
};