summaryrefslogtreecommitdiffstats
path: root/modules/http2/h2_request.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/http2/h2_request.c')
-rw-r--r--modules/http2/h2_request.c494
1 files changed, 374 insertions, 120 deletions
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
index 8899c4f..2713947 100644
--- a/modules/http2/h2_request.c
+++ b/modules/http2/h2_request.c
@@ -16,7 +16,12 @@
#include <assert.h>
-#include <apr_strings.h>
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
+
+#include <ap_mmn.h>
#include <httpd.h>
#include <http_core.h>
@@ -24,6 +29,7 @@
#include <http_protocol.h>
#include <http_request.h>
#include <http_log.h>
+#include <http_ssl.h>
#include <http_vhost.h>
#include <util_filter.h>
#include <ap_mpm.h>
@@ -32,11 +38,28 @@
#include "h2_private.h"
#include "h2_config.h"
+#include "h2_conn_ctx.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_util.h"
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+ const char *scheme, const char *authority,
+ const char *path, apr_table_t *header)
+{
+ h2_request *req = apr_pcalloc(pool, sizeof(h2_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;
+}
+
typedef struct {
apr_table_t *headers;
apr_pool_t *pool;
@@ -46,9 +69,9 @@ typedef struct {
static int set_h1_header(void *ctx, const char *key, const char *value)
{
h1_ctx *x = ctx;
- x->status = h2_req_add_header(x->headers, x->pool, key, strlen(key),
- value, strlen(value));
- return (x->status == APR_SUCCESS)? 1 : 0;
+ int was_added;
+ h2_req_add_header(x->headers, x->pool, key, strlen(key), value, strlen(value), 0, &was_added);
+ return 1;
}
apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
@@ -68,54 +91,67 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
return APR_EINVAL;
}
- if (!ap_strchr_c(authority, ':') && r->server && r->server->port) {
- apr_port_t defport = apr_uri_port_of_scheme(scheme);
- if (defport != r->server->port) {
- /* port info missing and port is not default for scheme: append */
- authority = apr_psprintf(pool, "%s:%d", authority,
- (int)r->server->port);
+ /* The authority we carry in h2_request is the 'authority' part of
+ * the URL for the request. r->hostname has stripped any port info that
+ * might have been present. Do we need to add it?
+ */
+ if (!ap_strchr_c(authority, ':')) {
+ if (r->parsed_uri.port_str) {
+ /* Yes, it was there, add it again. */
+ authority = apr_pstrcat(pool, authority, ":", r->parsed_uri.port_str, NULL);
+ }
+ else if (!r->parsed_uri.hostname && r->server && r->server->port) {
+ /* If there was no hostname in the parsed URL, the URL was relative.
+ * In that case, we restore port from our server->port, if it
+ * is known and not the default port for the scheme. */
+ apr_port_t defport = apr_uri_port_of_scheme(scheme);
+ if (defport != r->server->port) {
+ /* port info missing and port is not default for scheme: append */
+ authority = apr_psprintf(pool, "%s:%d", authority,
+ (int)r->server->port);
+ }
}
}
-
+
req = apr_pcalloc(pool, sizeof(*req));
- req->method = apr_pstrdup(pool, r->method);
- req->scheme = scheme;
- req->authority = authority;
- req->path = path;
- req->headers = apr_table_make(pool, 10);
- if (r->server) {
- req->serialize = h2_config_geti(h2_config_sget(r->server),
- H2_CONF_SER_HEADERS);
- }
+ req->method = apr_pstrdup(pool, r->method);
+ req->scheme = scheme;
+ req->authority = authority;
+ req->path = path;
+ req->headers = apr_table_make(pool, 10);
+ req->http_status = H2_HTTP_STATUS_UNSET;
+ req->request_time = apr_time_now();
x.pool = pool;
x.headers = req->headers;
x.status = APR_SUCCESS;
apr_table_do(set_h1_header, &x, r->headers_in, NULL);
-
+
*preq = req;
return x.status;
}
-apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
const char *name, size_t nlen,
- const char *value, size_t vlen)
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added)
{
apr_status_t status = APR_SUCCESS;
-
+
+ *pwas_added = 0;
if (nlen <= 0) {
return status;
}
-
+
if (name[0] == ':') {
/* pseudo header, see ch. 8.1.2.3, always should come first */
if (!apr_is_empty_table(req->headers)) {
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
- APLOGNO(02917)
+ APLOGNO(02917)
"h2_request: pseudo header after request start");
return APR_EGENERAL;
}
-
+
if (H2_HEADER_METHOD_LEN == nlen
&& !strncmp(H2_HEADER_METHOD, name, nlen)) {
req->method = apr_pstrndup(pool, value, vlen);
@@ -132,32 +168,36 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
&& !strncmp(H2_HEADER_AUTH, name, nlen)) {
req->authority = apr_pstrndup(pool, value, vlen);
}
+ else if (H2_HEADER_PROTO_LEN == nlen
+ && !strncmp(H2_HEADER_PROTO, name, nlen)) {
+ req->protocol = apr_pstrndup(pool, value, vlen);
+ }
else {
char buffer[32];
memset(buffer, 0, 32);
strncpy(buffer, name, (nlen > 31)? 31 : nlen);
ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
- APLOGNO(02954)
+ APLOGNO(02954)
"h2_request: ignoring unknown pseudo header %s",
buffer);
}
}
else {
- /* non-pseudo header, append to work bucket of stream */
- status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen);
+ /* non-pseudo header, add to table */
+ status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen,
+ max_field_len, pwas_added);
}
-
+
return status;
}
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos, size_t raw_bytes)
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
+ size_t raw_bytes)
{
- const char *s;
-
- /* rfc7540, ch. 8.1.2.3:
- * - if we have :authority, it overrides any Host header
- * - :authority MUST be ommited when converting h1->h2, so we
- * might get a stream without, but then Host needs to be there */
+ /* rfc7540, ch. 8.1.2.3: without :authority, Host: must be there */
+ if (req->authority && !strlen(req->authority)) {
+ req->authority = NULL;
+ }
if (!req->authority) {
const char *host = apr_table_get(req->headers, "Host");
if (!host) {
@@ -168,30 +208,8 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos,
else {
apr_table_setn(req->headers, "Host", req->authority);
}
-
- s = apr_table_get(req->headers, "Content-Length");
- if (!s) {
- /* HTTP/2 does not need a Content-Length for framing, but our
- * 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 we have a content-type, but already seen eos, no more
- * data will come. Signal a zero content length explicitly.
- */
- apr_table_setn(req->headers, "Content-Length", "0");
- }
- }
req->raw_bytes += raw_bytes;
-
+
return APR_SUCCESS;
}
@@ -202,17 +220,16 @@ h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
dst->scheme = apr_pstrdup(p, src->scheme);
dst->authority = apr_pstrdup(p, src->authority);
dst->path = apr_pstrdup(p, src->path);
+ dst->protocol = apr_pstrdup(p, src->protocol);
dst->headers = apr_table_clone(p, src->headers);
return dst;
}
-request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+static request_rec *my_ap_create_request(conn_rec *c)
{
- int access_status = HTTP_OK;
- const char *rpath;
apr_pool_t *p;
request_rec *r;
- const char *s;
apr_pool_create(&p, c->pool);
apr_pool_tag(p, "request");
@@ -221,86 +238,294 @@ request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
r->pool = p;
r->connection = c;
r->server = c->base_server;
-
+
r->user = NULL;
r->ap_auth_type = NULL;
-
+
r->allowed_methods = ap_make_method_list(p, 2);
-
- r->headers_in = apr_table_clone(r->pool, req->headers);
+
+ r->headers_in = apr_table_make(r->pool, 5);
r->trailers_in = apr_table_make(r->pool, 5);
r->subprocess_env = apr_table_make(r->pool, 25);
r->headers_out = apr_table_make(r->pool, 12);
r->err_headers_out = apr_table_make(r->pool, 5);
r->trailers_out = apr_table_make(r->pool, 5);
r->notes = apr_table_make(r->pool, 5);
-
+
r->request_config = ap_create_request_config(r->pool);
/* Must be set before we run create request hook */
-
+
r->proto_output_filters = c->output_filters;
r->output_filters = r->proto_output_filters;
r->proto_input_filters = c->input_filters;
r->input_filters = r->proto_input_filters;
ap_run_create_request(r);
r->per_dir_config = r->server->lookup_defaults;
-
+
r->sent_bodyct = 0; /* bytect isn't for body */
-
+
r->read_length = 0;
r->read_body = REQUEST_NO_BODY;
-
+
r->status = HTTP_OK; /* Until further notice */
r->header_only = 0;
r->the_request = NULL;
-
+
/* Begin by presuming any module can make its own path_info assumptions,
* until some module interjects and changes the value.
*/
r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
-
+
r->useragent_addr = c->client_addr;
r->useragent_ip = c->client_ip;
-
+ return r;
+}
+#endif
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_table_t *headers = apr_table_clone(r->pool, req->headers);
+ const char *uri = req->path;
+
+ AP_DEBUG_ASSERT(req->method);
+ AP_DEBUG_ASSERT(req->authority);
+ if (!ap_cstr_casecmp("CONNECT", req->method)) {
+ uri = req->authority;
+ }
+ else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+ /* Forward proxying: always absolute uris */
+ uri = apr_psprintf(r->pool, "%s://%s%s",
+ req->scheme, req->authority,
+ req->path ? req->path : "");
+ }
+ else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+ && ap_cstr_casecmp(req->scheme, "https")) {
+ /* Client sent a non-http ':scheme', use an absolute URI */
+ uri = apr_psprintf(r->pool, "%s://%s%s",
+ req->scheme, req->authority, req->path ? req->path : "");
+ }
+
+ return ap_bucket_request_create(req->method, uri, "HTTP/2.0", headers,
+ r->pool, c->bucket_alloc);
+}
+#endif
+
+static void assign_headers(request_rec *r, const h2_request *req,
+ int no_body, int is_connect)
+{
+ const char *cl;
+
+ r->headers_in = apr_table_clone(r->pool, req->headers);
+
+ if (req->authority && !is_connect) {
+ /* for internal handling, we have to simulate that :authority
+ * came in as Host:, RFC 9113 ch. says that mismatches between
+ * :authority and Host: SHOULD be rejected as malformed. However,
+ * we are more lenient and just replace any Host: if we have
+ * an :authority.
+ */
+ const char *orig_host = apr_table_get(req->headers, "Host");
+ if (orig_host && strcmp(req->authority, orig_host)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10401)
+ "overwriting 'Host: %s' with :authority: %s'",
+ orig_host, req->authority);
+ apr_table_setn(r->subprocess_env, "H2_ORIGINAL_HOST", orig_host);
+ }
+ apr_table_setn(r->headers_in, "Host", req->authority);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "set 'Host: %s' from :authority", req->authority);
+ }
+
+ /* Unless we open a byte stream via CONNECT, apply content-length guards. */
+ if (!is_connect) {
+ cl = apr_table_get(req->headers, "Content-Length");
+ if (no_body) {
+ if (!cl && 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.
+ */
+ apr_table_setn(req->headers, "Content-Length", "0");
+ }
+ }
+#if !AP_HAS_RESPONSE_BUCKETS
+ else if (!cl) {
+ /* there may be a body and we have internal HTTP/1.1 processing.
+ * If the Content-Length is unspecified, we MUST simulate
+ * chunked Transfer-Encoding.
+ *
+ * HTTP/2 does not need a Content-Length for framing. Ideally
+ * all clients set the EOS flag on the header frame if they
+ * do not intent to send a body. However, forwarding proxies
+ * might just no know at the time and send an empty DATA
+ * frame with EOS much later.
+ */
+ apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
+ }
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ }
+}
+
+request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
+ int no_body)
+{
+ int access_status = HTTP_OK;
+ int is_connect = !ap_cstr_casecmp("CONNECT", req->method);
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+ request_rec *r = ap_create_request(c);
+#else
+ request_rec *r = my_ap_create_request(c);
+#endif
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
+ assign_headers(r, req, no_body, is_connect);
ap_run_pre_read_request(r, c);
-
+
/* Time to populate r with the data we have. */
r->request_time = req->request_time;
- r->method = req->method;
- /* Provide quick information about the request method as soon as known */
- r->method_number = ap_method_number_of(r->method);
- if (r->method_number == M_GET && r->method[0] == 'H') {
- r->header_only = 1;
+ AP_DEBUG_ASSERT(req->authority);
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ access_status = req->http_status;
+ goto die;
+ }
+ else if (is_connect) {
+ /* CONNECT MUST NOT have scheme or path */
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->authority);
+ if (req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
+ "':scheme: %s' header present in CONNECT request",
+ req->scheme);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ else if (req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
+ "':path: %s' header present in CONNECT request",
+ req->path);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ }
+ else if (req->protocol) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10470)
+ "':protocol: %s' header present in %s request",
+ req->protocol, req->method);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+ if (!req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10468)
+ "H2ProxyRequests on, but request misses :scheme");
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ if (!req->authority) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10469)
+ "H2ProxyRequests on, but request misses :authority");
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
+ req->method, req->scheme, req->authority,
+ req->path ? req->path : "");
+ }
+ else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+ && ap_cstr_casecmp(req->scheme, "https")) {
+ /* Client sent a ':scheme' pseudo header for something else
+ * than what we have on this connection. Make an absolute URI. */
+ r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
+ req->method, req->scheme, req->authority,
+ req->path ? req->path : "");
+ }
+ else if (req->path) {
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->path);
+ }
+ else {
+ /* We should only come here on a request that is errored already.
+ * create a request line that passes parsing, we'll die anyway.
+ */
+ AP_DEBUG_ASSERT(req->http_status != H2_HTTP_STATUS_UNSET);
+ r->the_request = apr_psprintf(r->pool, "%s / HTTP/2.0", req->method);
}
- rpath = (req->path ? req->path : "");
- ap_parse_uri(r, rpath);
- r->protocol = (char*)"HTTP/2.0";
- r->proto_num = HTTP_VERSION(2, 0);
-
- r->the_request = apr_psprintf(r->pool, "%s %s %s",
- r->method, rpath, r->protocol);
-
- /* update what we think the virtual host is based on the headers we've
- * now read. may update status.
- * Leave r->hostname empty, vhost will parse if form our Host: header,
- * otherwise we get complains about port numbers.
+ /* Start with r->hostname = NULL, ap_check_request_header() will get it
+ * form Host: header, otherwise we get complains about port numbers.
*/
r->hostname = NULL;
- ap_update_vhost_from_headers(r);
-
- /* we may have switched to another server */
- r->per_dir_config = r->server->lookup_defaults;
-
- s = apr_table_get(r->headers_in, "Expect");
- if (s && s[0]) {
- if (ap_cstr_casecmp(s, "100-continue") == 0) {
- r->expecting_100 = 1;
+
+ /* Validate HTTP/1 request and select vhost. */
+ if (!ap_parse_request_line(r) || !ap_check_request_header(r)) {
+ /* we may have switched to another server still */
+ r->per_dir_config = r->server->lookup_defaults;
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ access_status = req->http_status;
+ /* Be safe and close the connection */
+ c->keepalive = AP_CONN_CLOSE;
}
else {
- r->status = HTTP_EXPECTATION_FAILED;
- ap_send_error_response(r, 0);
+ access_status = r->status;
+ }
+ r->status = HTTP_OK;
+ goto die;
+ }
+#else
+ {
+ const char *s;
+
+ assign_headers(r, req, no_body, is_connect);
+ ap_run_pre_read_request(r, c);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = req->request_time;
+ r->method = apr_pstrdup(r->pool, req->method);
+ /* Provide quick information about the request method as soon as known */
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
}
+ ap_parse_uri(r, req->path ? req->path : "");
+ r->protocol = (char*)"HTTP/2.0";
+ r->proto_num = HTTP_VERSION(2, 0);
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ r->method, req->path ? req->path : "");
+
+ /* Start with r->hostname = NULL, ap_check_request_header() will get it
+ * form Host: header, otherwise we get complains about port numbers.
+ */
+ r->hostname = NULL;
+ ap_update_vhost_from_headers(r);
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ s = apr_table_get(r->headers_in, "Expect");
+ if (s && s[0]) {
+ if (ap_cstr_casecmp(s, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ r->status = HTTP_EXPECTATION_FAILED;
+ access_status = r->status;
+ goto die;
+ }
+ }
+ }
+#endif
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ access_status = req->http_status;
+ r->status = HTTP_OK;
+ /* Be safe and close the connection */
+ c->keepalive = AP_CONN_CLOSE;
+ goto die;
}
/*
@@ -311,29 +536,58 @@ request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
*/
ap_add_input_filter_handle(ap_http_input_filter_handle,
NULL, r, r->connection);
-
- if (access_status != HTTP_OK
- || (access_status = ap_run_post_read_request(r))) {
+
+ if ((access_status = ap_post_read_request(r))) {
/* Request check post hooks failed. An example of this would be a
* request for a vhost where h2 is disabled --> 421.
*/
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03367)
"h2_request: access_status=%d, request_create failed",
access_status);
- ap_die(access_status, r);
- ap_update_child_status(c->sbh, SERVER_BUSY_LOG, r);
- ap_run_log_transaction(r);
- r = NULL;
- goto traceout;
+ goto die;
}
- AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
- (char *)r->uri, (char *)r->server->defn_name,
+ AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
+ (char *)r->uri, (char *)r->server->defn_name,
r->status);
return r;
-traceout:
- AP_READ_REQUEST_FAILURE((uintptr_t)r);
- return r;
-}
+die:
+ if (!r->method) {
+ /* if we fail early, `r` is not properly initialized for error
+ * processing which accesses fields in message generation.
+ * Make a best effort. */
+ if (!r->the_request) {
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->path);
+ }
+ ap_parse_request_line(r);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "ap_die(%d) for %s", access_status, r->the_request);
+ ap_die(access_status, r);
+ /* ap_die() sent the response through the output filters, we must now
+ * end the request with an EOR bucket for stream/pipeline accounting.
+ */
+ {
+ apr_bucket_brigade *eor_bb;
+#if AP_MODULE_MAGIC_AT_LEAST(20180905, 1)
+ eor_bb = ap_acquire_brigade(c);
+ APR_BRIGADE_INSERT_TAIL(eor_bb,
+ ap_bucket_eor_create(c->bucket_alloc, r));
+ ap_pass_brigade(c->output_filters, eor_bb);
+ ap_release_brigade(c, eor_bb);
+#else
+ eor_bb = apr_brigade_create(c->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(eor_bb,
+ ap_bucket_eor_create(c->bucket_alloc, r));
+ ap_pass_brigade(c->output_filters, eor_bb);
+ apr_brigade_destroy(eor_bb);
+#endif
+ }
+
+ r = NULL;
+ AP_READ_REQUEST_FAILURE((uintptr_t)r);
+ return NULL;
+}