520 lines
18 KiB
C
520 lines
18 KiB
C
/* 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.
|
|
*/
|
|
|
|
#ifndef __mod_h2__h2_util__
|
|
#define __mod_h2__h2_util__
|
|
|
|
#include <nghttp2/nghttp2.h>
|
|
#include <http_protocol.h>
|
|
|
|
#include "h2.h"
|
|
#include "h2_headers.h"
|
|
|
|
/*******************************************************************************
|
|
* some debugging/format helpers
|
|
******************************************************************************/
|
|
struct h2_request;
|
|
struct nghttp2_frame;
|
|
|
|
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
|
|
const char *data, size_t datalen);
|
|
|
|
void h2_util_camel_case_header(char *s, size_t len);
|
|
|
|
int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
|
|
|
|
/*******************************************************************************
|
|
* ihash - hash for structs with int identifier
|
|
******************************************************************************/
|
|
typedef struct h2_ihash_t h2_ihash_t;
|
|
typedef int h2_ihash_iter_t(void *ctx, void *val);
|
|
|
|
/**
|
|
* Create a hash for structures that have an identifying int member.
|
|
* @param pool the pool to use
|
|
* @param offset_of_int the offsetof() the int member in the struct
|
|
*/
|
|
h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int);
|
|
|
|
unsigned int h2_ihash_count(h2_ihash_t *ih);
|
|
int h2_ihash_empty(h2_ihash_t *ih);
|
|
void *h2_ihash_get(h2_ihash_t *ih, int id);
|
|
|
|
/**
|
|
* Iterate over the hash members (without defined order) and invoke
|
|
* fn for each member until 0 is returned.
|
|
* @param ih the hash to iterate over
|
|
* @param fn the function to invoke on each member
|
|
* @param ctx user supplied data passed into each iteration call
|
|
* @return 0 if one iteration returned 0, otherwise != 0
|
|
*/
|
|
int h2_ihash_iter(h2_ihash_t *ih, h2_ihash_iter_t *fn, void *ctx);
|
|
|
|
void h2_ihash_add(h2_ihash_t *ih, void *val);
|
|
void h2_ihash_remove(h2_ihash_t *ih, int id);
|
|
void h2_ihash_remove_val(h2_ihash_t *ih, void *val);
|
|
void h2_ihash_clear(h2_ihash_t *ih);
|
|
|
|
size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max);
|
|
|
|
/*******************************************************************************
|
|
* iqueue - sorted list of int with user defined ordering
|
|
******************************************************************************/
|
|
typedef struct h2_iqueue {
|
|
int *elts;
|
|
int head;
|
|
int nelts;
|
|
int nalloc;
|
|
apr_pool_t *pool;
|
|
} h2_iqueue;
|
|
|
|
/**
|
|
* Comparator for two int to determine their order.
|
|
*
|
|
* @param i1 first int to compare
|
|
* @param i2 second int to compare
|
|
* @param ctx provided user data
|
|
* @return value is the same as for strcmp() and has the effect:
|
|
* == 0: s1 and s2 are treated equal in ordering
|
|
* < 0: s1 should be sorted before s2
|
|
* > 0: s2 should be sorted before s1
|
|
*/
|
|
typedef int h2_iq_cmp(int i1, int i2, void *ctx);
|
|
|
|
/**
|
|
* Allocate a new queue from the pool and initialize.
|
|
* @param pool the memory pool
|
|
* @param capacity the initial capacity of the queue
|
|
*/
|
|
h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity);
|
|
|
|
/**
|
|
* Return != 0 iff there are no ints in the queue.
|
|
* @param q the queue to check
|
|
*/
|
|
int h2_iq_empty(h2_iqueue *q);
|
|
|
|
/**
|
|
* Return the number of int in the queue.
|
|
* @param q the queue to get size on
|
|
*/
|
|
int h2_iq_count(h2_iqueue *q);
|
|
|
|
/**
|
|
* Add a stream id to the queue.
|
|
*
|
|
* @param q the queue to append the id to
|
|
* @param sid the stream id to add
|
|
* @param cmp the comparator for sorting
|
|
* @param ctx user data for comparator
|
|
* @return != 0 iff id was not already there
|
|
*/
|
|
int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
|
|
|
|
/**
|
|
* Append the id to the queue if not already present.
|
|
*
|
|
* @param q the queue to append the id to
|
|
* @param sid the id to append
|
|
* @return != 0 iff id was not already there
|
|
*/
|
|
int h2_iq_append(h2_iqueue *q, int sid);
|
|
|
|
/**
|
|
* Remove the int from the queue. Return != 0 iff it was found.
|
|
* @param q the queue
|
|
* @param sid the stream id to remove
|
|
* @return != 0 iff int was found in queue
|
|
*/
|
|
int h2_iq_remove(h2_iqueue *q, int sid);
|
|
|
|
/**
|
|
* Remove all entries in the queue.
|
|
*/
|
|
void h2_iq_clear(h2_iqueue *q);
|
|
|
|
/**
|
|
* Sort the stream idqueue again. Call if the int ordering
|
|
* has changed.
|
|
*
|
|
* @param q the queue to sort
|
|
* @param cmp the comparator for sorting
|
|
* @param ctx user data for the comparator
|
|
*/
|
|
void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx);
|
|
|
|
/**
|
|
* Get the first id from the queue or 0 if the queue is empty.
|
|
* The id is being removed.
|
|
*
|
|
* @param q the queue to get the first id from
|
|
* @return the first id of the queue, 0 if empty
|
|
*/
|
|
int h2_iq_shift(h2_iqueue *q);
|
|
|
|
/**
|
|
* Get the first max ids from the queue. All these ids will be removed.
|
|
*
|
|
* @param q the queue to get the first ids from
|
|
* @param pint the int array to receive the values
|
|
* @param max the maximum number of ids to shift
|
|
* @return the actual number of ids shifted
|
|
*/
|
|
size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max);
|
|
|
|
/**
|
|
* Determine if int is in the queue already
|
|
*
|
|
* @param q the queue
|
|
* @param sid the integer id to check for
|
|
* @return != 0 iff sid is already in the queue
|
|
*/
|
|
int h2_iq_contains(h2_iqueue *q, int sid);
|
|
|
|
/*******************************************************************************
|
|
* FIFO queue (void* elements)
|
|
******************************************************************************/
|
|
|
|
/**
|
|
* A thread-safe FIFO queue with some extra bells and whistles, if you
|
|
* do not need anything special, better use 'apr_queue'.
|
|
*/
|
|
typedef struct h2_fifo h2_fifo;
|
|
|
|
/**
|
|
* Create a FIFO queue that can hold up to capacity elements. Elements can
|
|
* appear several times.
|
|
*/
|
|
apr_status_t h2_fifo_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
|
|
|
|
/**
|
|
* Create a FIFO set that can hold up to capacity elements. Elements only
|
|
* appear once. Pushing an element already present does not change the
|
|
* queue and is successful.
|
|
*/
|
|
apr_status_t h2_fifo_set_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
|
|
|
|
apr_status_t h2_fifo_term(h2_fifo *fifo);
|
|
|
|
int h2_fifo_count(h2_fifo *fifo);
|
|
|
|
/**
|
|
* Push en element into the queue. Blocks if there is no capacity left.
|
|
*
|
|
* @param fifo the FIFO queue
|
|
* @param elem the element to push
|
|
* @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
|
|
* APR_EEXIST when in set mode and elem already there.
|
|
*/
|
|
apr_status_t h2_fifo_push(h2_fifo *fifo, void *elem);
|
|
apr_status_t h2_fifo_try_push(h2_fifo *fifo, void *elem);
|
|
|
|
apr_status_t h2_fifo_pull(h2_fifo *fifo, void **pelem);
|
|
apr_status_t h2_fifo_try_pull(h2_fifo *fifo, void **pelem);
|
|
|
|
typedef enum {
|
|
H2_FIFO_OP_PULL, /* pull the element from the queue, ie discard it */
|
|
H2_FIFO_OP_REPUSH, /* pull and immediately re-push it */
|
|
} h2_fifo_op_t;
|
|
|
|
typedef h2_fifo_op_t h2_fifo_peek_fn(void *head, void *ctx);
|
|
|
|
/**
|
|
* Call given function on the head of the queue, once it exists, and
|
|
* perform the returned operation on it. The queue will hold its lock during
|
|
* this time, so no other operations on the queue are possible.
|
|
* @param fifo the queue to peek at
|
|
* @param fn the function to call on the head, once available
|
|
* @param ctx context to pass in call to function
|
|
*/
|
|
apr_status_t h2_fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx);
|
|
|
|
/**
|
|
* Non-blocking version of h2_fifo_peek.
|
|
*/
|
|
apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx);
|
|
|
|
/**
|
|
* Remove the elem from the queue, will remove multiple appearances.
|
|
* @param elem the element to remove
|
|
* @return APR_SUCCESS iff > 0 elems were removed, APR_EAGAIN otherwise.
|
|
*/
|
|
apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem);
|
|
|
|
/*******************************************************************************
|
|
* iFIFO queue (int elements)
|
|
******************************************************************************/
|
|
|
|
/**
|
|
* A thread-safe FIFO queue with some extra bells and whistles, if you
|
|
* do not need anything special, better use 'apr_queue'.
|
|
*/
|
|
typedef struct h2_ififo h2_ififo;
|
|
|
|
/**
|
|
* Create a FIFO queue that can hold up to capacity int. ints can
|
|
* appear several times.
|
|
*/
|
|
apr_status_t h2_ififo_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
|
|
|
|
/**
|
|
* Create a FIFO set that can hold up to capacity integers. Ints only
|
|
* appear once. Pushing an int already present does not change the
|
|
* queue and is successful.
|
|
*/
|
|
apr_status_t h2_ififo_set_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
|
|
|
|
apr_status_t h2_ififo_term(h2_ififo *fifo);
|
|
|
|
int h2_ififo_count(h2_ififo *fifo);
|
|
|
|
/**
|
|
* Push an int into the queue. Blocks if there is no capacity left.
|
|
*
|
|
* @param fifo the FIFO queue
|
|
* @param id the int to push
|
|
* @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
|
|
* APR_EEXIST when in set mode and elem already there.
|
|
*/
|
|
apr_status_t h2_ififo_push(h2_ififo *fifo, int id);
|
|
apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id);
|
|
|
|
apr_status_t h2_ififo_pull(h2_ififo *fifo, int *pi);
|
|
apr_status_t h2_ififo_try_pull(h2_ififo *fifo, int *pi);
|
|
|
|
typedef h2_fifo_op_t h2_ififo_peek_fn(int head, void *ctx);
|
|
|
|
/**
|
|
* Call given function on the head of the queue, once it exists, and
|
|
* perform the returned operation on it. The queue will hold its lock during
|
|
* this time, so no other operations on the queue are possible.
|
|
* @param fifo the queue to peek at
|
|
* @param fn the function to call on the head, once available
|
|
* @param ctx context to pass in call to function
|
|
*/
|
|
apr_status_t h2_ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx);
|
|
|
|
/**
|
|
* Non-blocking version of h2_fifo_peek.
|
|
*/
|
|
apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx);
|
|
|
|
/**
|
|
* Remove the integer from the queue, will remove multiple appearances.
|
|
* @param id the integer to remove
|
|
* @return APR_SUCCESS iff > 0 ints were removed, APR_EAGAIN otherwise.
|
|
*/
|
|
apr_status_t h2_ififo_remove(h2_ififo *fifo, int id);
|
|
|
|
/*******************************************************************************
|
|
* common helpers
|
|
******************************************************************************/
|
|
/* h2_log2(n) iff n is a power of 2 */
|
|
unsigned char h2_log2(int n);
|
|
|
|
/**
|
|
* Count the bytes that all key/value pairs in a table have
|
|
* in length (exlucding terminating 0s), plus additional extra per pair.
|
|
*
|
|
* @param t the table to inspect
|
|
* @param pair_extra the extra amount to add per pair
|
|
* @return the number of bytes all key/value pairs have
|
|
*/
|
|
apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra);
|
|
|
|
/** Match a header value against a string constance, case insensitive */
|
|
#define H2_HD_MATCH_LIT(l, name, nlen) \
|
|
((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
|
|
|
|
/*******************************************************************************
|
|
* HTTP/2 header helpers
|
|
******************************************************************************/
|
|
int h2_ignore_req_trailer(const char *name, size_t len);
|
|
int h2_ignore_resp_trailer(const char *name, size_t len);
|
|
|
|
/**
|
|
* Set the push policy for the given request. Takes request headers into
|
|
* account, see draft https://tools.ietf.org/html/draft-ruellan-http-accept-push-policy-00
|
|
* for details.
|
|
*
|
|
* @param headers the http headers to inspect
|
|
* @param p the pool to use
|
|
* @param push_enabled if HTTP/2 server push is generally enabled for this request
|
|
* @return the push policy desired
|
|
*/
|
|
int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabled);
|
|
|
|
/*******************************************************************************
|
|
* base64 url encoding, different table from normal base64
|
|
******************************************************************************/
|
|
/**
|
|
* I always wanted to write my own base64url decoder...not. See
|
|
* https://tools.ietf.org/html/rfc4648#section-5 for description.
|
|
*/
|
|
apr_size_t h2_util_base64url_decode(const char **decoded,
|
|
const char *encoded,
|
|
apr_pool_t *pool);
|
|
const char *h2_util_base64url_encode(const char *data,
|
|
apr_size_t len, apr_pool_t *pool);
|
|
|
|
/*******************************************************************************
|
|
* nghttp2 helpers
|
|
******************************************************************************/
|
|
|
|
int h2_util_ignore_resp_header(const char *name);
|
|
|
|
typedef struct h2_ngheader {
|
|
nghttp2_nv *nv;
|
|
apr_size_t nvlen;
|
|
} h2_ngheader;
|
|
|
|
#if AP_HAS_RESPONSE_BUCKETS
|
|
apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
|
|
ap_bucket_headers *headers);
|
|
apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
|
|
ap_bucket_response *response);
|
|
apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
|
|
const struct h2_request *req);
|
|
#else
|
|
apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
|
|
struct h2_headers *headers);
|
|
apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
|
|
struct h2_headers *headers);
|
|
apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
|
|
const struct h2_request *req);
|
|
#endif
|
|
|
|
/**
|
|
* Add a HTTP/2 header and return the table key if it really was added
|
|
* and not ignored.
|
|
*/
|
|
apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
|
|
const char *name, size_t nlen,
|
|
const char *value, size_t vlen,
|
|
size_t max_field_len, int *pwas_added);
|
|
|
|
/*******************************************************************************
|
|
* apr brigade helpers
|
|
******************************************************************************/
|
|
|
|
/**
|
|
* Concatenate at most length bytes from src to dest brigade, splitting
|
|
* buckets if necessary and reading buckets of indeterminate length.
|
|
*/
|
|
apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
|
|
apr_bucket_brigade *src,
|
|
apr_off_t length);
|
|
|
|
/**
|
|
* Copy at most length bytes from src to dest brigade, splitting
|
|
* buckets if necessary and reading buckets of indeterminate length.
|
|
*/
|
|
apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
|
|
apr_bucket_brigade *src,
|
|
apr_off_t length);
|
|
|
|
typedef apr_status_t h2_util_pass_cb(void *ctx,
|
|
const char *data, apr_off_t len);
|
|
|
|
/**
|
|
* Print a bucket's meta data (type and length) to the buffer.
|
|
* @return number of characters printed
|
|
*/
|
|
apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
|
|
apr_bucket *b, const char *sep);
|
|
|
|
/**
|
|
* Prints the brigade bucket types and lengths into the given buffer
|
|
* up to bmax.
|
|
* @return number of characters printed
|
|
*/
|
|
apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
|
|
const char *tag, const char *sep,
|
|
apr_bucket_brigade *bb);
|
|
/**
|
|
* Logs the bucket brigade (which bucket types with what length)
|
|
* to the log at the given level.
|
|
* @param c the connection to log for
|
|
* @param sid the stream identifier this brigade belongs to
|
|
* @param level the log level (as in APLOG_*)
|
|
* @param tag a short message text about the context
|
|
* @param bb the brigade to log
|
|
*/
|
|
#define h2_util_bb_log(c, sid, level, tag, bb) \
|
|
if (APLOG_C_IS_LEVEL(c, level)) { \
|
|
do { \
|
|
char buffer[4 * 1024]; \
|
|
const char *line = "(null)"; \
|
|
apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
|
|
len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
|
|
ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
|
|
((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
|
|
} while(0); \
|
|
}
|
|
|
|
|
|
typedef int h2_bucket_gate(apr_bucket *b);
|
|
/**
|
|
* Transfer buckets from one brigade to another with a limit on the
|
|
* maximum amount of bytes transferred. Does no setaside magic, lifetime
|
|
* of brigades must fit.
|
|
* @param to brigade to transfer buckets to
|
|
* @param from brigades to remove buckets from
|
|
* @param plen maximum bytes to transfer, actual bytes transferred
|
|
* @param peos if an EOS bucket was transferred
|
|
*/
|
|
apr_status_t h2_append_brigade(apr_bucket_brigade *to,
|
|
apr_bucket_brigade *from,
|
|
apr_off_t *plen,
|
|
int *peos,
|
|
h2_bucket_gate *should_append);
|
|
|
|
/**
|
|
* Get an approximnation of the memory footprint of the given
|
|
* brigade. This varies from apr_brigade_length as
|
|
* - no buckets are ever read
|
|
* - only buckets known to allocate memory (HEAP+POOL) are counted
|
|
* - the bucket struct itself is counted
|
|
*/
|
|
apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb);
|
|
|
|
/**
|
|
* Drain a pipe used for notification.
|
|
*/
|
|
void h2_util_drain_pipe(apr_file_t *pipe);
|
|
|
|
/**
|
|
* Wait on data arriving on a pipe.
|
|
*/
|
|
apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe);
|
|
|
|
|
|
#if AP_HAS_RESPONSE_BUCKETS
|
|
/**
|
|
* Give an estimate of the length of the header fields,
|
|
* without compression or other formatting decorations.
|
|
*/
|
|
apr_size_t headers_length_estimate(ap_bucket_headers *hdrs);
|
|
|
|
/**
|
|
* Give an estimate of the length of the response meta data size,
|
|
* without compression or other formatting decorations.
|
|
*/
|
|
apr_size_t response_length_estimate(ap_bucket_response *resp);
|
|
#endif /* AP_HAS_RESPONSE_BUCKETS */
|
|
|
|
#endif /* defined(__mod_h2__h2_util__) */
|