From a41cbc4dcdb42d95c6da01c478080954cb80b8d2 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 20 Nov 2015 13:58:32 +0000 Subject: [PATCH] incoming trailers passed into chunked request bodies, outgoing trailers not supported yet git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1715363 13f79535-47bb-0310-9956-ffa450edef68 --- modules/http2/h2_request.c | 44 ++++----------- modules/http2/h2_response.h | 1 + modules/http2/h2_session.c | 12 ++++ modules/http2/h2_stream.c | 49 ++++++++++++++--- modules/http2/h2_stream.h | 10 ++++ modules/http2/h2_util.c | 106 ++++++++++++++++++++++++++++++++++++ modules/http2/h2_util.h | 6 ++ 7 files changed, 187 insertions(+), 41 deletions(-) diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 2a697a0eda..a5f7d9d4fb 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -69,14 +69,7 @@ static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool, { char *hname, *hvalue; - if (H2_HD_MATCH_LIT("expect", name, nlen) - || H2_HD_MATCH_LIT("upgrade", name, nlen) - || H2_HD_MATCH_LIT("connection", name, nlen) - || H2_HD_MATCH_LIT("proxy-connection", name, nlen) - || H2_HD_MATCH_LIT("transfer-encoding", name, nlen) - || H2_HD_MATCH_LIT("keep-alive", name, nlen) - || H2_HD_MATCH_LIT("http2-settings", name, nlen)) { - /* ignore these. */ + if (h2_req_ignore_header(name, nlen)) { return APR_SUCCESS; } else if (H2_HD_MATCH_LIT("cookie", name, nlen)) { @@ -115,7 +108,10 @@ typedef struct { static int set_h1_header(void *ctx, const char *key, const char *value) { h1_ctx *x = ctx; - add_h1_header(x->req, x->pool, key, strlen(key), value, strlen(value)); + size_t klen = strlen(key); + if (!h2_req_ignore_header(key, klen)) { + add_h1_header(x->req, x->pool, key, klen, value, strlen(value)); + } return 1; } @@ -222,23 +218,11 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos) return APR_EINVAL; } - /* be safe, some header we do not accept on h2(c) */ - apr_table_unset(req->headers, "expect"); - apr_table_unset(req->headers, "upgrade"); - apr_table_unset(req->headers, "connection"); - apr_table_unset(req->headers, "proxy-connection"); - apr_table_unset(req->headers, "transfer-encoding"); - apr_table_unset(req->headers, "keep-alive"); - apr_table_unset(req->headers, "http2-settings"); - - if (!apr_table_get(req->headers, "Host")) { - /* Need to add a "Host" header if not already there to - * make virtual hosts work correctly. */ - if (!req->authority) { - return APR_BADARG; - } - apr_table_set(req->headers, "Host", req->authority); + /* Always set the "Host" header from :authority, see rfc7540, ch. 8.1.2.3 */ + if (!req->authority) { + return APR_BADARG; } + apr_table_setn(req->headers, "Host", req->authority); s = apr_table_get(req->headers, "Content-Length"); if (s) { @@ -290,15 +274,7 @@ static apr_status_t add_h1_trailer(h2_request *req, apr_pool_t *pool, { char *hname, *hvalue; - if (H2_HD_MATCH_LIT("expect", name, nlen) - || H2_HD_MATCH_LIT("upgrade", name, nlen) - || H2_HD_MATCH_LIT("connection", name, nlen) - || H2_HD_MATCH_LIT("host", name, nlen) - || H2_HD_MATCH_LIT("proxy-connection", name, nlen) - || H2_HD_MATCH_LIT("transfer-encoding", name, nlen) - || H2_HD_MATCH_LIT("keep-alive", name, nlen) - || H2_HD_MATCH_LIT("http2-settings", name, nlen)) { - /* ignore these. */ + if (h2_req_ignore_trailer(name, nlen)) { return APR_SUCCESS; } diff --git a/modules/http2/h2_response.h b/modules/http2/h2_response.h index 59f7b035aa..4085a41bdf 100644 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@ -24,6 +24,7 @@ typedef struct h2_response { int http_status; apr_off_t content_length; apr_table_t *header; + apr_table_t *trailer; } h2_response; h2_response *h2_response_create(int stream_id, diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 46898c4316..d70eefd296 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -1076,6 +1076,18 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, } if (eos) { + apr_table_t *trailers = h2_stream_get_trailers(stream); + if (trailers && !apr_is_empty_table(trailers)) { + h2_ngheader *nh; + int rv; + + nh = h2_util_ngheader_make(stream->pool, trailers); + rv = nghttp2_submit_trailer(ng2s, stream->id, nh->nv, nh->nvlen); + if (rv < 0) { + nread = rv; + } + } + *data_flags |= NGHTTP2_DATA_FLAG_EOF; } diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index 594175a785..ad7f5df102 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -304,17 +304,22 @@ apr_status_t h2_stream_schedule(h2_stream *stream, int eos, status = h2_mplx_process(stream->session->mplx, stream->id, stream->request, eos, cmp, ctx); stream->scheduled = 1; + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, + "h2_stream(%ld-%d): scheduled %s %s://%s%s", + stream->session->id, stream->id, + stream->request->method, stream->request->scheme, + stream->request->authority, stream->request->path); } else { h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, + "h2_stream(%ld-%d): RST=2 (internal err) %s %s://%s%s", + stream->session->id, stream->id, + stream->request->method, stream->request->scheme, + stream->request->authority, stream->request->path); } - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c, - "h2_stream(%ld-%d): scheduled %s %s://%s%s", - stream->session->id, stream->id, - stream->request->method, stream->request->scheme, - stream->request->authority, stream->request->path); - return status; } @@ -365,6 +370,22 @@ static apr_status_t input_add_data(h2_stream *stream, return status; } +static int input_add_header(void *str, const char *key, const char *value) +{ + h2_stream *stream = str; + apr_status_t status = input_add_data(stream, key, strlen(key), 0); + if (status == APR_SUCCESS) { + status = input_add_data(stream, ": ", 2, 0); + if (status == APR_SUCCESS) { + status = input_add_data(stream, value, strlen(value), 0); + if (status == APR_SUCCESS) { + status = input_add_data(stream, "\r\n", 2, 0); + } + } + } + return (status == APR_SUCCESS); +} + apr_status_t h2_stream_close_input(h2_stream *stream) { apr_status_t status = APR_SUCCESS; @@ -381,7 +402,15 @@ apr_status_t h2_stream_close_input(h2_stream *stream) H2_STREAM_IN(APLOG_TRACE2, stream, "close_pre"); if (close_input(stream) && stream->bbin) { if (stream->request->chunked) { - status = input_add_data(stream, "0\r\n\r\n", 5, 0); + apr_table_t *trailers = stream->request->trailers; + if (trailers && !apr_is_empty_table(trailers)) { + status = input_add_data(stream, "0\r\n", 3, 0); + apr_table_do(input_add_header, stream, trailers, NULL); + status = input_add_data(stream, "\r\n", 2, 0); + } + else { + status = input_add_data(stream, "0\r\n\r\n", 5, 0); + } } if (status == APR_SUCCESS) { @@ -610,3 +639,9 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream) } return status; } + +apr_table_t *h2_stream_get_trailers(h2_stream *stream) +{ + /* TODO */ + return NULL; +} diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index e5990a2bf3..79801722f4 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -290,4 +290,14 @@ int h2_stream_needs_submit(h2_stream *stream); */ apr_status_t h2_stream_submit_pushes(h2_stream *stream); +/** + * Get optional trailers for this stream, may be NULL. Meaningful + * results can only be expected when the end of the response body has + * been reached. + * + * @param stream to ask for trailers + * @return trailers for NULL + */ +apr_table_t *h2_stream_get_trailers(h2_stream *stream); + #endif /* defined(__mod_h2__h2_stream__) */ diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index e80a026880..76ecc27642 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -834,6 +834,21 @@ static int add_table_header(void *ctx, const char *key, const char *value) } +h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header) +{ + h2_ngheader *ngh; + size_t n; + + n = 0; + apr_table_do(count_header, &n, header, NULL); + + ngh = apr_pcalloc(p, sizeof(h2_ngheader)); + ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + apr_table_do(add_table_header, ngh, header, NULL); + + return ngh; +} + h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, int http_status, apr_table_t *header) @@ -879,3 +894,94 @@ h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, 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_ALEN(a) (sizeof(a)/sizeof((a)[0])) +#define H2_LIT_ARGS(a) (a),H2_ALEN(a) + +static literal IgnoredRequestHeaders[] = { + H2_DEF_LITERAL("host"), + H2_DEF_LITERAL("expect"), + 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 IgnoredRequestTrailers[] = { /* Ignore, see rfc7230, ch. 4.1.2 */ + H2_DEF_LITERAL("te"), + H2_DEF_LITERAL("host"), + H2_DEF_LITERAL("range"), + H2_DEF_LITERAL("cookie"), + H2_DEF_LITERAL("expect"), + H2_DEF_LITERAL("pragma"), + H2_DEF_LITERAL("max-forwards"), + H2_DEF_LITERAL("cache-control"), + H2_DEF_LITERAL("authorization"), + H2_DEF_LITERAL("content-length"), + H2_DEF_LITERAL("proxy-authorization"), +}; +static literal IgnoredResponseTrailers[] = { + H2_DEF_LITERAL("age"), + H2_DEF_LITERAL("date"), + H2_DEF_LITERAL("vary"), + H2_DEF_LITERAL("cookie"), + H2_DEF_LITERAL("expires"), + H2_DEF_LITERAL("warning"), + H2_DEF_LITERAL("location"), + H2_DEF_LITERAL("retry-after"), + H2_DEF_LITERAL("cache-control"), + H2_DEF_LITERAL("www-authenticate"), + H2_DEF_LITERAL("proxy-authenticate"), +}; + +static int ignore_header(const literal *lits, size_t llen, + const char *name, size_t nlen) +{ + const literal *lit; + int i; + + for (i = 0; i < llen; ++i) { + lit = &lits[i]; + if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) { + return 1; + } + } + return 0; +} + +int h2_req_ignore_header(const char *name, size_t len) +{ + return ignore_header(H2_LIT_ARGS(IgnoredRequestHeaders), name, len); +} + +int h2_req_ignore_trailer(const char *name, size_t len) +{ + return (h2_req_ignore_header(name, len) + || ignore_header(H2_LIT_ARGS(IgnoredRequestTrailers), name, len)); +} + +int h2_res_ignore_trailer(const char *name, size_t len) +{ + return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len); +} + +void h2_req_strip_ignored_header(apr_table_t *headers) +{ + int i; + for (i = 0; i < H2_ALEN(IgnoredRequestHeaders); ++i) { + apr_table_unset(headers, IgnoredRequestHeaders[i].name); + } +} + + diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index 51efb8cf2d..8f8be2993e 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -30,6 +30,11 @@ char *h2_strlwr(char *s); void h2_util_camel_case_header(char *s, size_t len); +int h2_req_ignore_header(const char *name, size_t len); +int h2_req_ignore_trailer(const char *name, size_t len); +void h2_req_strip_ignored_header(apr_table_t *headers); +int h2_res_ignore_trailer(const char *name, size_t len); + /** * Return != 0 iff the string s contains the token, as specified in * HTTP header syntax, rfc7230. @@ -75,6 +80,7 @@ typedef struct h2_ngheader { apr_size_t nvlen; } h2_ngheader; +h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header); h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, int http_status, apr_table_t *header); -- 2.50.0