summaryrefslogtreecommitdiffstats
path: root/modules/http2/h2_push.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/http2/h2_push.c')
-rw-r--r--modules/http2/h2_push.c339
1 files changed, 78 insertions, 261 deletions
diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c
index 9a3b19b..e6a10c5 100644
--- a/modules/http2/h2_push.c
+++ b/modules/http2/h2_push.c
@@ -23,19 +23,19 @@
#include <apr_time.h>
#ifdef H2_OPENSSL
-#include <openssl/sha.h>
+#include <openssl/evp.h>
#endif
#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
+#include <http_protocol.h>
#include "h2_private.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
#include "h2_util.h"
#include "h2_push.h"
#include "h2_request.h"
-#include "h2_headers.h"
#include "h2_session.h"
#include "h2_stream.h"
@@ -59,7 +59,7 @@ static const char *policy_str(h2_push_policy policy)
typedef struct {
const h2_request *req;
- int push_policy;
+ apr_uint32_t push_policy;
apr_pool_t *pool;
apr_array_header_t *pushes;
const char *s;
@@ -348,11 +348,10 @@ static int add_push(link_ctx *ctx)
}
headers = apr_table_make(ctx->pool, 5);
apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
- req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
- ctx->req->authority, path, headers,
- ctx->req->serialize);
+ req = h2_request_create(0, ctx->pool, method, ctx->req->scheme,
+ ctx->req->authority, path, headers);
/* atm, we do not push on pushes */
- h2_request_end_headers(req, ctx->pool, 1, 0);
+ h2_request_end_headers(req, ctx->pool, 0);
push->req = req;
if (has_param(ctx, "critical")) {
h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
@@ -427,14 +426,23 @@ static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
static int head_iter(void *ctx, const char *key, const char *value)
{
- if (!apr_strnatcasecmp("link", key)) {
+ if (!ap_cstr_casecmp("link", key)) {
inspect_link(ctx, value, strlen(value));
}
return 1;
}
-apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
- int push_policy, const h2_headers *res)
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const struct h2_headers *res)
+#endif
{
if (req && push_policy != H2_PUSH_NONE) {
/* Collect push candidates from the request/response pair.
@@ -464,33 +472,6 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
return NULL;
}
-/*******************************************************************************
- * push diary
- *
- * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
- * connection. It records a hash value from the absolute URL of the resource
- * pushed.
- * - Lacking openssl, it uses 'apr_hashfunc_default' for the value
- * - with openssl, it uses SHA256 to calculate the hash value
- * - whatever the method to generate the hash, the diary keeps a maximum of 64
- * bits per hash, limiting the memory consumption to about
- * H2PushDiarySize * 8
- * bytes. Entries are sorted by most recently used and oldest entries are
- * forgotten first.
- * - Clients can initialize/replace the push diary by sending a 'Cache-Digest'
- * header. Currently, this is the base64url encoded value of the cache digest
- * as specified in https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
- * This draft can be expected to evolve and the definition of the header
- * will be added there and refined.
- * - The cache digest header is a Golomb Coded Set of hash values, but it may
- * limit the amount of bits per hash value even further. For a good description
- * of GCS, read here:
- * http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters
- * - The means that the push diary might be initialized with hash values of much
- * less than 64 bits, leading to more false positives, but smaller digest size.
- ******************************************************************************/
-
-
#define GCSLOG_LEVEL APLOG_TRACE1
typedef struct h2_push_diary_entry {
@@ -499,27 +480,32 @@ typedef struct h2_push_diary_entry {
#ifdef H2_OPENSSL
-static void sha256_update(SHA256_CTX *ctx, const char *s)
+static void sha256_update(EVP_MD_CTX *ctx, const char *s)
{
- SHA256_Update(ctx, s, strlen(s));
+ EVP_DigestUpdate(ctx, s, strlen(s));
}
static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
{
- SHA256_CTX sha256;
+ EVP_MD_CTX *md;
apr_uint64_t val;
- unsigned char hash[SHA256_DIGEST_LENGTH];
- int i;
-
- SHA256_Init(&sha256);
- sha256_update(&sha256, push->req->scheme);
- sha256_update(&sha256, "://");
- sha256_update(&sha256, push->req->authority);
- sha256_update(&sha256, push->req->path);
- SHA256_Final(hash, &sha256);
+ unsigned char hash[EVP_MAX_MD_SIZE];
+ unsigned len, i;
+
+ md = EVP_MD_CTX_create();
+ ap_assert(md != NULL);
+
+ i = EVP_DigestInit_ex(md, EVP_sha256(), NULL);
+ ap_assert(i == 1);
+ sha256_update(md, push->req->scheme);
+ sha256_update(md, "://");
+ sha256_update(md, push->req->authority);
+ sha256_update(md, push->req->path);
+ EVP_DigestFinal(md, hash, &len);
+ EVP_MD_CTX_destroy(md);
val = 0;
- for (i = 0; i != sizeof(val); ++i)
+ for (i = 0; i != len; ++i)
val = val * 256 + hash[i];
*phash = val >> (64 - diary->mask_bits);
}
@@ -528,13 +514,14 @@ static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push
static unsigned int val_apr_hash(const char *str)
{
- apr_ssize_t len = strlen(str);
+ apr_ssize_t len = (apr_ssize_t)strlen(str);
return apr_hashfunc_default(str, &len);
}
static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
{
apr_uint64_t val;
+ (void)diary;
#if APR_UINT64_MAX > UINT_MAX
val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
@@ -617,38 +604,48 @@ static int h2_push_diary_find(h2_push_diary *diary, apr_uint64_t hash)
return -1;
}
-static h2_push_diary_entry *move_to_last(h2_push_diary *diary, apr_size_t idx)
+static void move_to_last(h2_push_diary *diary, apr_size_t idx)
{
h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
h2_push_diary_entry e;
- apr_size_t lastidx = diary->entries->nelts-1;
+ apr_size_t lastidx;
+ /* Move an existing entry to the last place */
+ if (diary->entries->nelts <= 0)
+ return;
+
/* move entry[idx] to the end */
+ lastidx = diary->entries->nelts - 1;
if (idx < lastidx) {
e = entries[idx];
- memmove(entries+idx, entries+idx+1, sizeof(e) * (lastidx - idx));
+ memmove(entries+idx, entries+idx+1, sizeof(h2_push_diary_entry) * (lastidx - idx));
entries[lastidx] = e;
}
- return &entries[lastidx];
}
-static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
+static void remove_first(h2_push_diary *diary)
{
- h2_push_diary_entry *ne;
+ h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
+ int lastidx;
- if (diary->entries->nelts < diary->N) {
- /* append a new diary entry at the end */
- APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
- ne = &APR_ARRAY_IDX(diary->entries, diary->entries->nelts-1, h2_push_diary_entry);
+ /* move remaining entries to index 0 */
+ lastidx = diary->entries->nelts - 1;
+ if (lastidx > 0) {
+ --diary->entries->nelts;
+ memmove(entries, entries+1, sizeof(h2_push_diary_entry) * diary->entries->nelts);
}
- else {
- /* replace content with new digest. keeps memory usage constant once diary is full */
- ne = move_to_last(diary, 0);
- *ne = *e;
+}
+
+static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
+{
+ while (diary->entries->nelts >= diary->N) {
+ remove_first(diary);
}
+ /* append a new diary entry at the end */
+ APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
/* Intentional no APLOGNO */
ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, diary->entries->pool,
- "push_diary_append: %"APR_UINT64_T_HEX_FMT, ne->hash);
+ "push_diary_append: %"APR_UINT64_T_HEX_FMT, e->hash);
}
apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t *pushes)
@@ -668,13 +665,13 @@ apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t
idx = h2_push_diary_find(session->push_diary, e.hash);
if (idx >= 0) {
/* Intentional no APLOGNO */
- ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
+ ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
"push_diary_update: already there PUSH %s", push->req->path);
- move_to_last(session->push_diary, idx);
+ move_to_last(session->push_diary, (apr_size_t)idx);
}
else {
/* Intentional no APLOGNO */
- ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
+ ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
"push_diary_update: adding PUSH %s", push->req->path);
if (!npushes) {
npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
@@ -687,34 +684,22 @@ apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t
return npushes;
}
-apr_array_header_t *h2_push_collect_update(h2_stream *stream,
- const struct h2_request *req,
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
const struct h2_headers *res)
+#endif
{
- h2_session *session = stream->session;
- const char *cache_digest = apr_table_get(req->headers, "Cache-Digest");
apr_array_header_t *pushes;
- apr_status_t status;
- if (cache_digest && session->push_diary) {
- status = h2_push_diary_digest64_set(session->push_diary, req->authority,
- cache_digest, stream->pool);
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
- H2_SSSN_LOG(APLOGNO(03057), session,
- "push diary set from Cache-Digest: %s"), cache_digest);
- }
- }
pushes = h2_push_collect(stream->pool, req, stream->push_policy, res);
return h2_push_diary_update(stream->session, pushes);
}
-static apr_int32_t h2_log2inv(unsigned char log2)
-{
- return log2? (1 << log2) : 1;
-}
-
-
typedef struct {
h2_push_diary *diary;
unsigned char log2p;
@@ -822,23 +807,18 @@ apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool,
int maxP, const char *authority,
const char **pdata, apr_size_t *plen)
{
- int nelts, N, i;
+ int nelts, N;
unsigned char log2n, log2pmax;
gset_encoder encoder;
apr_uint64_t *hashes;
- apr_size_t hash_count;
+ apr_size_t hash_count, i;
nelts = diary->entries->nelts;
-
- if (nelts > APR_UINT32_MAX) {
- /* should not happen */
- return APR_ENOTIMPL;
- }
N = ceil_power_of_2(nelts);
log2n = h2_log2(N);
/* Now log2p is the max number of relevant bits, so that
- * log2p + log2n == mask_bits. We can uise a lower log2p
+ * log2p + log2n == mask_bits. We can use a lower log2p
* and have a shorter set encoding...
*/
log2pmax = h2_log2(ceil_power_of_2(maxP));
@@ -895,166 +875,3 @@ apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool,
return APR_SUCCESS;
}
-typedef struct {
- h2_push_diary *diary;
- apr_pool_t *pool;
- unsigned char log2p;
- const unsigned char *data;
- apr_size_t datalen;
- apr_size_t offset;
- unsigned int bit;
- apr_uint64_t last_val;
-} gset_decoder;
-
-static int gset_decode_next_bit(gset_decoder *decoder)
-{
- if (++decoder->bit >= 8) {
- if (++decoder->offset >= decoder->datalen) {
- return -1;
- }
- decoder->bit = 0;
- }
- return (decoder->data[decoder->offset] & cbit_mask[decoder->bit])? 1 : 0;
-}
-
-static apr_status_t gset_decode_next(gset_decoder *decoder, apr_uint64_t *phash)
-{
- apr_uint64_t flex = 0, fixed = 0, delta;
- int i;
-
- /* read 1 bits until we encounter 0, then read log2n(diary-P) bits.
- * On a malformed bit-string, this will not fail, but produce results
- * which are pbly too large. Luckily, the diary will modulo the hash.
- */
- while (1) {
- int bit = gset_decode_next_bit(decoder);
- if (bit == -1) {
- return APR_EINVAL;
- }
- if (!bit) {
- break;
- }
- ++flex;
- }
-
- for (i = 0; i < decoder->log2p; ++i) {
- int bit = gset_decode_next_bit(decoder);
- if (bit == -1) {
- return APR_EINVAL;
- }
- fixed = (fixed << 1) | bit;
- }
-
- delta = (flex << decoder->log2p) | fixed;
- *phash = delta + decoder->last_val;
- decoder->last_val = *phash;
-
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, decoder->pool,
- "h2_push_diary_digest_dec: val=%"APR_UINT64_T_HEX_FMT", delta=%"
- APR_UINT64_T_HEX_FMT", flex=%d, fixed=%"APR_UINT64_T_HEX_FMT,
- *phash, delta, (int)flex, fixed);
-
- return APR_SUCCESS;
-}
-
-/**
- * Initialize the push diary by a cache digest as described in
- * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
- * .
- * @param diary the diary to set the digest into
- * @param data the binary cache digest
- * @param len the length of the cache digest
- * @return APR_EINVAL if digest was not successfully parsed
- */
-apr_status_t h2_push_diary_digest_set(h2_push_diary *diary, const char *authority,
- const char *data, apr_size_t len)
-{
- gset_decoder decoder;
- unsigned char log2n, log2p;
- int N, i;
- apr_pool_t *pool = diary->entries->pool;
- h2_push_diary_entry e;
- apr_status_t status = APR_SUCCESS;
-
- if (len < 2) {
- /* at least this should be there */
- return APR_EINVAL;
- }
- log2n = data[0];
- log2p = data[1];
- diary->mask_bits = log2n + log2p;
- if (diary->mask_bits > 64) {
- /* cannot handle */
- return APR_ENOTIMPL;
- }
-
- /* whatever is in the digest, it replaces the diary entries */
- apr_array_clear(diary->entries);
- if (!authority || !strcmp("*", authority)) {
- diary->authority = NULL;
- }
- else if (!diary->authority || strcmp(diary->authority, authority)) {
- diary->authority = apr_pstrdup(diary->entries->pool, authority);
- }
-
- N = h2_log2inv(log2n + log2p);
-
- decoder.diary = diary;
- decoder.pool = pool;
- decoder.log2p = log2p;
- decoder.data = (const unsigned char*)data;
- decoder.datalen = len;
- decoder.offset = 1;
- decoder.bit = 8;
- decoder.last_val = 0;
-
- diary->N = N;
- /* Determine effective N we use for storage */
- if (!N) {
- /* a totally empty cache digest. someone tells us that she has no
- * entries in the cache at all. Use our own preferences for N+mask
- */
- diary->N = diary->NMax;
- return APR_SUCCESS;
- }
- else if (N > diary->NMax) {
- /* Store not more than diary is configured to hold. We open us up
- * to DOS attacks otherwise. */
- diary->N = diary->NMax;
- }
-
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
- "h2_push_diary_digest_set: N=%d, log2n=%d, "
- "diary->mask_bits=%d, dec.log2p=%d",
- (int)diary->N, (int)log2n, diary->mask_bits,
- (int)decoder.log2p);
-
- for (i = 0; i < diary->N; ++i) {
- if (gset_decode_next(&decoder, &e.hash) != APR_SUCCESS) {
- /* the data may have less than N values */
- break;
- }
- h2_push_diary_append(diary, &e);
- }
-
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
- "h2_push_diary_digest_set: diary now with %d entries, mask_bits=%d",
- (int)diary->entries->nelts, diary->mask_bits);
- return status;
-}
-
-apr_status_t h2_push_diary_digest64_set(h2_push_diary *diary, const char *authority,
- const char *data64url, apr_pool_t *pool)
-{
- const char *data;
- apr_size_t len = h2_util_base64url_decode(&data, data64url, pool);
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
- "h2_push_diary_digest64_set: digest=%s, dlen=%d",
- data64url, (int)len);
- return h2_push_diary_digest_set(diary, authority, data, len);
-}
-