diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:01:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:01:30 +0000 |
commit | 6beeb1b708550be0d4a53b272283e17e5e35fe17 (patch) | |
tree | 1ce8673d4aaa948e5554000101f46536a1e4cc29 /modules/http2/h2_proxy_util.c | |
parent | Initial commit. (diff) | |
download | apache2-6beeb1b708550be0d4a53b272283e17e5e35fe17.tar.xz apache2-6beeb1b708550be0d4a53b272283e17e5e35fe17.zip |
Adding upstream version 2.4.57.upstream/2.4.57upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/http2/h2_proxy_util.c')
-rw-r--r-- | modules/http2/h2_proxy_util.c | 1355 |
1 files changed, 1355 insertions, 0 deletions
diff --git a/modules/http2/h2_proxy_util.c b/modules/http2/h2_proxy_util.c new file mode 100644 index 0000000..dc69ec0 --- /dev/null +++ b/modules/http2/h2_proxy_util.c @@ -0,0 +1,1355 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> +#include <apr_thread_mutex.h> +#include <apr_thread_cond.h> + +#include <httpd.h> +#include <http_core.h> +#include <http_log.h> +#include <http_request.h> +#include <mod_proxy.h> + +#include <nghttp2/nghttp2.h> + +#include "h2.h" +#include "h2_proxy_util.h" + +APLOG_USE_MODULE(proxy_http2); + +/* h2_log2(n) iff n is a power of 2 */ +unsigned char h2_proxy_log2(int n) +{ + int lz = 0; + if (!n) { + return 0; + } + if (!(n & 0xffff0000u)) { + lz += 16; + n = (n << 16); + } + if (!(n & 0xff000000u)) { + lz += 8; + n = (n << 8); + } + if (!(n & 0xf0000000u)) { + lz += 4; + n = (n << 4); + } + if (!(n & 0xc0000000u)) { + lz += 2; + n = (n << 2); + } + if (!(n & 0x80000000u)) { + lz += 1; + } + + return 31 - lz; +} + +/******************************************************************************* + * ihash - hash for structs with int identifier + ******************************************************************************/ +struct h2_proxy_ihash_t { + apr_hash_t *hash; + size_t ioff; +}; + +static unsigned int ihash(const char *key, apr_ssize_t *klen) +{ + return (unsigned int)(*((int*)key)); +} + +h2_proxy_ihash_t *h2_proxy_ihash_create(apr_pool_t *pool, size_t offset_of_int) +{ + h2_proxy_ihash_t *ih = apr_pcalloc(pool, sizeof(h2_proxy_ihash_t)); + ih->hash = apr_hash_make_custom(pool, ihash); + ih->ioff = offset_of_int; + return ih; +} + +size_t h2_proxy_ihash_count(h2_proxy_ihash_t *ih) +{ + return apr_hash_count(ih->hash); +} + +int h2_proxy_ihash_empty(h2_proxy_ihash_t *ih) +{ + return apr_hash_count(ih->hash) == 0; +} + +void *h2_proxy_ihash_get(h2_proxy_ihash_t *ih, int id) +{ + return apr_hash_get(ih->hash, &id, sizeof(id)); +} + +typedef struct { + h2_proxy_ihash_iter_t *iter; + void *ctx; +} iter_ctx; + +static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen, + const void *val) +{ + iter_ctx *ictx = ctx; + return ictx->iter(ictx->ctx, (void*)val); /* why is this passed const?*/ +} + +int h2_proxy_ihash_iter(h2_proxy_ihash_t *ih, h2_proxy_ihash_iter_t *fn, void *ctx) +{ + iter_ctx ictx; + ictx.iter = fn; + ictx.ctx = ctx; + return apr_hash_do(ihash_iter, &ictx, ih->hash); +} + +void h2_proxy_ihash_add(h2_proxy_ihash_t *ih, void *val) +{ + apr_hash_set(ih->hash, ((char *)val + ih->ioff), sizeof(int), val); +} + +void h2_proxy_ihash_remove(h2_proxy_ihash_t *ih, int id) +{ + apr_hash_set(ih->hash, &id, sizeof(id), NULL); +} + +void h2_proxy_ihash_remove_val(h2_proxy_ihash_t *ih, void *val) +{ + int id = *((int*)((char *)val + ih->ioff)); + apr_hash_set(ih->hash, &id, sizeof(id), NULL); +} + + +void h2_proxy_ihash_clear(h2_proxy_ihash_t *ih) +{ + apr_hash_clear(ih->hash); +} + +typedef struct { + h2_proxy_ihash_t *ih; + void **buffer; + size_t max; + size_t len; +} collect_ctx; + +static int collect_iter(void *x, void *val) +{ + collect_ctx *ctx = x; + if (ctx->len < ctx->max) { + ctx->buffer[ctx->len++] = val; + return 1; + } + return 0; +} + +size_t h2_proxy_ihash_shift(h2_proxy_ihash_t *ih, void **buffer, size_t max) +{ + collect_ctx ctx; + size_t i; + + ctx.ih = ih; + ctx.buffer = buffer; + ctx.max = max; + ctx.len = 0; + h2_proxy_ihash_iter(ih, collect_iter, &ctx); + for (i = 0; i < ctx.len; ++i) { + h2_proxy_ihash_remove_val(ih, buffer[i]); + } + return ctx.len; +} + +typedef struct { + h2_proxy_ihash_t *ih; + int *buffer; + size_t max; + size_t len; +} icollect_ctx; + +static int icollect_iter(void *x, void *val) +{ + icollect_ctx *ctx = x; + if (ctx->len < ctx->max) { + ctx->buffer[ctx->len++] = *((int*)((char *)val + ctx->ih->ioff)); + return 1; + } + return 0; +} + +size_t h2_proxy_ihash_ishift(h2_proxy_ihash_t *ih, int *buffer, size_t max) +{ + icollect_ctx ctx; + size_t i; + + ctx.ih = ih; + ctx.buffer = buffer; + ctx.max = max; + ctx.len = 0; + h2_proxy_ihash_iter(ih, icollect_iter, &ctx); + for (i = 0; i < ctx.len; ++i) { + h2_proxy_ihash_remove(ih, buffer[i]); + } + return ctx.len; +} + +/******************************************************************************* + * iqueue - sorted list of int + ******************************************************************************/ + +static void iq_grow(h2_proxy_iqueue *q, int nlen); +static void iq_swap(h2_proxy_iqueue *q, int i, int j); +static int iq_bubble_up(h2_proxy_iqueue *q, int i, int top, + h2_proxy_iq_cmp *cmp, void *ctx); +static int iq_bubble_down(h2_proxy_iqueue *q, int i, int bottom, + h2_proxy_iq_cmp *cmp, void *ctx); + +h2_proxy_iqueue *h2_proxy_iq_create(apr_pool_t *pool, int capacity) +{ + h2_proxy_iqueue *q = apr_pcalloc(pool, sizeof(h2_proxy_iqueue)); + if (q) { + q->pool = pool; + iq_grow(q, capacity); + q->nelts = 0; + } + return q; +} + +int h2_proxy_iq_empty(h2_proxy_iqueue *q) +{ + return q->nelts == 0; +} + +int h2_proxy_iq_count(h2_proxy_iqueue *q) +{ + return q->nelts; +} + + +void h2_proxy_iq_add(h2_proxy_iqueue *q, int sid, h2_proxy_iq_cmp *cmp, void *ctx) +{ + int i; + + if (q->nelts >= q->nalloc) { + iq_grow(q, q->nalloc * 2); + } + + i = (q->head + q->nelts) % q->nalloc; + q->elts[i] = sid; + ++q->nelts; + + if (cmp) { + /* bubble it to the front of the queue */ + iq_bubble_up(q, i, q->head, cmp, ctx); + } +} + +int h2_proxy_iq_remove(h2_proxy_iqueue *q, int sid) +{ + int i; + for (i = 0; i < q->nelts; ++i) { + if (sid == q->elts[(q->head + i) % q->nalloc]) { + break; + } + } + + if (i < q->nelts) { + ++i; + for (; i < q->nelts; ++i) { + q->elts[(q->head+i-1)%q->nalloc] = q->elts[(q->head+i)%q->nalloc]; + } + --q->nelts; + return 1; + } + return 0; +} + +void h2_proxy_iq_clear(h2_proxy_iqueue *q) +{ + q->nelts = 0; +} + +void h2_proxy_iq_sort(h2_proxy_iqueue *q, h2_proxy_iq_cmp *cmp, void *ctx) +{ + /* Assume that changes in ordering are minimal. This needs, + * best case, q->nelts - 1 comparisons to check that nothing + * changed. + */ + if (q->nelts > 0) { + int i, ni, prev, last; + + /* Start at the end of the queue and create a tail of sorted + * entries. Make that tail one element longer in each iteration. + */ + last = i = (q->head + q->nelts - 1) % q->nalloc; + while (i != q->head) { + prev = (q->nalloc + i - 1) % q->nalloc; + + ni = iq_bubble_up(q, i, prev, cmp, ctx); + if (ni == prev) { + /* i bubbled one up, bubble the new i down, which + * keeps all tasks below i sorted. */ + iq_bubble_down(q, i, last, cmp, ctx); + } + i = prev; + }; + } +} + + +int h2_proxy_iq_shift(h2_proxy_iqueue *q) +{ + int sid; + + if (q->nelts <= 0) { + return 0; + } + + sid = q->elts[q->head]; + q->head = (q->head + 1) % q->nalloc; + q->nelts--; + + return sid; +} + +static void iq_grow(h2_proxy_iqueue *q, int nlen) +{ + if (nlen > q->nalloc) { + int *nq = apr_pcalloc(q->pool, sizeof(int) * nlen); + if (q->nelts > 0) { + int l = ((q->head + q->nelts) % q->nalloc) - q->head; + + memmove(nq, q->elts + q->head, sizeof(int) * l); + if (l < q->nelts) { + /* elts wrapped, append elts in [0, remain] to nq */ + int remain = q->nelts - l; + memmove(nq + l, q->elts, sizeof(int) * remain); + } + } + q->elts = nq; + q->nalloc = nlen; + q->head = 0; + } +} + +static void iq_swap(h2_proxy_iqueue *q, int i, int j) +{ + int x = q->elts[i]; + q->elts[i] = q->elts[j]; + q->elts[j] = x; +} + +static int iq_bubble_up(h2_proxy_iqueue *q, int i, int top, + h2_proxy_iq_cmp *cmp, void *ctx) +{ + int prev; + while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top) + && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) { + iq_swap(q, prev, i); + i = prev; + } + return i; +} + +static int iq_bubble_down(h2_proxy_iqueue *q, int i, int bottom, + h2_proxy_iq_cmp *cmp, void *ctx) +{ + int next; + while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom) + && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) { + iq_swap(q, next, i); + i = next; + } + return i; +} + +/******************************************************************************* + * h2_proxy_ngheader + ******************************************************************************/ +#define H2_HD_MATCH_LIT_CS(l, name) \ + ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name)) + +static int h2_util_ignore_header(const char *name) +{ + /* never forward, ch. 8.1.2.2 */ + return (H2_HD_MATCH_LIT_CS("connection", name) + || H2_HD_MATCH_LIT_CS("proxy-connection", name) + || H2_HD_MATCH_LIT_CS("upgrade", name) + || H2_HD_MATCH_LIT_CS("keep-alive", name) + || H2_HD_MATCH_LIT_CS("transfer-encoding", name)); +} + +static int count_header(void *ctx, const char *key, const char *value) +{ + if (!h2_util_ignore_header(key)) { + (*((size_t*)ctx))++; + } + return 1; +} + +#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v)) +#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v)) + +static int add_header(h2_proxy_ngheader *ngh, + const char *key, size_t key_len, + const char *value, size_t val_len) +{ + nghttp2_nv *nv = &ngh->nv[ngh->nvlen++]; + + nv->name = (uint8_t*)key; + nv->namelen = key_len; + nv->value = (uint8_t*)value; + nv->valuelen = val_len; + return 1; +} + +static int add_table_header(void *ctx, const char *key, const char *value) +{ + if (!h2_util_ignore_header(key)) { + add_header(ctx, key, strlen(key), value, strlen(value)); + } + return 1; +} + +h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p, + const h2_proxy_request *req) +{ + + h2_proxy_ngheader *ngh; + size_t n; + + ap_assert(req); + ap_assert(req->scheme); + ap_assert(req->authority); + ap_assert(req->path); + ap_assert(req->method); + + n = 4; + apr_table_do(count_header, &n, req->headers, NULL); + + ngh = apr_pcalloc(p, sizeof(h2_proxy_ngheader)); + ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + NV_ADD_LIT_CS(ngh, ":scheme", req->scheme); + NV_ADD_LIT_CS(ngh, ":authority", req->authority); + NV_ADD_LIT_CS(ngh, ":path", req->path); + NV_ADD_LIT_CS(ngh, ":method", req->method); + apr_table_do(add_table_header, ngh, req->headers, NULL); + + return ngh; +} + +h2_proxy_ngheader *h2_proxy_util_nghd_make(apr_pool_t *p, apr_table_t *headers) +{ + + h2_proxy_ngheader *ngh; + size_t n; + + n = 0; + apr_table_do(count_header, &n, headers, NULL); + + ngh = apr_pcalloc(p, sizeof(h2_proxy_ngheader)); + ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + apr_table_do(add_table_header, ngh, headers, NULL); + + return ngh; +} + +/******************************************************************************* + * header HTTP/1 <-> HTTP/2 conversions + ******************************************************************************/ + +typedef struct { + const char *name; + size_t len; +} literal; + +#define H2_DEF_LITERAL(n) { (n), (sizeof(n)-1) } +#define H2_LIT_ARGS(a) (a),H2_ALEN(a) + +static literal IgnoredRequestHeaders[] = { + H2_DEF_LITERAL("upgrade"), + H2_DEF_LITERAL("connection"), + H2_DEF_LITERAL("keep-alive"), + H2_DEF_LITERAL("http2-settings"), + H2_DEF_LITERAL("proxy-connection"), + H2_DEF_LITERAL("transfer-encoding"), +}; +static literal IgnoredProxyRespHds[] = { + H2_DEF_LITERAL("alt-svc"), +}; + +static int ignore_header(const literal *lits, size_t llen, + const char *name, size_t nlen) +{ + const literal *lit; + size_t i; + + for (i = 0; i < llen; ++i) { + lit = &lits[i]; + if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) { + return 1; + } + } + return 0; +} + +static int h2_proxy_req_ignore_header(const char *name, size_t len) +{ + return ignore_header(H2_LIT_ARGS(IgnoredRequestHeaders), name, len); +} + +int h2_proxy_res_ignore_header(const char *name, size_t len) +{ + return (h2_proxy_req_ignore_header(name, len) + || ignore_header(H2_LIT_ARGS(IgnoredProxyRespHds), name, len)); +} + +void h2_proxy_util_camel_case_header(char *s, size_t len) +{ + size_t start = 1; + size_t i; + for (i = 0; i < len; ++i) { + if (start) { + if (s[i] >= 'a' && s[i] <= 'z') { + s[i] -= 'a' - 'A'; + } + + start = 0; + } + else if (s[i] == '-') { + start = 1; + } + } +} + +/******************************************************************************* + * h2 request handling + ******************************************************************************/ + +/** 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)) + +static apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, + const char *name, size_t nlen, + const char *value, size_t vlen) +{ + char *hname, *hvalue; + + if (h2_proxy_req_ignore_header(name, nlen)) { + return APR_SUCCESS; + } + else if (H2_HD_MATCH_LIT("cookie", name, nlen)) { + const char *existing = apr_table_get(headers, "cookie"); + if (existing) { + char *nval; + + /* Cookie header come separately in HTTP/2, but need + * to be merged by "; " (instead of default ", ") + */ + hvalue = apr_pstrndup(pool, value, vlen); + nval = apr_psprintf(pool, "%s; %s", existing, hvalue); + apr_table_setn(headers, "Cookie", nval); + return APR_SUCCESS; + } + } + else if (H2_HD_MATCH_LIT("host", name, nlen)) { + if (apr_table_get(headers, "Host")) { + return APR_SUCCESS; /* ignore duplicate */ + } + } + + hname = apr_pstrndup(pool, name, nlen); + hvalue = apr_pstrndup(pool, value, vlen); + h2_proxy_util_camel_case_header(hname, nlen); + apr_table_mergen(headers, hname, hvalue); + + return APR_SUCCESS; +} + +static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const char *method, + const char *scheme, const char *authority, + const char *path, apr_table_t *header) +{ + h2_proxy_request *req = apr_pcalloc(pool, sizeof(h2_proxy_request)); + + req->method = method; + req->scheme = scheme; + req->authority = authority; + req->path = path; + req->headers = header? header : apr_table_make(pool, 10); + req->request_time = apr_time_now(); + + return req; +} + +h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool) +{ + return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL); +} + +typedef struct { + apr_table_t *headers; + apr_pool_t *pool; +} h1_ctx; + +static int set_h1_header(void *ctx, const char *key, const char *value) +{ + h1_ctx *x = ctx; + size_t klen = strlen(key); + if (!h2_proxy_req_ignore_header(key, klen)) { + h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value)); + } + return 1; +} + +apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool, + const char *method, const char *scheme, + const char *authority, const char *path, + apr_table_t *headers) +{ + h1_ctx x; + const char *val; + + req->method = method; + req->scheme = scheme; + req->authority = authority; + req->path = path; + + ap_assert(req->scheme); + ap_assert(req->authority); + ap_assert(req->path); + ap_assert(req->method); + + x.pool = pool; + x.headers = req->headers; + apr_table_do(set_h1_header, &x, headers, NULL); + if ((val = apr_table_get(headers, "TE")) && ap_find_token(pool, val, "trailers")) { + /* client accepts trailers, forward this information */ + apr_table_addn(req->headers, "TE", "trailers"); + } + apr_table_setn(req->headers, "te", "trailers"); + return APR_SUCCESS; +} + +/******************************************************************************* + * frame logging + ******************************************************************************/ + +int h2_proxy_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen) +{ + char scratch[128]; + size_t s_len = sizeof(scratch)/sizeof(scratch[0]); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + return apr_snprintf(buffer, maxlen, + "DATA[length=%d, flags=%d, stream=%d, padlen=%d]", + (int)frame->hd.length, frame->hd.flags, + frame->hd.stream_id, (int)frame->data.padlen); + } + case NGHTTP2_HEADERS: { + return apr_snprintf(buffer, maxlen, + "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + frame->hd.stream_id, + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); + } + case NGHTTP2_PRIORITY: { + return apr_snprintf(buffer, maxlen, + "PRIORITY[length=%d, flags=%d, stream=%d]", + (int)frame->hd.length, + frame->hd.flags, frame->hd.stream_id); + } + case NGHTTP2_RST_STREAM: { + return apr_snprintf(buffer, maxlen, + "RST_STREAM[length=%d, flags=%d, stream=%d]", + (int)frame->hd.length, + frame->hd.flags, frame->hd.stream_id); + } + case NGHTTP2_SETTINGS: { + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + return apr_snprintf(buffer, maxlen, + "SETTINGS[ack=1, stream=%d]", + frame->hd.stream_id); + } + return apr_snprintf(buffer, maxlen, + "SETTINGS[length=%d, stream=%d]", + (int)frame->hd.length, frame->hd.stream_id); + } + case NGHTTP2_PUSH_PROMISE: { + return apr_snprintf(buffer, maxlen, + "PUSH_PROMISE[length=%d, hend=%d, stream=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + frame->hd.stream_id); + } + case NGHTTP2_PING: { + return apr_snprintf(buffer, maxlen, + "PING[length=%d, ack=%d, stream=%d]", + (int)frame->hd.length, + frame->hd.flags&NGHTTP2_FLAG_ACK, + frame->hd.stream_id); + } + case NGHTTP2_GOAWAY: { + size_t len = (frame->goaway.opaque_data_len < s_len)? + frame->goaway.opaque_data_len : s_len-1; + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); + } + case NGHTTP2_WINDOW_UPDATE: { + return apr_snprintf(buffer, maxlen, + "WINDOW_UPDATE[stream=%d, incr=%d]", + frame->hd.stream_id, + frame->window_update.window_size_increment); + } + default: + return apr_snprintf(buffer, maxlen, + "type=%d[length=%d, flags=%d, stream=%d]", + frame->hd.type, (int)frame->hd.length, + frame->hd.flags, frame->hd.stream_id); + } +} + +/******************************************************************************* + * link header handling + ******************************************************************************/ + +typedef struct { + apr_pool_t *pool; + request_rec *r; + proxy_dir_conf *conf; + const char *s; + int slen; + int i; + const char *server_uri; + int su_len; + const char *real_backend_uri; + int rbu_len; + const char *p_server_uri; + int psu_len; + int link_start; + int link_end; +} link_ctx; + +static int attr_char(char c) +{ + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return 1; + default: + return apr_isalnum(c); + } +} + +static int ptoken_char(char c) +{ + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case '-': + case '.': + case '/': + case ':': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case ']': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return 1; + default: + return apr_isalnum(c); + } +} + +static int skip_ws(link_ctx *ctx) +{ + char c; + while (ctx->i < ctx->slen + && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) { + ++ctx->i; + } + return (ctx->i < ctx->slen); +} + +static int find_chr(link_ctx *ctx, char c, int *pidx) +{ + int j; + for (j = ctx->i; j < ctx->slen; ++j) { + if (ctx->s[j] == c) { + *pidx = j; + return 1; + } + } + return 0; +} + +static int read_chr(link_ctx *ctx, char c) +{ + if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) { + ++ctx->i; + return 1; + } + return 0; +} + +static int skip_qstring(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '\"')) { + int end; + if (find_chr(ctx, '\"', &end)) { + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int skip_ptoken(link_ctx *ctx) +{ + if (skip_ws(ctx)) { + int i; + for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + ctx->i = i; + return 1; + } + } + return 0; +} + + +static int read_link(link_ctx *ctx) +{ + ctx->link_start = ctx->link_end = 0; + if (skip_ws(ctx) && read_chr(ctx, '<')) { + int end; + if (find_chr(ctx, '>', &end)) { + ctx->link_start = ctx->i; + ctx->link_end = end; + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int skip_pname(link_ctx *ctx) +{ + if (skip_ws(ctx)) { + int i; + for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + ctx->i = i; + return 1; + } + } + return 0; +} + +static int skip_pvalue(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '=')) { + if (skip_qstring(ctx) || skip_ptoken(ctx)) { + return 1; + } + } + return 0; +} + +static int skip_param(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ';')) { + if (skip_pname(ctx)) { + skip_pvalue(ctx); /* value is optional */ + return 1; + } + } + return 0; +} + +static int read_sep(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ',')) { + return 1; + } + return 0; +} + +static size_t subst_str(link_ctx *ctx, int start, int end, const char *ns) +{ + int olen, nlen, plen; + int delta; + char *p; + + olen = end - start; + nlen = (int)strlen(ns); + delta = nlen - olen; + plen = ctx->slen + delta + 1; + p = apr_palloc(ctx->pool, plen); + memcpy(p, ctx->s, start); + memcpy(p + start, ns, nlen); + strcpy(p + start + nlen, ctx->s + end); + ctx->s = p; + ctx->slen = plen - 1; /* (int)strlen(p) */ + if (ctx->i >= end) { + ctx->i += delta; + } + return nlen; +} + +static void map_link(link_ctx *ctx) +{ + if (ctx->link_start < ctx->link_end) { + char buffer[HUGE_STRING_LEN]; + size_t need_len, link_len, buffer_len, prepend_p_server; + const char *mapped; + + buffer[0] = '\0'; + buffer_len = 0; + link_len = ctx->link_end - ctx->link_start; + need_len = link_len + 1; + prepend_p_server = (ctx->s[ctx->link_start] == '/'); + if (prepend_p_server) { + /* common to use relative uris in link header, for mappings + * to work need to prefix the backend server uri */ + need_len += ctx->psu_len; + apr_cpystrn(buffer, ctx->p_server_uri, sizeof(buffer)); + buffer_len = ctx->psu_len; + } + if (need_len > sizeof(buffer)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ctx->r, APLOGNO(03482) + "link_reverse_map uri too long, skipped: %s", ctx->s); + return; + } + apr_cpystrn(buffer + buffer_len, ctx->s + ctx->link_start, link_len + 1); + if (!prepend_p_server + && strcmp(ctx->real_backend_uri, ctx->p_server_uri) + && !strncmp(buffer, ctx->real_backend_uri, ctx->rbu_len)) { + /* the server uri and our local proxy uri we use differ, for mapping + * to work, we need to use the proxy uri */ + int path_start = ctx->link_start + ctx->rbu_len; + link_len -= ctx->rbu_len; + memcpy(buffer, ctx->p_server_uri, ctx->psu_len); + memcpy(buffer + ctx->psu_len, ctx->s + path_start, link_len); + buffer_len = ctx->psu_len + link_len; + buffer[buffer_len] = '\0'; + } + mapped = ap_proxy_location_reverse_map(ctx->r, ctx->conf, buffer); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, ctx->r, + "reverse_map[%s] %s --> %s", ctx->p_server_uri, buffer, mapped); + if (mapped != buffer) { + if (prepend_p_server) { + if (ctx->server_uri == NULL) { + ctx->server_uri = ap_construct_url(ctx->pool, "", ctx->r); + ctx->su_len = (int)strlen(ctx->server_uri); + } + if (!strncmp(mapped, ctx->server_uri, ctx->su_len)) { + mapped += ctx->su_len; + } + } + subst_str(ctx, ctx->link_start, ctx->link_end, mapped); + } + } +} + +/* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1> + Link = "Link" ":" #link-value + link-value = "<" URI-Reference ">" *( ";" link-param ) + link-param = ( ( "rel" "=" relation-types ) + | ( "anchor" "=" <"> URI-Reference <"> ) + | ( "rev" "=" relation-types ) + | ( "hreflang" "=" Language-Tag ) + | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) + | ( "title" "=" quoted-string ) + | ( "title*" "=" ext-value ) + | ( "type" "=" ( media-type | quoted-mt ) ) + | ( link-extension ) ) + link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) + | ( ext-name-star "=" ext-value ) + ext-name-star = parmname "*" ; reserved for RFC2231-profiled + ; extensions. Whitespace NOT + ; allowed in between. + ptoken = 1*ptokenchar + ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" + | ")" | "*" | "+" | "-" | "." | "/" | DIGIT + | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA + | "[" | "]" | "^" | "_" | "`" | "{" | "|" + | "}" | "~" + media-type = type-name "/" subtype-name + quoted-mt = <"> media-type <"> + relation-types = relation-type + | <"> relation-type *( 1*SP relation-type ) <"> + relation-type = reg-rel-type | ext-rel-type + reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) + ext-rel-type = URI + + and from <https://tools.ietf.org/html/rfc5987> + parmname = 1*attr-char + attr-char = ALPHA / DIGIT + / "!" / "#" / "$" / "&" / "+" / "-" / "." + / "^" / "_" / "`" / "|" / "~" + */ + +const char *h2_proxy_link_reverse_map(request_rec *r, + proxy_dir_conf *conf, + const char *real_backend_uri, + const char *proxy_server_uri, + const char *s) +{ + link_ctx ctx; + + if (r->proxyreq != PROXYREQ_REVERSE) { + return s; + } + memset(&ctx, 0, sizeof(ctx)); + ctx.r = r; + ctx.pool = r->pool; + ctx.conf = conf; + ctx.real_backend_uri = real_backend_uri; + ctx.rbu_len = (int)strlen(ctx.real_backend_uri); + ctx.p_server_uri = proxy_server_uri; + ctx.psu_len = (int)strlen(ctx.p_server_uri); + ctx.s = s; + ctx.slen = (int)strlen(s); + while (read_link(&ctx)) { + while (skip_param(&ctx)) { + /* nop */ + } + map_link(&ctx); + if (!read_sep(&ctx)) { + break; + } + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "link_reverse_map %s --> %s", s, ctx.s); + return ctx.s; +} + +/******************************************************************************* + * FIFO queue + ******************************************************************************/ + +struct h2_proxy_fifo { + void **elems; + int nelems; + int set; + int head; + int count; + int aborted; + apr_thread_mutex_t *lock; + apr_thread_cond_t *not_empty; + apr_thread_cond_t *not_full; +}; + +static int nth_index(h2_proxy_fifo *fifo, int n) +{ + return (fifo->head + n) % fifo->nelems; +} + +static apr_status_t fifo_destroy(void *data) +{ + h2_proxy_fifo *fifo = data; + + apr_thread_cond_destroy(fifo->not_empty); + apr_thread_cond_destroy(fifo->not_full); + apr_thread_mutex_destroy(fifo->lock); + + return APR_SUCCESS; +} + +static int index_of(h2_proxy_fifo *fifo, void *elem) +{ + int i; + + for (i = 0; i < fifo->count; ++i) { + if (elem == fifo->elems[nth_index(fifo, i)]) { + return i; + } + } + return -1; +} + +static apr_status_t create_int(h2_proxy_fifo **pfifo, apr_pool_t *pool, + int capacity, int as_set) +{ + apr_status_t rv; + h2_proxy_fifo *fifo; + + fifo = apr_pcalloc(pool, sizeof(*fifo)); + if (fifo == NULL) { + return APR_ENOMEM; + } + + rv = apr_thread_mutex_create(&fifo->lock, + APR_THREAD_MUTEX_UNNESTED, pool); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_thread_cond_create(&fifo->not_empty, pool); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_thread_cond_create(&fifo->not_full, pool); + if (rv != APR_SUCCESS) { + return rv; + } + + fifo->elems = apr_pcalloc(pool, capacity * sizeof(void*)); + if (fifo->elems == NULL) { + return APR_ENOMEM; + } + fifo->nelems = capacity; + fifo->set = as_set; + + *pfifo = fifo; + apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null); + + return APR_SUCCESS; +} + +apr_status_t h2_proxy_fifo_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity) +{ + return create_int(pfifo, pool, capacity, 0); +} + +apr_status_t h2_proxy_fifo_set_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity) +{ + return create_int(pfifo, pool, capacity, 1); +} + +apr_status_t h2_proxy_fifo_term(h2_proxy_fifo *fifo) +{ + apr_status_t rv; + if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) { + fifo->aborted = 1; + apr_thread_mutex_unlock(fifo->lock); + } + return rv; +} + +apr_status_t h2_proxy_fifo_interrupt(h2_proxy_fifo *fifo) +{ + apr_status_t rv; + if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) { + apr_thread_cond_broadcast(fifo->not_empty); + apr_thread_cond_broadcast(fifo->not_full); + apr_thread_mutex_unlock(fifo->lock); + } + return rv; +} + +int h2_proxy_fifo_count(h2_proxy_fifo *fifo) +{ + return fifo->count; +} + +int h2_proxy_fifo_capacity(h2_proxy_fifo *fifo) +{ + return fifo->nelems; +} + +static apr_status_t check_not_empty(h2_proxy_fifo *fifo, int block) +{ + if (fifo->count == 0) { + if (!block) { + return APR_EAGAIN; + } + while (fifo->count == 0) { + if (fifo->aborted) { + return APR_EOF; + } + apr_thread_cond_wait(fifo->not_empty, fifo->lock); + } + } + return APR_SUCCESS; +} + +static apr_status_t fifo_push(h2_proxy_fifo *fifo, void *elem, int block) +{ + apr_status_t rv; + + if (fifo->aborted) { + return APR_EOF; + } + + if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) { + if (fifo->set && index_of(fifo, elem) >= 0) { + /* set mode, elem already member */ + apr_thread_mutex_unlock(fifo->lock); + return APR_EEXIST; + } + else if (fifo->count == fifo->nelems) { + if (block) { + while (fifo->count == fifo->nelems) { + if (fifo->aborted) { + apr_thread_mutex_unlock(fifo->lock); + return APR_EOF; + } + apr_thread_cond_wait(fifo->not_full, fifo->lock); + } + } + else { + apr_thread_mutex_unlock(fifo->lock); + return APR_EAGAIN; + } + } + + ap_assert(fifo->count < fifo->nelems); + fifo->elems[nth_index(fifo, fifo->count)] = elem; + ++fifo->count; + if (fifo->count == 1) { + apr_thread_cond_broadcast(fifo->not_empty); + } + apr_thread_mutex_unlock(fifo->lock); + } + return rv; +} + +apr_status_t h2_proxy_fifo_push(h2_proxy_fifo *fifo, void *elem) +{ + return fifo_push(fifo, elem, 1); +} + +apr_status_t h2_proxy_fifo_try_push(h2_proxy_fifo *fifo, void *elem) +{ + return fifo_push(fifo, elem, 0); +} + +static void *pull_head(h2_proxy_fifo *fifo) +{ + void *elem; + + ap_assert(fifo->count > 0); + elem = fifo->elems[fifo->head]; + --fifo->count; + if (fifo->count > 0) { + fifo->head = nth_index(fifo, 1); + if (fifo->count+1 == fifo->nelems) { + apr_thread_cond_broadcast(fifo->not_full); + } + } + return elem; +} + +static apr_status_t fifo_pull(h2_proxy_fifo *fifo, void **pelem, int block) +{ + apr_status_t rv; + + if (fifo->aborted) { + return APR_EOF; + } + + if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) { + if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) { + apr_thread_mutex_unlock(fifo->lock); + *pelem = NULL; + return rv; + } + + ap_assert(fifo->count > 0); + *pelem = pull_head(fifo); + + apr_thread_mutex_unlock(fifo->lock); + } + return rv; +} + +apr_status_t h2_proxy_fifo_pull(h2_proxy_fifo *fifo, void **pelem) +{ + return fifo_pull(fifo, pelem, 1); +} + +apr_status_t h2_proxy_fifo_try_pull(h2_proxy_fifo *fifo, void **pelem) +{ + return fifo_pull(fifo, pelem, 0); +} + +apr_status_t h2_proxy_fifo_remove(h2_proxy_fifo *fifo, void *elem) +{ + apr_status_t rv; + + if (fifo->aborted) { + return APR_EOF; + } + + if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) { + int i, rc; + void *e; + + rc = 0; + for (i = 0; i < fifo->count; ++i) { + e = fifo->elems[nth_index(fifo, i)]; + if (e == elem) { + ++rc; + } + else if (rc) { + fifo->elems[nth_index(fifo, i-rc)] = e; + } + } + if (rc) { + fifo->count -= rc; + if (fifo->count + rc == fifo->nelems) { + apr_thread_cond_broadcast(fifo->not_full); + } + rv = APR_SUCCESS; + } + else { + rv = APR_EAGAIN; + } + + apr_thread_mutex_unlock(fifo->lock); + } + return rv; +} |