| /* 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(unsigned 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) && !ap_cstr_casecmp(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 | |
| typedef struct h2_hd_scratch { | |
| size_t max_len; /* header field size name + ': ' + value */ | |
| char *name; /* max_len+1 sized */ | |
| char *value; /* max_len+1 sized */ | |
| } h2_hd_scratch; | |
| /** | |
| * 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, | |
| h2_hd_scratch *scratch, 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__) */ |