diff options
author | Stefan Eissing <icing@apache.org> | 2022-04-04 10:24:09 +0200 |
---|---|---|
committer | Stefan Eissing <icing@apache.org> | 2022-04-04 10:24:09 +0200 |
commit | 4442201e61616a2a75909b26909121f469bfae7c (patch) | |
tree | 63f3ea4cfe577f6892cd704c0ac4f719bc985093 | |
parent | fr doc rebuild. (diff) | |
download | apache2-4442201e61616a2a75909b26909121f469bfae7c.tar.xz apache2-4442201e61616a2a75909b26909121f469bfae7c.zip |
*) core/mod_http/mod_http2:
- adds new meta bucket types REQUEST, RESPONSE and HEADERS to the API.
- adds a new method for setting standard response headers Date and Server
- adds helper methods for formatting parts of HTTP/1.x, like headers and
end chunks for use in non-core parts of the server, e.g. mod_proxy
- splits the HTTP_IN filter into a "generic HTTP" and "specific HTTP/1.x"
filter. The latter one named HTTP1_BODY_IN.
- Uses HTTP1_BODY_IN only for requests with HTTP version <= 1.1
- Removes the chunked input simulation from mod_http2
- adds body_indeterminate flag to request_rec that indicates that a request
body may be present and needs to be read/discarded. This replaces logic
that thinks without Content-Length and Transfer-Encoding, no request
body can exist.
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899547 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | include/ap_mmn.h | 7 | ||||
-rw-r--r-- | include/http_protocol.h | 292 | ||||
-rw-r--r-- | include/httpd.h | 8 | ||||
-rw-r--r-- | include/mod_core.h | 5 | ||||
-rw-r--r-- | libhttpd.dsp | 4 | ||||
-rw-r--r-- | modules/http/chunk_filter.c | 89 | ||||
-rw-r--r-- | modules/http/http_core.c | 4 | ||||
-rw-r--r-- | modules/http/http_filters.c | 210 | ||||
-rw-r--r-- | modules/http/http_protocol.c | 105 | ||||
-rw-r--r-- | modules/http2/h2_c2.c | 10 | ||||
-rw-r--r-- | modules/http2/h2_c2_filter.c | 233 | ||||
-rw-r--r-- | modules/http2/h2_mplx.c | 4 | ||||
-rw-r--r-- | modules/http2/h2_request.c | 10 | ||||
-rw-r--r-- | modules/proxy/proxy_util.c | 3 | ||||
-rw-r--r-- | server/Makefile.in | 2 | ||||
-rw-r--r-- | server/headers_bucket.c | 269 | ||||
-rw-r--r-- | server/protocol.c | 53 |
18 files changed, 984 insertions, 325 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 71f992cfbb..8cc2eaa22c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -699,6 +699,7 @@ SET(LIBHTTPD_SOURCES server/eoc_bucket.c server/eor_bucket.c server/error_bucket.c + server/headers_bucket.c server/listen.c server/log.c server/mpm/winnt/child.c diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 6457cb21d9..04fb184048 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -705,7 +705,10 @@ * 20211221.5 (2.5.1-dev) Add hook create_secondary_connection and method * ap_create_secondary_connection() to have connection * setup of http2-like connections in core. - * + * 20211221.6 (2.5.1-dev) Add new meta buckets request/response/headers + * Add field `body_indeterminate` in request_rec + * Add new http/1.x formatting helpers + * Add ap_assign_request() */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ @@ -713,7 +716,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20211221 #endif -#define MODULE_MAGIC_NUMBER_MINOR 5 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 6 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/http_protocol.h b/include/http_protocol.h index 38eef396a3..b522f70928 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -68,6 +68,18 @@ AP_DECLARE(request_rec *) ap_create_request(conn_rec *c); request_rec *ap_read_request(conn_rec *c); /** + * Assign the method, uri and protocol to the request. + * @param r The current request + * @param method the HTTP method + * @param uri the request uri + * @param protocol the request protocol + * @return 1 on success, 0 on failure + */ +AP_DECLARE(int) ap_assign_request(request_rec *r, + const char *method, const char *uri, + const char *protocol); + +/** * Parse and validate the request line. * @param r The current request * @return 1 on success, 0 on failure @@ -1027,6 +1039,252 @@ AP_DECLARE(apr_bucket *) ap_bucket_error_create(int error, const char *buf, apr_pool_t *p, apr_bucket_alloc_t *list); +/** @see ap_bucket_type_request */ +typedef struct ap_bucket_request ap_bucket_request; + +/** + * @struct ap_bucket_request + * @brief A bucket referring to a HTTP request + * + */ +struct ap_bucket_request { + /** Number of buckets using this memory */ + apr_bucket_refcount refcount; + apr_pool_t *pool; /* pool that holds the contents, not for modification */ + const char *method; /* request method */ + const char *uri; /* request uri */ + const char *protocol; /* request protocol */ + apr_table_t *headers; /* request headers */ +}; + +/** @see ap_bucket_type_request */ +AP_DECLARE_DATA extern const apr_bucket_type_t ap_bucket_type_request; + +/** + * Determine if a bucket is a request bucket + * @param e The bucket to inspect + * @return true or false + */ +#define AP_BUCKET_IS_REQUEST(e) (e->type == &ap_bucket_type_request) + +/** + * Make the bucket passed in a request bucket + * Copies all parameters to the given pool. + * @param b The bucket to make into a request bucket + * @param method the HTTP method + * @param uri the uri requested + * @param protocol the protocol requested + * @param headers the table of response headers. + * @param p A pool to allocate out of. + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_request_make( + apr_bucket *b, + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p); + +/** + * Make the bucket passed in a request bucket + * Uses all paramters without copying. + * @param b The bucket to make into a request bucket + * @param method the HTTP method + * @param uri the uri requested + * @param protocol the protocol requested + * @param headers the table of response headers. + * @param p A pool to allocate out of. + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_request_maken( + apr_bucket *b, + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p); + +/** + * Create a bucket referring to a HTTP request. + * Copies all parameters to the given pool. + * @param method the HTTP method + * @param uri the uri requested + * @param protocol the protocol requested + * @param headers the table of response headers. + * @param p A pool to allocate the error string out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_request_create( + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +/** + * Create a bucket referring to a HTTP request. + * Uses all paramters without copying. + * @param method the HTTP method + * @param uri the uri requested + * @param protocol the protocol requested + * @param headers the HTTP response headers. + * @param p A pool to allocate the error string out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_request_createn( + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +/** + * Clone a request bucket into another pool/bucket_alloc that may + * have a separate lifetime than the source bucket/pool. + * @param source the request bucket to clone + * @param p A pool to allocate the data out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_request_clone(apr_bucket *source, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +/** @see ap_bucket_type_response */ +typedef struct ap_bucket_response ap_bucket_response; + +/** + * @struct ap_bucket_response + * @brief A bucket referring to a HTTP response + * + */ +struct ap_bucket_response { + /** Number of buckets using this memory */ + apr_bucket_refcount refcount; + apr_pool_t *pool; /* pool that holds the contents, not for modification */ + int status; /* The status code */ + const char *reason; /* The optional HTTP reason for the status. */ + apr_table_t *headers; /* The response headers */ + apr_table_t *notes; /* internal notes about the response */ +}; + +/** @see ap_bucket_type_headers */ +AP_DECLARE_DATA extern const apr_bucket_type_t ap_bucket_type_response; + +/** + * Determine if a bucket is a response bucket + * @param e The bucket to inspect + * @return true or false + */ +#define AP_BUCKET_IS_RESPONSE(e) (e->type == &ap_bucket_type_response) + +/** + * Make the bucket passed in a response bucket + * @param b The bucket to make into a response bucket + * @param status The HTTP status code of the response. + * @param reason textual description of status, can be NULL. + * @param headers the table of response headers. + * @param notes internal notes on the response + * @param p A pool to allocate out of. + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_response_make(apr_bucket *b, int status, + const char *reason, apr_table_t *headers, + apr_table_t *notes, apr_pool_t *p); + +/** + * Create a bucket referring to a HTTP response. + * @param status The HTTP status code. + * @param reason textual description of status, can be NULL. + * @param headers the HTTP response headers. + * @param notes internal notes on the response + * @param p A pool to allocate the error string out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_response_create( + int status, const char *reason, + apr_table_t *headers, + apr_table_t *notes, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +/** + * Clone a RESPONSE bucket into another pool/bucket_alloc that may + * have a separate lifetime than the source bucket/pool. + * @param source the response bucket to clone + * @param p A pool to allocate the data out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_response_clone(apr_bucket *source, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +/** @see ap_bucket_type_headers */ +typedef struct ap_bucket_headers ap_bucket_headers; + +/** + * @struct ap_bucket_headers + * @brief A bucket referring to an HTTP header set + * + */ +struct ap_bucket_headers { + /** Number of buckets using this memory */ + apr_bucket_refcount refcount; + apr_pool_t *pool; /* pool that holds the contents, not for modification */ + apr_table_t *headers; /* The headers */ + +}; + +/** @see ap_bucket_type_headers */ +AP_DECLARE_DATA extern const apr_bucket_type_t ap_bucket_type_headers; + +/** + * Determine if a bucket is an headers bucket + * @param e The bucket to inspect + * @return true or false + */ +#define AP_BUCKET_IS_HEADERS(e) (e->type == &ap_bucket_type_headers) + +/** + * Make the bucket passed in a headers bucket + * @param b The bucket to make into a headers bucket + * @param headers the table of headers. + * @param p A pool to allocate out of. + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_headers_make(apr_bucket *b, + apr_table_t *headers, apr_pool_t *p); + +/** + * Create a bucket referring to a table of HTTP headers. + * @param headers the HTTP headers in the bucket. + * @param p A pool to allocate the error string out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_headers_create(apr_table_t *headers, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +/** + * Clone a HEADER bucket into another pool/bucket_alloc that may + * have a separate lifetime than the source bucket/pool. + * @param source the header bucket to clone + * @param p A pool to allocate the data out of. + * @param list The bucket allocator from which to allocate the bucket + * @return The new bucket, or NULL if allocation failed + */ +AP_DECLARE(apr_bucket *) ap_bucket_headers_clone(apr_bucket *source, + apr_pool_t *p, + apr_bucket_alloc_t *list); + AP_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, apr_bucket_brigade *b); AP_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, apr_bucket_brigade *b); AP_DECLARE_NONSTD(apr_status_t) ap_content_length_filter(ap_filter_t *, @@ -1048,12 +1306,46 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew, const request_rec *r AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub_r); /** + * Set standard response headers, such as `Date` and `Server` + * in r->headers_out. Takes care of precedence of existing + * values from proxied requests. + */ +AP_DECLARE(void) ap_set_std_response_headers(request_rec *r); + +/** * Send an interim (HTTP 1xx) response immediately. * @param r The request * @param send_headers Whether to send&clear headers in r->headers_out */ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers); +/** + * Append the headers in HTTP/1.1 format to the brigade. + * @param b the brigade to append to + * @param r the reqeust this is done for (pool and logging) + * @param headers the headers to append + */ +AP_DECLARE(apr_status_t) ap_h1_append_headers(apr_bucket_brigade *b, + request_rec *r, + apr_table_t *headers); + +/** + * Append the HTTP/1.1 header termination (empty CRLF) to the brigade. + * @param b the brigade to append to + */ +AP_DECLARE(apr_status_t) ap_h1_terminate_header(apr_bucket_brigade *b); + +/** + * Insert/Append the last chunk in a HTTP/1.1 Transfer-Encoding chunked. + * @param b the brigade to add the chunk to + * @param eos the bucket before to add or NULL for insert at tail + * @param r the request handled + * @param trailers table of trailers or NULL + */ +AP_DECLARE(void) ap_h1_add_end_chunk(apr_bucket_brigade *b, + apr_bucket *eos, + request_rec *r, + apr_table_t *trailers); #ifdef __cplusplus } diff --git a/include/httpd.h b/include/httpd.h index 41e570799e..a4c32535c7 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1144,6 +1144,14 @@ struct request_rec { * the elements of this field. */ ap_request_bnotes_t bnotes; + /** Indicates that the request has a body of unknown length and + * protocol handlers need to read it, even if only to discard the + * data. In HTTP/1.1 this is set on chunked transfer encodings, but + * newer HTTP versions can transfer such bodies by other means. The + * absence of a "Transfer-Encoding" header is no longer sufficient + * to conclude that no body is there. + */ + int body_indeterminate; }; /** diff --git a/include/mod_core.h b/include/mod_core.h index 8eab3e12c4..4897fee6f5 100644 --- a/include/mod_core.h +++ b/include/mod_core.h @@ -40,6 +40,7 @@ extern "C" { /* Handles for core filters */ AP_DECLARE_DATA extern ap_filter_rec_t *ap_http_input_filter_handle; +AP_DECLARE_DATA extern ap_filter_rec_t *ap_h1_body_in_filter_handle; AP_DECLARE_DATA extern ap_filter_rec_t *ap_http_header_filter_handle; AP_DECLARE_DATA extern ap_filter_rec_t *ap_chunk_filter_handle; AP_DECLARE_DATA extern ap_filter_rec_t *ap_http_outerror_filter_handle; @@ -52,6 +53,10 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes); +apr_status_t ap_h1_body_in_filter(ap_filter_t *f, apr_bucket_brigade *b, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes); + /* HTTP/1.1 chunked transfer encoding filter. */ apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b); diff --git a/libhttpd.dsp b/libhttpd.dsp index f9a656ee3a..6467f8b840 100644 --- a/libhttpd.dsp +++ b/libhttpd.dsp @@ -525,6 +525,10 @@ SOURCE=.\server\error_bucket.c # End Source File # Begin Source File +SOURCE=.\server\headers_bucket.c +# End Source File +# Begin Source File + SOURCE=.\server\util.c # End Source File # Begin Source File diff --git a/modules/http/chunk_filter.c b/modules/http/chunk_filter.c index d18a84ec9b..f44543a765 100644 --- a/modules/http/chunk_filter.c +++ b/modules/http/chunk_filter.c @@ -19,6 +19,7 @@ */ #include "apr_strings.h" +#include "apr_lib.h" #include "apr_thread_proc.h" /* for RLIMIT stuff */ #define APR_WANT_STRFUNC @@ -28,6 +29,7 @@ #include "http_config.h" #include "http_connection.h" #include "http_core.h" +#include "http_log.h" #include "http_protocol.h" /* For index_of_response(). Grump. */ #include "http_request.h" @@ -38,19 +40,28 @@ #include "mod_core.h" -/* - * A pointer to this is used to memorize in the filter context that a bad - * gateway error bucket had been seen. It is used as an invented unique pointer. - */ -static char bad_gateway_seen; + +APLOG_USE_MODULE(http); + + +typedef struct chunk_out_ctx { + int bad_gateway_seen; + apr_table_t *trailers; +} chunk_out_ctx; + apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b) { conn_rec *c = f->r->connection; + chunk_out_ctx *ctx = f->ctx; apr_bucket_brigade *more, *tmp; apr_bucket *e; apr_status_t rv; + if (!ctx) { + ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); + } + for (more = tmp = NULL; b; b = more, more = NULL) { apr_off_t bytes = 0; apr_bucket *eos = NULL; @@ -65,28 +76,43 @@ apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b) e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) { - if (APR_BUCKET_IS_EOS(e)) { - /* there shouldn't be anything after the eos */ - ap_remove_output_filter(f); - eos = e; - break; - } - if (AP_BUCKET_IS_ERROR(e) && - (((ap_bucket_error *)(e->data))->status == HTTP_BAD_GATEWAY || - ((ap_bucket_error *)(e->data))->status == HTTP_GATEWAY_TIME_OUT)) { - /* - * We had a broken backend. Memorize this in the filter - * context. - */ - f->ctx = &bad_gateway_seen; - continue; - } - if (APR_BUCKET_IS_FLUSH(e)) { - flush = e; - if (e != APR_BRIGADE_LAST(b)) { - more = apr_brigade_split_ex(b, APR_BUCKET_NEXT(e), tmp); + if (APR_BUCKET_IS_METADATA(e)) { + if (APR_BUCKET_IS_EOS(e)) { + /* there shouldn't be anything after the eos */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r, + "ap_http_chunk_filter eos seen, removing filter"); + ap_remove_output_filter(f); + eos = e; + break; + } + if (AP_BUCKET_IS_ERROR(e) && + (((ap_bucket_error *)(e->data))->status == HTTP_BAD_GATEWAY || + ((ap_bucket_error *)(e->data))->status == HTTP_GATEWAY_TIME_OUT)) { + /* + * We had a broken backend. Memorize this in the filter + * context. + */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r, + "ap_http_chunk_filter bad gateway error, suppressing end chunk"); + ctx->bad_gateway_seen = 1; + continue; + } + if (APR_BUCKET_IS_FLUSH(e)) { + flush = e; + if (e != APR_BRIGADE_LAST(b)) { + more = apr_brigade_split_ex(b, APR_BUCKET_NEXT(e), tmp); + } + break; + } + if (AP_BUCKET_IS_HEADERS(e)) { + ap_bucket_headers *hdrs = e->data; + if (!apr_is_empty_table(hdrs->headers)) { + if (!ctx->trailers) { + ctx->trailers = apr_table_make(f->r->pool, 5); + } + apr_table_overlap(ctx->trailers, hdrs->headers, APR_OVERLAP_TABLES_MERGE); + } } - break; } else if (e->length == (apr_size_t)-1) { /* unknown amount of data (e.g. a pipe) */ @@ -132,6 +158,9 @@ apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b) * Insert the chunk header, specifying the number of bytes in * the chunk. */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r, + "ap_http_chunk_filter sending chunk of %" + APR_UINT64_T_HEX_FMT " bytes", (apr_uint64_t)bytes); hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr), "%" APR_UINT64_T_HEX_FMT CRLF, (apr_uint64_t)bytes); ap_xlate_proto_to_ascii(chunk_hdr, hdr_len); @@ -175,12 +204,8 @@ apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b) * marker above, but this is a bit more straight-forward for * now. */ - if (eos && !f->ctx) { - /* XXX: (2) trailers ... does not yet exist */ - e = apr_bucket_immortal_create(ZERO_ASCII CRLF_ASCII - /* <trailers> */ - CRLF_ASCII, 5, c->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(eos, e); + if (eos && !ctx->bad_gateway_seen) { + ap_h1_add_end_chunk(b, eos, f->r, ctx->trailers); } /* pass the brigade to the next filter. */ diff --git a/modules/http/http_core.c b/modules/http/http_core.c index 9e875ce2cc..ce87ef2045 100644 --- a/modules/http/http_core.c +++ b/modules/http/http_core.c @@ -36,6 +36,7 @@ /* Handles for core filters */ AP_DECLARE_DATA ap_filter_rec_t *ap_http_input_filter_handle; +AP_DECLARE_DATA ap_filter_rec_t *ap_h1_body_in_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_http_header_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_chunk_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_http_outerror_filter_handle; @@ -301,6 +302,9 @@ static void register_hooks(apr_pool_t *p) ap_http_input_filter_handle = ap_register_input_filter("HTTP_IN", ap_http_filter, NULL, AP_FTYPE_PROTOCOL); + ap_h1_body_in_filter_handle = + ap_register_input_filter("HTTP1_BODY_IN", ap_h1_body_in_filter, + NULL, AP_FTYPE_TRANSCODE); ap_http_header_filter_handle = ap_register_output_filter("HTTP_HEADER", ap_http_header_filter, NULL, AP_FTYPE_PROTOCOL); diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index d41a519b50..9bcf2297d0 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -254,21 +254,28 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, } static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, - apr_bucket_brigade *b, int merge) + apr_bucket_brigade *b) { int rv; apr_bucket *e; request_rec *r = f->r; + apr_table_t *trailers; apr_table_t *saved_headers_in = r->headers_in; int saved_status = r->status; + trailers = apr_table_make(r->pool, 5); r->status = HTTP_OK; - r->headers_in = r->trailers_in; - apr_table_clear(r->headers_in); + r->headers_in = trailers; ap_get_mime_headers(r); + r->headers_in = saved_headers_in; - if(r->status == HTTP_OK) { + if (r->status == HTTP_OK) { r->status = saved_status; + + if (!apr_is_empty_table(trailers)) { + e = ap_bucket_headers_create(trailers, r->pool, b->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(b, e); + } e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); ctx->at_eos = 1; @@ -284,17 +291,16 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, rv = APR_EINVAL; } - if(!merge) { - r->headers_in = saved_headers_in; - } - else { - r->headers_in = apr_table_overlay(r->pool, saved_headers_in, - r->trailers_in); - } - return rv; } +typedef struct h1_in_ctx_t +{ + unsigned int at_trailers:1; + unsigned int at_eos:1; + unsigned int seen_data:1; +} h1_in_ctx_t; + /* This is the HTTP_INPUT filter for HTTP requests and responses from * proxied servers (mod_proxy). It handles chunked and content-length * bodies. This can only be inserted/used after the headers @@ -304,6 +310,122 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { + apr_bucket *e, *next; + h1_in_ctx_t *ctx = f->ctx; + request_rec *r = f->r; + apr_status_t rv; + + if (!ctx) { + f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); + } + + /* Since we're about to read data, send 100-Continue if needed. + * Only valid on chunked and C-L bodies where the C-L is > 0. + * + * If the read is to be nonblocking though, the caller may not want to + * handle this just now (e.g. mod_proxy_http), and is prepared to read + * nothing if the client really waits for 100 continue, so we don't + * send it now and wait for later blocking read. + * + * In any case, even if r->expecting remains set at the end of the + * request handling, ap_set_keepalive() will finally do the right + * thing (i.e. "Connection: close" the connection). + */ + if (block == APR_BLOCK_READ + && r->expecting_100 && r->proto_num >= HTTP_VERSION(1,1) + && !(ctx->at_eos || r->eos_sent || r->bytes_sent)) { + if (!ap_is_HTTP_SUCCESS(r->status)) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "ap_http_in_filter: status != OK, not sending 100-continue"); + ctx->at_eos = 1; /* send EOS below */ + } + else if (!ctx->seen_data) { + int saved_status = r->status; + const char *saved_status_line = r->status_line; + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "ap_http_in_filter: sending 100-continue"); + r->status = HTTP_CONTINUE; + r->status_line = NULL; + ap_send_interim_response(r, 0); + AP_DEBUG_ASSERT(!r->expecting_100); + r->status_line = saved_status_line; + r->status = saved_status; + } + else { + /* https://tools.ietf.org/html/rfc7231#section-5.1.1 + * A server MAY omit sending a 100 (Continue) response if it + * has already received some or all of the message body for + * the corresponding request [...] + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10260) + "request body already/partly received while " + "100-continue is expected, omit sending interim " + "response"); + r->expecting_100 = 0; + } + } + + /* sanity check in case we're read twice */ + if (ctx->at_eos) { + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(b, e); + rv = APR_SUCCESS; + goto cleanup; + } + + rv = ap_get_brigade(f->next, b, mode, block, readbytes); + if (APR_SUCCESS == rv) { + for (e = APR_BRIGADE_FIRST(b); + e != APR_BRIGADE_SENTINEL(b); + e = next) + { + next = APR_BUCKET_NEXT(e); + if (!APR_BUCKET_IS_METADATA(e)) { + if (e->length != 0) { + ctx->seen_data = 1; + } + if (ctx->at_trailers) { + /* DATA after trailers? Someone smuggling something? */ + rv = AP_FILTER_ERROR; + goto cleanup; + } + continue; + } + if (AP_BUCKET_IS_HEADERS(e)) { + /* trailers */ + ap_bucket_headers * hdrs = e->data; + + /* Allow multiple HEADERS buckets carrying trailers here, + * will not happen from HTTP/1.x and current H2 implementation, + * but is an option. */ + ctx->at_trailers = 1; + if (!apr_is_empty_table(hdrs->headers)) { + r->trailers_in = apr_table_overlay(r->pool, r->trailers_in, hdrs->headers); + } + apr_bucket_delete(e); + } + if (APR_BUCKET_IS_EOS(e)) { + ctx->at_eos = 1; + if (!apr_is_empty_table(r->trailers_in)) { + core_server_config *conf = ap_get_module_config( + r->server->module_config, &core_module); + if (conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) { + r->headers_in = apr_table_overlay(r->pool, r->headers_in, r->trailers_in); + } + } + goto cleanup; + } + } + } + +cleanup: + return rv; +} + +apr_status_t ap_h1_body_in_filter(ap_filter_t *f, apr_bucket_brigade *b, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes) +{ core_server_config *conf = (core_server_config *) ap_get_module_config(f->r->server->module_config, &core_module); @@ -360,6 +482,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, */ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01585) "Unknown Transfer-Encoding: %s", tenc); + ap_die(HTTP_NOT_IMPLEMENTED, f->r); return APR_EGENERAL; } lenp = NULL; @@ -407,51 +530,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, } } - /* Since we're about to read data, send 100-Continue if needed. - * Only valid on chunked and C-L bodies where the C-L is > 0. - * - * If the read is to be nonblocking though, the caller may not want to - * handle this just now (e.g. mod_proxy_http), and is prepared to read - * nothing if the client really waits for 100 continue, so we don't - * send it now and wait for later blocking read. - * - * In any case, even if r->expecting remains set at the end of the - * request handling, ap_set_keepalive() will finally do the right - * thing (i.e. "Connection: close" the connection). - */ - if (block == APR_BLOCK_READ - && (ctx->state == BODY_CHUNK - || (ctx->state == BODY_LENGTH && ctx->remaining > 0)) - && f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1) - && !(ctx->at_eos || f->r->eos_sent || f->r->bytes_sent)) { - if (!ap_is_HTTP_SUCCESS(f->r->status)) { - ctx->state = BODY_NONE; - ctx->at_eos = 1; /* send EOS below */ - } - else if (!ctx->seen_data) { - int saved_status = f->r->status; - const char *saved_status_line = f->r->status_line; - f->r->status = HTTP_CONTINUE; - f->r->status_line = NULL; - ap_send_interim_response(f->r, 0); - AP_DEBUG_ASSERT(!f->r->expecting_100); - f->r->status_line = saved_status_line; - f->r->status = saved_status; - } - else { - /* https://tools.ietf.org/html/rfc7231#section-5.1.1 - * A server MAY omit sending a 100 (Continue) response if it - * has already received some or all of the message body for - * the corresponding request [...] - */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(10260) - "request body already/partly received while " - "100-continue is expected, omit sending interim " - "response"); - f->r->expecting_100 = 0; - } - } - /* sanity check in case we're read twice */ if (ctx->at_eos) { e = apr_bucket_eos_create(f->c->bucket_alloc); @@ -519,8 +597,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, if (ctx->state == BODY_CHUNK_TRAILER) { /* Treat UNSET as DISABLE - trailers aren't merged by default */ - return read_chunked_trailers(ctx, f, b, - conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); + return read_chunked_trailers(ctx, f, b); } break; @@ -1636,25 +1713,22 @@ cleanup: AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) { - const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); const char *lenp = apr_table_get(r->headers_in, "Content-Length"); r->read_body = read_policy; r->read_chunked = 0; r->remaining = 0; - if (tenc) { - if (ap_cstr_casecmp(tenc, "chunked")) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01592) - "Unknown Transfer-Encoding %s", tenc); - return HTTP_NOT_IMPLEMENTED; - } + if (r->body_indeterminate) { + /* Protocols like HTTP/2 can carry bodies without length and + * HTTP/1.1 has chunked encoding signalled via this note. + */ if (r->read_body == REQUEST_CHUNKED_ERROR) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01593) - "chunked Transfer-Encoding forbidden: %s", r->uri); + "indeterminate request body length forbidden: %s", r->uri); + r->read_chunked = 0; return (lenp) ? HTTP_BAD_REQUEST : HTTP_LENGTH_REQUIRED; } - r->read_chunked = 1; } else if (lenp) { diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c index 9cae54aee7..59fef62756 100644 --- a/modules/http/http_protocol.c +++ b/modules/http/http_protocol.c @@ -1485,3 +1485,108 @@ AP_DECLARE(void) ap_clear_method_list(ap_method_list_t *l) l->method_list->nelts = 0; } +/* Send a request's HTTP response headers to the client. + */ +AP_DECLARE(apr_status_t) ap_h1_append_headers(apr_bucket_brigade *bb, + request_rec *r, + apr_table_t *headers) +{ + const apr_array_header_t *elts; + const apr_table_entry_t *t_elt; + const apr_table_entry_t *t_end; + struct iovec *vec; + struct iovec *vec_next; + + elts = apr_table_elts(headers); + if (elts->nelts == 0) { + return APR_SUCCESS; + } + t_elt = (const apr_table_entry_t *)(elts->elts); + t_end = t_elt + elts->nelts; + vec = (struct iovec *)apr_palloc(r->pool, 4 * elts->nelts * + sizeof(struct iovec)); + vec_next = vec; + + /* For each field, generate + * name ": " value CRLF + */ + do { + if (t_elt->key && t_elt->val) { + vec_next->iov_base = (void*)(t_elt->key); + vec_next->iov_len = strlen(t_elt->key); + vec_next++; + vec_next->iov_base = ": "; + vec_next->iov_len = sizeof(": ") - 1; + vec_next++; + vec_next->iov_base = (void*)(t_elt->val); + vec_next->iov_len = strlen(t_elt->val); + vec_next++; + vec_next->iov_base = CRLF; + vec_next->iov_len = sizeof(CRLF) - 1; + vec_next++; + } + t_elt++; + } while (t_elt < t_end); + + if (APLOGrtrace4(r)) { + t_elt = (const apr_table_entry_t *)(elts->elts); + do { + ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, " %s: %s", + t_elt->key, t_elt->val); + t_elt++; + } while (t_elt < t_end); + } + +#if APR_CHARSET_EBCDIC + { + apr_size_t len; + char *tmp = apr_pstrcatv(r->pool, vec, vec_next - vec, &len); + ap_xlate_proto_to_ascii(tmp, len); + return apr_brigade_write(bb, NULL, NULL, tmp, len); + } +#else + return apr_brigade_writev(bb, NULL, NULL, vec, vec_next - vec); +#endif +} + +AP_DECLARE(apr_status_t) ap_h1_terminate_header(apr_bucket_brigade *bb) +{ + char crlf[] = CRLF; + apr_size_t buflen; + + buflen = strlen(crlf); + ap_xlate_proto_to_ascii(crlf, buflen); + return apr_brigade_write(bb, NULL, NULL, crlf, buflen); +} + +AP_DECLARE(void) ap_h1_add_end_chunk(apr_bucket_brigade *b, + apr_bucket *eos, + request_rec *r, + apr_table_t *trailers) +{ + if (!trailers || apr_is_empty_table(trailers)) { + apr_bucket *e; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "append empty end chunk"); + e = apr_bucket_immortal_create(ZERO_ASCII CRLF_ASCII + CRLF_ASCII, 5, b->bucket_alloc); + if (eos) { + APR_BUCKET_INSERT_BEFORE(eos, e); + } + else { + APR_BRIGADE_INSERT_TAIL(b, e); + } + } + else { + apr_bucket_brigade *tmp; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "append end chunk with trailers"); + tmp = eos? apr_brigade_split_ex(b, eos, NULL) : NULL; + apr_brigade_write(b, NULL, NULL, ZERO_ASCII CRLF_ASCII, 3); + ap_h1_append_headers(b, r, trailers); + ap_h1_terminate_header(b); + if (tmp) APR_BRIGADE_CONCAT(b, tmp); + } +} diff --git a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c index 751a509ba2..acb28ff3de 100644 --- a/modules/http2/h2_c2.c +++ b/modules/http2/h2_c2.c @@ -478,6 +478,16 @@ static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c) /* the request_rec->server carries the timeout value that applies */ h2_conn_ctx_set_timeout(conn_ctx, r->server->timeout); + /* We only handle this one request on the connection and tell everyone + * that there is no need to keep it "clean" if something fails. Also, + * this prevents mod_reqtimeout from doing funny business with monitoring + * keepalive timeouts. + */ + r->connection->keepalive = AP_CONN_CLOSE; + + if (conn_ctx->beam_in && !apr_table_get(r->headers_in, "Content-Length")) { + r->body_indeterminate = 1; + } if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, diff --git a/modules/http2/h2_c2_filter.c b/modules/http2/h2_c2_filter.c index 96e56cdd6f..dabfd420ea 100644 --- a/modules/http2/h2_c2_filter.c +++ b/modules/http2/h2_c2_filter.c @@ -668,139 +668,6 @@ apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb) } -struct h2_chunk_filter_t { - const char *id; - int eos_chunk_added; - apr_bucket_brigade *bbchunk; - apr_off_t chunked_total; -}; -typedef struct h2_chunk_filter_t h2_chunk_filter_t; - - -static void make_chunk(conn_rec *c, h2_chunk_filter_t *fctx, apr_bucket_brigade *bb, - apr_bucket *first, apr_off_t chunk_len, - apr_bucket *tail) -{ - /* Surround the buckets [first, tail[ with new buckets carrying the - * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends - * to the end of the brigade. */ - char buffer[128]; - apr_bucket *b; - apr_size_t len; - - len = (apr_size_t)apr_snprintf(buffer, H2_ALEN(buffer), - "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len); - b = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(first, b); - b = apr_bucket_immortal_create("\r\n", 2, bb->bucket_alloc); - if (tail) { - APR_BUCKET_INSERT_BEFORE(tail, b); - } - else { - APR_BRIGADE_INSERT_TAIL(bb, b); - } - fctx->chunked_total += chunk_len; - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, - "h2_c2(%s): added chunk %ld, total %ld", - fctx->id, (long)chunk_len, (long)fctx->chunked_total); -} - -static int ser_header(void *ctx, const char *name, const char *value) -{ - apr_bucket_brigade *bb = ctx; - apr_brigade_printf(bb, NULL, NULL, "%s: %s\r\n", name, value); - return 1; -} - -static apr_status_t read_and_chunk(ap_filter_t *f, h2_conn_ctx_t *conn_ctx, - apr_read_type_e block) { - h2_chunk_filter_t *fctx = f->ctx; - request_rec *r = f->r; - apr_status_t status = APR_SUCCESS; - - if (!fctx->bbchunk) { - fctx->bbchunk = apr_brigade_create(r->pool, f->c->bucket_alloc); - } - - if (APR_BRIGADE_EMPTY(fctx->bbchunk)) { - apr_bucket *b, *next, *first_data = NULL; - apr_bucket_brigade *tmp; - apr_off_t bblen = 0; - - /* get more data from the lower layer filters. Always do this - * in larger pieces, since we handle the read modes ourself. */ - status = ap_get_brigade(f->next, fctx->bbchunk, - AP_MODE_READBYTES, block, conn_ctx->mplx->stream_max_mem); - if (status != APR_SUCCESS) { - return status; - } - - for (b = APR_BRIGADE_FIRST(fctx->bbchunk); - b != APR_BRIGADE_SENTINEL(fctx->bbchunk); - b = next) { - next = APR_BUCKET_NEXT(b); - if (APR_BUCKET_IS_METADATA(b)) { - if (first_data) { - make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, b); - first_data = NULL; - } - - if (H2_BUCKET_IS_HEADERS(b)) { - h2_headers *headers = h2_bucket_headers_get(b); - - ap_assert(headers); - ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, - "h2_c2(%s-%d): receiving trailers", - conn_ctx->id, conn_ctx->stream_id); - tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL); - if (!apr_is_empty_table(headers->headers)) { - status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n"); - apr_table_do(ser_header, fctx->bbchunk, headers->headers, NULL); - status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "\r\n"); - } - else { - status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n"); - } - r->trailers_in = apr_table_clone(r->pool, headers->headers); - APR_BUCKET_REMOVE(b); - apr_bucket_destroy(b); - APR_BRIGADE_CONCAT(fctx->bbchunk, tmp); - apr_brigade_destroy(tmp); - fctx->eos_chunk_added = 1; - } - else if (APR_BUCKET_IS_EOS(b)) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, - "h2_c2(%s-%d): receiving eos", - conn_ctx->id, conn_ctx->stream_id); - if (!fctx->eos_chunk_added) { - tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL); - status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n"); - APR_BRIGADE_CONCAT(fctx->bbchunk, tmp); - apr_brigade_destroy(tmp); - } - fctx->eos_chunk_added = 0; - } - } - else if (b->length == 0) { - APR_BUCKET_REMOVE(b); - apr_bucket_destroy(b); - } - else { - if (!first_data) { - first_data = b; - bblen = 0; - } - bblen += b->length; - } - } - - if (first_data) { - make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, NULL); - } - } - return status; -} - apr_status_t h2_c2_filter_request_in(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, @@ -808,7 +675,6 @@ apr_status_t h2_c2_filter_request_in(ap_filter_t* f, apr_off_t readbytes) { h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c); - h2_chunk_filter_t *fctx = f->ctx; request_rec *r = f->r; apr_status_t status = APR_SUCCESS; apr_bucket *b, *next; @@ -817,89 +683,36 @@ apr_status_t h2_c2_filter_request_in(ap_filter_t* f, &core_module); ap_assert(conn_ctx); - if (!fctx) { - fctx = apr_pcalloc(r->pool, sizeof(*fctx)); - fctx->id = apr_psprintf(r->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id); - f->ctx = fctx; - } - ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r, "h2_c2(%s-%d): request input, exp=%d", conn_ctx->id, conn_ctx->stream_id, r->expecting_100); - if (!conn_ctx->request->chunked) { - status = ap_get_brigade(f->next, bb, mode, block, readbytes); - /* pipe data through, just take care of trailers */ - for (b = APR_BRIGADE_FIRST(bb); - b != APR_BRIGADE_SENTINEL(bb); b = next) { - next = APR_BUCKET_NEXT(b); - if (H2_BUCKET_IS_HEADERS(b)) { - h2_headers *headers = h2_bucket_headers_get(b); - ap_assert(headers); - ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, - "h2_c2(%s-%d): receiving trailers", - conn_ctx->id, conn_ctx->stream_id); - r->trailers_in = headers->headers; - if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) { - r->headers_in = apr_table_overlay(r->pool, r->headers_in, - r->trailers_in); - } - APR_BUCKET_REMOVE(b); - apr_bucket_destroy(b); - ap_remove_input_filter(f); - - if (headers->raw_bytes && h2_c_logio_add_bytes_in) { - h2_c_logio_add_bytes_in(f->c, headers->raw_bytes); - } - break; + + status = ap_get_brigade(f->next, bb, mode, block, readbytes); + /* pipe data through, just take care of trailers */ + for (b = APR_BRIGADE_FIRST(bb); + b != APR_BRIGADE_SENTINEL(bb); b = next) { + next = APR_BUCKET_NEXT(b); + if (H2_BUCKET_IS_HEADERS(b)) { + h2_headers *headers = h2_bucket_headers_get(b); + ap_assert(headers); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "h2_c2(%s-%d): receiving trailers", + conn_ctx->id, conn_ctx->stream_id); + r->trailers_in = headers->headers; + if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) { + r->headers_in = apr_table_overlay(r->pool, r->headers_in, + r->trailers_in); } - } - return status; - } + APR_BUCKET_REMOVE(b); + apr_bucket_destroy(b); + ap_remove_input_filter(f); - /* Things are more complicated. The standard HTTP input filter, which - * does a lot what we do not want to duplicate, also cares about chunked - * transfer encoding and trailers. - * We need to simulate chunked encoding for it to be happy. - */ - if ((status = read_and_chunk(f, conn_ctx, block)) != APR_SUCCESS) { - return status; - } - - if (mode == AP_MODE_EXHAUSTIVE) { - /* return all we have */ - APR_BRIGADE_CONCAT(bb, fctx->bbchunk); - } - else if (mode == AP_MODE_READBYTES) { - status = h2_brigade_concat_length(bb, fctx->bbchunk, readbytes); - } - else if (mode == AP_MODE_SPECULATIVE) { - status = h2_brigade_copy_length(bb, fctx->bbchunk, readbytes); - } - else if (mode == AP_MODE_GETLINE) { - /* we are reading a single LF line, e.g. the HTTP headers. - * this has the nasty side effect to split the bucket, even - * though it ends with CRLF and creates a 0 length bucket */ - status = apr_brigade_split_line(bb, fctx->bbchunk, block, HUGE_STRING_LEN); - if (APLOGctrace1(f->c)) { - char buffer[1024]; - apr_size_t len = sizeof(buffer)-1; - apr_brigade_flatten(bb, buffer, &len); - buffer[len] = 0; - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, - "h2_c2(%s-%d): getline: %s", - conn_ctx->id, conn_ctx->stream_id, buffer); + if (headers->raw_bytes && h2_c_logio_add_bytes_in) { + h2_c_logio_add_bytes_in(f->c, headers->raw_bytes); + } + break; } } - else { - /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not - * to support it. Seems to work. */ - ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c, - APLOGNO(02942) - "h2_c2, unsupported READ mode %d", mode); - status = APR_ENOTIMPL; - } - - h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2, "returning input", bb); return status; } diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 1557449785..37a7a9339c 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -632,8 +632,8 @@ static apr_status_t c1_process_stream(h2_mplx *m, if (APLOGctrace1(m->c1)) { const h2_request *r = stream->request; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1, - H2_STRM_MSG(stream, "process %s %s://%s%s chunked=%d"), - r->method, r->scheme, r->authority, r->path, r->chunked); + H2_STRM_MSG(stream, "process %s %s://%s%s"), + r->method, r->scheme, r->authority, r->path); } rv = h2_stream_setup_input(stream); diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index c65f88d334..aa5f764a98 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -211,15 +211,7 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos, * internal request processing is used to HTTP/1.1, so we * need to either add a Content-Length or a Transfer-Encoding * if any content can be expected. */ - if (!eos) { - /* We have not seen a content-length and have no eos, - * simulate a chunked encoding for our HTTP/1.1 infrastructure, - * in case we have "H2SerializeHeaders on" here - */ - req->chunked = 1; - apr_table_mergen(req->headers, "Transfer-Encoding", "chunked"); - } - else if (apr_table_get(req->headers, "Content-Type")) { + if (eos && apr_table_get(req->headers, "Content-Type")) { /* If we have a content-type, but already seen eos, no more * data will come. Signal a zero content length explicitly. */ diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index 1cc275e18c..d8739e8a7f 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -815,7 +815,8 @@ PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *c PROXY_DECLARE(int) ap_proxy_pre_http_request(conn_rec *c, request_rec *r) { ap_add_input_filter("HTTP_IN", NULL, r, c); - return OK; + ap_add_input_filter("HTTP1_BODY_IN", NULL, r, c); + return OK; } PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r, diff --git a/server/Makefile.in b/server/Makefile.in index ea11546632..3b25ebcafd 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -14,7 +14,7 @@ LTLIBRARY_SOURCES = \ util_charset.c util_cookies.c util_debug.c util_xml.c \ util_filter.c util_pcre.c util_regex.c $(EXPORTS_DOT_C) \ scoreboard.c error_bucket.c protocol.c core.c request.c ssl.c provider.c \ - eoc_bucket.c eor_bucket.c core_filters.c \ + eoc_bucket.c eor_bucket.c headers_bucket.c core_filters.c \ util_expr_parse.c util_expr_scan.c util_expr_eval.c \ apreq_cookie.c apreq_error.c apreq_module.c \ apreq_module_cgi.c apreq_module_custom.c apreq_param.c \ diff --git a/server/headers_bucket.c b/server/headers_bucket.c new file mode 100644 index 0000000000..1fa782d884 --- /dev/null +++ b/server/headers_bucket.c @@ -0,0 +1,269 @@ +/* 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 "http_protocol.h" +#include "apr_buckets.h" +#include "apr_strings.h" +#if APR_HAVE_STRINGS_H +#include <strings.h> +#endif + +static apr_status_t dummy_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block) +{ + *str = NULL; + *len = 0; + return APR_SUCCESS; +} + +static void request_bucket_destroy(void *data) +{ + ap_bucket_request *h = data; + + if (apr_bucket_shared_destroy(h)) { + apr_bucket_free(h); + } +} + +AP_DECLARE(apr_bucket *) ap_bucket_request_make( + apr_bucket *b, + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p) +{ + return ap_bucket_request_maken(b, apr_pstrdup(p, method), + apr_pstrdup(p, uri), protocol, + headers? apr_table_clone(p, headers) : NULL, + p); +} + +AP_DECLARE(apr_bucket *) ap_bucket_request_maken( + apr_bucket *b, + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p) +{ + ap_bucket_request *h; + + h = apr_bucket_alloc(sizeof(*h), b->list); + h->pool = p; + h->method = method; + h->uri = uri; + h->protocol = protocol; + h->headers = headers; + + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_request; + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_request_create( + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + return ap_bucket_request_make(b, method, uri, protocol, headers, p); +} + +AP_DECLARE(apr_bucket *) ap_bucket_request_createn( + const char *method, + const char *uri, + const char *protocol, + apr_table_t *headers, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + return ap_bucket_request_maken(b, method, uri, protocol, headers, p); +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_request = { + "REQUEST", 5, APR_BUCKET_METADATA, + request_bucket_destroy, + dummy_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_shared_copy +}; + +AP_DECLARE(apr_bucket *) ap_bucket_request_clone( + apr_bucket *source, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + ap_bucket_request *sreq = source->data; + + AP_DEBUG_ASSERT(AP_BUCKET_IS_REQUEST(source)); + return ap_bucket_request_create(sreq->method, sreq->uri, + sreq->protocol, sreq->headers, p, list); +} + +static void response_bucket_destroy(void *data) +{ + ap_bucket_response *h = data; + + if (apr_bucket_shared_destroy(h)) { + apr_bucket_free(h); + } +} + +AP_DECLARE(apr_bucket *) ap_bucket_response_make(apr_bucket *b, int status, + const char *reason, + apr_table_t *headers, + apr_table_t *notes, + apr_pool_t *p) +{ + ap_bucket_response *h; + + h = apr_bucket_alloc(sizeof(*h), b->list); + h->pool = p; + h->status = status; + h->reason = reason? apr_pstrdup(p, reason) : NULL; + h->headers = headers? apr_table_copy(p, headers) : apr_table_make(p, 5); + h->notes = notes? apr_table_copy(p, notes) : apr_table_make(p, 5); + + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_response; + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_response_create(int status, const char *reason, + apr_table_t *headers, + apr_table_t *notes, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + return ap_bucket_response_make(b, status, reason, headers, notes, p); +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_response = { + "RESPONSE", 5, APR_BUCKET_METADATA, + response_bucket_destroy, + dummy_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_shared_copy +}; + +AP_DECLARE(apr_bucket *) ap_bucket_response_clone(apr_bucket *source, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + ap_bucket_response *sresp = source->data; + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + ap_bucket_response *h; + + AP_DEBUG_ASSERT(AP_BUCKET_IS_RESPONSE(source)); + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + h = apr_bucket_alloc(sizeof(*h), b->list); + h->status = sresp->status; + h->reason = sresp->reason? apr_pstrdup(p, sresp->reason) : NULL; + h->headers = apr_table_clone(p, sresp->headers); + h->notes = apr_table_clone(p, sresp->notes); + + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_response; + return b; +} + +static void headers_bucket_destroy(void *data) +{ + ap_bucket_headers *h = data; + + if (apr_bucket_shared_destroy(h)) { + apr_bucket_free(h); + } +} + +AP_DECLARE(apr_bucket *) ap_bucket_headers_make(apr_bucket *b, + apr_table_t *headers, + apr_pool_t *p) +{ + ap_bucket_headers *h; + + h = apr_bucket_alloc(sizeof(*h), b->list); + h->pool = p; + h->headers = headers? apr_table_copy(p, headers) : apr_table_make(p, 5); + + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_headers; + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_headers_create(apr_table_t *headers, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + return ap_bucket_headers_make(b, headers, p); +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_headers = { + "HEADERS", 5, APR_BUCKET_METADATA, + headers_bucket_destroy, + dummy_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_shared_copy +}; + +AP_DECLARE(apr_bucket *) ap_bucket_headers_clone(apr_bucket *source, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + ap_bucket_headers *shdrs = source->data; + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + ap_bucket_headers *h; + + AP_DEBUG_ASSERT(AP_BUCKET_IS_HEADERS(source)); + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + h = apr_bucket_alloc(sizeof(*h), b->list); + h->headers = apr_table_clone(p, shdrs->headers); + + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_headers; + return b; +} + diff --git a/server/protocol.c b/server/protocol.c index c5570f6d5b..ee2bfd4cb2 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -47,6 +47,7 @@ #include "mod_core.h" #include "util_charset.h" #include "util_ebcdic.h" +#include "util_time.h" #include "scoreboard.h" #if APR_HAVE_STDARG_H @@ -1465,6 +1466,18 @@ static void apply_server_config(request_rec *r) r->per_dir_config = r->server->lookup_defaults; } +AP_DECLARE(int) ap_assign_request(request_rec *r, + const char *method, const char *uri, + const char *protocol) +{ + /* dummy, for now */ + (void)r; + (void)method; + (void)uri; + (void)protocol; + return 0; +} + request_rec *ap_read_request(conn_rec *conn) { int access_status; @@ -1583,6 +1596,14 @@ request_rec *ap_read_request(conn_rec *conn) */ ap_add_input_filter_handle(ap_http_input_filter_handle, NULL, r, r->connection); + if (r->proto_num <= HTTP_VERSION(1,1)) { + ap_add_input_filter_handle(ap_h1_body_in_filter_handle, + NULL, r, r->connection); + if (r->proto_num >= HTTP_VERSION(1,0) + && apr_table_get(r->headers_in, "Transfer-Encoding")) { + r->body_indeterminate = 1; + } + } /* Validate Host/Expect headers and select vhost. */ if (!ap_check_request_header(r)) { @@ -2385,6 +2406,38 @@ static int send_header(void *data, const char *key, const char *val) } #endif +AP_DECLARE(void) ap_set_std_response_headers(request_rec *r) +{ + const char *server = NULL, *date; + char *s; + + /* Before generating a response, we make sure that `Date` and `Server` + * headers are present. When proxying requests, we preserver existing + * values and replace them otherwise. + */ + if (r->proxyreq != PROXYREQ_NONE) { + date = apr_table_get(r->headers_out, "Date"); + if (!date) { + s = apr_palloc(r->pool, APR_RFC822_DATE_LEN); + ap_recent_rfc822_date(s, r->request_time); + date = s; + } + server = apr_table_get(r->headers_out, "Server"); + } + else { + s = apr_palloc(r->pool, APR_RFC822_DATE_LEN); + ap_recent_rfc822_date(s, r->request_time); + date = s; + } + + apr_table_setn(r->headers_out, "Date", date); + + if (!server) + server = ap_get_server_banner(); + if (server && *server) + apr_table_setn(r->headers_out, "Server", server); +} + AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) { hdr_ptr x; |