mod_http3/src/h3_filter.c¶
Functions¶
| Name | |
|---|---|
| apr_status_t | h3_filter_out(ap_filter_t *f H3_UNUSED, apr_bucket_brigade * bb) |
| apr_status_t | input_filter_eos(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) |
| apr_status_t | h3_filter_in(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) |
| apr_status_t | serve_request_body(ap_filter_t * f, h3_stream * h3s, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block H3_UNUSED, apr_off_t readbytes) |
| apr_status_t | capture_body_bucket(request_rec * r, h3_conn_ctx_t * ctx, apr_bucket * b) |
| apr_status_t | h3_filter_out_proto(ap_filter_t * f, apr_bucket_brigade * bb) |
| apr_status_t | h3_filter_in_proto(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) |
Attributes¶
| Name | |
|---|---|
| ap_filter_rec_t * | h3_net_in_filter_handle |
| ap_filter_rec_t * | h3_net_out_filter_handle |
| ap_filter_rec_t * | h3_proto_out_filter_handle |
| ap_filter_rec_t * | h3_proto_in_filter_handle |
Functions Documentation¶
function h3_filter_out¶
function input_filter_eos¶
static apr_status_t input_filter_eos(
ap_filter_t * f,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes
)
function h3_filter_in¶
apr_status_t h3_filter_in(
ap_filter_t * f,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes
)
Return: APR_SUCCESS (with EOS bucket inserted) or APR_EOF.
Network-layer input filter callback. Unconditional EOS stub.
function serve_request_body¶
static apr_status_t serve_request_body(
ap_filter_t * f,
h3_stream * h3s,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block H3_UNUSED,
apr_off_t readbytes
)
function capture_body_bucket¶
function h3_filter_out_proto¶
Parameters:
- f The filter handle.
- bb The outbound brigade.
Return: APR_SUCCESS, or an APR error if pool/brigade access fails.
Protocol-layer output filter callback. Captures the response into the per-request h3_conn_ctx_t. In the default mode it submits headers early and feeds body bytes through a bounded per-stream queue with backpressure. When H3MaxResponseBodySize is finite, it retains bounded whole-response capture so an over-limit response can be replaced before transmission.
function h3_filter_in_proto¶
apr_status_t h3_filter_in_proto(
ap_filter_t * f,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes
)
Return: APR_SUCCESS, with data and/or an EOS bucket inserted into bb.
Protocol-layer input filter callback. Serves the request body.
Attributes Documentation¶
variable h3_net_in_filter_handle¶
variable h3_net_out_filter_handle¶
variable h3_proto_out_filter_handle¶
variable h3_proto_in_filter_handle¶
Source code¶
/*
* Copyright (c) 2026 The mod_http3 Project Authors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed 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 <httpd.h>
#include <http_config.h>
#include <http_connection.h>
#include <http_core.h>
#include <http_log.h>
#include <http_protocol.h>
#include <http_request.h>
#include <util_time.h>
#include <apr_buckets.h>
#include <apr_lib.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_tables.h>
#include <string.h>
#include <nghttp3/nghttp3.h>
#include "h3.h"
#include "h3_check.h"
#include "h3_config.h"
#include "h3_filter.h"
#include "h3_os.h"
#include "h3_request.h"
#include "h3_session.h"
#include "mod_http3.h"
ap_filter_rec_t* h3_net_in_filter_handle;
ap_filter_rec_t* h3_net_out_filter_handle;
ap_filter_rec_t* h3_proto_out_filter_handle;
ap_filter_rec_t* h3_proto_in_filter_handle;
apr_status_t h3_filter_out(ap_filter_t* f H3_UNUSED, apr_bucket_brigade* bb)
{
CHECK(bb);
apr_brigade_cleanup(bb);
return APR_SUCCESS;
}
static apr_status_t input_filter_eos(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)
{
if (!f->next)
{
return APR_EOF;
}
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
if (!APR_BRIGADE_EMPTY(bb))
{
return APR_SUCCESS;
}
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc));
return APR_SUCCESS;
}
apr_status_t h3_filter_in(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
return input_filter_eos(f, bb, mode, block, readbytes);
}
/* Serve request body. */
static apr_status_t serve_request_body(ap_filter_t* f, h3_stream* h3s, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block H3_UNUSED, apr_off_t readbytes)
{
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "serve_request_body started, h3s=%p", (void*)h3s);
apr_bucket_alloc_t* ba = f->c->bucket_alloc;
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE && mode != AP_MODE_EXHAUSTIVE && mode != AP_MODE_SPECULATIVE)
{
/* AP_MODE_INIT, AP_MODE_EATCRLF: nothing for us to do. */
return APR_SUCCESS;
}
apr_size_t avail = h3s->request_body_len - h3s->request_body_offset;
if (avail == 0)
{
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(ba));
return APR_SUCCESS;
}
const uint8_t* unread = h3s->request_body + h3s->request_body_offset;
apr_size_t want = avail;
if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE)
{
if (readbytes > 0 && (apr_size_t)readbytes < want)
{
want = (apr_size_t)readbytes;
}
}
else if (mode == AP_MODE_GETLINE)
{
const void* nl = memchr(unread, '\n', avail);
if (nl)
{
want = (apr_size_t)((const uint8_t*)nl - unread) + 1;
}
}
/* AP_MODE_EXHAUSTIVE: take everything remaining, as already set above. */
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "serve_request_body: unread=%p, want=%" APR_SIZE_T_FMT, (void*)unread, want);
apr_bucket* b = apr_bucket_pool_create((const char*)unread, want, h3s->pool, ba);
APR_BRIGADE_INSERT_TAIL(bb, b);
if (mode != AP_MODE_SPECULATIVE)
{
h3s->request_body_offset += want;
if (h3s->request_body_offset >= h3s->request_body_len)
{
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(ba));
}
}
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "serve_request_body done, want=%" APR_SIZE_T_FMT ", offset=%" APR_SIZE_T_FMT, want, h3s->request_body_offset);
return APR_SUCCESS;
}
static apr_status_t capture_body_bucket(request_rec* r, h3_conn_ctx_t* ctx, apr_bucket* b)
{
CHECK(ctx);
CHECK(b);
if (ctx->response_too_large)
{
return APR_SUCCESS;
}
const char* data = NULL;
apr_size_t len = 0;
if (apr_bucket_read(b, &data, &len, APR_BLOCK_READ) != APR_SUCCESS || !data || !len)
{
return APR_SUCCESS;
}
/* Read from the H3-owning vhost (ctx->s), not r->server: only it is defaulted in h3_post_config. */
h3_server_conf* conf = ap_get_module_config(ctx->s->module_config, &http3_module);
apr_size_t limit = conf ? conf->h3_max_response_body_size : H3_MAX_RESPONSE_BODY_SIZE_DEFAULT;
if (ctx->dataheaplen > limit || len > limit - ctx->dataheaplen)
{
ctx->response_too_large = 1;
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "HTTP/3 response body for %s exceeds H3MaxResponseBodySize (%" APR_SIZE_T_FMT " bytes); aborting response with 500", r->uri, limit);
return APR_SUCCESS;
}
apr_size_t new_len = ctx->dataheaplen + len;
if (new_len > ctx->dataheapcap)
{
apr_size_t new_cap = ctx->dataheapcap ? ctx->dataheapcap * 2 : H3_BODY_BUF_INIT_CAP;
while (new_cap < new_len)
{
new_cap *= 2;
}
char* new_heap = apr_palloc(ctx->c3reqpool, new_cap);
if (ctx->dataheaplen > 0)
{
memcpy(new_heap, ctx->dataheap, ctx->dataheaplen);
}
ctx->dataheap = new_heap;
ctx->dataheapcap = new_cap;
}
memcpy(ctx->dataheap + ctx->dataheaplen, data, len);
ctx->dataheaplen = new_len;
return APR_SUCCESS;
}
#if H3_STABLE
static int uniq_field_values(void* d, const char* key H3_UNUSED, const char* val)
{
apr_array_header_t* values = d;
char* start;
char* e = apr_pstrdup(values->pool, val);
do
{
while (*e == ',' || apr_isspace(*e))
{
++e;
}
if (*e == '\0')
{
break;
}
start = e;
while (*e != '\0' && *e != ',' && !apr_isspace(*e))
{
++e;
}
if (*e != '\0')
{
*e++ = '\0';
}
int i;
char** strpp;
for (i = 0, strpp = (char**)values->elts; i < values->nelts; ++i, ++strpp)
{
if (*strpp && ap_cstr_casecmp(*strpp, start) == 0)
{
break;
}
}
if (i == values->nelts)
{
*(char**)apr_array_push(values) = start;
}
} while (*e != '\0');
return 1;
}
static void fix_vary(request_rec* r)
{
apr_array_header_t* varies = apr_array_make(r->pool, 5, sizeof(char*));
apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
if (varies->nelts > 0)
{
apr_table_setn(r->headers_out, "Vary", apr_array_pstrcat(r->pool, varies, ','));
}
}
void h3_response_finalize(request_rec* r, h3_conn_ctx_t* h3ctx)
{
CHECK(r);
CHECK(h3ctx);
if (h3ctx->resp_headers)
{
return;
}
if (!apr_is_empty_table(r->err_headers_out))
{
r->headers_out = apr_table_overlay(r->pool, r->err_headers_out, r->headers_out);
apr_table_clear(r->err_headers_out);
}
if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL)
{
apr_table_unset(r->headers_out, "Vary");
}
else
{
fix_vary(r);
}
if (apr_table_get(r->notes, "no-etag") != NULL)
{
apr_table_unset(r->headers_out, "ETag");
}
if (AP_STATUS_IS_HEADER_ONLY(r->status))
{
apr_table_unset(r->headers_out, "Transfer-Encoding");
apr_table_unset(r->headers_out, "Content-Length");
r->content_type = r->content_encoding = NULL;
r->content_languages = NULL;
r->clength = r->chunked = 0;
}
const char* ctype = ap_make_content_type(r, r->content_type);
if (ctype)
{
apr_table_setn(r->headers_out, "Content-Type", ctype);
}
if (r->content_encoding)
{
apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
}
if (!apr_is_empty_array(r->content_languages))
{
const char* field = apr_table_get(r->headers_out, "Content-Language");
char* token;
while (field && (token = ap_get_list_item(r->pool, &field)) != NULL)
{
int i;
char** languages = (char**)r->content_languages->elts;
for (i = 0; i < r->content_languages->nelts; ++i)
{
if (!ap_cstr_casecmp(token, languages[i]))
{
break;
}
}
if (i == r->content_languages->nelts)
{
*((char**)apr_array_push(r->content_languages)) = token;
}
}
apr_table_setn(r->headers_out, "Content-Language", apr_array_pstrcat(r->pool, r->content_languages, ','));
}
if (r->no_cache && !apr_table_get(r->headers_out, "Expires"))
{
char* date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
ap_recent_rfc822_date(date, r->request_time);
apr_table_add(r->headers_out, "Expires", date);
}
const char* clheader = apr_table_get(r->headers_out, "Content-Length");
if (r->header_only && clheader && !strcmp(clheader, "0"))
{
apr_table_unset(r->headers_out, "Content-Length");
}
if (r->proxyreq == PROXYREQ_NONE || !apr_table_get(r->headers_out, "Date"))
{
char* date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
ap_recent_rfc822_date(date, r->request_time);
apr_table_setn(r->headers_out, "Date", date);
}
if (r->proxyreq == PROXYREQ_NONE || !apr_table_get(r->headers_out, "Server"))
{
const char* us = ap_get_server_banner();
if (us && *us)
{
apr_table_setn(r->headers_out, "Server", us);
}
}
h3ctx->resp_status = r->status;
h3ctx->resp_headers = r->headers_out;
}
#endif /* H3_STABLE */
apr_status_t h3_filter_out_proto(ap_filter_t* f, apr_bucket_brigade* bb)
{
CHECK(f);
CHECK(bb);
h3_conn_ctx_t* ctx = (h3_conn_ctx_t*)f->ctx;
if (!ctx || f->r->main != NULL)
{
return ap_pass_brigade(f->next, bb);
}
for (apr_bucket* b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);)
{
apr_bucket* next = APR_BUCKET_NEXT(b);
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "h3_filter_out_proto: received bucket type=%s length=%" APR_SIZE_T_FMT, b->type->name, b->length);
if (AP_BUCKET_IS_ERROR(b))
{
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "h3_filter_out_proto: generating error response");
ap_send_error_response(f->r, 0);
return OK;
}
#if H3_DEVEL
if (AP_BUCKET_IS_RESPONSE(b))
{
ap_bucket_response* resp = b->data;
ctx->resp_status = resp->status;
if (resp->headers)
{
apr_table_t* dup = apr_table_make(ctx->c3reqpool, apr_table_elts(resp->headers)->nelts);
const apr_array_header_t* src_arr = apr_table_elts(resp->headers);
const apr_table_entry_t* src = (const apr_table_entry_t*)src_arr->elts;
for (int i = 0; i < src_arr->nelts; i++)
{
if (src[i].key && src[i].val)
{
apr_table_add(dup, apr_pstrdup(ctx->c3reqpool, src[i].key), apr_pstrdup(ctx->c3reqpool, src[i].val));
}
}
ctx->resp_headers = dup;
}
APR_BUCKET_REMOVE(b);
apr_bucket_destroy(b);
if (ctx->streaming)
{
apr_status_t rv = h3_response_start(f->r, ctx);
if (rv != APR_SUCCESS)
{
apr_brigade_cleanup(bb);
return rv;
}
}
b = next;
continue;
}
#else
if (!APR_BUCKET_IS_METADATA(b) || APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b))
{
h3_response_finalize(f->r, ctx);
}
if (!APR_BUCKET_IS_METADATA(b) && (f->r->header_only || AP_STATUS_IS_HEADER_ONLY(f->r->status)))
{
b = next;
continue;
}
#endif
if (!APR_BUCKET_IS_METADATA(b))
{
apr_status_t rv;
if (ctx->streaming)
{
rv = h3_response_start(f->r, ctx);
if (rv == APR_SUCCESS)
{
const char* data = NULL;
apr_size_t len = 0;
rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
if (rv == APR_SUCCESS && data && len > 0)
{
rv = h3_stream_response_append(ctx->stream, (const uint8_t*)data, len);
}
}
}
else
{
rv = capture_body_bucket(f->r, ctx, b);
}
if (rv != APR_SUCCESS)
{
apr_brigade_cleanup(bb);
return rv;
}
next = APR_BUCKET_NEXT(b);
}
else if (ctx->streaming && (APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b)))
{
apr_status_t rv = h3_response_start(f->r, ctx);
if (rv != APR_SUCCESS)
{
apr_brigade_cleanup(bb);
return rv;
}
if (APR_BUCKET_IS_EOS(b))
{
h3_stream_response_complete(ctx->stream);
}
}
b = next;
}
apr_brigade_cleanup(bb);
return OK;
}
apr_status_t h3_filter_in_proto(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "h3_filter_in_proto called, readbytes=%" APR_OFF_T_FMT, readbytes);
h3_conn_ctx_t* ctx = (h3_conn_ctx_t*)ap_get_module_config(f->r->request_config, &http3_module);
h3_stream* h3s = ctx ? ctx->stream : NULL;
if (!h3s)
{
return input_filter_eos(f, bb, mode, block, readbytes);
}
return serve_request_body(f, h3s, bb, mode, block, readbytes);
}