From c54018b07a9085c0a3aedbc2bd01a85a3b3e20cf Mon Sep 17 00:00:00 2001
From: Daniel Baumann
Date: Sat, 25 May 2024 06:41:27 +0200
Subject: Merging upstream version 2.4.59.
Signed-off-by: Daniel Baumann
---
modules/aaa/mod_auth_basic.c | 14 +-
modules/aaa/mod_auth_digest.c | 91 +-
modules/aaa/mod_auth_form.c | 13 +-
modules/aaa/mod_authn_core.c | 61 +-
modules/aaa/mod_authn_dbd.c | 6 +-
modules/aaa/mod_authn_dbm.c | 55 +-
modules/aaa/mod_authn_socache.c | 4 +-
modules/aaa/mod_authnz_fcgi.c | 15 +-
modules/aaa/mod_authnz_ldap.c | 45 +-
modules/aaa/mod_authz_core.c | 22 +-
modules/aaa/mod_authz_dbd.c | 4 +-
modules/aaa/mod_authz_dbm.c | 32 +-
modules/aaa/mod_authz_groupfile.c | 4 +-
modules/arch/unix/config5.m4 | 9 +
modules/arch/unix/mod_systemd.c | 119 +
modules/arch/win32/mod_isapi.c | 9 +-
modules/arch/win32/mod_isapi.h | 2 +-
modules/cache/cache_storage.c | 5 +-
modules/cache/cache_util.c | 2 +-
modules/cache/config.m4 | 1 +
modules/cache/mod_cache.c | 27 +-
modules/cache/mod_cache_disk.c | 41 +-
modules/cache/mod_cache_socache.c | 98 +-
modules/cache/mod_file_cache.c | 2 +-
modules/cache/mod_socache_dbm.c | 171 +-
modules/cache/mod_socache_dc.c | 2 +-
modules/cache/mod_socache_memcache.c | 13 -
modules/cache/mod_socache_redis.c | 486 ++++
modules/cache/mod_socache_redis.dep | 5 +
modules/cache/mod_socache_redis.dsp | 111 +
modules/cache/mod_socache_redis.mak | 353 +++
modules/cache/mod_socache_shmcb.c | 27 +-
modules/cluster/mod_heartmonitor.c | 22 +-
modules/core/mod_macro.c | 2 +-
modules/core/mod_so.c | 2 +-
modules/core/mod_watchdog.c | 67 +-
modules/database/mod_dbd.c | 3 +
modules/dav/fs/dbm.c | 47 +-
modules/dav/fs/lock.c | 2 +-
modules/dav/fs/repos.c | 75 +-
modules/dav/lock/locks.c | 28 +-
modules/dav/main/mod_dav.c | 635 +++--
modules/dav/main/mod_dav.h | 132 +-
modules/dav/main/props.c | 88 +-
modules/dav/main/std_liveprop.c | 8 +-
modules/dav/main/util.c | 87 +-
modules/examples/mod_case_filter_in.c | 2 +-
modules/examples/mod_example_hooks.c | 26 +-
modules/filters/libsed.h | 12 +-
modules/filters/mod_brotli.c | 15 +-
modules/filters/mod_charset_lite.c | 4 +-
modules/filters/mod_data.c | 4 +-
modules/filters/mod_deflate.c | 349 +--
modules/filters/mod_ext_filter.c | 6 +-
modules/filters/mod_include.c | 41 +-
modules/filters/mod_proxy_html.c | 31 +-
modules/filters/mod_reflector.c | 11 +-
modules/filters/mod_reqtimeout.c | 237 +-
modules/filters/mod_request.c | 28 +-
modules/filters/mod_sed.c | 116 +-
modules/filters/mod_substitute.c | 9 +-
modules/filters/mod_xml2enc.c | 47 +-
modules/filters/regexp.h | 4 +-
modules/filters/sed1.c | 207 +-
modules/generators/mod_autoindex.c | 10 +-
modules/generators/mod_cgi.c | 75 +-
modules/generators/mod_cgid.c | 35 +-
modules/generators/mod_info.c | 37 +-
modules/generators/mod_status.c | 48 +-
modules/http/byterange_filter.c | 7 +-
modules/http/http_core.c | 16 +-
modules/http/http_etag.c | 349 ++-
modules/http/http_filters.c | 290 ++-
modules/http/http_protocol.c | 128 +-
modules/http/http_request.c | 33 +-
modules/http/mod_mime.c | 19 +-
modules/http2/.gitignore | 35 -
modules/http2/config2.m4 | 22 +-
modules/http2/h2.h | 85 +-
modules/http2/h2_alt_svc.c | 131 --
modules/http2/h2_alt_svc.h | 40 -
modules/http2/h2_bucket_beam.c | 1470 +++++-------
modules/http2/h2_bucket_beam.h | 375 +--
modules/http2/h2_bucket_eos.c | 17 +-
modules/http2/h2_c1.c | 323 +++
modules/http2/h2_c1.h | 83 +
modules/http2/h2_c1_io.c | 559 +++++
modules/http2/h2_c1_io.h | 101 +
modules/http2/h2_c2.c | 942 ++++++++
modules/http2/h2_c2.h | 57 +
modules/http2/h2_c2_filter.c | 1056 +++++++++
modules/http2/h2_c2_filter.h | 68 +
modules/http2/h2_config.c | 859 +++++--
modules/http2/h2_config.h | 74 +-
modules/http2/h2_conn.c | 370 ---
modules/http2/h2_conn.h | 77 -
modules/http2/h2_conn_ctx.c | 123 +
modules/http2/h2_conn_ctx.h | 100 +
modules/http2/h2_conn_io.c | 389 ---
modules/http2/h2_conn_io.h | 77 -
modules/http2/h2_ctx.c | 121 -
modules/http2/h2_ctx.h | 78 -
modules/http2/h2_filter.c | 568 -----
modules/http2/h2_filter.h | 73 -
modules/http2/h2_from_h1.c | 875 -------
modules/http2/h2_from_h1.h | 50 -
modules/http2/h2_h2.c | 765 ------
modules/http2/h2_h2.h | 79 -
modules/http2/h2_headers.c | 99 +-
modules/http2/h2_headers.h | 50 +-
modules/http2/h2_mplx.c | 1834 +++++++--------
modules/http2/h2_mplx.h | 314 +--
modules/http2/h2_ngn_shed.c | 392 ---
modules/http2/h2_ngn_shed.h | 79 -
modules/http2/h2_protocol.c | 485 ++++
modules/http2/h2_protocol.h | 56 +
modules/http2/h2_proxy_session.c | 423 ++--
modules/http2/h2_proxy_session.h | 18 +-
modules/http2/h2_proxy_util.c | 40 +-
modules/http2/h2_proxy_util.h | 7 +-
modules/http2/h2_push.c | 339 +--
modules/http2/h2_push.h | 86 +-
modules/http2/h2_request.c | 494 +++-
modules/http2/h2_request.h | 19 +-
modules/http2/h2_session.c | 1816 ++++++--------
modules/http2/h2_session.h | 118 +-
modules/http2/h2_stream.c | 1520 +++++++++---
modules/http2/h2_stream.h | 129 +-
modules/http2/h2_switch.c | 85 +-
modules/http2/h2_task.c | 769 ------
modules/http2/h2_task.h | 127 -
modules/http2/h2_util.c | 983 ++++----
modules/http2/h2_util.h | 161 +-
modules/http2/h2_version.h | 4 +-
modules/http2/h2_workers.c | 755 ++++--
modules/http2/h2_workers.h | 131 +-
modules/http2/h2_ws.c | 362 +++
modules/http2/h2_ws.h | 35 +
modules/http2/mod_http2.c | 164 +-
modules/http2/mod_http2.dep | 2 -
modules/http2/mod_http2.dsp | 34 +-
modules/http2/mod_http2.h | 75 +-
modules/http2/mod_http2.mak | 9 -
modules/http2/mod_proxy_http2.c | 494 ++--
modules/ldap/util_ldap.c | 162 +-
modules/ldap/util_ldap_cache.c | 14 +-
modules/ldap/util_ldap_cache_mgr.c | 14 +-
modules/loggers/mod_log_config.c | 37 +-
modules/loggers/mod_log_debug.c | 8 +
modules/loggers/mod_log_forensic.c | 8 +-
modules/lua/config.m4 | 33 +-
modules/lua/lua_apr.c | 8 +-
modules/lua/lua_request.c | 225 +-
modules/lua/mod_lua.c | 71 +-
modules/lua/mod_lua.h | 12 +-
modules/mappers/config9.m4 | 5 +
modules/mappers/mod_alias.c | 186 +-
modules/mappers/mod_imagemap.c | 2 +-
modules/mappers/mod_negotiation.c | 10 +-
modules/mappers/mod_rewrite.c | 288 ++-
modules/mappers/mod_rewrite.mak | 4 +-
modules/mappers/mod_speling.c | 37 +-
modules/mappers/mod_vhost_alias.c | 2 +-
modules/md/config2.m4 | 15 +-
modules/md/md.h | 218 +-
modules/md/md_acme.c | 724 ++++--
modules/md/md_acme.h | 216 +-
modules/md/md_acme_acct.c | 785 +++---
modules/md/md_acme_acct.h | 103 +-
modules/md/md_acme_authz.c | 801 ++++---
modules/md/md_acme_authz.h | 65 +-
modules/md/md_acme_drive.c | 1316 ++++++-----
modules/md/md_acme_drive.h | 55 +
modules/md/md_acme_order.c | 562 +++++
modules/md/md_acme_order.h | 91 +
modules/md/md_acmev2_drive.c | 181 ++
modules/md/md_acmev2_drive.h | 27 +
modules/md/md_core.c | 316 +--
modules/md/md_crypt.c | 1334 +++++++++--
modules/md/md_crypt.h | 156 +-
modules/md/md_curl.c | 482 +++-
modules/md/md_event.c | 89 +
modules/md/md_event.h | 46 +
modules/md/md_http.c | 330 ++-
modules/md/md_http.h | 210 +-
modules/md/md_json.c | 376 ++-
modules/md/md_json.h | 75 +-
modules/md/md_jws.c | 116 +-
modules/md/md_jws.h | 26 +-
modules/md/md_log.h | 4 +
modules/md/md_ocsp.c | 1063 +++++++++
modules/md/md_ocsp.h | 71 +
modules/md/md_reg.c | 1283 ++++++----
modules/md/md_reg.h | 242 +-
modules/md/md_result.c | 285 +++
modules/md/md_result.h | 87 +
modules/md/md_status.c | 653 +++++
modules/md/md_status.h | 126 +
modules/md/md_store.c | 134 +-
modules/md/md_store.h | 310 ++-
modules/md/md_store_fs.c | 426 +++-
modules/md/md_store_fs.h | 2 +-
modules/md/md_tailscale.c | 383 +++
modules/md/md_tailscale.h | 25 +
modules/md/md_time.c | 325 +++
modules/md/md_time.h | 77 +
modules/md/md_util.c | 388 ++-
modules/md/md_util.h | 134 +-
modules/md/md_version.h | 7 +-
modules/md/mod_md.c | 2187 ++++++++---------
modules/md/mod_md.dsp | 63 +-
modules/md/mod_md.h | 30 -
modules/md/mod_md.mak | 102 +-
modules/md/mod_md_config.c | 1048 ++++++---
modules/md/mod_md_config.h | 70 +-
modules/md/mod_md_drive.c | 345 +++
modules/md/mod_md_drive.h | 35 +
modules/md/mod_md_ocsp.c | 272 +++
modules/md/mod_md_ocsp.h | 33 +
modules/md/mod_md_os.c | 33 +-
modules/md/mod_md_status.c | 987 ++++++++
modules/md/mod_md_status.h | 27 +
modules/metadata/mod_cern_meta.c | 4 +-
modules/metadata/mod_headers.c | 30 +-
modules/metadata/mod_mime_magic.c | 26 +-
modules/metadata/mod_remoteip.c | 38 +-
modules/metadata/mod_unique_id.c | 54 +-
modules/metadata/mod_usertrack.c | 67 +-
modules/proxy/ajp.h | 4 +-
modules/proxy/ajp_header.c | 48 +-
modules/proxy/balancers/mod_lbmethod_heartbeat.c | 5 +-
modules/proxy/mod_proxy.c | 704 ++++--
modules/proxy/mod_proxy.h | 296 ++-
modules/proxy/mod_proxy_ajp.c | 135 +-
modules/proxy/mod_proxy_balancer.c | 456 ++--
modules/proxy/mod_proxy_connect.c | 168 +-
modules/proxy/mod_proxy_express.c | 29 +
modules/proxy/mod_proxy_fcgi.c | 196 +-
modules/proxy/mod_proxy_fdpass.c | 4 +-
modules/proxy/mod_proxy_ftp.c | 171 +-
modules/proxy/mod_proxy_hcheck.c | 348 ++-
modules/proxy/mod_proxy_http.c | 1812 +++++++-------
modules/proxy/mod_proxy_scgi.c | 20 +-
modules/proxy/mod_proxy_uwsgi.c | 148 +-
modules/proxy/mod_proxy_wstunnel.c | 266 ++-
modules/proxy/proxy_util.c | 2751 +++++++++++++++++-----
modules/proxy/proxy_util.h | 6 +-
modules/session/mod_session.c | 51 +-
modules/session/mod_session.h | 3 +
modules/session/mod_session_cookie.c | 6 +-
modules/session/mod_session_crypto.c | 6 +-
modules/session/mod_session_dbd.c | 8 +-
modules/slotmem/mod_slotmem_shm.c | 6 +-
modules/ssl/mod_ssl.c | 162 +-
modules/ssl/mod_ssl.h | 29 +
modules/ssl/mod_ssl_openssl.h | 49 +-
modules/ssl/ssl_engine_config.c | 56 +-
modules/ssl/ssl_engine_init.c | 723 +++---
modules/ssl/ssl_engine_io.c | 341 ++-
modules/ssl/ssl_engine_kernel.c | 355 ++-
modules/ssl/ssl_engine_log.c | 18 +-
modules/ssl/ssl_engine_ocsp.c | 3 +-
modules/ssl/ssl_engine_pphrase.c | 322 ++-
modules/ssl/ssl_engine_vars.c | 52 +-
modules/ssl/ssl_private.h | 171 +-
modules/ssl/ssl_scache.c | 4 +-
modules/ssl/ssl_util.c | 51 +-
modules/ssl/ssl_util_ocsp.c | 6 +-
modules/ssl/ssl_util_ssl.c | 180 +-
modules/ssl/ssl_util_ssl.h | 31 +-
modules/ssl/ssl_util_stapling.c | 190 +-
modules/test/mod_dialup.c | 4 +-
modules/test/mod_optional_hook_import.c | 4 +-
modules/tls/Makefile.in | 20 +
modules/tls/config2.m4 | 173 ++
modules/tls/mod_tls.c | 288 +++
modules/tls/mod_tls.h | 19 +
modules/tls/tls_cache.c | 310 +++
modules/tls/tls_cache.h | 63 +
modules/tls/tls_cert.c | 564 +++++
modules/tls/tls_cert.h | 211 ++
modules/tls/tls_conf.c | 780 ++++++
modules/tls/tls_conf.h | 185 ++
modules/tls/tls_core.c | 1433 +++++++++++
modules/tls/tls_core.h | 184 ++
modules/tls/tls_filter.c | 1017 ++++++++
modules/tls/tls_filter.h | 90 +
modules/tls/tls_ocsp.c | 120 +
modules/tls/tls_ocsp.h | 47 +
modules/tls/tls_proto.c | 603 +++++
modules/tls/tls_proto.h | 124 +
modules/tls/tls_util.c | 367 +++
modules/tls/tls_util.h | 157 ++
modules/tls/tls_var.c | 397 ++++
modules/tls/tls_var.h | 39 +
modules/tls/tls_version.h | 39 +
296 files changed, 45744 insertions(+), 20994 deletions(-)
create mode 100644 modules/arch/unix/mod_systemd.c
create mode 100644 modules/cache/mod_socache_redis.c
create mode 100644 modules/cache/mod_socache_redis.dep
create mode 100644 modules/cache/mod_socache_redis.dsp
create mode 100644 modules/cache/mod_socache_redis.mak
delete mode 100644 modules/http2/.gitignore
delete mode 100644 modules/http2/h2_alt_svc.c
delete mode 100644 modules/http2/h2_alt_svc.h
create mode 100644 modules/http2/h2_c1.c
create mode 100644 modules/http2/h2_c1.h
create mode 100644 modules/http2/h2_c1_io.c
create mode 100644 modules/http2/h2_c1_io.h
create mode 100644 modules/http2/h2_c2.c
create mode 100644 modules/http2/h2_c2.h
create mode 100644 modules/http2/h2_c2_filter.c
create mode 100644 modules/http2/h2_c2_filter.h
delete mode 100644 modules/http2/h2_conn.c
delete mode 100644 modules/http2/h2_conn.h
create mode 100644 modules/http2/h2_conn_ctx.c
create mode 100644 modules/http2/h2_conn_ctx.h
delete mode 100644 modules/http2/h2_conn_io.c
delete mode 100644 modules/http2/h2_conn_io.h
delete mode 100644 modules/http2/h2_ctx.c
delete mode 100644 modules/http2/h2_ctx.h
delete mode 100644 modules/http2/h2_filter.c
delete mode 100644 modules/http2/h2_filter.h
delete mode 100644 modules/http2/h2_from_h1.c
delete mode 100644 modules/http2/h2_from_h1.h
delete mode 100644 modules/http2/h2_h2.c
delete mode 100644 modules/http2/h2_h2.h
delete mode 100644 modules/http2/h2_ngn_shed.c
delete mode 100644 modules/http2/h2_ngn_shed.h
create mode 100644 modules/http2/h2_protocol.c
create mode 100644 modules/http2/h2_protocol.h
delete mode 100644 modules/http2/h2_task.c
delete mode 100644 modules/http2/h2_task.h
create mode 100644 modules/http2/h2_ws.c
create mode 100644 modules/http2/h2_ws.h
create mode 100644 modules/md/md_acme_drive.h
create mode 100644 modules/md/md_acme_order.c
create mode 100644 modules/md/md_acme_order.h
create mode 100644 modules/md/md_acmev2_drive.c
create mode 100644 modules/md/md_acmev2_drive.h
create mode 100644 modules/md/md_event.c
create mode 100644 modules/md/md_event.h
create mode 100644 modules/md/md_ocsp.c
create mode 100644 modules/md/md_ocsp.h
create mode 100644 modules/md/md_result.c
create mode 100644 modules/md/md_result.h
create mode 100644 modules/md/md_status.c
create mode 100644 modules/md/md_status.h
create mode 100644 modules/md/md_tailscale.c
create mode 100644 modules/md/md_tailscale.h
create mode 100644 modules/md/md_time.c
create mode 100644 modules/md/md_time.h
create mode 100644 modules/md/mod_md_drive.c
create mode 100644 modules/md/mod_md_drive.h
create mode 100644 modules/md/mod_md_ocsp.c
create mode 100644 modules/md/mod_md_ocsp.h
create mode 100644 modules/md/mod_md_status.c
create mode 100644 modules/md/mod_md_status.h
create mode 100644 modules/tls/Makefile.in
create mode 100644 modules/tls/config2.m4
create mode 100644 modules/tls/mod_tls.c
create mode 100644 modules/tls/mod_tls.h
create mode 100644 modules/tls/tls_cache.c
create mode 100644 modules/tls/tls_cache.h
create mode 100644 modules/tls/tls_cert.c
create mode 100644 modules/tls/tls_cert.h
create mode 100644 modules/tls/tls_conf.c
create mode 100644 modules/tls/tls_conf.h
create mode 100644 modules/tls/tls_core.c
create mode 100644 modules/tls/tls_core.h
create mode 100644 modules/tls/tls_filter.c
create mode 100644 modules/tls/tls_filter.h
create mode 100644 modules/tls/tls_ocsp.c
create mode 100644 modules/tls/tls_ocsp.h
create mode 100644 modules/tls/tls_proto.c
create mode 100644 modules/tls/tls_proto.h
create mode 100644 modules/tls/tls_util.c
create mode 100644 modules/tls/tls_util.h
create mode 100644 modules/tls/tls_var.c
create mode 100644 modules/tls/tls_var.h
create mode 100644 modules/tls/tls_version.h
(limited to 'modules')
diff --git a/modules/aaa/mod_auth_basic.c b/modules/aaa/mod_auth_basic.c
index e8163d0..c8c9492 100644
--- a/modules/aaa/mod_auth_basic.c
+++ b/modules/aaa/mod_auth_basic.c
@@ -40,9 +40,9 @@ typedef struct {
ap_expr_info_t *fakeuser;
ap_expr_info_t *fakepass;
const char *use_digest_algorithm;
- int fake_set:1;
- int use_digest_algorithm_set:1;
- int authoritative_set:1;
+ unsigned int fake_set:1,
+ use_digest_algorithm_set:1,
+ authoritative_set:1;
} auth_basic_config_rec;
static void *create_auth_basic_dir_config(apr_pool_t *p, char *d)
@@ -238,7 +238,7 @@ static void note_basic_auth_failure(request_rec *r)
static int hook_note_basic_auth_failure(request_rec *r, const char *auth_type)
{
- if (strcasecmp(auth_type, "Basic"))
+ if (ap_cstr_casecmp(auth_type, "Basic"))
return DECLINED;
note_basic_auth_failure(r);
@@ -261,7 +261,7 @@ static int get_basic_auth(request_rec *r, const char **user,
return HTTP_UNAUTHORIZED;
}
- if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+ if (ap_cstr_casecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
/* Client tried to authenticate using wrong auth scheme */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01614)
"client used wrong authentication scheme: %s", r->uri);
@@ -301,7 +301,7 @@ static int authenticate_basic_user(request_rec *r)
/* Are we configured to be Basic auth? */
current_auth = ap_auth_type(r);
- if (!current_auth || strcasecmp(current_auth, "Basic")) {
+ if (!current_auth || ap_cstr_casecmp(current_auth, "Basic")) {
return DECLINED;
}
@@ -320,7 +320,7 @@ static int authenticate_basic_user(request_rec *r)
}
if (conf->use_digest_algorithm
- && !strcasecmp(conf->use_digest_algorithm, "MD5")) {
+ && !ap_cstr_casecmp(conf->use_digest_algorithm, "MD5")) {
realm = ap_auth_name(r);
digest = ap_md5(r->pool,
(unsigned char *)apr_pstrcat(r->pool, sent_user, ":",
diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c
index a67f069..791cec2 100644
--- a/modules/aaa/mod_auth_digest.c
+++ b/modules/aaa/mod_auth_digest.c
@@ -92,7 +92,6 @@ typedef struct digest_config_struct {
int check_nc;
const char *algorithm;
char *uri_list;
- const char *ha1;
} digest_config_rec;
@@ -153,6 +152,7 @@ typedef struct digest_header_struct {
apr_time_t nonce_time;
enum hdr_sts auth_hdr_sts;
int needed_auth;
+ const char *ha1;
client_entry *client;
} digest_header_rec;
@@ -262,6 +262,12 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx)
/* Create the shared memory segment */
+ client_shm = NULL;
+ client_rmm = NULL;
+ client_lock = NULL;
+ opaque_lock = NULL;
+ client_list = NULL;
+
/*
* Create a unique filename using our pid. This information is
* stashed in the global variable so the children inherit it.
@@ -408,8 +414,6 @@ static int initialize_module(apr_pool_t *p, apr_pool_t *plog,
if (initialize_tables(s, p) != OK) {
return !OK;
}
- /* Call cleanup_tables on exit or restart */
- apr_pool_cleanup_register(p, NULL, cleanup_tables, apr_pool_cleanup_null);
#endif /* APR_HAS_SHARED_MEMORY */
return OK;
}
@@ -553,16 +557,16 @@ static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
{
digest_config_rec *conf = (digest_config_rec *) config;
- if (!strcasecmp(op, "none")) {
+ if (!ap_cstr_casecmp(op, "none")) {
apr_array_clear(conf->qop_list);
*(const char **)apr_array_push(conf->qop_list) = "none";
return NULL;
}
- if (!strcasecmp(op, "auth-int")) {
+ if (!ap_cstr_casecmp(op, "auth-int")) {
return "AuthDigestQop auth-int is not implemented";
}
- else if (strcasecmp(op, "auth")) {
+ else if (ap_cstr_casecmp(op, "auth")) {
return apr_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);
}
@@ -610,11 +614,11 @@ static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
{
- if (!strcasecmp(alg, "MD5-sess")) {
+ if (!ap_cstr_casecmp(alg, "MD5-sess")) {
return "AuthDigestAlgorithm: ERROR: algorithm `MD5-sess' "
"is not implemented";
}
- else if (strcasecmp(alg, "MD5")) {
+ else if (ap_cstr_casecmp(alg, "MD5")) {
return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);
}
@@ -927,7 +931,7 @@ static int get_digest_rec(request_rec *r, digest_header_rec *resp)
}
resp->scheme = ap_getword_white(r->pool, &auth_line);
- if (strcasecmp(resp->scheme, "Digest")) {
+ if (ap_cstr_casecmp(resp->scheme, "Digest")) {
resp->auth_hdr_sts = NOT_DIGEST;
return !OK;
}
@@ -991,25 +995,25 @@ static int get_digest_rec(request_rec *r, digest_header_rec *resp)
auth_line++;
}
- if (!strcasecmp(key, "username"))
+ if (!ap_cstr_casecmp(key, "username"))
resp->username = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "realm"))
+ else if (!ap_cstr_casecmp(key, "realm"))
resp->realm = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "nonce"))
+ else if (!ap_cstr_casecmp(key, "nonce"))
resp->nonce = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "uri"))
+ else if (!ap_cstr_casecmp(key, "uri"))
resp->uri = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "response"))
+ else if (!ap_cstr_casecmp(key, "response"))
resp->digest = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "algorithm"))
+ else if (!ap_cstr_casecmp(key, "algorithm"))
resp->algorithm = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "cnonce"))
+ else if (!ap_cstr_casecmp(key, "cnonce"))
resp->cnonce = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "opaque"))
+ else if (!ap_cstr_casecmp(key, "opaque"))
resp->opaque = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "qop"))
+ else if (!ap_cstr_casecmp(key, "qop"))
resp->message_qop = apr_pstrdup(r->pool, value);
- else if (!strcasecmp(key, "nc"))
+ else if (!ap_cstr_casecmp(key, "nc"))
resp->nonce_count = apr_pstrdup(r->pool, value);
}
@@ -1182,7 +1186,7 @@ static void note_digest_auth_failure(request_rec *r,
if (apr_is_empty_array(conf->qop_list)) {
qop = ", qop=\"auth\"";
}
- else if (!strcasecmp(*(const char **)(conf->qop_list->elts), "none")) {
+ else if (!ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) {
qop = "";
}
else {
@@ -1271,7 +1275,7 @@ static int hook_note_digest_auth_failure(request_rec *r, const char *auth_type)
digest_header_rec *resp;
digest_config_rec *conf;
- if (strcasecmp(auth_type, "Digest"))
+ if (ap_cstr_casecmp(auth_type, "Digest"))
return DECLINED;
/* get the client response and mark */
@@ -1304,7 +1308,7 @@ static int hook_note_digest_auth_failure(request_rec *r, const char *auth_type)
*/
static authn_status get_hash(request_rec *r, const char *user,
- digest_config_rec *conf)
+ digest_config_rec *conf, const char **rethash)
{
authn_status auth_result;
char *password;
@@ -1356,7 +1360,7 @@ static authn_status get_hash(request_rec *r, const char *user,
} while (current_provider);
if (auth_result == AUTH_USER_FOUND) {
- conf->ha1 = password;
+ *rethash = password;
}
return auth_result;
@@ -1381,7 +1385,7 @@ static int check_nc(const request_rec *r, const digest_header_rec *resp,
}
if (!apr_is_empty_array(conf->qop_list) &&
- !strcasecmp(*(const char **)(conf->qop_list->elts), "none")) {
+ !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) {
/* qop is none, client must not send a nonce count */
if (snc != NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01772)
@@ -1422,9 +1426,14 @@ static int check_nonce(request_rec *r, digest_header_rec *resp,
time_rec nonce_time;
char tmp, hash[NONCE_HASH_LEN+1];
- if (strlen(resp->nonce) != NONCE_LEN) {
+ /* Since the time part of the nonce is a base64 encoding of an
+ * apr_time_t (8 bytes), it should end with a '=', fail early otherwise.
+ */
+ if (strlen(resp->nonce) != NONCE_LEN
+ || resp->nonce[NONCE_TIME_LEN - 1] != '=') {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01775)
- "invalid nonce %s received - length is not %d",
+ "invalid nonce '%s' received - length is not %d "
+ "or time encoding is incorrect",
resp->nonce, NONCE_LEN);
note_digest_auth_failure(r, conf, resp, 1);
return HTTP_UNAUTHORIZED;
@@ -1483,25 +1492,24 @@ static int check_nonce(request_rec *r, digest_header_rec *resp,
/* RFC-2069 */
static const char *old_digest(const request_rec *r,
- const digest_header_rec *resp, const char *ha1)
+ const digest_header_rec *resp)
{
const char *ha2;
ha2 = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, resp->method, ":",
resp->uri, NULL));
return ap_md5(r->pool,
- (unsigned char *)apr_pstrcat(r->pool, ha1, ":", resp->nonce,
- ":", ha2, NULL));
+ (unsigned char *)apr_pstrcat(r->pool, resp->ha1, ":",
+ resp->nonce, ":", ha2, NULL));
}
/* RFC-2617 */
static const char *new_digest(const request_rec *r,
- digest_header_rec *resp,
- const digest_config_rec *conf)
+ digest_header_rec *resp)
{
const char *ha1, *ha2, *a2;
- ha1 = conf->ha1;
+ ha1 = resp->ha1;
a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, NULL);
ha2 = ap_md5(r->pool, (const unsigned char *)a2);
@@ -1514,7 +1522,6 @@ static const char *new_digest(const request_rec *r,
NULL));
}
-
static void copy_uri_components(apr_uri_t *dst,
apr_uri_t *src, request_rec *r) {
if (src->scheme && src->scheme[0] != '\0') {
@@ -1583,7 +1590,7 @@ static int authenticate_digest_user(request_rec *r)
/* do we require Digest auth for this URI? */
- if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest")) {
+ if (!(t = ap_auth_type(r)) || ap_cstr_casecmp(t, "Digest")) {
return DECLINED;
}
@@ -1751,7 +1758,7 @@ static int authenticate_digest_user(request_rec *r)
}
if (resp->algorithm != NULL
- && strcasecmp(resp->algorithm, "MD5")) {
+ && ap_cstr_casecmp(resp->algorithm, "MD5")) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01789)
"unknown algorithm `%s' received: %s",
resp->algorithm, r->uri);
@@ -1759,7 +1766,7 @@ static int authenticate_digest_user(request_rec *r)
return HTTP_UNAUTHORIZED;
}
- return_code = get_hash(r, r->user, conf);
+ return_code = get_hash(r, r->user, conf, &resp->ha1);
if (return_code == AUTH_USER_NOT_FOUND) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01790)
@@ -1789,7 +1796,7 @@ static int authenticate_digest_user(request_rec *r)
if (resp->message_qop == NULL) {
/* old (rfc-2069) style digest */
- if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) {
+ if (strcmp(resp->digest, old_digest(r, resp))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01792)
"user %s: password mismatch: %s", r->user,
r->uri);
@@ -1802,7 +1809,7 @@ static int authenticate_digest_user(request_rec *r)
int match = 0, idx;
const char **tmp = (const char **)(conf->qop_list->elts);
for (idx = 0; idx < conf->qop_list->nelts; idx++) {
- if (!strcasecmp(*tmp, resp->message_qop)) {
+ if (!ap_cstr_casecmp(*tmp, resp->message_qop)) {
match = 1;
break;
}
@@ -1811,7 +1818,7 @@ static int authenticate_digest_user(request_rec *r)
if (!match
&& !(apr_is_empty_array(conf->qop_list)
- && !strcasecmp(resp->message_qop, "auth"))) {
+ && !ap_cstr_casecmp(resp->message_qop, "auth"))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01793)
"invalid qop `%s' received: %s",
resp->message_qop, r->uri);
@@ -1819,7 +1826,7 @@ static int authenticate_digest_user(request_rec *r)
return HTTP_UNAUTHORIZED;
}
- exp_digest = new_digest(r, resp, conf);
+ exp_digest = new_digest(r, resp);
if (!exp_digest) {
/* we failed to allocate a client struct */
return HTTP_INTERNAL_SERVER_ERROR;
@@ -1893,7 +1900,7 @@ static int add_auth_info(request_rec *r)
/* do rfc-2069 digest
*/
if (!apr_is_empty_array(conf->qop_list) &&
- !strcasecmp(*(const char **)(conf->qop_list->elts), "none")
+ !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")
&& resp->message_qop == NULL) {
/* use only RFC-2069 format */
ai = nextnonce;
@@ -1903,7 +1910,7 @@ static int add_auth_info(request_rec *r)
/* calculate rspauth attribute
*/
- ha1 = conf->ha1;
+ ha1 = resp->ha1;
a2 = apr_pstrcat(r->pool, ":", resp->uri, NULL);
ha2 = ap_md5(r->pool, (const unsigned char *)a2);
diff --git a/modules/aaa/mod_auth_form.c b/modules/aaa/mod_auth_form.c
index bea7d51..d443092 100644
--- a/modules/aaa/mod_auth_form.c
+++ b/modules/aaa/mod_auth_form.c
@@ -40,11 +40,10 @@
#define FORM_REDIRECT_HANDLER "form-redirect-handler"
#define MOD_AUTH_FORM_HASH "site"
-static int (*ap_session_load_fn) (request_rec * r, session_rec ** z) = NULL;
-static apr_status_t (*ap_session_get_fn)(request_rec * r, session_rec * z,
- const char *key, const char **value) = NULL;
-static apr_status_t (*ap_session_set_fn)(request_rec * r, session_rec * z,
- const char *key, const char *value) = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_session_load) *ap_session_load_fn = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_session_get) *ap_session_get_fn = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_session_set) *ap_session_set_fn = NULL;
+
static void (*ap_request_insert_filter_fn) (request_rec * r) = NULL;
static void (*ap_request_remove_filter_fn) (request_rec * r) = NULL;
@@ -420,7 +419,7 @@ static void note_cookie_auth_failure(request_rec * r)
static int hook_note_cookie_auth_failure(request_rec * r,
const char *auth_type)
{
- if (strcasecmp(auth_type, "form"))
+ if (ap_cstr_casecmp(auth_type, "form"))
return DECLINED;
note_cookie_auth_failure(r);
@@ -892,7 +891,7 @@ static int authenticate_form_authn(request_rec * r)
/* Are we configured to be Form auth? */
current_auth = ap_auth_type(r);
- if (!current_auth || strcasecmp(current_auth, "form")) {
+ if (!current_auth || ap_cstr_casecmp(current_auth, "form")) {
return DECLINED;
}
diff --git a/modules/aaa/mod_authn_core.c b/modules/aaa/mod_authn_core.c
index 7af1265..f3a494c 100644
--- a/modules/aaa/mod_authn_core.c
+++ b/modules/aaa/mod_authn_core.c
@@ -34,6 +34,7 @@
#include "http_log.h"
#include "http_request.h"
#include "http_protocol.h"
+#include "ap_expr.h"
#include "ap_provider.h"
#include "mod_auth.h"
@@ -52,9 +53,9 @@
*/
typedef struct {
- const char *ap_auth_type;
+ ap_expr_info_t *ap_auth_type;
int auth_type_set;
- const char *ap_auth_name;
+ ap_expr_info_t *ap_auth_name;
} authn_core_dir_conf;
typedef struct provider_alias_rec {
@@ -298,8 +299,16 @@ static const char *set_authname(cmd_parms *cmd, void *mconfig,
const char *word1)
{
authn_core_dir_conf *aconfig = (authn_core_dir_conf *)mconfig;
+ const char *expr_err = NULL;
+
+ aconfig->ap_auth_name = ap_expr_parse_cmd(cmd, word1, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+ if (expr_err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression '", word1, "' in AuthName: ",
+ expr_err, NULL);
+ }
- aconfig->ap_auth_name = ap_escape_quotes(cmd->pool, word1);
return NULL;
}
@@ -307,9 +316,17 @@ static const char *set_authtype(cmd_parms *cmd, void *mconfig,
const char *word1)
{
authn_core_dir_conf *aconfig = (authn_core_dir_conf *)mconfig;
+ const char *expr_err = NULL;
+
+ aconfig->ap_auth_type = ap_expr_parse_cmd(cmd, word1, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+ if (expr_err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression '", word1, "' in AuthType: ",
+ expr_err, NULL);
+ }
aconfig->auth_type_set = 1;
- aconfig->ap_auth_type = strcasecmp(word1, "None") ? word1 : NULL;
return NULL;
}
@@ -318,20 +335,44 @@ static const char *authn_ap_auth_type(request_rec *r)
{
authn_core_dir_conf *conf;
- conf = (authn_core_dir_conf *)ap_get_module_config(r->per_dir_config,
- &authn_core_module);
+ conf = (authn_core_dir_conf *) ap_get_module_config(r->per_dir_config,
+ &authn_core_module);
+
+ if (conf->ap_auth_type) {
+ const char *err = NULL, *type;
+ type = ap_expr_str_exec(r, conf->ap_auth_type, &err);
+ if (err) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(02834) "AuthType expression could not be evaluated: %s", err);
+ return NULL;
+ }
+
+ return strcasecmp(type, "None") ? type : NULL;
+ }
- return conf->ap_auth_type;
+ return NULL;
}
static const char *authn_ap_auth_name(request_rec *r)
{
authn_core_dir_conf *conf;
+ const char *err = NULL, *name;
+
+ conf = (authn_core_dir_conf *) ap_get_module_config(r->per_dir_config,
+ &authn_core_module);
+
+ if (conf->ap_auth_name) {
+ name = ap_expr_str_exec(r, conf->ap_auth_name, &err);
+ if (err) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(02835) "AuthName expression could not be evaluated: %s", err);
+ return NULL;
+ }
- conf = (authn_core_dir_conf *)ap_get_module_config(r->per_dir_config,
- &authn_core_module);
+ return ap_escape_quotes(r->pool, name);
+ }
- return apr_pstrdup(r->pool, conf->ap_auth_name);
+ return NULL;
}
static const command_rec authn_cmds[] =
diff --git a/modules/aaa/mod_authn_dbd.c b/modules/aaa/mod_authn_dbd.c
index 57090d2..08e5993 100644
--- a/modules/aaa/mod_authn_dbd.c
+++ b/modules/aaa/mod_authn_dbd.c
@@ -143,7 +143,6 @@ static authn_status authn_dbd_password(request_rec *r, const char *user,
return AUTH_GENERAL_ERROR;
}
if (dbd_password == NULL) {
-#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 3)
/* add the rest of the columns to the environment */
int i = 1;
const char *name;
@@ -168,7 +167,7 @@ static authn_status authn_dbd_password(request_rec *r, const char *user,
apr_dbd_get_entry(dbd->driver, row, i));
i++;
}
-#endif
+
dbd_password = apr_pstrdup(r->pool,
apr_dbd_get_entry(dbd->driver, row, 0));
}
@@ -239,7 +238,6 @@ static authn_status authn_dbd_realm(request_rec *r, const char *user,
return AUTH_GENERAL_ERROR;
}
if (dbd_hash == NULL) {
-#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 3)
/* add the rest of the columns to the environment */
int i = 1;
const char *name;
@@ -264,7 +262,7 @@ static authn_status authn_dbd_realm(request_rec *r, const char *user,
apr_dbd_get_entry(dbd->driver, row, i));
i++;
}
-#endif
+
dbd_hash = apr_pstrdup(r->pool,
apr_dbd_get_entry(dbd->driver, row, 0));
}
diff --git a/modules/aaa/mod_authn_dbm.c b/modules/aaa/mod_authn_dbm.c
index f4fb736..9f47350 100644
--- a/modules/aaa/mod_authn_dbm.c
+++ b/modules/aaa/mod_authn_dbm.c
@@ -39,6 +39,11 @@
#include "mod_auth.h"
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
static APR_OPTIONAL_FN_TYPE(ap_authn_cache_store) *authn_cache_store = NULL;
#define AUTHN_CACHE_STORE(r,user,realm,data) \
if (authn_cache_store != NULL) \
@@ -72,18 +77,39 @@ static const command_rec authn_dbm_cmds[] =
module AP_MODULE_DECLARE_DATA authn_dbm_module;
-static apr_status_t fetch_dbm_value(const char *dbmtype, const char *dbmfile,
- const char *user, char **value,
- apr_pool_t *pool)
+static apr_status_t fetch_dbm_value(request_rec *r, const char *dbmtype,
+ const char *dbmfile,
+ const char *user, char **value)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *f;
apr_datum_t key, val;
apr_status_t rv;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ rv = apr_dbm_get_driver(&driver, dbmtype, &err, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10284)
+ "could not load '%s' dbm library: %s",
+ err->reason, err->msg);
+ return rv;
+ }
+
+ rv = apr_dbm_open2(&f, driver, dbmfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+#else
rv = apr_dbm_open_ex(&f, dbmtype, dbmfile, APR_DBM_READONLY,
- APR_OS_DEFAULT, pool);
+ APR_OS_DEFAULT, r->pool);
+#endif
if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10285)
+ "could not open dbm (type %s) file: %s",
+ dbmtype, dbmfile);
return rv;
}
@@ -97,12 +123,16 @@ static apr_status_t fetch_dbm_value(const char *dbmtype, const char *dbmfile,
*value = NULL;
if (apr_dbm_fetch(f, key, &val) == APR_SUCCESS && val.dptr) {
- *value = apr_pstrmemdup(pool, val.dptr, val.dsize);
+ *value = apr_pstrmemdup(r->pool, val.dptr, val.dsize);
}
apr_dbm_close(f);
- return rv;
+ /* NOT FOUND is not an error case; this is indicated by a NULL result.
+ * Treat all NULL lookup/error results as success for the simple case
+ * of auth credential lookup, these are DECLINED in both cases.
+ */
+ return APR_SUCCESS;
}
static authn_status check_dbm_pw(request_rec *r, const char *user,
@@ -114,13 +144,9 @@ static authn_status check_dbm_pw(request_rec *r, const char *user,
char *dbm_password;
char *colon_pw;
- rv = fetch_dbm_value(conf->dbmtype, conf->pwfile, user, &dbm_password,
- r->pool);
+ rv = fetch_dbm_value(r, conf->dbmtype, conf->pwfile, user, &dbm_password);
if (rv != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01754)
- "could not open dbm (type %s) auth file: %s",
- conf->dbmtype, conf->pwfile);
return AUTH_GENERAL_ERROR;
}
@@ -152,14 +178,11 @@ static authn_status get_dbm_realm_hash(request_rec *r, const char *user,
char *dbm_hash;
char *colon_hash;
- rv = fetch_dbm_value(conf->dbmtype, conf->pwfile,
+ rv = fetch_dbm_value(r, conf->dbmtype, conf->pwfile,
apr_pstrcat(r->pool, user, ":", realm, NULL),
- &dbm_hash, r->pool);
+ &dbm_hash);
if (rv != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01755)
- "Could not open dbm (type %s) hash file: %s",
- conf->dbmtype, conf->pwfile);
return AUTH_GENERAL_ERROR;
}
diff --git a/modules/aaa/mod_authn_socache.c b/modules/aaa/mod_authn_socache.c
index 550bc66..0e4454a 100644
--- a/modules/aaa/mod_authn_socache.c
+++ b/modules/aaa/mod_authn_socache.c
@@ -299,7 +299,7 @@ static void ap_authn_cache_store(request_rec *r, const char *module,
const char *key;
apr_time_t expiry;
- /* first check whether we're cacheing for this module */
+ /* first check whether we're caching for this module */
dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
if (!configured || !dcfg->providers) {
return;
@@ -350,7 +350,7 @@ static void ap_authn_cache_store(request_rec *r, const char *module,
}
}
-#define MAX_VAL_LEN 100
+#define MAX_VAL_LEN 256
static authn_status check_password(request_rec *r, const char *user,
const char *password)
{
diff --git a/modules/aaa/mod_authnz_fcgi.c b/modules/aaa/mod_authnz_fcgi.c
index d99f391..69743f1 100644
--- a/modules/aaa/mod_authnz_fcgi.c
+++ b/modules/aaa/mod_authnz_fcgi.c
@@ -571,6 +571,14 @@ static apr_status_t handle_response(const fcgi_provider_conf *conf,
"parsing -> %d/%d",
fn, status, r->status);
+ /* FCGI has its own body framing mechanism which we don't
+ * match against any provided Content-Length, so let the
+ * core determine C-L vs T-E based on what's actually sent.
+ */
+ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+
if (rspbuf) { /* caller wants to see response body,
* if any
*/
@@ -681,7 +689,7 @@ static int mod_fcgid_modify_auth_header(void *vars,
/* When the application gives a 200 response, the server ignores response
headers whose names aren't prefixed with Variable- prefix, and ignores
any response content */
- if (strncasecmp(key, "Variable-", 9) == 0)
+ if (ap_cstr_casecmpn(key, "Variable-", 9) == 0)
apr_table_setn(vars, key, val);
return 1;
}
@@ -714,6 +722,7 @@ static void req_rsp(request_rec *r, const fcgi_provider_conf *conf,
}
apr_pool_create(&temp_pool, r->pool);
+ apr_pool_tag(temp_pool, "mod_authnz_fcgi (req_rsp)");
setupenv(r, password, apache_role);
@@ -809,7 +818,7 @@ static int fcgi_check_authn(request_rec *r)
prov = dconf && dconf->name ? dconf->name : NULL;
- if (!prov || !strcasecmp(prov, "None")) {
+ if (!prov || !ap_cstr_casecmp(prov, "None")) {
return DECLINED;
}
@@ -824,7 +833,7 @@ static int fcgi_check_authn(request_rec *r)
dconf->user_expr ? "yes" : "no",
auth_type);
- if (auth_type && !strcasecmp(auth_type, "Basic")) {
+ if (auth_type && !ap_cstr_casecmp(auth_type, "Basic")) {
if ((res = ap_get_basic_auth_pw(r, &password))) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
APLOGNO(02517) "%s: couldn't retrieve basic auth "
diff --git a/modules/aaa/mod_authnz_ldap.c b/modules/aaa/mod_authnz_ldap.c
index 4634fe9..a7b4939 100644
--- a/modules/aaa/mod_authnz_ldap.c
+++ b/modules/aaa/mod_authnz_ldap.c
@@ -500,6 +500,32 @@ static authn_status authn_ldap_check_password(request_rec *r, const char *user,
return AUTH_GENERAL_ERROR;
}
+ /* Get the password that the client sent */
+ if (password == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01692)
+ "auth_ldap authenticate: no password specified");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ if (user == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01693)
+ "auth_ldap authenticate: no user specified");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ /*
+ * A bind to the server with an empty password always succeeds, so
+ * we check to ensure that the password is not empty. This implies
+ * that users who actually do have empty passwords will never be
+ * able to authenticate with this module. I don't see this as a big
+ * problem.
+ */
+ if (!(*password)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10263)
+ "auth_ldap authenticate: empty password specified");
+ return AUTH_DENIED;
+ }
+
/* There is a good AuthLDAPURL, right? */
if (sec->host) {
const char *binddn = sec->binddn;
@@ -522,21 +548,6 @@ static authn_status authn_ldap_check_password(request_rec *r, const char *user,
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01691)
"auth_ldap authenticate: using URL %s", sec->url);
- /* Get the password that the client sent */
- if (password == NULL) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01692)
- "auth_ldap authenticate: no password specified");
- util_ldap_connection_close(ldc);
- return AUTH_GENERAL_ERROR;
- }
-
- if (user == NULL) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01693)
- "auth_ldap authenticate: no user specified");
- util_ldap_connection_close(ldc);
- return AUTH_GENERAL_ERROR;
- }
-
/* build the username filter */
authn_ldap_build_filter(filtbuf, r, user, NULL, sec);
@@ -1673,6 +1684,10 @@ static const char *set_bind_password(cmd_parms *cmd, void *_cfg, const char *arg
sec->bindpw = (char *)arg;
}
+ if (!(*sec->bindpw)) {
+ return "Empty passwords are invalid for AuthLDAPBindPassword";
+ }
+
return NULL;
}
diff --git a/modules/aaa/mod_authz_core.c b/modules/aaa/mod_authz_core.c
index 9585114..40e5fe1 100644
--- a/modules/aaa/mod_authz_core.c
+++ b/modules/aaa/mod_authz_core.c
@@ -193,12 +193,11 @@ static authz_status authz_alias_check_authorization(request_rec *r,
const void *parsed_require_args)
{
const char *provider_name;
- authz_status ret = AUTHZ_DENIED;
/* Look up the provider alias in the alias list.
- * Get the dir_config and call ap_Merge_per_dir_configs()
+ * Get the dir_config and call ap_merge_per_dir_configs()
* Call the real provider->check_authorization() function
- * return the result of the above function call
+ * Return the result of the above function call
*/
provider_name = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE);
@@ -217,6 +216,7 @@ static authz_status authz_alias_check_authorization(request_rec *r,
configurations and call the real provider */
if (prvdraliasrec) {
ap_conf_vector_t *orig_dir_config = r->per_dir_config;
+ authz_status ret;
r->per_dir_config =
ap_merge_per_dir_configs(r->pool, orig_dir_config,
@@ -227,18 +227,16 @@ static authz_status authz_alias_check_authorization(request_rec *r,
prvdraliasrec->provider_parsed_args);
r->per_dir_config = orig_dir_config;
+
+ return ret;
}
- else {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02305)
- "no alias provider found for '%s' (BUG?)",
- provider_name);
- }
- }
- else {
- ap_assert(provider_name != NULL);
}
- return ret;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02305)
+ "no alias provider found for '%s' (BUG?)",
+ provider_name ? provider_name : "n/a");
+
+ return AUTHZ_DENIED;
}
static const authz_provider authz_alias_provider =
diff --git a/modules/aaa/mod_authz_dbd.c b/modules/aaa/mod_authz_dbd.c
index e1bb623..5d169e1 100644
--- a/modules/aaa/mod_authz_dbd.c
+++ b/modules/aaa/mod_authz_dbd.c
@@ -212,7 +212,7 @@ static int authz_dbd_login(request_rec *r, authz_dbd_cfg *cfg,
static int authz_dbd_group_query(request_rec *r, authz_dbd_cfg *cfg,
apr_array_header_t *groups)
{
- /* SELECT group FROM authz WHERE user = %s */
+ /* SELECT user_group FROM authz WHERE user = %s */
int rv;
const char *message;
ap_dbd_t *dbd;
@@ -254,7 +254,7 @@ static int authz_dbd_group_query(request_rec *r, authz_dbd_cfg *cfg,
else {
message = apr_dbd_error(dbd->driver, dbd->handle, rv);
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01651)
- "authz_dbd in get_row; group query for user=%s [%s]",
+ "authz_dbd in get_row; user_group query for user=%s [%s]",
r->user, message?message:noerror);
return HTTP_INTERNAL_SERVER_ERROR;
}
diff --git a/modules/aaa/mod_authz_dbm.c b/modules/aaa/mod_authz_dbm.c
index 843d9a8..f11de68 100644
--- a/modules/aaa/mod_authz_dbm.c
+++ b/modules/aaa/mod_authz_dbm.c
@@ -20,6 +20,11 @@
#include "apr_dbm.h"
#include "apr_md5.h"
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
#include "httpd.h"
#include "http_config.h"
#include "ap_provider.h"
@@ -96,14 +101,35 @@ static apr_status_t get_dbm_grp(request_rec *r, char *key1, char *key2,
const char *dbmgrpfile, const char *dbtype,
const char ** out)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
char *grp_colon, *val;
apr_status_t retval;
apr_dbm_t *f;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ retval = apr_dbm_get_driver(&driver, dbtype, &err, r->pool);
+
+ if (retval != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, retval, r, APLOGNO(10286)
+ "could not load '%s' dbm library: %s",
+ err->reason, err->msg);
+ return retval;
+ }
+
+ retval = apr_dbm_open2(&f, driver, dbmgrpfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+#else
retval = apr_dbm_open_ex(&f, dbtype, dbmgrpfile, APR_DBM_READONLY,
APR_OS_DEFAULT, r->pool);
+#endif
if (retval != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, retval, r, APLOGNO(01799)
+ "could not open dbm (type %s) group access "
+ "file: %s", dbtype, dbmgrpfile);
return retval;
}
@@ -166,9 +192,6 @@ static authz_status dbmgroup_check_authorization(request_rec *r,
user, conf->grpfile, conf->dbmtype, &groups);
if (status != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01799)
- "could not open dbm (type %s) group access "
- "file: %s", conf->dbmtype, conf->grpfile);
return AUTHZ_GENERAL_ERROR;
}
@@ -241,9 +264,6 @@ static authz_status dbmfilegroup_check_authorization(request_rec *r,
user, conf->grpfile, conf->dbmtype, &groups);
if (status != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01803)
- "could not open dbm (type %s) group access "
- "file: %s", conf->dbmtype, conf->grpfile);
return AUTHZ_DENIED;
}
diff --git a/modules/aaa/mod_authz_groupfile.c b/modules/aaa/mod_authz_groupfile.c
index 76957f7..c2431e0 100644
--- a/modules/aaa/mod_authz_groupfile.c
+++ b/modules/aaa/mod_authz_groupfile.c
@@ -98,6 +98,8 @@ static apr_status_t groups_for_user(apr_pool_t *p, char *user, char *grpfile,
}
apr_pool_create(&sp, p);
+ apr_pool_tag(sp, "authz_groupfile (groups_for_user)");
+
ap_varbuf_init(p, &vb, VARBUF_INIT_LEN);
while (!(ap_varbuf_cfg_getline(&vb, f, VARBUF_MAX_LEN))) {
@@ -172,7 +174,7 @@ static authz_status group_check_authorization(request_rec *r,
if (apr_is_empty_table(grpstatus)) {
/* no groups available, so exit immediately */
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01666)
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01666)
"Authorization of user %s to access %s failed, reason: "
"user doesn't appear in group file (%s).",
r->user, r->uri, conf->groupfile);
diff --git a/modules/arch/unix/config5.m4 b/modules/arch/unix/config5.m4
index 77027a8..3d099f8 100644
--- a/modules/arch/unix/config5.m4
+++ b/modules/arch/unix/config5.m4
@@ -18,6 +18,15 @@ APACHE_MODULE(privileges, Per-virtualhost Unix UserIDs and enhanced security for
fi
])
+APACHE_MODULE(systemd, Systemd support, , , no, [
+ if test "${ac_cv_header_systemd_sd_daemon_h}" = "no" || test -z "${SYSTEMD_LIBS}"; then
+ AC_MSG_WARN([Your system does not support systemd.])
+ enable_systemd="no"
+ else
+ APR_ADDTO(MOD_SYSTEMD_LDADD, [$SYSTEMD_LIBS])
+ fi
+])
+
APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
APACHE_MODPATH_FINISH
diff --git a/modules/arch/unix/mod_systemd.c b/modules/arch/unix/mod_systemd.c
new file mode 100644
index 0000000..c3e7082
--- /dev/null
+++ b/modules/arch/unix/mod_systemd.c
@@ -0,0 +1,119 @@
+/* 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
+#include
+#include "ap_mpm.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include "unixd.h"
+#include "scoreboard.h"
+#include "mpm_common.h"
+
+#include "systemd/sd-daemon.h"
+
+#if APR_HAVE_UNISTD_H
+#include
+#endif
+
+static int systemd_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ sd_notify(0,
+ "RELOADING=1\n"
+ "STATUS=Reading configuration...\n");
+ ap_extended_status = 1;
+ return OK;
+}
+
+/* Report the service is ready in post_config, which could be during
+ * startup or after a reload. The server could still hit a fatal
+ * startup error after this point during ap_run_mpm(), so this is
+ * perhaps too early, but by post_config listen() has been called on
+ * the TCP ports so new connections will not be rejected. There will
+ * always be a possible async failure event simultaneous to the
+ * service reporting "ready", so this should be good enough. */
+static int systemd_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *main_server)
+{
+ sd_notify(0, "READY=1\n"
+ "STATUS=Configuration loaded.\n");
+ return OK;
+}
+
+static int systemd_pre_mpm(apr_pool_t *p, ap_scoreboard_e sb_type)
+{
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests...\n"
+ "MAINPID=%" APR_PID_T_FMT, getpid());
+
+ return OK;
+}
+
+static int systemd_monitor(apr_pool_t *p, server_rec *s)
+{
+ ap_sload_t sload;
+ apr_interval_time_t up_time;
+ char bps[5];
+
+ if (!ap_extended_status) {
+ /* Nothing useful to report with ExtendedStatus disabled. */
+ return DECLINED;
+ }
+
+ ap_get_sload(&sload);
+ /* up_time in seconds */
+ up_time = (apr_uint32_t) apr_time_sec(apr_time_now() -
+ ap_scoreboard_image->global->restart_time);
+
+ apr_strfsize((unsigned long)((float) (sload.bytes_served)
+ / (float) up_time), bps);
+
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Total requests: %lu; Idle/Busy workers %d/%d;"
+ "Requests/sec: %.3g; Bytes served/sec: %sB/sec\n",
+ sload.access_count, sload.idle, sload.busy,
+ ((float) sload.access_count) / (float) up_time, bps);
+
+ return DECLINED;
+}
+
+static void systemd_register_hooks(apr_pool_t *p)
+{
+ /* Enable ap_extended_status. */
+ ap_hook_pre_config(systemd_pre_config, NULL, NULL, APR_HOOK_LAST);
+ /* Signal service is ready. */
+ ap_hook_post_config(systemd_post_config, NULL, NULL, APR_HOOK_REALLY_LAST);
+ /* We know the PID in this hook ... */
+ ap_hook_pre_mpm(systemd_pre_mpm, NULL, NULL, APR_HOOK_LAST);
+ /* Used to update httpd's status line using sd_notifyf */
+ ap_hook_monitor(systemd_monitor, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(systemd) = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ systemd_register_hooks,
+};
diff --git a/modules/arch/win32/mod_isapi.c b/modules/arch/win32/mod_isapi.c
index 2e51d51..a9816e5 100644
--- a/modules/arch/win32/mod_isapi.c
+++ b/modules/arch/win32/mod_isapi.c
@@ -178,7 +178,7 @@ static const command_rec isapi_cmds[] = {
" on or off (default: off)"),
AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot,
(void *)APR_OFFSETOF(isapi_dir_conf, log_to_query),
- OR_FILEINFO, "Append Log requests are concatinated to the query args"
+ OR_FILEINFO, "Append Log requests are concatenated to the query args"
" on or off (default: on)"),
AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot,
(void *)APR_OFFSETOF(isapi_dir_conf, fake_async),
@@ -257,7 +257,7 @@ static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa)
isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
- /* TODO: These aught to become overrideable, so that we
+ /* TODO: These aught to become overridable, so that we
* assure a given isapi can be fooled into behaving well.
*
* The tricky bit, they aren't really a per-dir sort of
@@ -976,11 +976,11 @@ static int APR_THREAD_FUNC regfnServerSupportFunction(isapi_cid *cid,
return 0;
}
- len = (apr_uint32_t)strlen(r->filename);
+ len = (apr_uint32_t)strlen(subreq->filename);
if ((subreq->finfo.filetype == APR_DIR)
&& (!subreq->path_info)
- && (file[len - 1] != '/'))
+ && (subreq->filename[len - 1] != '/'))
file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL);
else
file = apr_pstrcat(cid->r->pool, subreq->filename,
@@ -1692,6 +1692,7 @@ static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pte
"could not create the isapi cache pool");
return APR_EGENERAL;
}
+ apr_pool_tag(loaded.pool, "mod_isapi_load");
loaded.hash = apr_hash_make(loaded.pool);
if (!loaded.hash) {
diff --git a/modules/arch/win32/mod_isapi.h b/modules/arch/win32/mod_isapi.h
index 6afa27b..5284386 100644
--- a/modules/arch/win32/mod_isapi.h
+++ b/modules/arch/win32/mod_isapi.h
@@ -183,7 +183,7 @@ typedef struct HSE_URL_MAPEX_INFO {
#define HSE_REQ_SEND_RESPONSE_HEADER 3
#define HSE_REQ_DONE_WITH_SESSION 4
-/* MS Extented methods to ISAPI ServerSupportFunction() HSE_code */
+/* MS Extended methods to ISAPI ServerSupportFunction() HSE_code */
#define HSE_REQ_MAP_URL_TO_PATH 1001 /* Emulated */
#define HSE_REQ_GET_SSPI_INFO 1002 /* Not Supported */
#define HSE_APPEND_LOG_PARAMETER 1003 /* Supported */
diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c
index 41f638c..dfda34b 100644
--- a/modules/cache/cache_storage.c
+++ b/modules/cache/cache_storage.c
@@ -270,8 +270,7 @@ int cache_select(cache_request_rec *cache, request_rec *r)
* language negotiated document in a different language by mistake.
*
* This code makes the assumption that the storage manager will
- * cache the req_hdrs if the response contains a Vary
- * header.
+ * cache the req_hdrs if the response contains a Vary header.
*
* RFC2616 13.6 and 14.44 describe the Vary mechanism.
*/
@@ -549,7 +548,7 @@ static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p,
}
else {
if (conf->base_uri && conf->base_uri->port_str) {
- port_str = conf->base_uri->port_str;
+ port_str = apr_pstrcat(p, ":", conf->base_uri->port_str, NULL);
}
else if (conf->base_uri && conf->base_uri->hostname) {
port_str = "";
diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c
index aa04913..fc36431 100644
--- a/modules/cache/cache_util.c
+++ b/modules/cache/cache_util.c
@@ -30,7 +30,7 @@ extern module AP_MODULE_DECLARE_DATA cache_module;
/* Determine if "url" matches the hostname, scheme and port and path
* in "filter". All but the path comparisons are case-insensitive.
*/
-static int uri_meets_conditions(const apr_uri_t *filter, const int pathlen,
+static int uri_meets_conditions(const apr_uri_t *filter, const apr_size_t pathlen,
const apr_uri_t *url, const char *path)
{
/* Scheme, hostname port and local part. The filter URI and the
diff --git a/modules/cache/config.m4 b/modules/cache/config.m4
index 8115094..4fa414b 100644
--- a/modules/cache/config.m4
+++ b/modules/cache/config.m4
@@ -133,6 +133,7 @@ fi
APACHE_MODULE(socache_shmcb, shmcb small object cache provider, , , most)
APACHE_MODULE(socache_dbm, dbm small object cache provider, , , most)
APACHE_MODULE(socache_memcache, memcache small object cache provider, , , most)
+APACHE_MODULE(socache_redis, redis small object cache provider, , , most)
APACHE_MODULE(socache_dc, distcache small object cache provider, , , no, [
APACHE_CHECK_DISTCACHE
])
diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c
index 56a09f5..3b4469e 100644
--- a/modules/cache/mod_cache.c
+++ b/modules/cache/mod_cache.c
@@ -986,7 +986,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
/* 304 does not contain Content-Type and mod_mime regenerates the
* Content-Type based on the r->filename. This would lead to original
- * Content-Type to be lost (overwriten by whatever mod_mime generates).
+ * Content-Type to be lost (overwritten by whatever mod_mime generates).
* We preserves the original Content-Type here. */
ap_set_content_type(r, apr_table_get(
cache->stale_handle->resp_hdrs, "Content-Type"));
@@ -1229,6 +1229,16 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
return APR_SUCCESS;
}
+ /* Set the content length if known.
+ */
+ cl = apr_table_get(r->err_headers_out, "Content-Length");
+ if (cl == NULL) {
+ cl = apr_table_get(r->headers_out, "Content-Length");
+ }
+ if (cl && !ap_parse_strict_length(&size, cl)) {
+ reason = "invalid content length";
+ }
+
if (reason) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00768)
"cache: %s not cached for request %s. Reason: %s",
@@ -1251,19 +1261,6 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
/* Make it so that we don't execute this path again. */
cache->in_checked = 1;
- /* Set the content length if known.
- */
- cl = apr_table_get(r->err_headers_out, "Content-Length");
- if (cl == NULL) {
- cl = apr_table_get(r->headers_out, "Content-Length");
- }
- if (cl) {
- char *errp;
- if (apr_strtoff(&size, cl, &errp, 10) || *errp || size < 0) {
- cl = NULL; /* parse error, see next 'if' block */
- }
- }
-
if (!cl) {
/* if we don't get the content-length, see if we have all the
* buckets and use their length to calculate the size
@@ -2533,7 +2530,7 @@ static const command_rec cache_cmds[] =
{
/* XXX
* Consider a new config directive that enables loading specific cache
- * implememtations (like mod_cache_mem, mod_cache_file, etc.).
+ * implementations (like mod_cache_mem, mod_cache_file, etc.).
* Rather than using a LoadModule directive, admin would use something
* like CacheModule mem_cache_module | file_cache_module, etc,
* which would cause the approprpriate cache module to be loaded.
diff --git a/modules/cache/mod_cache_disk.c b/modules/cache/mod_cache_disk.c
index 52d5dba..8d17a19 100644
--- a/modules/cache/mod_cache_disk.c
+++ b/modules/cache/mod_cache_disk.c
@@ -284,11 +284,11 @@ static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
* HTTP URI's (3.2.3) [host and scheme are insensitive]
* HTTP method (5.1.1)
* HTTP-date values (3.3.1)
- * 3.7 Media Types [exerpt]
+ * 3.7 Media Types [excerpt]
* The type, subtype, and parameter attribute names are case-
* insensitive. Parameter values might or might not be case-sensitive,
* depending on the semantics of the parameter name.
- * 4.20 Except [exerpt]
+ * 4.20 Except [excerpt]
* Comparison of expectation values is case-insensitive for unquoted
* tokens (including the 100-continue token), and is case-sensitive for
* quoted-string expectation-extensions.
@@ -713,7 +713,7 @@ static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
apr_file_t *file)
{
char w[MAX_STRING_LEN];
- int p;
+ apr_size_t p;
apr_status_t rv;
while (1) {
@@ -778,7 +778,7 @@ static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
{
char w[MAX_STRING_LEN];
char *l;
- int p;
+ apr_size_t p;
apr_status_t rv;
while (1) {
@@ -994,10 +994,11 @@ static apr_status_t write_headers(cache_handle_t *h, request_rec *r)
}
rv = mkdir_structure(conf, dobj->hdrs.file, r->pool);
-
- rv = apr_file_mktemp(&dobj->vary.tempfd, dobj->vary.tempfile,
- APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL,
- dobj->vary.pool);
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_mktemp(&dobj->vary.tempfd, dobj->vary.tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL,
+ dobj->vary.pool);
+ }
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00721)
@@ -1275,9 +1276,9 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
* sanity checks.
*/
if (seen_eos) {
- const char *cl_header = apr_table_get(r->headers_out, "Content-Length");
-
if (!dobj->disk_info.header_only) {
+ const char *cl_header;
+ apr_off_t cl;
if (dobj->data.tempfd) {
rv = apr_file_close(dobj->data.tempfd);
@@ -1296,6 +1297,7 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
apr_pool_destroy(dobj->data.pool);
return APR_EGENERAL;
}
+
if (dobj->file_size < dconf->minfs) {
ap_log_rerror(
APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734) "URL %s failed the size check "
@@ -1304,17 +1306,16 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
apr_pool_destroy(dobj->data.pool);
return APR_EGENERAL;
}
- if (cl_header) {
- apr_int64_t cl = apr_atoi64(cl_header);
- if ((errno == 0) && (dobj->file_size != cl)) {
- ap_log_rerror(
- APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) "URL %s didn't receive complete response, not caching", h->cache_obj->key);
- /* Remove the intermediate cache file and return non-APR_SUCCESS */
- apr_pool_destroy(dobj->data.pool);
- return APR_EGENERAL;
- }
- }
+ cl_header = apr_table_get(r->headers_out, "Content-Length");
+ if (cl_header && (!ap_parse_strict_length(&cl, cl_header)
+ || cl != dobj->file_size)) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) "URL %s didn't receive complete response, not caching", h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
}
/* All checks were fine, we're good to go when the commit comes */
diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c
index 0d76760..5f9e1d6 100644
--- a/modules/cache/mod_cache_socache.c
+++ b/modules/cache/mod_cache_socache.c
@@ -18,6 +18,12 @@
#include "apr_file_io.h"
#include "apr_strings.h"
#include "apr_buckets.h"
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
@@ -217,8 +223,8 @@ static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
while (apr_isspace(buffer[colon]) && (colon < *slider)) {
colon++;
}
- apr_table_addn(table, apr_pstrndup(r->pool, (const char *) buffer
- + key, len - key), apr_pstrndup(r->pool,
+ apr_table_addn(table, apr_pstrmemdup(r->pool, (const char *) buffer
+ + key, len - key), apr_pstrmemdup(r->pool,
(const char *) buffer + colon, *slider - colon));
(*slider)++;
if (buffer[*slider] == '\n') {
@@ -298,11 +304,11 @@ static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
* HTTP URI's (3.2.3) [host and scheme are insensitive]
* HTTP method (5.1.1)
* HTTP-date values (3.3.1)
- * 3.7 Media Types [exerpt]
+ * 3.7 Media Types [excerpt]
* The type, subtype, and parameter attribute names are case-
* insensitive. Parameter values might or might not be case-sensitive,
* depending on the semantics of the parameter name.
- * 4.20 Except [exerpt]
+ * 4.20 Except [excerpt]
* Comparison of expectation values is case-insensitive for unquoted
* tokens (including the 100-continue token), and is case-sensitive for
* quoted-string expectation-extensions.
@@ -429,6 +435,14 @@ static int create_entity(cache_handle_t *h, request_rec *r, const char *key,
return OK;
}
+static apr_status_t sobj_body_pre_cleanup(void *baton)
+{
+ cache_socache_object_t *sobj = baton;
+ apr_brigade_cleanup(sobj->body);
+ sobj->body = NULL;
+ return APR_SUCCESS;
+}
+
static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
{
cache_socache_dir_conf *dconf =
@@ -463,6 +477,7 @@ static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
* about for the lifetime of the response.
*/
apr_pool_create(&sobj->pool, r->pool);
+ apr_pool_tag(sobj->pool, "mod_cache_socache (open_entity)");
sobj->buffer = apr_palloc(sobj->pool, dconf->max);
sobj->buffer_len = dconf->max;
@@ -648,36 +663,25 @@ static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
}
/* Retrieve the body if we have one */
- sobj->body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
len = buffer_len - slider;
-
- /*
- * Optimisation: if the body is small, we want to make a
- * copy of the body and free the temporary pool, as we
- * don't want large blocks of unused memory hanging around
- * to the end of the response. In contrast, if the body is
- * large, we would rather leave the body where it is in the
- * temporary pool, and save ourselves the copy.
- */
- if (len * 2 > dconf->max) {
+ if (len > 0) {
apr_bucket *e;
-
- /* large - use the brigade as is, we're done */
- e = apr_bucket_immortal_create((const char *) sobj->buffer + slider,
- len, r->connection->bucket_alloc);
-
+ /* Create the body brigade later concatenated to the output filters'
+ * brigade by recall_body(). Since sobj->buffer (the data) point to
+ * sobj->pool (a subpool of r->pool), be safe by using a pool bucket
+ * which can morph to heap if sobj->pool is destroyed while the bucket
+ * is still alive. But if sobj->pool gets destroyed while the bucket is
+ * still in sobj->body (i.e. recall_body() was never called), we don't
+ * need to morph to something just about to be freed, so a pre_cleanup
+ * will take care of cleaning up sobj->body before this happens (and is
+ * a noop otherwise).
+ */
+ sobj->body = apr_brigade_create(sobj->pool, r->connection->bucket_alloc);
+ apr_pool_pre_cleanup_register(sobj->pool, sobj, sobj_body_pre_cleanup);
+ e = apr_bucket_pool_create((const char *) sobj->buffer + slider, len,
+ sobj->pool, r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(sobj->body, e);
}
- else {
-
- /* small - make a copy of the data... */
- apr_brigade_write(sobj->body, NULL, NULL, (const char *) sobj->buffer
- + slider, len);
-
- /* ...and get rid of the large memory buffer */
- apr_pool_destroy(sobj->pool);
- sobj->pool = NULL;
- }
/* make the configuration stick */
h->cache_obj = obj;
@@ -766,13 +770,9 @@ static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
apr_bucket_brigade *bb)
{
cache_socache_object_t *sobj = (cache_socache_object_t*) h->cache_obj->vobj;
- apr_bucket *e;
- e = APR_BRIGADE_FIRST(sobj->body);
-
- if (e != APR_BRIGADE_SENTINEL(sobj->body)) {
- APR_BUCKET_REMOVE(e);
- APR_BRIGADE_INSERT_TAIL(bb, e);
+ if (sobj->body) {
+ APR_BRIGADE_CONCAT(bb, sobj->body);
}
return APR_SUCCESS;
@@ -807,6 +807,7 @@ static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
: obj->info.expire + dconf->mintime;
apr_pool_create(&sobj->pool, r->pool);
+ apr_pool_tag(sobj->pool, "mod_cache_socache (store_headers)");
sobj->buffer = apr_palloc(sobj->pool, dconf->max);
sobj->buffer_len = dconf->max;
@@ -1051,7 +1052,8 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
/* Was this the final bucket? If yes, perform sanity checks.
*/
if (seen_eos) {
- const char *cl_header = apr_table_get(r->headers_out, "Content-Length");
+ const char *cl_header;
+ apr_off_t cl;
if (r->connection->aborted || r->no_cache) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02380)
@@ -1062,18 +1064,16 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
sobj->pool = NULL;
return APR_EGENERAL;
}
- if (cl_header) {
- apr_off_t cl;
- char *cl_endp;
- if (apr_strtoff(&cl, cl_header, &cl_endp, 10) != APR_SUCCESS
- || *cl_endp != '\0' || cl != sobj->body_length) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381)
- "URL %s didn't receive complete response, not caching",
- h->cache_obj->key);
- apr_pool_destroy(sobj->pool);
- sobj->pool = NULL;
- return APR_EGENERAL;
- }
+
+ cl_header = apr_table_get(r->headers_out, "Content-Length");
+ if (cl_header && (!ap_parse_strict_length(&cl, cl_header)
+ || cl != sobj->body_length)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381)
+ "URL %s didn't receive complete response, not caching",
+ h->cache_obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
}
/* All checks were fine, we're good to go when the commit comes */
diff --git a/modules/cache/mod_file_cache.c b/modules/cache/mod_file_cache.c
index 4199361..ce1db2d 100644
--- a/modules/cache/mod_file_cache.c
+++ b/modules/cache/mod_file_cache.c
@@ -380,7 +380,7 @@ static int file_cache_handler(request_rec *r)
return rc;
}
-static command_rec file_cache_cmds[] =
+static const command_rec file_cache_cmds[] =
{
AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
"A space separated list of files to add to the file handle cache at config time"),
diff --git a/modules/cache/mod_socache_dbm.c b/modules/cache/mod_socache_dbm.c
index 579d2ff..df97573 100644
--- a/modules/cache/mod_socache_dbm.c
+++ b/modules/cache/mod_socache_dbm.c
@@ -92,6 +92,7 @@ static const char *socache_dbm_create(ap_socache_instance_t **context,
}
apr_pool_create(&ctx->pool, p);
+ apr_pool_tag(ctx->pool, "socache_dbm_instance");
return NULL;
}
@@ -120,6 +121,10 @@ static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx,
const struct ap_socache_hints *hints,
server_rec *s, apr_pool_t *p)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_status_t rv;
@@ -141,6 +146,22 @@ static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx,
/* open it once to create it and to make sure it _can_ be created */
apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10277)
+ "Cannot load socache DBM library '%s': %s",
+ err->reason, err->msg);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00804)
+ "Cannot create socache DBM file `%s'",
+ ctx->data_file);
+ return DECLINED;
+ }
+#else
if ((rv = apr_dbm_open(&dbm, ctx->data_file,
APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00804)
@@ -148,6 +169,7 @@ static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx,
ctx->data_file);
return rv;
}
+#endif
apr_dbm_close(dbm);
ctx->expiry_interval = (hints && hints->expiry_interval
@@ -192,6 +214,10 @@ static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx,
unsigned char *ucaData,
unsigned int nData, apr_pool_t *pool)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_datum_t dbmkey;
apr_datum_t dbmval;
@@ -227,6 +253,25 @@ static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx,
/* and store it to the DBM file */
apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10278)
+ "Cannot load socache DBM library '%s' (store): %s",
+ err->reason, err->msg);
+ free(dbmval.dptr);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00807)
+ "Cannot open socache DBM file `%s' for writing "
+ "(store)",
+ ctx->data_file);
+ free(dbmval.dptr);
+ return rv;
+ }
+#else
if ((rv = apr_dbm_open(&dbm, ctx->data_file,
APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00807)
@@ -236,6 +281,7 @@ static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx,
free(dbmval.dptr);
return rv;
}
+#endif
if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00808)
"Cannot store socache object to DBM file `%s'",
@@ -260,6 +306,10 @@ static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec
unsigned char *dest, unsigned int *destlen,
apr_pool_t *p)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_datum_t dbmkey;
apr_datum_t dbmval;
@@ -280,6 +330,23 @@ static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec
* do the apr_dbm_close? This would make the code a bit cleaner.
*/
apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10279)
+ "Cannot load socache DBM library '%s' (fetch): %s",
+ err->reason, err->msg);
+ return rc;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00809)
+ "Cannot open socache DBM file `%s' for reading "
+ "(fetch)",
+ ctx->data_file);
+ return rc;
+ }
+#else
if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00809)
@@ -288,6 +355,7 @@ static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec
ctx->data_file);
return rc;
}
+#endif
rc = apr_dbm_fetch(dbm, dbmkey, &dbmval);
if (rc != APR_SUCCESS) {
apr_dbm_close(dbm);
@@ -325,6 +393,10 @@ static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx,
server_rec *s, const unsigned char *id,
unsigned int idlen, apr_pool_t *p)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_datum_t dbmkey;
apr_status_t rv;
@@ -336,6 +408,23 @@ static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx,
/* and delete it from the DBM file */
apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10280)
+ "Cannot load socache DBM library '%s' (delete): %s",
+ err->reason, err->msg);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00810)
+ "Cannot open socache DBM file `%s' for writing "
+ "(delete)",
+ ctx->data_file);
+ return rv;
+ }
+#else
if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00810)
@@ -344,6 +433,7 @@ static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx,
ctx->data_file);
return rv;
}
+#endif
apr_dbm_delete(dbm, dbmkey);
apr_dbm_close(dbm);
@@ -352,6 +442,10 @@ static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx,
static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_datum_t dbmkey;
apr_datum_t dbmval;
@@ -377,6 +471,16 @@ static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
ctx->last_expiry = now;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10281)
+ "Cannot load socache DBM library '%s' (expire): %s",
+ err->reason, err->msg);
+ return rv;
+ }
+#endif
+
/*
* Here we have to be very carefully: Not all DBM libraries are
* smart enough to allow one to iterate over the elements and at the
@@ -400,6 +504,16 @@ static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
/* pass 1: scan DBM database */
keyidx = 0;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00811)
+ "Cannot open socache DBM file `%s' for "
+ "scanning",
+ ctx->data_file);
+ break;
+ }
+#else
if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00811)
@@ -408,6 +522,7 @@ static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
ctx->data_file);
break;
}
+#endif
apr_dbm_firstkey(dbm, &dbmkey);
while (dbmkey.dptr != NULL) {
elts++;
@@ -433,6 +548,16 @@ static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
apr_dbm_close(dbm);
/* pass 2: delete expired elements */
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if (apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00812)
+ "Cannot re-open socache DBM file `%s' for "
+ "expiring",
+ ctx->data_file);
+ break;
+ }
+#else
if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00812)
@@ -441,6 +566,7 @@ static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
ctx->data_file);
break;
}
+#endif
for (i = 0; i < keyidx; i++) {
apr_dbm_delete(dbm, keylist[i]);
deleted++;
@@ -460,6 +586,10 @@ static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r,
int flags)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_datum_t dbmkey;
apr_datum_t dbmval;
@@ -472,14 +602,32 @@ static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r,
size = 0;
apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10282)
+ "Cannot load socache DBM library '%s' (status retrieval): %s",
+ err->reason, err->msg);
+ return;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00814)
+ "Cannot open socache DBM file `%s' for status "
+ "retrieval",
+ ctx->data_file);
+ return;
+ }
+#else
if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00814)
"Cannot open socache DBM file `%s' for status "
- "retrival",
+ "retrieval",
ctx->data_file);
return;
}
+#endif
/*
* XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD
*/
@@ -515,6 +663,10 @@ static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx,
ap_socache_iterator_t *iterator,
apr_pool_t *pool)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *dbm;
apr_datum_t dbmkey;
apr_datum_t dbmval;
@@ -527,6 +679,22 @@ static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx,
* make sure the expired records are omitted
*/
now = apr_time_now();
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10283)
+ "Cannot load socache DBM library '%s' (iterating): %s",
+ err->reason, err->msg);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00815)
+ "Cannot open socache DBM file `%s' for "
+ "iterating", ctx->data_file);
+ return rv;
+ }
+#else
if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00815)
@@ -534,6 +702,7 @@ static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx,
"iterating", ctx->data_file);
return rv;
}
+#endif
rv = apr_dbm_firstkey(dbm, &dbmkey);
while (rv == APR_SUCCESS && dbmkey.dptr != NULL) {
expired = FALSE;
diff --git a/modules/cache/mod_socache_dc.c b/modules/cache/mod_socache_dc.c
index c1d4ab8..1fc52a7 100644
--- a/modules/cache/mod_socache_dc.c
+++ b/modules/cache/mod_socache_dc.c
@@ -69,7 +69,7 @@ static apr_status_t socache_dc_init(ap_socache_instance_t *ctx,
/* This mode of operation will open a temporary connection to the 'target'
* for each cache operation - this makes it safe against fork()
* automatically. This mode is preferred when running a local proxy (over
- * unix domain sockets) because overhead is negligable and it reduces the
+ * unix domain sockets) because overhead is negligible and it reduces the
* performance/stability danger of file-descriptor bloatage. */
#define SESSION_CTX_FLAGS 0
#endif
diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c
index e943b9b..f122ba4 100644
--- a/modules/cache/mod_socache_memcache.c
+++ b/modules/cache/mod_socache_memcache.c
@@ -20,15 +20,6 @@
#include "http_protocol.h"
#include "apr.h"
-#include "apu_version.h"
-
-/* apr_memcache support requires >= 1.3 */
-#if APU_MAJOR_VERSION > 1 || \
- (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 2)
-#define HAVE_APU_MEMCACHE 1
-#endif
-
-#ifdef HAVE_APU_MEMCACHE
#include "ap_socache.h"
#include "ap_mpm.h"
@@ -371,8 +362,6 @@ static const ap_socache_provider_t socache_mc = {
socache_mc_iterate
};
-#endif /* HAVE_APU_MEMCACHE */
-
static void *create_server_config(apr_pool_t *p, server_rec *s)
{
socache_mc_svr_cfg *sconf = apr_pcalloc(p, sizeof(socache_mc_svr_cfg));
@@ -407,11 +396,9 @@ static const char *socache_mc_set_ttl(cmd_parms *cmd, void *dummy,
static void register_hooks(apr_pool_t *p)
{
-#ifdef HAVE_APU_MEMCACHE
ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache",
AP_SOCACHE_PROVIDER_VERSION,
&socache_mc);
-#endif
}
static const command_rec socache_memcache_cmds[] = {
diff --git a/modules/cache/mod_socache_redis.c b/modules/cache/mod_socache_redis.c
new file mode 100644
index 0000000..450f406
--- /dev/null
+++ b/modules/cache/mod_socache_redis.c
@@ -0,0 +1,486 @@
+/* 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 "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+
+#include "apr.h"
+#include "apu_version.h"
+
+#include "ap_socache.h"
+#include "ap_mpm.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "mod_status.h"
+
+typedef struct {
+ apr_uint32_t ttl;
+ apr_uint32_t rwto;
+} socache_rd_svr_cfg;
+
+/* apr_redis support requires >= 1.6 */
+#if APU_MAJOR_VERSION > 1 || \
+ (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 5)
+#define HAVE_APU_REDIS 1
+#endif
+
+/* The underlying apr_redis system is thread safe.. */
+#define RD_KEY_LEN 254
+
+#ifndef RD_DEFAULT_SERVER_PORT
+#define RD_DEFAULT_SERVER_PORT 6379
+#endif
+
+
+#ifndef RD_DEFAULT_SERVER_MIN
+#define RD_DEFAULT_SERVER_MIN 0
+#endif
+
+#ifndef RD_DEFAULT_SERVER_SMAX
+#define RD_DEFAULT_SERVER_SMAX 1
+#endif
+
+#ifndef RD_DEFAULT_SERVER_TTL
+#define RD_DEFAULT_SERVER_TTL apr_time_from_sec(15)
+#endif
+
+#ifndef RD_DEFAULT_SERVER_RWTO
+#define RD_DEFAULT_SERVER_RWTO apr_time_from_sec(5)
+#endif
+
+module AP_MODULE_DECLARE_DATA socache_redis_module;
+
+#ifdef HAVE_APU_REDIS
+#include "apr_redis.h"
+struct ap_socache_instance_t {
+ const char *servers;
+ apr_redis_t *rc;
+ const char *tag;
+ apr_size_t taglen; /* strlen(tag) + 1 */
+};
+
+static const char *socache_rd_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ ap_socache_instance_t *ctx;
+
+ *context = ctx = apr_pcalloc(p, sizeof *ctx);
+
+ if (!arg || !*arg) {
+ return "List of server names required to create redis socache.";
+ }
+
+ ctx->servers = apr_pstrdup(p, arg);
+
+ return NULL;
+}
+
+static apr_status_t socache_rd_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int thread_limit = 0;
+ apr_uint16_t nservers = 0;
+ char *cache_config;
+ char *split;
+ char *tok;
+
+ socache_rd_svr_cfg *sconf = ap_get_module_config(s->module_config,
+ &socache_redis_module);
+
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+
+ /* Find all the servers in the first run to get a total count */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ nservers++;
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ rv = apr_redis_create(p, nservers, 0, &ctx->rc);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03473)
+ "Failed to create Redis Object of '%d' size.",
+ nservers);
+ return rv;
+ }
+
+ /* Now add each server to the redis */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ apr_redis_server_t *st;
+ char *host_str;
+ char *scope_id;
+ apr_port_t port;
+
+ rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03474)
+ "Failed to Parse redis Server: '%s'", split);
+ return rv;
+ }
+
+ if (host_str == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03475)
+ "Failed to Parse Server, "
+ "no hostname specified: '%s'", split);
+ return APR_EINVAL;
+ }
+
+ if (port == 0) {
+ port = RD_DEFAULT_SERVER_PORT;
+ }
+
+ rv = apr_redis_server_create(p,
+ host_str, port,
+ RD_DEFAULT_SERVER_MIN,
+ RD_DEFAULT_SERVER_SMAX,
+ thread_limit,
+ sconf->ttl,
+ sconf->rwto,
+ &st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03476)
+ "Failed to Create redis Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ rv = apr_redis_add_server(ctx->rc, st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03477)
+ "Failed to Add redis Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ ctx->tag = apr_pstrcat(p, namespace, ":", NULL);
+ ctx->taglen = strlen(ctx->tag) + 1;
+
+ /* socache API constraint: */
+ AP_DEBUG_ASSERT(ctx->taglen <= 16);
+
+ return APR_SUCCESS;
+}
+
+static void socache_rd_destroy(ap_socache_instance_t *context, server_rec *s)
+{
+ /* noop. */
+}
+
+/* Converts (binary) id into a key prefixed by the predetermined
+ * namespace tag; writes output to key buffer. Returns non-zero if
+ * the id won't fit in the key buffer. */
+static int socache_rd_id2key(ap_socache_instance_t *ctx,
+ const unsigned char *id, unsigned int idlen,
+ char *key, apr_size_t keylen)
+{
+ char *cp;
+
+ if (idlen * 2 + ctx->taglen >= keylen)
+ return 1;
+
+ cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
+ ap_bin2hex(id, idlen, cp);
+
+ return 0;
+}
+
+static apr_status_t socache_rd_store(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ apr_time_t expiry,
+ unsigned char *ucaData, unsigned int nData,
+ apr_pool_t *p)
+{
+ char buf[RD_KEY_LEN];
+ apr_status_t rv;
+ apr_uint32_t timeout;
+
+ if (socache_rd_id2key(ctx, id, idlen, buf, sizeof(buf))) {
+ return APR_EINVAL;
+ }
+ timeout = apr_time_sec(expiry - apr_time_now());
+ if (timeout <= 0) {
+ return APR_EINVAL;
+ }
+
+ rv = apr_redis_setex(ctx->rc, buf, (char*)ucaData, nData, timeout, 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03478)
+ "scache_rd: error setting key '%s' "
+ "with %d bytes of data", buf, nData);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_rd_retrieve(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+ apr_size_t data_len;
+ char buf[RD_KEY_LEN], *data;
+ apr_status_t rv;
+
+ if (socache_rd_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ /* ### this could do with a subpool, but _getp looks like it will
+ * eat memory like it's going out of fashion anyway. */
+
+ rv = apr_redis_getp(ctx->rc, p, buf, &data, &data_len, NULL);
+ if (rv) {
+ if (rv != APR_NOTFOUND) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03479)
+ "scache_rd: 'retrieve' FAIL");
+ }
+ return rv;
+ }
+ else if (data_len > *destlen) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03480)
+ "scache_rd: 'retrieve' OVERFLOW");
+ return APR_ENOMEM;
+ }
+
+ memcpy(dest, data, data_len);
+ *destlen = data_len;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_rd_remove(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+ char buf[RD_KEY_LEN];
+ apr_status_t rv;
+
+ if (socache_rd_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ rv = apr_redis_delete(ctx->rc, buf, 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03481)
+ "scache_rd: error deleting key '%s' ",
+ buf);
+ }
+
+ return rv;
+}
+
+static void socache_rd_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
+{
+ apr_redis_t *rc = ctx->rc;
+ int i;
+
+ for (i = 0; i < rc->ntotal; i++) {
+ apr_redis_server_t *rs;
+ apr_redis_stats_t *stats;
+ char *role;
+ apr_status_t rv;
+ char *br = (!(flags & AP_STATUS_SHORT) ? "
" : "");
+
+ rs = rc->live_servers[i];
+
+ ap_rprintf(r, "Redis server: %s:%d [%s]%s\n", rs->host, (int)rs->port,
+ (rs->status == APR_RC_SERVER_LIVE) ? "Up" : "Down",
+ br);
+ rv = apr_redis_stats(rs, r->pool, &stats);
+ if (rv != APR_SUCCESS)
+ continue;
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "General:: Version: %u.%u.%u [%u bits], PID: %u, Uptime: %u hrs
\n",
+ stats->major, stats->minor, stats->patch, stats->arch_bits,
+ stats->process_id, stats->uptime_in_seconds/3600);
+ ap_rprintf(r, "Clients:: Connected: %d, Blocked: %d
\n",
+ stats->connected_clients, stats->blocked_clients);
+ ap_rprintf(r, "Memory:: Total: %" APR_UINT64_T_FMT ", Max: %" APR_UINT64_T_FMT ", Used: %" APR_UINT64_T_FMT "
\n",
+ stats->total_system_memory, stats->maxmemory, stats->used_memory);
+ ap_rprintf(r, "CPU:: System: %u, User: %u
\n",
+ stats->used_cpu_sys, stats->used_cpu_user );
+ ap_rprintf(r, "Connections:: Recd: %" APR_UINT64_T_FMT ", Processed: %" APR_UINT64_T_FMT ", Rejected: %" APR_UINT64_T_FMT "
\n",
+ stats->total_connections_received, stats->total_commands_processed,
+ stats->rejected_connections);
+ ap_rprintf(r, "Cache:: Hits: %" APR_UINT64_T_FMT ", Misses: %" APR_UINT64_T_FMT "
\n",
+ stats->keyspace_hits, stats->keyspace_misses);
+ ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT "
\n",
+ stats->total_net_input_bytes, stats->total_net_output_bytes);
+ if (stats->role == APR_RS_SERVER_MASTER)
+ role = "master";
+ else if (stats->role == APR_RS_SERVER_SLAVE)
+ role = "slave";
+ else
+ role = "unknown";
+ ap_rprintf(r, "Misc:: Role: %s, Connected Slaves: %u, Is Cluster?: %s \n",
+ role, stats->connected_clients,
+ (stats->cluster_enabled ? "yes" : "no"));
+ ap_rputs("
\n", r);
+ }
+ else {
+ ap_rprintf(r, "Version: %u.%u.%u [%u bits], PID: %u, Uptime: %u hrs %s\n",
+ stats->major, stats->minor, stats->patch, stats->arch_bits,
+ stats->process_id, stats->uptime_in_seconds/3600, br);
+ ap_rprintf(r, "Clients:: Connected: %d, Blocked: %d %s\n",
+ stats->connected_clients, stats->blocked_clients, br);
+ ap_rprintf(r, "Memory:: Total: %" APR_UINT64_T_FMT ", Max: %" APR_UINT64_T_FMT ", Used: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_system_memory, stats->maxmemory, stats->used_memory,
+ br);
+ ap_rprintf(r, "CPU:: System: %u, User: %u %s\n",
+ stats->used_cpu_sys, stats->used_cpu_user , br);
+ ap_rprintf(r, "Connections:: Recd: %" APR_UINT64_T_FMT ", Processed: %" APR_UINT64_T_FMT ", Rejected: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_connections_received, stats->total_commands_processed,
+ stats->rejected_connections, br);
+ ap_rprintf(r, "Cache:: Hits: %" APR_UINT64_T_FMT ", Misses: %" APR_UINT64_T_FMT " %s\n",
+ stats->keyspace_hits, stats->keyspace_misses, br);
+ ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_net_input_bytes, stats->total_net_output_bytes, br);
+ if (stats->role == APR_RS_SERVER_MASTER)
+ role = "master";
+ else if (stats->role == APR_RS_SERVER_SLAVE)
+ role = "slave";
+ else
+ role = "unknown";
+ ap_rprintf(r, "Misc:: Role: %s, Connected Slaves: %u, Is Cluster?: %s %s\n",
+ role, stats->connected_clients,
+ (stats->cluster_enabled ? "yes" : "no"), br);
+ }
+ }
+
+}
+
+static apr_status_t socache_rd_iterate(ap_socache_instance_t *instance,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+ return APR_ENOTIMPL;
+}
+
+static const ap_socache_provider_t socache_mc = {
+ "redis",
+ 0,
+ socache_rd_create,
+ socache_rd_init,
+ socache_rd_destroy,
+ socache_rd_store,
+ socache_rd_retrieve,
+ socache_rd_remove,
+ socache_rd_status,
+ socache_rd_iterate,
+};
+
+#endif /* HAVE_APU_REDIS */
+
+static void* create_server_config(apr_pool_t* p, server_rec* s)
+{
+ socache_rd_svr_cfg *sconf = apr_palloc(p, sizeof(socache_rd_svr_cfg));
+
+ sconf->ttl = RD_DEFAULT_SERVER_TTL;
+ sconf->rwto = RD_DEFAULT_SERVER_RWTO;
+
+ return sconf;
+}
+
+static const char *socache_rd_set_ttl(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t ttl;
+ socache_rd_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
+ &socache_redis_module);
+
+ if (ap_timeout_parameter_parse(arg, &ttl, "s") != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has wrong format", NULL);
+ }
+
+ if ((ttl < apr_time_from_sec(0)) || (ttl > apr_time_from_sec(3600))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " can only be 0 or up to one hour.", NULL);
+ }
+
+ /* apr_redis_server_create needs a ttl in usec. */
+ sconf->ttl = ttl;
+
+ return NULL;
+}
+
+static const char *socache_rd_set_rwto(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t rwto;
+ socache_rd_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
+ &socache_redis_module);
+
+ if (ap_timeout_parameter_parse(arg, &rwto, "s") != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has wrong format", NULL);
+ }
+
+ if ((rwto < apr_time_from_sec(0)) || (rwto > apr_time_from_sec(3600))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " can only be 0 or up to one hour.", NULL);
+ }
+
+ /* apr_redis_server_create needs a ttl in usec. */
+ sconf->rwto = rwto;
+
+ return NULL;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+#ifdef HAVE_APU_REDIS
+
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "redis",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_mc);
+#endif
+}
+
+static const command_rec socache_redis_cmds[] =
+{
+ AP_INIT_TAKE1("RedisConnPoolTTL", socache_rd_set_ttl, NULL, RSRC_CONF,
+ "TTL used for the connection pool with the Redis server(s)"),
+ AP_INIT_TAKE1("RedisTimeout", socache_rd_set_rwto, NULL, RSRC_CONF,
+ "R/W timeout used for the connection with the Redis server(s)"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(socache_redis) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ create_server_config, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ socache_redis_cmds, /* table of config file commands */
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/cache/mod_socache_redis.dep b/modules/cache/mod_socache_redis.dep
new file mode 100644
index 0000000..a450a54
--- /dev/null
+++ b/modules/cache/mod_socache_redis.dep
@@ -0,0 +1,5 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_socache_shmcb.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/cache/mod_socache_redis.dsp b/modules/cache/mod_socache_redis.dsp
new file mode 100644
index 0000000..18f5962
--- /dev/null
+++ b/modules/cache/mod_socache_redis.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_socache_redis" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_socache_redis - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_redis.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_redis.mak" CFG="mod_socache_redis - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_redis - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_redis - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../generators" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Release\mod_socache_redis_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../generators" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Debug\mod_socache_redis_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_socache_redis - Win32 Release"
+# Name "mod_socache_redis - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_socache_redis.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_socache_redis.mak b/modules/cache/mod_socache_redis.mak
new file mode 100644
index 0000000..e4aab37
--- /dev/null
+++ b/modules/cache/mod_socache_redis.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_redis.dsp
+!IF "$(CFG)" == ""
+CFG=mod_socache_redis - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_socache_redis - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_socache_redis - Win32 Release" && "$(CFG)" != "mod_socache_redis - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_redis.mak" CFG="mod_socache_redis - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_redis - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_redis - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_redis.obj"
+ -@erase "$(INTDIR)\mod_socache_redis.res"
+ -@erase "$(INTDIR)\mod_socache_redis_src.idb"
+ -@erase "$(INTDIR)\mod_socache_redis_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.exp"
+ -@erase "$(OUTDIR)\mod_socache_redis.lib"
+ -@erase "$(OUTDIR)\mod_socache_redis.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_redis_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_redis.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_redis.pdb" /debug /out:"$(OUTDIR)\mod_socache_redis.so" /implib:"$(OUTDIR)\mod_socache_redis.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_redis.obj" \
+ "$(INTDIR)\mod_socache_redis.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_redis.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_redis.so"
+ if exist .\Release\mod_socache_redis.so.manifest mt.exe -manifest .\Release\mod_socache_redis.so.manifest -outputresource:.\Release\mod_socache_redis.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_redis.obj"
+ -@erase "$(INTDIR)\mod_socache_redis.res"
+ -@erase "$(INTDIR)\mod_socache_redis_src.idb"
+ -@erase "$(INTDIR)\mod_socache_redis_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.exp"
+ -@erase "$(OUTDIR)\mod_socache_redis.lib"
+ -@erase "$(OUTDIR)\mod_socache_redis.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_redis_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_redis.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_redis.pdb" /debug /out:"$(OUTDIR)\mod_socache_redis.so" /implib:"$(OUTDIR)\mod_socache_redis.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_redis.obj" \
+ "$(INTDIR)\mod_socache_redis.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_redis.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_redis.so"
+ if exist .\Debug\mod_socache_redis.so.manifest mt.exe -manifest .\Debug\mod_socache_redis.so.manifest -outputresource:.\Debug\mod_socache_redis.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_socache_redis.dep")
+!INCLUDE "mod_socache_redis.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_socache_redis.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release" || "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+
+"$(INTDIR)\mod_socache_redis.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+
+"$(INTDIR)\mod_socache_redis.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_socache_redis.c
+
+"$(INTDIR)\mod_socache_redis.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_socache_shmcb.c b/modules/cache/mod_socache_shmcb.c
index 2750f25..1785839 100644
--- a/modules/cache/mod_socache_shmcb.c
+++ b/modules/cache/mod_socache_shmcb.c
@@ -105,6 +105,7 @@ typedef struct {
} SHMCBIndex;
struct ap_socache_instance_t {
+ apr_pool_t *pool;
const char *data_file;
apr_size_t shm_size;
apr_shm_t *shm;
@@ -295,6 +296,7 @@ static const char *socache_shmcb_create(ap_socache_instance_t **context,
/* Allocate the context. */
*context = ctx = apr_pcalloc(p, sizeof *ctx);
+ ctx->pool = p;
ctx->shm_size = 1024*512; /* 512KB */
@@ -340,6 +342,16 @@ static const char *socache_shmcb_create(ap_socache_instance_t **context,
return NULL;
}
+static apr_status_t socache_shmcb_cleanup(void *arg)
+{
+ ap_socache_instance_t *ctx = arg;
+ if (ctx->shm) {
+ apr_shm_destroy(ctx->shm);
+ ctx->shm = NULL;
+ }
+ return APR_SUCCESS;
+}
+
static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx,
const char *namespace,
const struct ap_socache_hints *hints,
@@ -368,8 +380,9 @@ static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx,
* above will return NULL for invalid paths. */
if (ctx->data_file == NULL) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00818)
- "Could not use default path '%s' for shmcb socache",
- ctx->data_file);
+ "Could not use anonymous shm for '%s' cache",
+ namespace);
+ ctx->shm = NULL;
return APR_EINVAL;
}
@@ -384,8 +397,11 @@ static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx,
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00819)
"Could not allocate shared memory segment for shmcb "
"socache");
+ ctx->shm = NULL;
return rv;
}
+ apr_pool_cleanup_register(ctx->pool, ctx, socache_shmcb_cleanup,
+ apr_pool_cleanup_null);
shm_segment = apr_shm_baseaddr_get(ctx->shm);
shm_segsize = apr_shm_size_get(ctx->shm);
@@ -473,9 +489,8 @@ static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx,
static void socache_shmcb_destroy(ap_socache_instance_t *ctx, server_rec *s)
{
- if (ctx && ctx->shm) {
- apr_shm_destroy(ctx->shm);
- ctx->shm = NULL;
+ if (ctx) {
+ apr_pool_cleanup_run(ctx->pool, ctx, socache_shmcb_cleanup);
}
}
@@ -778,7 +793,6 @@ static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
*/
if (header->subcache_data_size - subcache->data_used < total_len
|| subcache->idx_used == header->index_num) {
- unsigned int loop = 0;
idx = SHMCB_INDEX(subcache, subcache->idx_pos);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00845)
@@ -805,7 +819,6 @@ static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
header->stat_scrolled++;
/* Loop admin */
idx = idx2;
- loop++;
} while (header->subcache_data_size - subcache->data_used < total_len);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00846)
diff --git a/modules/cluster/mod_heartmonitor.c b/modules/cluster/mod_heartmonitor.c
index 965fef5..53b6504 100644
--- a/modules/cluster/mod_heartmonitor.c
+++ b/modules/cluster/mod_heartmonitor.c
@@ -39,7 +39,7 @@
static const ap_slotmem_provider_t *storage = NULL;
static ap_slotmem_instance_t *slotmem = NULL;
-static int maxworkers = 0;
+static int maxworkers = 10;
module AP_MODULE_DECLARE_DATA heartmonitor_module;
@@ -171,7 +171,7 @@ static apr_status_t hm_update(void* mem, void *data, apr_pool_t *p)
hm_slot_server_t *old = (hm_slot_server_t *) mem;
hm_slot_server_ctx_t *s = (hm_slot_server_ctx_t *) data;
hm_server_t *new = s->s;
- if (strncmp(old->ip, new->ip, MAXIPSIZE)==0) {
+ if (strcmp(old->ip, new->ip)==0) {
s->found = 1;
old->busy = new->busy;
old->ready = new->ready;
@@ -185,7 +185,7 @@ static apr_status_t hm_readid(void* mem, void *data, apr_pool_t *p)
hm_slot_server_t *old = (hm_slot_server_t *) mem;
hm_slot_server_ctx_t *s = (hm_slot_server_ctx_t *) data;
hm_server_t *new = s->s;
- if (strncmp(old->ip, new->ip, MAXIPSIZE)==0) {
+ if (strcmp(old->ip, new->ip)==0) {
s->found = 1;
s->item_id = old->id;
}
@@ -202,7 +202,8 @@ static apr_status_t hm_slotmem_update_stat(hm_server_t *s, apr_pool_t *pool)
if (!ctx.found) {
unsigned int i;
hm_slot_server_t hmserver;
- memcpy(hmserver.ip, s->ip, MAXIPSIZE);
+ memset(&hmserver, 0, sizeof(hmserver));
+ apr_cpystrn(hmserver.ip, s->ip, sizeof(hmserver.ip));
hmserver.busy = s->busy;
hmserver.ready = s->ready;
hmserver.seen = s->seen;
@@ -528,7 +529,7 @@ static hm_server_t *hm_get_server(hm_ctx_t *ctx, const char *ip, const int port)
/* Process a message received from a backend node */
static void hm_processmsg(hm_ctx_t *ctx, apr_pool_t *p,
- apr_sockaddr_t *from, char *buf, int len)
+ apr_sockaddr_t *from, char *buf, apr_size_t len)
{
apr_table_t *tbl;
@@ -624,9 +625,7 @@ static apr_status_t hm_watchdog_callback(int state, void *data,
/* store in the slotmem or in the file depending on configuration */
hm_update_stats(ctx, pool);
cur = now = apr_time_sec(apr_time_now());
- /* TODO: Insted HN_UPDATE_SEC use
- * the ctx->interval
- */
+
while ((now - cur) < apr_time_sec(ctx->interval)) {
int n;
apr_status_t rc;
@@ -635,6 +634,7 @@ static apr_status_t hm_watchdog_callback(int state, void *data,
apr_interval_time_t timeout;
apr_pool_create(&p, pool);
+ apr_pool_tag(p, "hm_running");
pfd.desc_type = APR_POLL_SOCKET;
pfd.desc.s = ctx->sock;
@@ -809,6 +809,7 @@ static void *hm_create_config(apr_pool_t *p, server_rec *s)
ctx->interval = apr_time_from_sec(HM_UPDATE_SEC);
ctx->s = s;
apr_pool_create(&ctx->p, p);
+ apr_pool_tag(ctx->p, "hm_ctx");
ctx->servers = apr_hash_make(ctx->p);
return ctx;
@@ -891,8 +892,9 @@ static const char *cmd_hm_maxworkers(cmd_parms *cmd,
}
maxworkers = atoi(data);
- if (maxworkers <= 10)
- return "HeartbeatMaxServers: Should be bigger than 10";
+ if (maxworkers != 0 && maxworkers < 10)
+ return "HeartbeatMaxServers: Should be 0 for file storage, "
+ "or greater or equal than 10 for slotmem";
return NULL;
}
diff --git a/modules/core/mod_macro.c b/modules/core/mod_macro.c
index 04af43b..cc42d0b 100644
--- a/modules/core/mod_macro.c
+++ b/modules/core/mod_macro.c
@@ -465,7 +465,7 @@ static const char *process_content(apr_pool_t * pool,
for (i = 0; i < contents->nelts; i++) {
const char *errmsg;
/* copy the line and substitute macro parameters */
- strncpy(line, ((char **) contents->elts)[i], MAX_STRING_LEN - 1);
+ apr_cpystrn(line, ((char **) contents->elts)[i], MAX_STRING_LEN);
errmsg = substitute_macro_args(line, MAX_STRING_LEN,
macro, replacements, used);
if (errmsg) {
diff --git a/modules/core/mod_so.c b/modules/core/mod_so.c
index 6eafbe9..f5d18c1 100644
--- a/modules/core/mod_so.c
+++ b/modules/core/mod_so.c
@@ -159,7 +159,7 @@ static const char *dso_load(cmd_parms *cmd, apr_dso_handle_t **modhandlep,
cmd->cmd->name, filename);
}
*used_filename = fullname;
- if (apr_dso_load(modhandlep, fullname, cmd->pool) == APR_SUCCESS) {
+ if (fullname && apr_dso_load(modhandlep, fullname, cmd->pool) == APR_SUCCESS) {
return NULL;
}
if (retry) {
diff --git a/modules/core/mod_watchdog.c b/modules/core/mod_watchdog.c
index 61f4675..99ec7cf 100644
--- a/modules/core/mod_watchdog.c
+++ b/modules/core/mod_watchdog.c
@@ -24,6 +24,8 @@
#include "http_core.h"
#include "util_mutex.h"
+#include "apr_atomic.h"
+
#define AP_WATCHDOG_PGROUP "watchdog"
#define AP_WATCHDOG_PVERSION "parent"
#define AP_WATCHDOG_CVERSION "child"
@@ -43,7 +45,7 @@ struct watchdog_list_t
struct ap_watchdog_t
{
- apr_thread_mutex_t *startup;
+ apr_uint32_t thread_started; /* set to 1 once thread running */
apr_proc_mutex_t *mutex;
const char *name;
watchdog_list_t *callbacks;
@@ -74,6 +76,10 @@ static apr_status_t wd_worker_cleanup(void *data)
apr_status_t rv;
ap_watchdog_t *w = (ap_watchdog_t *)data;
+ /* Do nothing if the thread wasn't started. */
+ if (apr_atomic_read32(&w->thread_started) != 1)
+ return APR_SUCCESS;
+
if (w->is_running) {
watchdog_list_t *wl = w->callbacks;
while (wl) {
@@ -106,11 +112,13 @@ static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
int probed = 0;
int inited = 0;
int mpmq_s = 0;
+ apr_pool_t *temp_pool = NULL;
w->pool = apr_thread_pool_get(thread);
w->is_running = 1;
- apr_thread_mutex_unlock(w->startup);
+ apr_atomic_set32(&w->thread_started, 1); /* thread started */
+
if (w->mutex) {
while (w->is_running) {
if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
@@ -151,6 +159,10 @@ static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
apr_sleep(AP_WD_TM_SLICE);
}
}
+
+ apr_pool_create(&temp_pool, w->pool);
+ apr_pool_tag(temp_pool, "wd_running");
+
if (w->is_running) {
watchdog_list_t *wl = w->callbacks;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
@@ -158,15 +170,13 @@ static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
w->singleton ? "Singleton " : "", w->name);
apr_time_clock_hires(w->pool);
if (wl) {
- apr_pool_t *ctx = NULL;
- apr_pool_create(&ctx, w->pool);
while (wl && w->is_running) {
/* Execute watchdog callback */
wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_STARTING,
- (void *)wl->data, ctx);
+ (void *)wl->data, temp_pool);
wl = wl->next;
}
- apr_pool_destroy(ctx);
+ apr_pool_clear(temp_pool);
}
else {
ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool);
@@ -176,7 +186,6 @@ static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
/* Main execution loop */
while (w->is_running) {
- apr_pool_t *ctx = NULL;
apr_time_t curr;
watchdog_list_t *wl = w->callbacks;
@@ -195,12 +204,10 @@ static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
if (wl->status == APR_SUCCESS) {
wl->step += (apr_time_now() - curr);
if (wl->step >= wl->interval) {
- if (!ctx)
- apr_pool_create(&ctx, w->pool);
wl->step = 0;
/* Execute watchdog callback */
wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_RUNNING,
- (void *)wl->data, ctx);
+ (void *)wl->data, temp_pool);
if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
w->is_running = 0;
}
@@ -217,19 +224,17 @@ static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
*/
w->step += (apr_time_now() - curr);
if (w->step >= wd_interval) {
- if (!ctx)
- apr_pool_create(&ctx, w->pool);
w->step = 0;
/* Run watchdog step hook */
- ap_run_watchdog_step(wd_server_conf->s, w->name, ctx);
+ ap_run_watchdog_step(wd_server_conf->s, w->name, temp_pool);
}
}
- if (ctx)
- apr_pool_destroy(ctx);
- if (!w->is_running) {
- break;
- }
+
+ apr_pool_clear(temp_pool);
}
+
+ apr_pool_destroy(temp_pool);
+
if (inited) {
/* Run the watchdog exit hooks.
* If this was singleton watchdog the init hook
@@ -264,10 +269,7 @@ static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
{
apr_status_t rc;
- /* Create thread startup mutex */
- rc = apr_thread_mutex_create(&w->startup, APR_THREAD_MUTEX_UNNESTED, p);
- if (rc != APR_SUCCESS)
- return rc;
+ apr_atomic_set32(&w->thread_started, 0);
if (w->singleton) {
/* Initialize singleton mutex in child */
@@ -277,22 +279,12 @@ static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
return rc;
}
- /* This mutex fixes problems with a fast start/fast end, where the pool
- * cleanup was being invoked before the thread completely spawned.
- */
- apr_thread_mutex_lock(w->startup);
- apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
-
/* Start the newly created watchdog */
- rc = apr_thread_create(&w->thread, NULL, wd_worker, w, p);
- if (rc) {
- apr_pool_cleanup_kill(p, w, wd_worker_cleanup);
+ rc = ap_thread_create(&w->thread, NULL, wd_worker, w, p);
+ if (rc == APR_SUCCESS) {
+ apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
}
- apr_thread_mutex_lock(w->startup);
- apr_thread_mutex_unlock(w->startup);
- apr_thread_mutex_destroy(w->startup);
-
return rc;
}
@@ -447,6 +439,7 @@ static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
if (!(wd_server_conf = apr_pcalloc(ppconf, sizeof(wd_server_conf_t))))
return APR_ENOMEM;
apr_pool_create(&wd_server_conf->pool, ppconf);
+ apr_pool_tag(wd_server_conf->pool, "wd_server_conf");
apr_pool_userdata_set(wd_server_conf, pk, apr_pool_cleanup_null, ppconf);
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(010033)
@@ -473,7 +466,7 @@ static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
int status = ap_run_watchdog_need(s, w->name, 1,
w->singleton);
if (status == OK) {
- /* One of the modules returned OK to this watchog.
+ /* One of the modules returned OK to this watchdog.
* Mark it as active
*/
w->active = 1;
@@ -519,7 +512,7 @@ static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
int status = ap_run_watchdog_need(s, w->name, 0,
w->singleton);
if (status == OK) {
- /* One of the modules returned OK to this watchog.
+ /* One of the modules returned OK to this watchdog.
* Mark it as active
*/
w->active = 1;
diff --git a/modules/database/mod_dbd.c b/modules/database/mod_dbd.c
index 7212665..aa6b764 100644
--- a/modules/database/mod_dbd.c
+++ b/modules/database/mod_dbd.c
@@ -525,6 +525,7 @@ static apr_status_t dbd_construct(void **data_ptr,
"Failed to create memory pool");
return rv;
}
+ apr_pool_tag(rec_pool, "dbd_rec_pool");
rec = apr_pcalloc(rec_pool, sizeof(ap_dbd_t));
@@ -589,6 +590,7 @@ static apr_status_t dbd_construct(void **data_ptr,
apr_pool_destroy(rec->pool);
return rv;
}
+ apr_pool_tag(prepared_pool, "dbd_prepared_pool");
rv = dbd_prepared_init(prepared_pool, cfg, rec);
if (rv != APR_SUCCESS) {
@@ -673,6 +675,7 @@ static apr_status_t dbd_setup_init(apr_pool_t *pool, server_rec *s)
"Failed to create reslist cleanup memory pool");
return rv2;
}
+ apr_pool_tag(group->pool, "dbd_group");
#if APR_HAS_THREADS
rv2 = dbd_setup(s, group);
diff --git a/modules/dav/fs/dbm.c b/modules/dav/fs/dbm.c
index 821168e..347d75d 100644
--- a/modules/dav/fs/dbm.c
+++ b/modules/dav/fs/dbm.c
@@ -37,6 +37,11 @@
#define APR_WANT_BYTEFUNC
#include "apr_want.h" /* for ntohs and htons */
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
#include "mod_dav.h"
#include "repos.h"
#include "http_log.h"
@@ -127,11 +132,30 @@ void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
dav_db **pdb)
{
- apr_status_t status;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
apr_dbm_t *file = NULL;
+ apr_status_t status;
*pdb = NULL;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((status = apr_dbm_get_driver(&driver, NULL, &err, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(10289)
+ "mod_dav_fs: The DBM library '%s' could not be loaded: %s",
+ err->reason, err->msg);
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 1, status,
+ "Could not load library for property database.");
+ }
+ if ((status = apr_dbm_open2(&file, driver, pathname,
+ ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, p))
+ != APR_SUCCESS && !ro) {
+ return dav_fs_dbm_error(NULL, p, status);
+ }
+#else
if ((status = apr_dbm_open(&file, pathname,
ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
APR_OS_DEFAULT, p))
@@ -143,6 +167,7 @@ dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
and we need to write */
return dav_fs_dbm_error(NULL, p, status);
}
+#endif
/* may be NULL if we tried to open a non-existent db as read-only */
if (file != NULL) {
@@ -355,29 +380,33 @@ static void dav_append_prop(apr_pool_t *pool,
/* the property is an empty value */
if (*name == ':') {
/* "no namespace" case */
- s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name+1);
+ s = apr_pstrcat(pool, "<", name+1, "/>" DEBUG_CR, NULL);
}
else {
- s = apr_psprintf(pool, "" DEBUG_CR, name);
+ s = apr_pstrcat(pool, "" DEBUG_CR, NULL);
}
}
else if (*lang != '\0') {
if (*name == ':') {
/* "no namespace" case */
- s = apr_psprintf(pool, "<%s xml:lang=\"%s\">%s%s>" DEBUG_CR,
- name+1, lang, value, name+1);
+ s = apr_pstrcat(pool, "<", name+1, " xml:lang=\"",
+ lang, "\">", value, "", name+1, ">" DEBUG_CR,
+ NULL);
}
else {
- s = apr_psprintf(pool, "%s" DEBUG_CR,
- name, lang, value, name);
+ s = apr_pstrcat(pool, "", value, "" DEBUG_CR,
+ NULL);
}
}
else if (*name == ':') {
/* "no namespace" case */
- s = apr_psprintf(pool, "<%s>%s%s>" DEBUG_CR, name+1, value, name+1);
+ s = apr_pstrcat(pool, "<", name+1, ">", value, "", name+1, ">"
+ DEBUG_CR, NULL);
}
else {
- s = apr_psprintf(pool, "%s" DEBUG_CR, name, value, name);
+ s = apr_pstrcat(pool, "", value, ""
+ DEBUG_CR, NULL);
}
apr_text_append(pool, phdr, s);
diff --git a/modules/dav/fs/lock.c b/modules/dav/fs/lock.c
index c058e2e..ef18c4a 100644
--- a/modules/dav/fs/lock.c
+++ b/modules/dav/fs/lock.c
@@ -953,7 +953,7 @@ static dav_error * dav_fs_add_locknull_state(
/*
** dav_fs_remove_locknull_state: Given a request, check to see if r->filename
-** is/was a lock-null resource. If so, return it to an existant state, i.e.
+** is/was a lock-null resource. If so, return it to an existent state, i.e.
** remove it from the list in the appropriate .DAV/locknull file.
*/
static dav_error * dav_fs_remove_locknull_state(
diff --git a/modules/dav/fs/repos.c b/modules/dav/fs/repos.c
index 6a5ff76..64bc894 100644
--- a/modules/dav/fs/repos.c
+++ b/modules/dav/fs/repos.c
@@ -35,6 +35,7 @@
#include "mod_dav.h"
#include "repos.h"
+APLOG_USE_MODULE(dav_fs);
/* to assist in debugging mod_dav's GET handling */
#define DEBUG_GET_HANDLER 0
@@ -948,15 +949,29 @@ static dav_error * dav_fs_open_stream(const dav_resource *resource,
else if (APR_STATUS_IS_EEXIST(rv)) {
rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT,
ds->p);
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ apr_psprintf(p, "Could not open an existing "
+ "resource for writing: %s.",
+ ds->pathname));
+ }
}
}
else {
rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ apr_psprintf(p, "Could not open an existing "
+ "resource for reading: %s.",
+ ds->pathname));
+ }
}
if (rv != APR_SUCCESS) {
return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
- "An error occurred while opening a resource.");
+ apr_psprintf(p, "An error occurred while opening "
+ "a resource for writing: %s.",
+ ds->pathname));
}
/* (APR registers cleanups for the fd with the pool) */
@@ -1572,6 +1587,19 @@ static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
DAV_FINFO_MASK, pool);
if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
+ dav_resource_private *ctx = params->root->info;
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, ctx->r,
+ APLOGNO(10472) "could not access file (%s) during directory walk",
+ fsctx->path1.buf);
+
+ /* If being tolerant, ignore failure due to losing a race
+ * with some other process deleting files out from under
+ * the directory walk. */
+ if ((params->walk_type & DAV_WALKTYPE_TOLERANT)
+ && APR_STATUS_IS_ENOENT(status)) {
+ continue;
+ }
/* woah! where'd it go? */
/* ### should have a better error here */
err = dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
@@ -1663,7 +1691,7 @@ static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
/* put a slash back on the end of the directory */
fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
- /* these are all non-existant (files) */
+ /* these are all non-existent (files) */
fsctx->res1.exists = 0;
fsctx->res1.collection = 0;
memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
@@ -1853,27 +1881,26 @@ static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
return dav_fs_internal_walk(params, depth, 0, NULL, response);
}
-/* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
- * for file path.
- * ### do we need to return weak tags sometimes?
+/* dav_fs_etag: Creates an etag for the file path.
*/
static const char *dav_fs_getetag(const dav_resource *resource)
{
- dav_resource_private *ctx = resource->info;
- /* XXX: This should really honor the FileETag setting */
+ etag_rec er;
- if (!resource->exists)
- return apr_pstrdup(ctx->pool, "");
+ dav_resource_private *ctx = resource->info;
- if (ctx->finfo.filetype != APR_NOFILE) {
- return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "-%"
- APR_UINT64_T_HEX_FMT "\"",
- (apr_uint64_t) ctx->finfo.size,
- (apr_uint64_t) ctx->finfo.mtime);
+ if (!resource->exists || !ctx->r) {
+ return "";
}
- return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "\"",
- (apr_uint64_t) ctx->finfo.mtime);
+ er.vlist_validator = NULL;
+ er.request_time = ctx->r->request_time;
+ er.finfo = &ctx->finfo;
+ er.pathname = ctx->pathname;
+ er.fd = NULL;
+ er.force_weak = 0;
+
+ return ap_make_etag_ex(ctx->r, &er);
}
static const dav_hooks_repository dav_hooks_repository_fs =
@@ -1913,7 +1940,7 @@ static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
const char *s;
apr_pool_t *p = resource->info->pool;
const dav_liveprop_spec *info;
- int global_ns;
+ long global_ns;
/* an HTTP-date can be 29 chars plus a null term */
/* a 64-bit size can be 20 chars plus a null term */
@@ -1994,18 +2021,20 @@ static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
/* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
if (what == DAV_PROP_INSERT_VALUE) {
- s = apr_psprintf(p, "%s" DEBUG_CR,
+ s = apr_psprintf(p, "%s" DEBUG_CR,
global_ns, info->name, value, global_ns, info->name);
}
else if (what == DAV_PROP_INSERT_NAME) {
- s = apr_psprintf(p, "" DEBUG_CR, global_ns, info->name);
+ s = apr_psprintf(p, "" DEBUG_CR, global_ns, info->name);
}
else {
/* assert: what == DAV_PROP_INSERT_SUPPORTED */
- s = apr_psprintf(p,
- "" DEBUG_CR,
- info->name, dav_fs_namespace_uris[info->ns]);
+ s = apr_pstrcat(p,
+ "name,
+ "\" D:namespace=\"",
+ dav_fs_namespace_uris[info->ns],
+ "\"/>" DEBUG_CR, NULL);
}
apr_text_append(p, phdr, s);
diff --git a/modules/dav/lock/locks.c b/modules/dav/lock/locks.c
index 17b9ee6..0f072ec 100644
--- a/modules/dav/lock/locks.c
+++ b/modules/dav/lock/locks.c
@@ -26,8 +26,14 @@
#define APR_WANT_MEMFUNC
#include "apr_want.h"
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
#include "httpd.h"
#include "http_log.h"
+#include "http_main.h" /* for ap_server_conf */
#include "mod_dav.h"
@@ -311,16 +317,36 @@ static int dav_generic_compare_locktoken(const dav_locktoken *lt1,
*/
static dav_error * dav_generic_really_open_lockdb(dav_lockdb *lockdb)
{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *er;
+#endif
dav_error *err;
- apr_status_t status;
+ apr_status_t status = APR_SUCCESS;
if (lockdb->info->opened) {
return NULL;
}
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ status = apr_dbm_get_driver(&driver, NULL, &er, lockdb->info->pool);
+
+ if (status) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(10288)
+ "mod_dav_lock: The DBM library '%s' could not be loaded: %s",
+ er->reason, er->msg);
+ return dav_new_error(lockdb->info->pool, HTTP_INTERNAL_SERVER_ERROR, 1,
+ status, "Could not load library for property database.");
+ }
+
+ status = apr_dbm_open2(&lockdb->info->db, driver, lockdb->info->lockdb_path,
+ lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, lockdb->info->pool);
+#else
status = apr_dbm_open(&lockdb->info->db, lockdb->info->lockdb_path,
lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
APR_OS_DEFAULT, lockdb->info->pool);
+#endif
if (status) {
err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c
index 84012f2..dea3f18 100644
--- a/modules/dav/main/mod_dav.c
+++ b/modules/dav/main/mod_dav.c
@@ -81,8 +81,10 @@ typedef struct {
const char *provider_name;
const dav_provider *provider;
const char *dir;
+ const char *base;
int locktimeout;
int allow_depthinfinity;
+ int allow_lockdiscovery;
} dav_dir_conf;
@@ -195,8 +197,11 @@ static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
+ newconf->base = DAV_INHERIT_VALUE(parent, child, base);
newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
allow_depthinfinity);
+ newconf->allow_lockdiscovery = DAV_INHERIT_VALUE(parent, child,
+ allow_lockdiscovery);
return newconf;
}
@@ -207,7 +212,7 @@ DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r)
return conf ? conf->provider_name : NULL;
}
-static const dav_provider *dav_get_provider(request_rec *r)
+DAV_DECLARE(const dav_provider *) dav_get_provider(request_rec *r)
{
dav_dir_conf *conf;
@@ -279,6 +284,18 @@ static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
return NULL;
}
+/*
+ * Command handler for the DAVBasePath directive, which is TAKE1
+ */
+static const char *dav_cmd_davbasepath(cmd_parms *cmd, void *config, const char *arg1)
+{
+ dav_dir_conf *conf = config;
+
+ conf->base = arg1;
+
+ return NULL;
+}
+
/*
* Command handler for the DAVDepthInfinity directive, which is FLAG.
*/
@@ -294,6 +311,21 @@ static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
return NULL;
}
+/*
+ * Command handler for the DAVLockDiscovery directive, which is FLAG.
+ */
+static const char *dav_cmd_davlockdiscovery(cmd_parms *cmd, void *config,
+ int arg)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ if (arg)
+ conf->allow_lockdiscovery = DAV_ENABLED_ON;
+ else
+ conf->allow_lockdiscovery = DAV_ENABLED_OFF;
+ return NULL;
+}
+
/*
* Command handler for DAVMinTimeout directive, which is TAKE1
*/
@@ -436,7 +468,7 @@ static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
}
-/* Write a complete RESPONSE object out as a xml
+/* Write a complete RESPONSE object out as a xml
element. Data is sent into brigade BB, which is auto-flushed into
the output filter stack for request R. Use POOL for any temporary
allocations.
@@ -557,6 +589,7 @@ DAV_DECLARE(void) dav_send_multistatus(request_rec *r, int status,
dav_begin_multistatus(bb, r, status, namespaces);
apr_pool_create(&subpool, r->pool);
+ apr_pool_tag(subpool, "mod_dav-multistatus");
for (; first != NULL; first = first->next) {
apr_pool_clear(subpool);
@@ -662,8 +695,8 @@ static int dav_created(request_rec *r, const char *locn, const char *what,
/* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
* we must manufacture the entire response. */
- body = apr_psprintf(r->pool, "%s %s has been created.",
- what, ap_escape_html(r->pool, locn));
+ body = apr_pstrcat(r->pool, what, " ", ap_escape_html(r->pool, locn),
+ " has been created.", NULL);
return dav_error_response(r, HTTP_CREATED, body);
}
@@ -676,7 +709,7 @@ DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth)
return def_depth;
}
- if (strcasecmp(depth, "infinity") == 0) {
+ if (ap_cstr_casecmp(depth, "infinity") == 0) {
return DAV_INFINITY;
}
else if (strcmp(depth, "0") == 0) {
@@ -725,11 +758,11 @@ static int dav_get_overwrite(request_rec *r)
* the resource identified by the DAV:checked-in property of the resource
* identified by the Request-URI.
*/
-static dav_error *dav_get_resource(request_rec *r, int label_allowed,
+DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed,
int use_checked_in, dav_resource **res_p)
{
dav_dir_conf *conf;
- const char *label = NULL;
+ const char *label = NULL, *base;
dav_error *err;
/* if the request target can be overridden, get any target selector */
@@ -746,11 +779,27 @@ static dav_error *dav_get_resource(request_rec *r, int label_allowed,
ap_escape_html(r->pool, r->uri)));
}
+ /* Take the repos root from DAVBasePath if configured, else the
+ * path of the enclosing section. */
+ base = conf->base ? conf->base : conf->dir;
+
/* resolve the resource */
- err = (*conf->provider->repos->get_resource)(r, conf->dir,
+ err = (*conf->provider->repos->get_resource)(r, base,
label, use_checked_in,
res_p);
if (err != NULL) {
+ /* In the error path, give a hint that DavBasePath needs to be
+ * used if the location was configured via a regex match. */
+ if (!conf->base) {
+ core_dir_config *cdc = ap_get_core_module_config(r->per_dir_config);
+
+ if (cdc->r) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(10484)
+ "failed to find repository for location configured "
+ "via regex match - missing DAVBasePath?");
+ }
+ }
+
err = dav_push_error(r->pool, err->status, 0,
"Could not fetch resource information.", err);
return err;
@@ -774,7 +823,9 @@ static dav_error *dav_get_resource(request_rec *r, int label_allowed,
return NULL;
}
-static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
+DAV_DECLARE(dav_error *) dav_open_lockdb(request_rec *r,
+ int ro,
+ dav_lockdb **lockdb)
{
const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
@@ -787,6 +838,11 @@ static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
return (*hooks->open_lockdb)(r, ro, 0, lockdb);
}
+DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb)
+{
+ (lockdb->hooks->close_lockdb)(lockdb);
+}
+
/**
* @return 1 if valid content-range,
* 0 if no content-range,
@@ -799,16 +855,15 @@ static int dav_parse_range(request_rec *r,
char *range;
char *dash;
char *slash;
- char *errp;
range_c = apr_table_get(r->headers_in, "content-range");
if (range_c == NULL)
return 0;
range = apr_pstrdup(r->pool, range_c);
- if (strncasecmp(range, "bytes ", 6) != 0
- || (dash = ap_strchr(range, '-')) == NULL
- || (slash = ap_strchr(range, '/')) == NULL) {
+ if (ap_cstr_casecmpn(range, "bytes ", 6) != 0
+ || (dash = ap_strchr(range + 6, '-')) == NULL
+ || (slash = ap_strchr(range + 6, '/')) == NULL) {
/* malformed header */
return -1;
}
@@ -816,20 +871,19 @@ static int dav_parse_range(request_rec *r,
*dash++ = *slash++ = '\0';
/* detect invalid ranges */
- if (apr_strtoff(range_start, range + 6, &errp, 10)
- || *errp || *range_start < 0) {
+ if (!ap_parse_strict_length(range_start, range + 6)) {
return -1;
}
- if (apr_strtoff(range_end, dash, &errp, 10)
- || *errp || *range_end < 0 || *range_end < *range_start) {
+ if (!ap_parse_strict_length(range_end, dash)
+ || *range_end < *range_start) {
return -1;
}
if (*slash != '*') {
apr_off_t dummy;
- if (apr_strtoff(&dummy, slash, &errp, 10)
- || *errp || dummy <= *range_end) {
+ if (!ap_parse_strict_length(&dummy, slash)
+ || dummy <= *range_end) {
return -1;
}
}
@@ -854,6 +908,12 @@ static int dav_method_get(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -901,6 +961,12 @@ static int dav_method_post(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
/* Note: depth == 0. Implies no need for a multistatus response. */
if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
@@ -934,6 +1000,12 @@ static int dav_method_put(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
/* If not a file or collection resource, PUT not allowed */
if (resource->type != DAV_RESOURCE_TYPE_REGULAR
&& resource->type != DAV_RESOURCE_TYPE_WORKING) {
@@ -996,12 +1068,17 @@ static int dav_method_put(request_rec *r)
/* Create the new file in the repository */
if ((err = (*resource->hooks->open_stream)(resource, mode,
&stream)) != NULL) {
- /* ### assuming FORBIDDEN is probably not quite right... */
- err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
- apr_psprintf(r->pool,
- "Unable to PUT new contents for %s.",
- ap_escape_html(r->pool, r->uri)),
- err);
+ int status = err->status ? err->status : HTTP_FORBIDDEN;
+ if (status > 299) {
+ err = dav_push_error(r->pool, status, 0,
+ apr_psprintf(r->pool,
+ "Unable to PUT new contents for %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ }
+ else {
+ err = NULL;
+ }
}
if (err == NULL && has_range) {
@@ -1204,6 +1281,13 @@ static int dav_method_delete(request_rec *r)
&resource);
if (err != NULL)
return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -1319,10 +1403,10 @@ static dav_error *dav_gen_supported_methods(request_rec *r,
if (elts[i].key == NULL)
continue;
- s = apr_psprintf(r->pool,
- ""
- DEBUG_CR,
- elts[i].key);
+ s = apr_pstrcat(r->pool,
+ "" DEBUG_CR, NULL);
apr_text_append(r->pool, body, s);
}
}
@@ -1348,10 +1432,9 @@ static dav_error *dav_gen_supported_methods(request_rec *r,
/* see if method is supported */
if (apr_table_get(methods, name) != NULL) {
- s = apr_psprintf(r->pool,
- ""
- DEBUG_CR,
- name);
+ s = apr_pstrcat(r->pool,
+ "" DEBUG_CR, NULL);
apr_text_append(r->pool, body, s);
}
}
@@ -1375,8 +1458,7 @@ static dav_error *dav_gen_supported_live_props(request_rec *r,
dav_error *err;
/* open lock database, to report on supported lock properties */
- /* ### should open read-only */
- if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
+ if ((err = dav_open_lockdb(r, 1, &lockdb)) != NULL) {
return dav_push_error(r->pool, err->status, 0,
"The lock database could not be opened, "
"preventing the reporting of supported lock "
@@ -1385,7 +1467,7 @@ static dav_error *dav_gen_supported_live_props(request_rec *r,
}
/* open the property database (readonly) for the resource */
- if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
+ if ((err = dav_open_propdb(r, lockdb, resource, DAV_PROPDB_RO, NULL,
&propdb)) != NULL) {
if (lockdb != NULL)
(*lockdb->hooks->close_lockdb)(lockdb);
@@ -1451,89 +1533,95 @@ static dav_error *dav_gen_supported_live_props(request_rec *r,
return err;
}
+
/* generate DAV:supported-report-set OPTIONS response */
static dav_error *dav_gen_supported_reports(request_rec *r,
const dav_resource *resource,
const apr_xml_elem *elem,
- const dav_hooks_vsn *vsn_hooks,
apr_text_header *body)
{
apr_xml_elem *child;
apr_xml_attr *attr;
- dav_error *err;
+ dav_error *err = NULL;
char *s;
+ apr_array_header_t *reports;
+ const dav_report_elem *rp;
apr_text_append(r->pool, body, "" DEBUG_CR);
- if (vsn_hooks != NULL) {
- const dav_report_elem *reports;
- const dav_report_elem *rp;
+ reports = apr_array_make(r->pool, 5, sizeof(const char *));
+ dav_run_gather_reports(r, resource, reports, &err);
+ if (err != NULL) {
+ return dav_push_error(r->pool, err->status, 0,
+ "DAV:supported-report-set could not be "
+ "determined due to a problem fetching the "
+ "available reports for this resource.",
+ err);
+ }
- if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
- return dav_push_error(r->pool, err->status, 0,
- "DAV:supported-report-set could not be "
- "determined due to a problem fetching the "
- "available reports for this resource.",
- err);
+ if (elem->first_child == NULL) {
+ int i;
+
+ /* show all supported reports */
+ rp = (const dav_report_elem *)reports->elts;
+ for (i = 0; i < reports->nelts; i++, rp++) {
+ /* Note: we presume reports->namespace is
+ * properly XML/URL quoted */
+ s = apr_pstrcat(r->pool,
+ "name,
+ "\" D:namespace=\"",
+ rp->nmspace,
+ "\"/>" DEBUG_CR, NULL);
+ apr_text_append(r->pool, body, s);
}
+ }
+ else {
+ /* check for support of specific report */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-report") == 0) {
+ const char *name = NULL;
+ const char *nmspace = NULL;
+ int i;
- if (reports != NULL) {
- if (elem->first_child == NULL) {
- /* show all supported reports */
- for (rp = reports; rp->nmspace != NULL; ++rp) {
- /* Note: we presume reports->namespace is
- * properly XML/URL quoted */
- s = apr_psprintf(r->pool,
- "" DEBUG_CR,
- rp->name, rp->nmspace);
- apr_text_append(r->pool, body, s);
+ /* go through attributes to find name and namespace */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ else if (strcmp(attr->name, "namespace") == 0)
+ nmspace = attr->value;
+ }
}
- }
- else {
- /* check for support of specific report */
- for (child = elem->first_child; child != NULL; child = child->next) {
- if (child->ns == APR_XML_NS_DAV_ID
- && strcmp(child->name, "supported-report") == 0) {
- const char *name = NULL;
- const char *nmspace = NULL;
-
- /* go through attributes to find name and namespace */
- for (attr = child->attr; attr != NULL; attr = attr->next) {
- if (attr->ns == APR_XML_NS_DAV_ID) {
- if (strcmp(attr->name, "name") == 0)
- name = attr->value;
- else if (strcmp(attr->name, "namespace") == 0)
- nmspace = attr->value;
- }
- }
-
- if (name == NULL) {
- return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
- "A DAV:supported-report element "
- "does not have a \"name\" attribute");
- }
-
- /* default namespace to DAV: */
- if (nmspace == NULL)
- nmspace = "DAV:";
-
- for (rp = reports; rp->nmspace != NULL; ++rp) {
- if (strcmp(name, rp->name) == 0
- && strcmp(nmspace, rp->nmspace) == 0) {
- /* Note: we presume reports->nmspace is
- * properly XML/URL quoted
- */
- s = apr_psprintf(r->pool,
- ""
- DEBUG_CR,
- rp->name, rp->nmspace);
- apr_text_append(r->pool, body, s);
- break;
- }
- }
+
+ if (name == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-report element "
+ "does not have a \"name\" attribute");
+ }
+
+ /* default namespace to DAV: */
+ if (nmspace == NULL) {
+ nmspace = "DAV:";
+ }
+
+ rp = (const dav_report_elem *)reports->elts;
+ for (i = 0; i < reports->nelts; i++, rp++) {
+ if (strcmp(name, rp->name) == 0
+ && strcmp(nmspace, rp->nmspace) == 0) {
+ /* Note: we presume reports->nmspace is
+ * properly XML/URL quoted
+ */
+ s = apr_pstrcat(r->pool,
+ "name,
+ "\" D:namespace=\"",
+ rp->nmspace,
+ "\"/>" DEBUG_CR, NULL);
+ apr_text_append(r->pool, body, s);
+ break;
}
}
}
@@ -1640,6 +1728,12 @@ static int dav_method_options(request_rec *r)
}
/* note: doc == NULL if no request body */
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (doc && !dav_validate_root(doc, "options")) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00584)
"The \"options\" element was not found.");
@@ -1903,7 +1997,7 @@ static int dav_method_options(request_rec *r)
core_option = 1;
}
else if (strcmp(elem->name, "supported-report-set") == 0) {
- err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
+ err = dav_gen_supported_reports(r, resource, elem, &body);
core_option = 1;
}
}
@@ -1968,10 +2062,23 @@ static void dav_cache_badprops(dav_walker_ctx *ctx)
static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
{
dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_dir_conf *conf;
+ int flags = DAV_PROPDB_RO;
dav_error *err;
dav_propdb *propdb;
dav_get_props_result propstats = { 0 };
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(ctx->r, NULL, wres->resource, ctx->doc, &err) != DECLINED
+ && err) {
+ apr_pool_clear(ctx->scratchpool);
+ return NULL;
+ }
+
+ conf = ap_get_module_config(ctx->r->per_dir_config, &dav_module);
+ if (conf && conf->allow_lockdiscovery == DAV_ENABLED_OFF)
+ flags |= DAV_PROPDB_DISABLE_LOCKDISCOVERY;
+
/*
** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
** dav_get_allprops() does not need to do namespace translation,
@@ -1980,8 +2087,9 @@ static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
** Note: we cast to lose the "const". The propdb won't try to change
** the resource, however, since we are opening readonly.
*/
- err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
- ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
+ err = dav_popen_propdb(ctx->scratchpool,
+ ctx->r, ctx->w.lockdb, wres->resource, flags,
+ ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
if (err != NULL) {
/* ### do something with err! */
@@ -2012,10 +2120,10 @@ static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
: DAV_PROP_INSERT_NAME;
propstats = dav_get_allprops(propdb, what);
}
- dav_close_propdb(propdb);
-
dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
+ dav_close_propdb(propdb);
+
/* at this point, ctx->scratchpool has been used to stream a
single response. this function fully controls the pool, and
thus has the right to clear it for the next iteration of this
@@ -2042,6 +2150,17 @@ static int dav_method_propfind(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -2069,11 +2188,6 @@ static int dav_method_propfind(request_rec *r)
}
}
- if ((result = ap_xml_parse_input(r, &doc)) != OK) {
- return result;
- }
- /* note: doc == NULL if no request body */
-
if (doc && !dav_validate_root(doc, "propfind")) {
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00585)
@@ -2103,7 +2217,7 @@ static int dav_method_propfind(request_rec *r)
return HTTP_BAD_REQUEST;
}
- ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH | DAV_WALKTYPE_TOLERANT;
ctx.w.func = dav_propfind_walker;
ctx.w.walk_ctx = &ctx;
ctx.w.pool = r->pool;
@@ -2113,9 +2227,9 @@ static int dav_method_propfind(request_rec *r)
ctx.r = r;
ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
apr_pool_create(&ctx.scratchpool, r->pool);
+ apr_pool_tag(ctx.scratchpool, "mod_dav-scratch");
- /* ### should open read-only */
- if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
+ if ((err = dav_open_lockdb(r, 1, &ctx.w.lockdb)) != NULL) {
err = dav_push_error(r->pool, err->status, 0,
"The lock database could not be opened, "
"preventing access to the various lock "
@@ -2318,16 +2432,23 @@ static int dav_method_proppatch(request_rec *r)
&resource);
if (err != NULL)
return dav_handle_err(r, err, NULL);
- if (!resource->exists) {
- /* Apache will supply a default error for this. */
- return HTTP_NOT_FOUND;
- }
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
return result;
}
/* note: doc == NULL if no request body */
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00587)
@@ -2352,7 +2473,8 @@ static int dav_method_proppatch(request_rec *r)
return dav_handle_err(r, err, NULL);
}
- if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
+ if ((err = dav_open_propdb(r, NULL, resource,
+ DAV_PROPDB_NONE, doc->namespaces,
&propdb)) != NULL) {
/* undo any auto-checkout */
dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
@@ -2470,7 +2592,7 @@ static int process_mkcol_body(request_rec *r)
r->remaining = 0;
if (tenc) {
- if (strcasecmp(tenc, "chunked")) {
+ if (ap_cstr_casecmp(tenc, "chunked")) {
/* Use this instead of Apache's default error string */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00589)
"Unknown Transfer-Encoding %s", tenc);
@@ -2480,20 +2602,13 @@ static int process_mkcol_body(request_rec *r)
r->read_chunked = 1;
}
else if (lenp) {
- const char *pos = lenp;
-
- while (apr_isdigit(*pos) || apr_isspace(*pos)) {
- ++pos;
- }
-
- if (*pos != '\0') {
+ if (!ap_parse_strict_length(&r->remaining, lenp)) {
+ r->remaining = 0;
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00590)
"Invalid Content-Length %s", lenp);
return HTTP_BAD_REQUEST;
}
-
- r->remaining = apr_atoi64(lenp);
}
if (r->read_chunked || r->remaining > 0) {
@@ -2534,6 +2649,12 @@ static int dav_method_mkcol(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (resource->exists) {
/* oops. something was already there! */
@@ -2654,6 +2775,12 @@ static int dav_method_copymove(request_rec *r, int is_move)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -2676,7 +2803,7 @@ static int dav_method_copymove(request_rec *r, int is_move)
const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
if (nscp_host != NULL && nscp_path != NULL)
- dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
+ dest = apr_pstrcat(r->pool, "http://", nscp_host, nscp_path, NULL);
}
if (dest == NULL) {
/* This supplies additional information for the default message. */
@@ -2720,6 +2847,12 @@ static int dav_method_copymove(request_rec *r, int is_move)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, resnew, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
/* are the two resources handled by the same repository? */
if (resource->hooks != resnew->hooks) {
/* ### this message exposes some backend config, but screw it... */
@@ -3071,6 +3204,12 @@ static int dav_method_lock(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
/* Check if parent collection exists */
if ((err = resource->hooks->get_parent_resource(resource, &parent)) != NULL) {
/* ### add a higher-level description? */
@@ -3270,6 +3409,12 @@ static int dav_method_unlock(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
resource_state = dav_get_resource_state(r, resource);
/*
@@ -3294,8 +3439,8 @@ static int dav_method_unlock(request_rec *r)
/* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
* _all_ resources locked by locktoken are released. It does not say
- * resource has to be the root of an infinte lock. Thus, an UNLOCK
- * on any part of an infinte lock will remove the lock on all resources.
+ * resource has to be the root of an infinite lock. Thus, an UNLOCK
+ * on any part of an infinite lock will remove the lock on all resources.
*
* For us, if r->filename represents an indirect lock (part of an infinity lock),
* we must actually perform an UNLOCK on the direct lock for this resource.
@@ -3329,15 +3474,21 @@ static int dav_method_vsn_control(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
- /* remember the pre-creation resource state */
- resource_state = dav_get_resource_state(r, resource);
-
/* parse the request body (may be a version-control element) */
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
return result;
}
/* note: doc == NULL if no request body */
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* remember the pre-creation resource state */
+ resource_state = dav_get_resource_state(r, resource);
+
if (doc != NULL) {
const apr_xml_elem *child;
apr_size_t tsize;
@@ -3582,6 +3733,12 @@ static int dav_method_checkout(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -3658,6 +3815,12 @@ static int dav_method_uncheckout(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -3735,6 +3898,12 @@ static int dav_method_checkin(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -3856,6 +4025,12 @@ static int dav_method_update(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -3930,6 +4105,9 @@ typedef struct dav_label_walker_ctx
/* input: */
dav_walk_params w;
+ /* original request */
+ request_rec *r;
+
/* label being manipulated */
const char *label;
@@ -3949,13 +4127,19 @@ static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
dav_label_walker_ctx *ctx = wres->walk_ctx;
dav_error *err = NULL;
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(ctx->r, NULL, wres->resource, NULL, &err) != DECLINED
+ && err) {
+ /* precondition failed, dropping through */
+ }
+
/* Check the state of the resource: must be a version or
* non-checkedout version selector
*/
/* ### need a general mechanism for reporting precondition violations
* ### (should be returning XML document for 403/409 responses)
*/
- if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
+ else if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
(wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
|| !wres->resource->versioned)) {
err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0,
@@ -4001,11 +4185,23 @@ static int dav_method_label(request_rec *r)
if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
return DECLINED;
+ /* parse the request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
/* Ask repository module to resolve the resource */
err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
&resource);
if (err != NULL)
return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -4017,11 +4213,6 @@ static int dav_method_label(request_rec *r)
return HTTP_BAD_REQUEST;
}
- /* parse the request body */
- if ((result = ap_xml_parse_input(r, &doc)) != OK) {
- return result;
- }
-
if (doc == NULL || !dav_validate_root(doc, "label")) {
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00610)
@@ -4070,6 +4261,7 @@ static int dav_method_label(request_rec *r)
ctx.w.walk_ctx = &ctx;
ctx.w.pool = r->pool;
ctx.w.root = resource;
+ ctx.r = r;
ctx.vsn_hooks = vsn_hooks;
err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
@@ -4109,21 +4301,60 @@ static int dav_method_label(request_rec *r)
return DONE;
}
+static int dav_core_deliver_report(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output, dav_error **err)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+
+ if (vsn_hooks) {
+ *err = (*vsn_hooks->deliver_report)(r, resource, doc,
+ r->output_filters);
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static void dav_core_gather_reports(
+ request_rec *r,
+ const dav_resource *resource,
+ apr_array_header_t *reports,
+ dav_error **err)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+
+ if (vsn_hooks) {
+ const dav_report_elem *rp;
+
+ (*err) = (*vsn_hooks->avail_reports)(resource, &rp);
+ while (rp && rp->name) {
+
+ dav_report_elem *report = apr_array_push(reports);
+
+ report->nmspace = rp->nmspace;
+ report->name = rp->name;
+
+ rp++;
+ }
+ }
+
+}
+
static int dav_method_report(request_rec *r)
{
dav_resource *resource;
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
- int result;
- int label_allowed;
apr_xml_doc *doc;
- dav_error *err;
+ dav_error *err = NULL;
- /* If no versioning provider, decline the request */
- if (vsn_hooks == NULL)
- return DECLINED;
+ int result;
+ int label_allowed;
- if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
return result;
+ }
if (doc == NULL) {
/* This supplies additional information for the default msg. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00614)
@@ -4135,11 +4366,18 @@ static int dav_method_report(request_rec *r)
* First determine whether a Target-Selector header is allowed
* for this report.
*/
- label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
+ label_allowed = vsn_hooks ? (*vsn_hooks->report_label_header_allowed)(doc) : 0;
err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
&resource);
- if (err != NULL)
+ if (err != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
return dav_handle_err(r, err, NULL);
+ }
if (!resource->exists) {
/* Apache will supply a default error for this. */
@@ -4149,13 +4387,17 @@ static int dav_method_report(request_rec *r)
/* set up defaults for the report response */
r->status = HTTP_OK;
ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+ err = NULL;
/* run report hook */
- if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
- r->output_filters)) != NULL) {
- if (! r->sent_bodyct)
+ result = dav_run_deliver_report(r, resource, doc,
+ r->output_filters, &err);
+ if (err != NULL) {
+
+ if (! r->sent_bodyct) {
/* No data has been sent to client yet; throw normal error. */
return dav_handle_err(r, err, NULL);
+ }
/* If an error occurred during the report delivery, there's
basically nothing we can do but abort the connection and
@@ -4168,6 +4410,16 @@ static int dav_method_report(request_rec *r)
" a REPORT response.", err);
dav_log_err(r, err, APLOG_ERR);
r->connection->aborted = 1;
+
+ return DONE;
+ }
+ switch (result) {
+ case OK:
+ return DONE;
+ case DECLINED:
+ /* No one handled the report */
+ return HTTP_NOT_IMPLEMENTED;
+ default:
return DONE;
}
@@ -4199,6 +4451,12 @@ static int dav_method_make_workspace(request_rec *r)
return result;
}
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (doc == NULL
|| !dav_validate_root(doc, "mkworkspace")) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00615)
@@ -4258,6 +4516,12 @@ static int dav_method_make_activity(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
/* MKACTIVITY does not have a defined request body. */
if ((result = ap_discard_request_body(r)) != OK) {
return result;
@@ -4383,6 +4647,12 @@ static int dav_method_merge(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, source_resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
@@ -4400,6 +4670,13 @@ static int dav_method_merge(request_rec *r)
&resource);
if (err != NULL)
return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, source_resource, resource, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -4467,6 +4744,12 @@ static int dav_method_bind(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
@@ -4517,6 +4800,12 @@ static int dav_method_bind(request_rec *r)
if (err != NULL)
return dav_handle_err(r, err, NULL);
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, binding, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
/* are the two resources handled by the same repository? */
if (resource->hooks != binding->hooks) {
/* ### this message exposes some backend config, but screw it... */
@@ -4886,6 +5175,11 @@ static void register_hooks(apr_pool_t *p)
dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
NULL, NULL, APR_HOOK_MIDDLE);
+ dav_hook_deliver_report(dav_core_deliver_report,
+ NULL, NULL, APR_HOOK_LAST);
+ dav_hook_gather_reports(dav_core_gather_reports,
+ NULL, NULL, APR_HOOK_LAST);
+
dav_core_register_uris(p);
}
@@ -4900,6 +5194,10 @@ static const command_rec dav_cmds[] =
AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
"specify the DAV provider for a directory or location"),
+ /* per directory/location */
+ AP_INIT_TAKE1("DAVBasePath", dav_cmd_davbasepath, NULL, ACCESS_CONF,
+ "specify the DAV repository base URL"),
+
/* per directory/location, or per server */
AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
ACCESS_CONF|RSRC_CONF,
@@ -4910,6 +5208,11 @@ static const command_rec dav_cmds[] =
ACCESS_CONF|RSRC_CONF,
"allow Depth infinity PROPFIND requests"),
+ /* per directory/location, or per server */
+ AP_INIT_FLAG("DAVLockDiscovery", dav_cmd_davlockdiscovery, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "allow lock discovery by PROPFIND requests"),
+
{ NULL }
};
@@ -4928,6 +5231,9 @@ APR_HOOK_STRUCT(
APR_HOOK_LINK(gather_propsets)
APR_HOOK_LINK(find_liveprop)
APR_HOOK_LINK(insert_all_liveprops)
+ APR_HOOK_LINK(deliver_report)
+ APR_HOOK_LINK(gather_reports)
+ APR_HOOK_LINK(method_precondition)
)
APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
@@ -4944,3 +5250,22 @@ APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
(request_rec *r, const dav_resource *resource,
dav_prop_insert what, apr_text_header *phdr),
(r, resource, what, phdr))
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, deliver_report,
+ (request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output, dav_error **err),
+ (r, resource, doc, output, err), DECLINED)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_reports,
+ (request_rec *r, const dav_resource *resource,
+ apr_array_header_t *reports, dav_error **err),
+ (r, resource, reports, err))
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, method_precondition,
+ (request_rec *r,
+ dav_resource *src, const dav_resource *dest,
+ const apr_xml_doc *doc,
+ dav_error **err),
+ (r, src, dest, doc, err), DECLINED)
diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h
index 80ad117..c8c54f3 100644
--- a/modules/dav/main/mod_dav.h
+++ b/modules/dav/main/mod_dav.h
@@ -50,7 +50,7 @@ extern "C" {
#define DAV_READ_BLOCKSIZE 2048 /* used for reading input blocks */
-#define DAV_RESPONSE_BODY_1 "\n\n"
+#define DAV_RESPONSE_BODY_1 "\n\n\n"
#define DAV_RESPONSE_BODY_2 "\n\n"
#define DAV_RESPONSE_BODY_3 "
\n"
#define DAV_RESPONSE_BODY_4 "
\n"
@@ -427,6 +427,9 @@ typedef struct dav_resource {
*/
typedef struct dav_locktoken dav_locktoken;
+DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed,
+ int use_checked_in, dav_resource **res_p);
+
/* --------------------------------------------------------------------
**
@@ -574,8 +577,22 @@ DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth);
DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
const char *tagname);
+DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc,
+ int ns, const char *tagname);
DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
const char *tagname);
+DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname);
+DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname);
+
+/* find and return the attribute with a name in the given namespace */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem,
+ int ns, const char *attrname);
+
+/* find and return the attribute with a given DAV: tagname */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem,
+ const char *attrname);
/* gather up all the CDATA into a single string */
DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
@@ -644,10 +661,10 @@ DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
** mod_dav 1.0). There are too many dependencies between a dav_resource
** (defined by ) and the other functionality.
**
-** Live properties are not part of the dav_provider structure because they
-** are handled through the APR_HOOK interface (to allow for multiple liveprop
-** providers). The core always provides some properties, and then a given
-** provider will add more properties.
+** Live properties and report extensions are not part of the dav_provider
+** structure because they are handled through the APR_HOOK interface (to
+** allow for multiple providers). The core always provides some
+** properties, and then a given provider will add more properties.
**
** Some providers may need to associate a context with the dav_provider
** structure -- the ctx field is available for storing this context. Just
@@ -711,6 +728,68 @@ APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, insert_all_liveprops,
(request_rec *r, const dav_resource *resource,
dav_prop_insert what, apr_text_header *phdr))
+/*
+** deliver_report: given a parsed report request, process the request
+** an deliver the resulting report.
+**
+** The hook implementer should decide whether it should handle the given
+** report, and if so, write the response to the output filter. If the
+** report is not relevant, return DECLINED.
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, deliver_report,
+ (request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output, dav_error **err))
+
+/*
+** gather_reports: get all reports.
+**
+** The hook implementor should push one or more dav_report_elem structures
+** containing report names into the specified array. These names are returned
+** in the DAV:supported-reports-set property to let clients know
+** what reports are supported by the installation.
+**
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, gather_reports,
+ (request_rec *r, const dav_resource *resource,
+ apr_array_header_t *reports, dav_error **err))
+
+/*
+ ** method_precondition: check method preconditions.
+ **
+ ** If a WebDAV extension needs to set any preconditions on a method, this
+ ** hook is where to do it. If the precondition fails, return an error
+ ** response with the tagname set to the value of the failed precondition.
+ **
+ ** If the method requires an XML body, this will be read and provided as
+ ** the doc value. If not, doc is NULL. An extension that needs to verify
+ ** the non-XML body of a request should register an input filter to do so
+ ** within this hook.
+ **
+ ** Methods like PUT will supply a single src resource, and the dst will
+ ** be NULL.
+ **
+ ** Methods like COPY or MOVE will trigger this hook twice. The first
+ ** invocation will supply just the source resource. The second invocation
+ ** will supply a source and destination. This allows preconditions on the
+ ** source resource to be verified before making an attempt to get the
+ ** destination resource.
+ **
+ ** Methods like PROPFIND and LABEL will trigger this hook initially for
+ ** the src resource, and then subsequently for each resource that has
+ ** been walked during processing, with the walked resource passed in dst,
+ ** and NULL passed in src.
+ **
+ ** As a rule, the src resource originates from a request that has passed
+ ** through httpd's authn/authz hooks, while the dst resource has not.
+ */
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, method_precondition,
+ (request_rec *r,
+ dav_resource *src, const dav_resource *dst,
+ const apr_xml_doc *doc, dav_error **err))
+
+
DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r);
DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r);
DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r);
@@ -721,6 +800,7 @@ DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name,
const dav_provider *hooks);
DAV_DECLARE(const dav_provider *) dav_lookup_provider(const char *name);
DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r);
+DAV_DECLARE(const dav_provider *) dav_get_provider(request_rec *r);
/* ### deprecated */
@@ -827,6 +907,14 @@ struct dav_hooks_liveprop
** property, and does not want it handled as a dead property, it should
** return DAV_PROP_INSERT_NOTSUPP.
**
+ ** Some DAV extensions, like CalDAV, specify both document elements
+ ** and property elements that need to be taken into account when
+ ** generating a property. The document element and property element
+ ** are made available in the dav_liveprop_elem structure under the
+ ** resource, accessible as follows:
+ **
+ ** dav_get_liveprop_element(resource);
+ **
** Returns one of DAV_PROP_INSERT_* based on what happened.
**
** ### we may need more context... ie. the lock database
@@ -974,6 +1062,18 @@ DAV_DECLARE(long) dav_get_liveprop_ns_count(void);
DAV_DECLARE(void) dav_add_all_liveprop_xmlns(apr_pool_t *p,
apr_text_header *phdr);
+typedef struct {
+ const apr_xml_doc *doc;
+ const apr_xml_elem *elem;
+} dav_liveprop_elem;
+
+/*
+ ** When calling insert_prop(), the associated request element and
+ ** document is accessible using the following call.
+ */
+DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource
+ *resource);
+
/*
** The following three functions are part of mod_dav's internal handling
** for the core WebDAV properties. They are not part of mod_dav's API.
@@ -1314,8 +1414,12 @@ DAV_DECLARE(const char *)dav_lock_get_activelock(request_rec *r,
dav_buffer *pbuf);
/* LockDB-related public lock functions */
+DAV_DECLARE(dav_error *) dav_open_lockdb(request_rec *r,
+ int ro,
+ dav_lockdb **lockdb);
+DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb);
DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
- const dav_resource *resrouce,
+ const dav_resource *resource,
dav_lockdb *lockdb,
const apr_xml_doc *doc,
dav_lock **lock_request);
@@ -1581,15 +1685,28 @@ struct dav_hooks_locks
typedef struct dav_propdb dav_propdb;
+#define DAV_PROPDB_NONE 0
+#define DAV_PROPDB_RO 1
+#define DAV_PROPDB_DISABLE_LOCKDISCOVERY 2
DAV_DECLARE(dav_error *) dav_open_propdb(
request_rec *r,
dav_lockdb *lockdb,
const dav_resource *resource,
- int ro,
+ int flags,
apr_array_header_t *ns_xlate,
dav_propdb **propdb);
+DAV_DECLARE(dav_error *) dav_popen_propdb(
+ apr_pool_t *p,
+ request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int flags,
+ apr_array_header_t *ns_xlate,
+ dav_propdb **propdb);
+
+
DAV_DECLARE(void) dav_close_propdb(dav_propdb *db);
DAV_DECLARE(dav_get_props_result) dav_get_props(
@@ -1706,6 +1823,7 @@ typedef struct
#define DAV_WALKTYPE_AUTH 0x0001 /* limit to authorized files */
#define DAV_WALKTYPE_NORMAL 0x0002 /* walk normal files */
#define DAV_WALKTYPE_LOCKNULL 0x0004 /* walk locknull resources */
+#define DAV_WALKTYPE_TOLERANT 0x0008 /* tolerate non-fatal errors */
/* callback function and a client context for the walk */
dav_error * (*func)(dav_walk_resource *wres, int calltype);
diff --git a/modules/dav/main/props.c b/modules/dav/main/props.c
index f64878e..c320f8a 100644
--- a/modules/dav/main/props.c
+++ b/modules/dav/main/props.c
@@ -167,6 +167,8 @@
#define DAV_EMPTY_VALUE "\0" /* TWO null terms */
+#define DAV_PROP_ELEMENT "mod_dav-element"
+
struct dav_propdb {
apr_pool_t *p; /* the pool we should use */
request_rec *r; /* the request record */
@@ -183,6 +185,8 @@ struct dav_propdb {
dav_buffer wb_lock; /* work buffer for lockdiscovery property */
+ int flags; /* ro, disable lock discovery */
+
/* if we ever run a GET subreq, it will be stored here */
request_rec *subreq;
@@ -323,7 +327,7 @@ static void dav_do_prop_subreq(dav_propdb *propdb)
{
/* need to escape the uri that's in the resource struct because during
* the property walker it's not encoded. */
- const char *e_uri = ap_escape_uri(propdb->resource->pool,
+ const char *e_uri = ap_escape_uri(propdb->p,
propdb->resource->uri);
/* perform a "GET" on the resource's URI (note that the resource
@@ -349,6 +353,11 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
switch (propid) {
case DAV_PROPID_CORE_lockdiscovery:
+ if (propdb->flags & DAV_PROPDB_DISABLE_LOCKDISCOVERY) {
+ value = "";
+ break;
+ }
+
if (propdb->lockdb != NULL) {
dav_lock *locks;
@@ -421,18 +430,18 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
/* use D: prefix to refer to the DAV: namespace URI,
* and let the namespace attribute default to "DAV:"
*/
- s = apr_psprintf(propdb->p,
- "" DEBUG_CR,
- name);
+ s = apr_pstrcat(propdb->p,
+ "" DEBUG_CR, NULL);
}
else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
/* use D: prefix to refer to the DAV: namespace URI */
- s = apr_psprintf(propdb->p, "%s" DEBUG_CR,
- name, value, name);
+ s = apr_pstrcat(propdb->p, "", value, "" DEBUG_CR, NULL);
}
else {
/* use D: prefix to refer to the DAV: namespace URI */
- s = apr_psprintf(propdb->p, "" DEBUG_CR, name);
+ s = apr_pstrcat(propdb->p, "" DEBUG_CR, NULL);
}
apr_text_append(propdb->p, phdr, s);
@@ -473,11 +482,11 @@ static void dav_output_prop_name(apr_pool_t *pool,
const char *s;
if (*name->ns == '\0')
- s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name);
+ s = apr_pstrcat(pool, "<", name->name, "/>" DEBUG_CR, NULL);
else {
const char *prefix = dav_xmlns_add_uri(xi, name->ns);
- s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name);
+ s = apr_pstrcat(pool, "<", prefix, ":", name->name, "/>" DEBUG_CR, NULL);
}
apr_text_append(pool, phdr, s);
@@ -520,11 +529,25 @@ static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
const dav_resource *resource,
- int ro,
+ int flags,
apr_array_header_t * ns_xlate,
dav_propdb **p_propdb)
{
- dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb));
+ return dav_popen_propdb(r->pool, r, lockdb, resource,
+ flags, ns_xlate, p_propdb);
+}
+
+DAV_DECLARE(dav_error *)dav_popen_propdb(apr_pool_t *p,
+ request_rec *r, dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int flags,
+ apr_array_header_t * ns_xlate,
+ dav_propdb **p_propdb)
+{
+ dav_propdb *propdb = NULL;
+
+ propdb = apr_pcalloc(p, sizeof(*propdb));
+ propdb->p = p;
*p_propdb = NULL;
@@ -537,7 +560,6 @@ DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
#endif
propdb->r = r;
- apr_pool_create(&propdb->p, r->pool);
propdb->resource = resource;
propdb->ns_xlate = ns_xlate;
@@ -545,6 +567,8 @@ DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
propdb->lockdb = lockdb;
+ propdb->flags = flags;
+
/* always defer actual open, to avoid expense of accessing db
* when only live properties are involved
*/
@@ -562,10 +586,10 @@ DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb)
(*propdb->db_hooks->close)(propdb->db);
}
- /* Currently, mod_dav's pool usage doesn't allow clearing this pool. */
-#if 0
- apr_pool_destroy(propdb->p);
-#endif
+ if (propdb->subreq) {
+ ap_destroy_sub_req(propdb->subreq);
+ propdb->subreq = NULL;
+ }
}
DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb,
@@ -706,10 +730,24 @@ DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb,
apr_text_header hdr_ns = { 0 };
int have_good = 0;
dav_get_props_result result = { 0 };
+ dav_liveprop_elem *element;
char *marks_liveprop;
dav_xmlns_info *xi;
int xi_filled = 0;
+ /* we lose both the document and the element when calling (insert_prop),
+ * make these available in the pool.
+ */
+ element = dav_get_liveprop_element(propdb->resource);
+ if (!element) {
+ element = apr_pcalloc(propdb->resource->pool, sizeof(dav_liveprop_elem));
+ apr_pool_userdata_setn(element, DAV_PROP_ELEMENT, NULL, propdb->resource->pool);
+ }
+ else {
+ memset(element, 0, sizeof(dav_liveprop_elem));
+ }
+ element->doc = doc;
+
/* ### NOTE: we should pass in TWO buffers -- one for keys, one for
the marks */
@@ -733,13 +771,16 @@ DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb,
dav_prop_insert inserted;
dav_prop_name name;
+ element->elem = elem;
+
/*
** First try live property providers; if they don't handle
** the property, then try looking it up in the propdb.
*/
if (elem->priv == NULL) {
- elem->priv = apr_pcalloc(propdb->p, sizeof(*priv));
+ /* elem->priv outlives propdb->p. Hence use the request pool */
+ elem->priv = apr_pcalloc(propdb->r->pool, sizeof(*priv));
}
priv = elem->priv;
@@ -909,6 +950,15 @@ DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb,
}
}
+DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource *resource)
+{
+ dav_liveprop_elem *element;
+
+ apr_pool_userdata_get((void **)&element, DAV_PROP_ELEMENT, resource->pool);
+
+ return element;
+}
+
DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx)
{
dav_propdb *propdb = ctx->propdb;
@@ -1047,6 +1097,10 @@ DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx)
/*
** Delete the property. Ignore errors -- the property is there, or
** we are deleting it for a second time.
+ **
+ ** http://tools.ietf.org/html/rfc4918#section-14.23 says
+ ** "Specifying the removal of a property that does not exist is
+ ** not an error"
*/
/* ### but what about other errors? */
(void) (*propdb->db_hooks->remove)(propdb->db, &name);
diff --git a/modules/dav/main/std_liveprop.c b/modules/dav/main/std_liveprop.c
index e760c65..cb46b65 100644
--- a/modules/dav/main/std_liveprop.c
+++ b/modules/dav/main/std_liveprop.c
@@ -154,10 +154,10 @@ static dav_prop_insert dav_core_insert_prop(const dav_resource *resource,
/* assert: info != NULL && info->name != NULL */
if (what == DAV_PROP_INSERT_SUPPORTED) {
- s = apr_psprintf(p,
- "" DEBUG_CR,
- info->name, dav_core_namespace_uris[info->ns]);
+ s = apr_pstrcat(p,
+ "name,
+ "\" D:namespace=\"", dav_core_namespace_uris[info->ns],
+ "\"/>" DEBUG_CR, NULL);
}
else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
s = apr_psprintf(p, "%s" DEBUG_CR,
diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c
index 9f24604..3f7822f 100644
--- a/modules/dav/main/util.c
+++ b/modules/dav/main/util.c
@@ -101,6 +101,9 @@ DAV_DECLARE(dav_error*) dav_join_error(dav_error *dest, dav_error *src)
return dest;
}
+/* ### Unclear if this was designed to be used with an uninitialized
+ * dav_buffer struct, but is used on by dav_lock_get_activelock().
+ * Hence check for pbuf->buf. */
DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
apr_size_t extra_needed)
{
@@ -110,7 +113,8 @@ DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
newbuf = apr_palloc(p, pbuf->alloc_len);
- memcpy(newbuf, pbuf->buf, pbuf->cur_len);
+ if (pbuf->buf)
+ memcpy(newbuf, pbuf->buf, pbuf->cur_len);
pbuf->buf = newbuf;
}
}
@@ -240,7 +244,7 @@ DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
request. the port must match our port.
*/
port = r->connection->local_addr->port;
- if (strcasecmp(comp.scheme, scheme) != 0
+ if (ap_cstr_casecmp(comp.scheme, scheme) != 0
#ifdef APACHE_PORT_HANDLING_IS_BUSTED
|| comp.port != port
#endif
@@ -312,26 +316,71 @@ DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
*/
/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
-DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
- const char *tagname)
+DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc,
+ int ns, const char *tagname)
{
return doc->root &&
- doc->root->ns == APR_XML_NS_DAV_ID &&
+ doc->root->ns == ns &&
strcmp(doc->root->name, tagname) == 0;
}
-/* find and return the (unique) child with a given DAV: tagname */
-DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
- const char *tagname)
+/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
+DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
+ const char *tagname)
+{
+ return dav_validate_root_ns(doc, APR_XML_NS_DAV_ID, tagname);
+}
+
+/* find and return the next child with a tagname in the given namespace */
+DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname)
+{
+ apr_xml_elem *child = elem->next;
+
+ for (; child; child = child->next)
+ if (child->ns == ns && !strcmp(child->name, tagname))
+ return child;
+ return NULL;
+}
+
+/* find and return the (unique) child with a tagname in the given namespace */
+DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname)
{
apr_xml_elem *child = elem->first_child;
for (; child; child = child->next)
- if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname))
+ if (child->ns == ns && !strcmp(child->name, tagname))
return child;
return NULL;
}
+/* find and return the (unique) child with a given DAV: tagname */
+DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
+ const char *tagname)
+{
+ return dav_find_child_ns(elem, APR_XML_NS_DAV_ID, tagname);
+}
+
+/* find and return the attribute with a name in the given namespace */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem,
+ int ns, const char *attrname)
+{
+ apr_xml_attr *attr = elem->attr;
+
+ for (; attr; attr = attr->next)
+ if (attr->ns == ns && !strcmp(attr->name, attrname))
+ return attr;
+ return NULL;
+}
+
+/* find and return the attribute with a given DAV: tagname */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem,
+ const char *attrname)
+{
+ return dav_find_attr_ns(elem, APR_XML_NS_DAV_ID, attrname);
+}
+
/* gather up all the CDATA into a single string */
DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
int strip_white)
@@ -470,8 +519,8 @@ DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
apr_hash_this(hi, &prefix, NULL, &uri);
- s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"",
- (const char *)prefix, (const char *)uri);
+ s = apr_pstrcat(xi->pool, " xmlns:", (const char *)prefix, "=\"",
+ (const char *)uri, "\"", NULL);
apr_text_append(xi->pool, phdr, s);
}
}
@@ -660,7 +709,13 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
/* note that parsed_uri.path is allocated; we can trash it */
/* clean up the URI a bit */
- ap_getparents(parsed_uri.path);
+ if (!ap_normalize_path(parsed_uri.path,
+ AP_NORMALIZE_NOT_ABOVE_ROOT |
+ AP_NORMALIZE_DECODE_UNRESERVED)) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid URI path tagged If-header.");
+ }
/* the resources we will compare to have unencoded paths */
if (ap_unescape_url(parsed_uri.path) != OK) {
@@ -746,8 +801,14 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
"for the same state.");
}
condition = DAV_IF_COND_NOT;
+ list += 2;
+ }
+ else {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNK_CHAR, 0,
+ "Invalid \"If:\" header: "
+ "Unexpected character in List");
}
- list += 2;
break;
case ' ':
diff --git a/modules/examples/mod_case_filter_in.c b/modules/examples/mod_case_filter_in.c
index 5116e3b..c70a9eb 100644
--- a/modules/examples/mod_case_filter_in.c
+++ b/modules/examples/mod_case_filter_in.c
@@ -114,7 +114,7 @@ static apr_status_t CaseFilterInFilter(ap_filter_t *f,
buf[n] = apr_toupper(data[n]);
}
- pbktOut = apr_bucket_heap_create(buf, len, 0, c->bucket_alloc);
+ pbktOut = apr_bucket_heap_create(buf, len, free, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut);
apr_bucket_delete(pbktIn);
}
diff --git a/modules/examples/mod_example_hooks.c b/modules/examples/mod_example_hooks.c
index d818dc1..f7ef5a5 100644
--- a/modules/examples/mod_example_hooks.c
+++ b/modules/examples/mod_example_hooks.c
@@ -742,7 +742,7 @@ static int x_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
/*
* Log the call and exit.
*/
- trace_startup(ptemp, NULL, NULL, "x_pre_config()");
+ trace_startup(pconf, NULL, NULL, "x_pre_config()");
return OK;
}
@@ -763,7 +763,7 @@ static int x_check_config(apr_pool_t *pconf, apr_pool_t *plog,
/*
* Log the call and exit.
*/
- trace_startup(ptemp, s, NULL, "x_check_config()");
+ trace_startup(pconf, s, NULL, "x_check_config()");
return OK;
}
@@ -800,7 +800,7 @@ static int x_open_logs(apr_pool_t *pconf, apr_pool_t *plog,
/*
* Log the call and exit.
*/
- trace_startup(ptemp, s, NULL, "x_open_logs()");
+ trace_startup(pconf, s, NULL, "x_open_logs()");
return OK;
}
@@ -820,7 +820,7 @@ static int x_post_config(apr_pool_t *pconf, apr_pool_t *plog,
/*
* Log the call and exit.
*/
- trace_startup(ptemp, s, NULL, "x_post_config()");
+ trace_startup(pconf, s, NULL, "x_post_config()");
return OK;
}
@@ -1173,6 +1173,22 @@ static int x_post_read_request(request_rec *r)
return DECLINED;
}
+/*
+ * This routine gives our module an opportunity to translate the URI into an
+ * actual filename, before URL decoding happens.
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_pre_translate_name(request_rec *r)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_pre_translate_name()");
+ return DECLINED;
+}
+
/*
* This routine gives our module an opportunity to translate the URI into an
* actual filename. If we don't do anything special, the server's default
@@ -1448,6 +1464,7 @@ static int x_monitor(apr_pool_t *p, server_rec *s)
*/
static void x_register_hooks(apr_pool_t *p)
{
+ trace = NULL;
ap_hook_pre_config(x_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_check_config(x_check_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_test_config(x_test_config, NULL, NULL, APR_HOOK_MIDDLE);
@@ -1466,6 +1483,7 @@ static void x_register_hooks(apr_pool_t *p)
ap_hook_log_transaction(x_log_transaction, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_http_scheme(x_http_scheme, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_default_port(x_default_port, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_translate_name(x_pre_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_translate_name(x_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_map_to_storage(x_map_to_storage, NULL,NULL, APR_HOOK_MIDDLE);
ap_hook_header_parser(x_header_parser, NULL, NULL, APR_HOOK_MIDDLE);
diff --git a/modules/filters/libsed.h b/modules/filters/libsed.h
index 76cbc0c..0256b1e 100644
--- a/modules/filters/libsed.h
+++ b/modules/filters/libsed.h
@@ -60,7 +60,7 @@ struct sed_label_s {
};
typedef apr_status_t (sed_err_fn_t)(void *data, const char *error);
-typedef apr_status_t (sed_write_fn_t)(void *ctx, char *buf, int sz);
+typedef apr_status_t (sed_write_fn_t)(void *ctx, char *buf, apr_size_t sz);
typedef struct sed_commands_s sed_commands_t;
#define NWFILES 11 /* 10 plus one for standard output */
@@ -69,7 +69,7 @@ struct sed_commands_s {
sed_err_fn_t *errfn;
void *data;
- unsigned lsize;
+ apr_size_t lsize;
char *linebuf;
char *lbend;
const char *saveq;
@@ -116,15 +116,15 @@ struct sed_eval_s {
apr_int64_t lnum;
void *fout;
- unsigned lsize;
+ apr_size_t lsize;
char *linebuf;
char *lspend;
- unsigned hsize;
+ apr_size_t hsize;
char *holdbuf;
char *hspend;
- unsigned gsize;
+ apr_size_t gsize;
char *genbuf;
char *lcomend;
@@ -160,7 +160,7 @@ apr_status_t sed_init_eval(sed_eval_t *eval, sed_commands_t *commands,
sed_err_fn_t *errfn, void *data,
sed_write_fn_t *writefn, apr_pool_t *p);
apr_status_t sed_reset_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data);
-apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void *fout);
+apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout);
apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout);
apr_status_t sed_finalize_eval(sed_eval_t *eval, void *f);
void sed_destroy_eval(sed_eval_t *eval);
diff --git a/modules/filters/mod_brotli.c b/modules/filters/mod_brotli.c
index 56717e7..0f7d770 100644
--- a/modules/filters/mod_brotli.c
+++ b/modules/filters/mod_brotli.c
@@ -344,6 +344,7 @@ static apr_status_t compress_filter(ap_filter_t *f, apr_bucket_brigade *bb)
const char *encoding;
const char *token;
const char *accepts;
+ const char *q = NULL;
/* Only work on main request, not subrequests, that are not
* a 204 response with no content, and are not tagged with the
@@ -411,7 +412,19 @@ static apr_status_t compress_filter(ap_filter_t *f, apr_bucket_brigade *bb)
token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
}
- if (!token || token[0] == '\0') {
+ /* Find the qvalue, if provided */
+ if (*accepts) {
+ while (*accepts == ';') {
+ ++accepts;
+ }
+ q = ap_get_token(r->pool, &accepts, 1);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "token: '%s' - q: '%s'", token ? token : "NULL", q);
+ }
+
+ /* No acceptable token found or q=0 */
+ if (!token || token[0] == '\0' ||
+ (q && strlen(q) >= 3 && strncmp("q=0.000", q, strlen(q)) == 0)) {
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
diff --git a/modules/filters/mod_charset_lite.c b/modules/filters/mod_charset_lite.c
index ed76f61..e3d1ce9 100644
--- a/modules/filters/mod_charset_lite.c
+++ b/modules/filters/mod_charset_lite.c
@@ -790,7 +790,7 @@ static apr_status_t xlate_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
if (!ctx->noop && ctx->xlate == NULL) {
const char *mime_type = f->r->content_type;
- if (mime_type && (strncasecmp(mime_type, "text/", 5) == 0 ||
+ if (mime_type && (ap_cstr_casecmpn(mime_type, "text/", 5) == 0 ||
#if APR_CHARSET_EBCDIC
/* On an EBCDIC machine, be willing to translate mod_autoindex-
* generated output. Otherwise, it doesn't look too cool.
@@ -806,7 +806,7 @@ static apr_status_t xlate_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
*/
strcmp(mime_type, DIR_MAGIC_TYPE) == 0 ||
#endif
- strncasecmp(mime_type, "message/", 8) == 0 ||
+ ap_cstr_casecmpn(mime_type, "message/", 8) == 0 ||
dc->force_xlate == FX_FORCE)) {
rv = apr_xlate_open(&ctx->xlate,
diff --git a/modules/filters/mod_data.c b/modules/filters/mod_data.c
index d083d32..ddadd1b 100644
--- a/modules/filters/mod_data.c
+++ b/modules/filters/mod_data.c
@@ -107,8 +107,8 @@ static apr_status_t data_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
if (content_length) {
apr_off_t len, clen;
apr_brigade_length(ctx->bb, 1, &len);
- clen = apr_atoi64(content_length);
- if (clen >= 0 && clen < APR_INT32_MAX) {
+ if (ap_parse_strict_length(&clen, content_length)
+ && clen < APR_INT32_MAX) {
ap_set_content_length(r, len +
apr_base64_encode_len((int)clen) - 1);
}
diff --git a/modules/filters/mod_deflate.c b/modules/filters/mod_deflate.c
index d60b2de..5a541e7 100644
--- a/modules/filters/mod_deflate.c
+++ b/modules/filters/mod_deflate.c
@@ -43,10 +43,11 @@
#include "apr_general.h"
#include "util_filter.h"
#include "apr_buckets.h"
+#include "http_protocol.h"
#include "http_request.h"
+#include "http_ssl.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"
-#include "mod_ssl.h"
#include "zlib.h"
@@ -56,15 +57,20 @@ module AP_MODULE_DECLARE_DATA deflate_module;
#define AP_INFLATE_RATIO_LIMIT 200
#define AP_INFLATE_RATIO_BURST 3
+#define AP_DEFLATE_ETAG_ADDSUFFIX 0
+#define AP_DEFLATE_ETAG_NOCHANGE 1
+#define AP_DEFLATE_ETAG_REMOVE 2
+
typedef struct deflate_filter_config_t
{
int windowSize;
int memlevel;
int compressionlevel;
- apr_size_t bufferSize;
+ int bufferSize;
const char *note_ratio_name;
const char *note_input_name;
const char *note_output_name;
+ int etag_opt;
} deflate_filter_config;
typedef struct deflate_dirconf_t {
@@ -94,8 +100,6 @@ static const char deflate_magic[2] = { '\037', '\213' };
#define DEFAULT_MEMLEVEL 9
#define DEFAULT_BUFFERSIZE 8096
-static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *mod_deflate_ssl_var = NULL;
-
/* Check whether a request is gzipped, so we can un-gzip it.
* If a request has multiple encodings, we need the gzip
* to be the outermost non-identity encoding.
@@ -118,8 +122,8 @@ static int check_gzip(request_rec *r, apr_table_t *hdrs1, apr_table_t *hdrs2)
if (encoding && *encoding) {
/* check the usual/simple case first */
- if (!strcasecmp(encoding, "gzip")
- || !strcasecmp(encoding, "x-gzip")) {
+ if (!ap_cstr_casecmp(encoding, "gzip")
+ || !ap_cstr_casecmp(encoding, "x-gzip")) {
found = 1;
if (hdrs) {
apr_table_unset(hdrs, "Content-Encoding");
@@ -137,8 +141,8 @@ static int check_gzip(request_rec *r, apr_table_t *hdrs1, apr_table_t *hdrs2)
for(;;) {
char *token = ap_strrchr(new_encoding, ',');
if (!token) { /* gzip:identity or other:identity */
- if (!strcasecmp(new_encoding, "gzip")
- || !strcasecmp(new_encoding, "x-gzip")) {
+ if (!ap_cstr_casecmp(new_encoding, "gzip")
+ || !ap_cstr_casecmp(new_encoding, "x-gzip")) {
found = 1;
if (hdrs) {
apr_table_unset(hdrs, "Content-Encoding");
@@ -150,8 +154,8 @@ static int check_gzip(request_rec *r, apr_table_t *hdrs1, apr_table_t *hdrs2)
break; /* seen all tokens */
}
for (ptr=token+1; apr_isspace(*ptr); ++ptr);
- if (!strcasecmp(ptr, "gzip")
- || !strcasecmp(ptr, "x-gzip")) {
+ if (!ap_cstr_casecmp(ptr, "gzip")
+ || !ap_cstr_casecmp(ptr, "x-gzip")) {
*token = '\0';
if (hdrs) {
apr_table_setn(hdrs, "Content-Encoding", new_encoding);
@@ -161,7 +165,7 @@ static int check_gzip(request_rec *r, apr_table_t *hdrs1, apr_table_t *hdrs2)
}
found = 1;
}
- else if (!ptr[0] || !strcasecmp(ptr, "identity")) {
+ else if (!ptr[0] || !ap_cstr_casecmp(ptr, "identity")) {
*token = '\0';
continue; /* strip the token and find the next one */
}
@@ -250,7 +254,7 @@ static const char *deflate_set_buffer_size(cmd_parms *cmd, void *dummy,
return "DeflateBufferSize should be positive";
}
- c->bufferSize = (apr_size_t)n;
+ c->bufferSize = n;
return NULL;
}
@@ -296,6 +300,29 @@ static const char *deflate_set_memlevel(cmd_parms *cmd, void *dummy,
return NULL;
}
+static const char *deflate_set_etag(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
+ &deflate_module);
+
+ if (!strcasecmp(arg, "NoChange")) {
+ c->etag_opt = AP_DEFLATE_ETAG_NOCHANGE;
+ }
+ else if (!strcasecmp(arg, "AddSuffix")) {
+ c->etag_opt = AP_DEFLATE_ETAG_ADDSUFFIX;
+ }
+ else if (!strcasecmp(arg, "Remove")) {
+ c->etag_opt = AP_DEFLATE_ETAG_REMOVE;
+ }
+ else {
+ return "DeflateAlterETAG accepts only 'NoChange', 'AddSuffix', and 'Remove'";
+ }
+
+ return NULL;
+}
+
+
static const char *deflate_set_compressionlevel(cmd_parms *cmd, void *dummy,
const char *arg)
{
@@ -389,35 +416,40 @@ typedef struct deflate_ctx_t
/* Do update ctx->crc, see comment in flush_libz_buffer */
#define UPDATE_CRC 1
+static void consume_buffer(deflate_ctx *ctx, deflate_filter_config *c,
+ int len, int crc, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+
+ /*
+ * Do we need to update ctx->crc? Usually this is the case for
+ * inflate action where we need to do a crc on the output, whereas
+ * in the deflate case we need to do a crc on the input
+ */
+ if (crc) {
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+ }
+
+ b = apr_bucket_heap_create((char *)ctx->buffer, len, NULL,
+ bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ ctx->stream.next_out = ctx->buffer;
+ ctx->stream.avail_out = c->bufferSize;
+}
+
static int flush_libz_buffer(deflate_ctx *ctx, deflate_filter_config *c,
- struct apr_bucket_alloc_t *bucket_alloc,
int (*libz_func)(z_streamp, int), int flush,
int crc)
{
int zRC = Z_OK;
int done = 0;
- unsigned int deflate_len;
- apr_bucket *b;
+ int deflate_len;
for (;;) {
deflate_len = c->bufferSize - ctx->stream.avail_out;
-
- if (deflate_len != 0) {
- /*
- * Do we need to update ctx->crc? Usually this is the case for
- * inflate action where we need to do a crc on the output, whereas
- * in the deflate case we need to do a crc on the input
- */
- if (crc) {
- ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer,
- deflate_len);
- }
- b = apr_bucket_heap_create((char *)ctx->buffer,
- deflate_len, NULL,
- bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
- ctx->stream.next_out = ctx->buffer;
- ctx->stream.avail_out = c->bufferSize;
+ if (deflate_len > 0) {
+ consume_buffer(ctx, c, deflate_len, crc, ctx->bb);
}
if (done)
@@ -465,11 +497,16 @@ static apr_status_t deflate_ctx_cleanup(void *data)
* value inside the double-quotes if an ETag has already been set
* and its value already contains double-quotes. PR 39727
*/
-static void deflate_check_etag(request_rec *r, const char *transform)
+static void deflate_check_etag(request_rec *r, const char *transform, int etag_opt)
{
const char *etag = apr_table_get(r->headers_out, "ETag");
apr_size_t etaglen;
+ if (etag_opt == AP_DEFLATE_ETAG_REMOVE) {
+ apr_table_unset(r->headers_out, "ETag");
+ return;
+ }
+
if ((etag && ((etaglen = strlen(etag)) > 2))) {
if (etag[etaglen - 1] == '"') {
apr_size_t transformlen = strlen(transform);
@@ -514,10 +551,8 @@ static int check_ratio(request_rec *r, deflate_ctx *ctx,
static int have_ssl_compression(request_rec *r)
{
const char *comp;
- if (mod_deflate_ssl_var == NULL)
- return 0;
- comp = mod_deflate_ssl_var(r->pool, r->server, r->connection, r,
- "SSL_COMPRESS_METHOD");
+ comp = ap_ssl_var_lookup(r->pool, r->server, r->connection, r,
+ "SSL_COMPRESS_METHOD");
if (comp == NULL || *comp == '\0' || strcmp(comp, "NULL") == 0)
return 0;
return 1;
@@ -530,6 +565,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
request_rec *r = f->r;
deflate_ctx *ctx = f->ctx;
int zRC;
+ apr_status_t rv;
apr_size_t len = 0, blen;
const char *data;
deflate_filter_config *c;
@@ -586,9 +622,14 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
continue;
}
- rc = apr_bucket_read(e, &data, &blen, APR_BLOCK_READ);
- if (rc != APR_SUCCESS)
- return rc;
+ if (e->length == (apr_size_t)-1) {
+ rc = apr_bucket_read(e, &data, &blen, APR_BLOCK_READ);
+ if (rc != APR_SUCCESS)
+ return rc;
+ }
+ else {
+ blen = e->length;
+ }
len += blen;
/* 50 is for Content-Encoding and Vary headers and ETag suffix */
if (len > sizeof(gzip_header) + VALIDATION_SIZE + 50)
@@ -699,6 +740,8 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
*/
if (!apr_table_get(r->subprocess_env, "force-gzip")) {
const char *accepts;
+ const char *q = NULL;
+
/* if they don't have the line, then they can't play */
accepts = apr_table_get(r->headers_in, "Accept-Encoding");
if (accepts == NULL) {
@@ -707,7 +750,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
}
token = ap_get_token(r->pool, &accepts, 0);
- while (token && token[0] && strcasecmp(token, "gzip")) {
+ while (token && token[0] && ap_cstr_casecmp(token, "gzip")) {
/* skip parameters, XXX: ;q=foo evaluation? */
while (*accepts == ';') {
++accepts;
@@ -721,10 +764,21 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
}
- /* No acceptable token found. */
- if (token == NULL || token[0] == '\0') {
+ /* Find the qvalue, if provided */
+ if (*accepts) {
+ while (*accepts == ';') {
+ ++accepts;
+ }
+ q = ap_get_token(r->pool, &accepts, 1);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "token: '%s' - q: '%s'", token ? token : "NULL", q);
+ }
+
+ /* No acceptable token found or q=0 */
+ if (!token || token[0] == '\0' ||
+ (q && strlen(q) >= 3 && strncmp("q=0.000", q, strlen(q)) == 0)) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "Not compressing (no Accept-Encoding: gzip)");
+ "Not compressing (no Accept-Encoding: gzip or q=0)");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
@@ -781,7 +835,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
*/
/* If the entire Content-Encoding is "identity", we can replace it. */
- if (!encoding || !strcasecmp(encoding, "identity")) {
+ if (!encoding || !ap_cstr_casecmp(encoding, "identity")) {
apr_table_setn(r->headers_out, "Content-Encoding", "gzip");
}
else {
@@ -794,7 +848,9 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
}
apr_table_unset(r->headers_out, "Content-Length");
apr_table_unset(r->headers_out, "Content-MD5");
- deflate_check_etag(r, "gzip");
+ if (c->etag_opt != AP_DEFLATE_ETAG_NOCHANGE) {
+ deflate_check_etag(r, "gzip", c->etag_opt);
+ }
/* For a 304 response, only change the headers */
if (r->status == HTTP_NOT_MODIFIED) {
@@ -841,8 +897,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
ctx->stream.avail_in = 0; /* should be zero already anyway */
/* flush the remaining data from the zlib buffers */
- flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate, Z_FINISH,
- NO_UPDATE_CRC);
+ flush_libz_buffer(ctx, c, deflate, Z_FINISH, NO_UPDATE_CRC);
buf = apr_palloc(r->pool, VALIDATION_SIZE);
putLong((unsigned char *)&buf[0], ctx->crc);
@@ -852,8 +907,10 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01384)
- "Zlib: Compressed %ld to %ld : URL %s",
- ctx->stream.total_in, ctx->stream.total_out, r->uri);
+ "Zlib: Compressed %" APR_UINT64_T_FMT
+ " to %" APR_UINT64_T_FMT " : URL %s",
+ (apr_uint64_t)ctx->stream.total_in,
+ (apr_uint64_t)ctx->stream.total_out, r->uri);
/* leave notes for logging */
if (c->note_input_name) {
@@ -866,7 +923,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
if (c->note_output_name) {
apr_table_setn(r->notes, c->note_output_name,
- (ctx->stream.total_in > 0)
+ (ctx->stream.total_out > 0)
? apr_off_t_toa(r->pool,
ctx->stream.total_out)
: "-");
@@ -883,6 +940,10 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
}
deflateEnd(&ctx->stream);
+
+ /* We've ended the libz stream, so remove ourselves. */
+ ap_remove_output_filter(f);
+
/* No need for cleanup any longer */
apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
@@ -893,15 +954,15 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
/* Okay, we've seen the EOS.
* Time to pass it along down the chain.
*/
- return ap_pass_brigade(f->next, ctx->bb);
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
+ return rv;
}
if (APR_BUCKET_IS_FLUSH(e)) {
- apr_status_t rv;
-
/* flush the remaining data from the zlib buffers */
- zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate,
- Z_SYNC_FLUSH, NO_UPDATE_CRC);
+ zRC = flush_libz_buffer(ctx, c, deflate, Z_SYNC_FLUSH,
+ NO_UPDATE_CRC);
if (zRC != Z_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01385)
"Zlib error %d flushing zlib output buffer (%s)",
@@ -913,6 +974,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
APR_BUCKET_REMOVE(e);
APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
if (rv != APR_SUCCESS) {
return rv;
}
@@ -930,7 +992,12 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
}
/* read */
- apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10298)
+ "failed reading from %s bucket", e->type->name);
+ return rv;
+ }
if (!len) {
apr_bucket_delete(e);
continue;
@@ -947,21 +1014,15 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
ctx->stream.next_in = (unsigned char *)data; /* We just lost const-ness,
* but we'll just have to
* trust zlib */
- ctx->stream.avail_in = len;
+ ctx->stream.avail_in = (int)len;
while (ctx->stream.avail_in != 0) {
if (ctx->stream.avail_out == 0) {
- apr_status_t rv;
-
- ctx->stream.next_out = ctx->buffer;
- len = c->bufferSize - ctx->stream.avail_out;
+ consume_buffer(ctx, c, c->bufferSize, NO_UPDATE_CRC, ctx->bb);
- b = apr_bucket_heap_create((char *)ctx->buffer, len,
- NULL, f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
- ctx->stream.avail_out = c->bufferSize;
/* Send what we have right now to the next filter. */
rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
if (rv != APR_SUCCESS) {
return rv;
}
@@ -1258,44 +1319,40 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
if (APR_BUCKET_IS_FLUSH(bkt)) {
apr_bucket *tmp_b;
- ctx->inflate_total += ctx->stream.avail_out;
- zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
- ctx->inflate_total -= ctx->stream.avail_out;
- if (zRC != Z_OK) {
- inflateEnd(&ctx->stream);
- ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01391)
- "Zlib error %d inflating data (%s)", zRC,
- ctx->stream.msg);
- return APR_EGENERAL;
- }
+ if (!ctx->done) {
+ ctx->inflate_total += ctx->stream.avail_out;
+ zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
+ ctx->inflate_total -= ctx->stream.avail_out;
+ if (zRC != Z_OK) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01391)
+ "Zlib error %d inflating data (%s)", zRC,
+ ctx->stream.msg);
+ return APR_EGENERAL;
+ }
- if (inflate_limit && ctx->inflate_total > inflate_limit) {
- inflateEnd(&ctx->stream);
- ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02647)
- "Inflated content length of %" APR_OFF_T_FMT
- " is larger than the configured limit"
- " of %" APR_OFF_T_FMT,
- ctx->inflate_total, inflate_limit);
- return APR_ENOSPC;
- }
-
- if (!check_ratio(r, ctx, dc)) {
- inflateEnd(&ctx->stream);
- ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02805)
- "Inflated content ratio is larger than the "
- "configured limit %i by %i time(s)",
- dc->ratio_limit, dc->ratio_burst);
- return APR_EINVAL;
- }
+ if (inflate_limit && ctx->inflate_total > inflate_limit) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02647)
+ "Inflated content length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT,
+ ctx->inflate_total, inflate_limit);
+ return APR_ENOSPC;
+ }
- len = c->bufferSize - ctx->stream.avail_out;
- ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
- tmp_b = apr_bucket_heap_create((char *)ctx->buffer, len,
- NULL, f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_b);
+ if (!check_ratio(r, ctx, dc)) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02805)
+ "Inflated content ratio is larger than the "
+ "configured limit %i by %i time(s)",
+ dc->ratio_limit, dc->ratio_burst);
+ return APR_EINVAL;
+ }
- ctx->stream.next_out = ctx->buffer;
- ctx->stream.avail_out = c->bufferSize;
+ consume_buffer(ctx, c, c->bufferSize - ctx->stream.avail_out,
+ UPDATE_CRC, ctx->proc_bb);
+ }
/* Flush everything so far in the returning brigade, but continue
* reading should EOS/more follow (don't lose them).
@@ -1338,21 +1395,11 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
ctx->stream.next_in = (unsigned char *)data;
ctx->stream.avail_in = (int)len;
- zRC = Z_OK;
-
if (!ctx->validation_buffer) {
while (ctx->stream.avail_in != 0) {
if (ctx->stream.avail_out == 0) {
- apr_bucket *tmp_heap;
-
- ctx->stream.next_out = ctx->buffer;
- len = c->bufferSize - ctx->stream.avail_out;
-
- ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
- tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
- NULL, f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
- ctx->stream.avail_out = c->bufferSize;
+ consume_buffer(ctx, c, c->bufferSize, UPDATE_CRC,
+ ctx->proc_bb);
}
ctx->inflate_total += ctx->stream.avail_out;
@@ -1395,7 +1442,6 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
}
if (ctx->validation_buffer) {
- apr_bucket *tmp_heap;
apr_size_t avail, valid;
unsigned char *buf = ctx->validation_buffer;
@@ -1419,17 +1465,13 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
ctx->validation_buffer_length += valid;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01393)
- "Zlib: Inflated %ld to %ld : URL %s",
- ctx->stream.total_in, ctx->stream.total_out,
- r->uri);
+ "Zlib: Inflated %" APR_UINT64_T_FMT
+ " to %" APR_UINT64_T_FMT " : URL %s",
+ (apr_uint64_t)ctx->stream.total_in,
+ (apr_uint64_t)ctx->stream.total_out, r->uri);
- len = c->bufferSize - ctx->stream.avail_out;
-
- ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
- tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
- NULL, f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
- ctx->stream.avail_out = c->bufferSize;
+ consume_buffer(ctx, c, c->bufferSize - ctx->stream.avail_out,
+ UPDATE_CRC, ctx->proc_bb);
{
unsigned long compCRC, compLen;
@@ -1445,9 +1487,10 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
if ((ctx->stream.total_out & 0xFFFFFFFF) != compLen) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01395)
- "Zlib: Length %ld of inflated data does "
- "not match expected value %ld",
- ctx->stream.total_out, compLen);
+ "Zlib: Length %" APR_UINT64_T_FMT
+ " of inflated data does not match"
+ " expected value %ld",
+ (apr_uint64_t)ctx->stream.total_out, compLen);
return APR_EGENERAL;
}
}
@@ -1474,16 +1517,8 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
if (block == APR_BLOCK_READ &&
APR_BRIGADE_EMPTY(ctx->proc_bb) &&
ctx->stream.avail_out < c->bufferSize) {
- apr_bucket *tmp_heap;
- apr_size_t len;
- ctx->stream.next_out = ctx->buffer;
- len = c->bufferSize - ctx->stream.avail_out;
-
- ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
- tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
- NULL, f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
- ctx->stream.avail_out = c->bufferSize;
+ consume_buffer(ctx, c, c->bufferSize - ctx->stream.avail_out,
+ UPDATE_CRC, ctx->proc_bb);
}
if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
@@ -1549,7 +1584,9 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
*/
apr_table_unset(r->headers_out, "Content-Length");
apr_table_unset(r->headers_out, "Content-MD5");
- deflate_check_etag(r, "gunzip");
+ if (c->etag_opt != AP_DEFLATE_ETAG_NOCHANGE) {
+ deflate_check_etag(r, "gunzip", c->etag_opt);
+ }
/* For a 304 response, only change the headers */
if (r->status == HTTP_NOT_MODIFIED) {
@@ -1597,7 +1634,6 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
while (!APR_BRIGADE_EMPTY(bb))
{
const char *data;
- apr_bucket *b;
apr_size_t len;
e = APR_BRIGADE_FIRST(bb);
@@ -1619,11 +1655,12 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
* fails, whereas in the deflate case you can empty a filled output
* buffer and call it again until no more output can be created.
*/
- flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate, Z_SYNC_FLUSH,
- UPDATE_CRC);
+ flush_libz_buffer(ctx, c, inflate, Z_SYNC_FLUSH, UPDATE_CRC);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01398)
- "Zlib: Inflated %ld to %ld : URL %s",
- ctx->stream.total_in, ctx->stream.total_out, r->uri);
+ "Zlib: Inflated %" APR_UINT64_T_FMT
+ " to %" APR_UINT64_T_FMT " : URL %s",
+ (apr_uint64_t)ctx->stream.total_in,
+ (apr_uint64_t)ctx->stream.total_out, r->uri);
if (ctx->validation_buffer_length == VALIDATION_SIZE) {
unsigned long compCRC, compLen;
@@ -1660,15 +1697,14 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
* Okay, we've seen the EOS.
* Time to pass it along down the chain.
*/
- return ap_pass_brigade(f->next, ctx->bb);
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
+ return rv;
}
if (APR_BUCKET_IS_FLUSH(e)) {
- apr_status_t rv;
-
/* flush the remaining data from the zlib buffers */
- zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate,
- Z_SYNC_FLUSH, UPDATE_CRC);
+ zRC = flush_libz_buffer(ctx, c, inflate, Z_SYNC_FLUSH, UPDATE_CRC);
if (zRC == Z_STREAM_END) {
if (ctx->validation_buffer == NULL) {
ctx->validation_buffer = apr_pcalloc(f->r->pool,
@@ -1686,6 +1722,7 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
APR_BUCKET_REMOVE(e);
APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
if (rv != APR_SUCCESS) {
return rv;
}
@@ -1802,16 +1839,11 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
while (ctx->stream.avail_in != 0) {
if (ctx->stream.avail_out == 0) {
- ctx->stream.next_out = ctx->buffer;
- len = c->bufferSize - ctx->stream.avail_out;
-
- ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
- b = apr_bucket_heap_create((char *)ctx->buffer, len,
- NULL, f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
- ctx->stream.avail_out = c->bufferSize;
+ consume_buffer(ctx, c, c->bufferSize, UPDATE_CRC, ctx->bb);
+
/* Send what we have right now to the next filter. */
rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
if (rv != APR_SUCCESS) {
return rv;
}
@@ -1826,6 +1858,7 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
return APR_EGENERAL;
}
+ /* Don't check length limits on inflate_out */
if (!check_ratio(r, ctx, dc)) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02650)
"Inflated content ratio is larger than the "
@@ -1868,7 +1901,6 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
static int mod_deflate_post_config(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
- mod_deflate_ssl_var = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
return OK;
}
@@ -1904,6 +1936,9 @@ static const command_rec deflate_filter_cmds[] = {
AP_INIT_TAKE1("DeflateInflateRatioBurst", deflate_set_inflate_ratio_burst, NULL, OR_ALL,
"Set the maximum number of following inflate ratios above limit "
"(default: " APR_STRINGIFY(AP_INFLATE_RATIO_BURST) ")"),
+ AP_INIT_TAKE1("DeflateAlterEtag", deflate_set_etag, NULL, RSRC_CONF,
+ "Set how mod_deflate should modify ETAG response headers: 'AddSuffix' (default), 'NoChange' (2.2.x behavior), 'Remove'"),
+
{NULL}
};
diff --git a/modules/filters/mod_ext_filter.c b/modules/filters/mod_ext_filter.c
index 7aac19d..7afd8dd 100644
--- a/modules/filters/mod_ext_filter.c
+++ b/modules/filters/mod_ext_filter.c
@@ -655,8 +655,7 @@ static apr_status_t drain_available_output(ap_filter_t *f,
if (rv && !APR_STATUS_IS_EAGAIN(rv))
lvl = APLOG_DEBUG;
ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01460)
- "apr_file_read(child output), len %" APR_SIZE_T_FMT,
- !rv ? len : -1);
+ "apr_file_read(child output), len %" APR_SIZE_T_FMT, len);
if (rv != APR_SUCCESS) {
return rv;
}
@@ -810,8 +809,7 @@ static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
if (rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv))
lvl = APLOG_ERR;
ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01466)
- "apr_file_read(child output), len %" APR_SIZE_T_FMT,
- !rv ? len : -1);
+ "apr_file_read(child output), len %" APR_SIZE_T_FMT, len);
if (APR_STATUS_IS_EAGAIN(rv)) {
if (eos) {
/* should not occur, because we have an APR timeout in place */
diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c
index a46a944..584d8fb 100644
--- a/modules/filters/mod_include.c
+++ b/modules/filters/mod_include.c
@@ -1967,25 +1967,25 @@ static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
token = apr_strtok(d, ", \t", &last);
while (token) {
- if (!strcasecmp(token, "none")) {
+ if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
- else if (!strcasecmp(token, "url")) {
+ else if (!ap_cstr_casecmp(token, "url")) {
char *buf = apr_pstrdup(ctx->pool, echo_text);
ap_unescape_url(buf);
echo_text = buf;
}
- else if (!strcasecmp(token, "urlencoded")) {
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
char *buf = apr_pstrdup(ctx->pool, echo_text);
ap_unescape_urlencoded(buf);
echo_text = buf;
}
- else if (!strcasecmp(token, "entity")) {
+ else if (!ap_cstr_casecmp(token, "entity")) {
char *buf = apr_pstrdup(ctx->pool, echo_text);
decodehtml(buf);
echo_text = buf;
}
- else if (!strcasecmp(token, "base64")) {
+ else if (!ap_cstr_casecmp(token, "base64")) {
echo_text = ap_pbase64decode(ctx->dpool, echo_text);
}
else {
@@ -2003,19 +2003,19 @@ static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
token = apr_strtok(e, ", \t", &last);
while (token) {
- if (!strcasecmp(token, "none")) {
+ if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
- else if (!strcasecmp(token, "url")) {
+ else if (!ap_cstr_casecmp(token, "url")) {
echo_text = ap_escape_uri(ctx->dpool, echo_text);
}
- else if (!strcasecmp(token, "urlencoded")) {
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
echo_text = ap_escape_urlencoded(ctx->dpool, echo_text);
}
- else if (!strcasecmp(token, "entity")) {
+ else if (!ap_cstr_casecmp(token, "entity")) {
echo_text = ap_escape_html2(ctx->dpool, echo_text, 0);
}
- else if (!strcasecmp(token, "base64")) {
+ else if (!ap_cstr_casecmp(token, "base64")) {
char *buf;
buf = ap_pbase64encode(ctx->dpool, (char *)echo_text);
echo_text = buf;
@@ -2605,25 +2605,25 @@ static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
token = apr_strtok(d, ", \t", &last);
while (token) {
- if (!strcasecmp(token, "none")) {
+ if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
- else if (!strcasecmp(token, "url")) {
+ else if (!ap_cstr_casecmp(token, "url")) {
char *buf = apr_pstrdup(ctx->pool, parsed_string);
ap_unescape_url(buf);
parsed_string = buf;
}
- else if (!strcasecmp(token, "urlencoded")) {
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
char *buf = apr_pstrdup(ctx->pool, parsed_string);
ap_unescape_urlencoded(buf);
parsed_string = buf;
}
- else if (!strcasecmp(token, "entity")) {
+ else if (!ap_cstr_casecmp(token, "entity")) {
char *buf = apr_pstrdup(ctx->pool, parsed_string);
decodehtml(buf);
parsed_string = buf;
}
- else if (!strcasecmp(token, "base64")) {
+ else if (!ap_cstr_casecmp(token, "base64")) {
parsed_string = ap_pbase64decode(ctx->dpool, parsed_string);
}
else {
@@ -2641,19 +2641,19 @@ static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
token = apr_strtok(e, ", \t", &last);
while (token) {
- if (!strcasecmp(token, "none")) {
+ if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
- else if (!strcasecmp(token, "url")) {
+ else if (!ap_cstr_casecmp(token, "url")) {
parsed_string = ap_escape_uri(ctx->dpool, parsed_string);
}
- else if (!strcasecmp(token, "urlencoded")) {
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string);
}
- else if (!strcasecmp(token, "entity")) {
+ else if (!ap_cstr_casecmp(token, "entity")) {
parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0);
}
- else if (!strcasecmp(token, "base64")) {
+ else if (!ap_cstr_casecmp(token, "base64")) {
char *buf;
buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string);
parsed_string = buf;
@@ -3855,6 +3855,7 @@ static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
ctx->pool = r->pool;
apr_pool_create(&ctx->dpool, ctx->pool);
+ apr_pool_tag(ctx->dpool, "includes_dpool");
/* runtime data */
intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
diff --git a/modules/filters/mod_proxy_html.c b/modules/filters/mod_proxy_html.c
index ea6bf03..7783da1 100644
--- a/modules/filters/mod_proxy_html.c
+++ b/modules/filters/mod_proxy_html.c
@@ -29,9 +29,28 @@
#define VERBOSEB(x) if (verbose) {x}
#endif
+/* libxml2 includes unicode/[...].h files which uses C++ comments */
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wcomment"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wcomment"
+#endif
+#endif
+
/* libxml2 */
#include
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
#include "http_protocol.h"
#include "http_config.h"
#include "http_log.h"
@@ -88,7 +107,7 @@ typedef struct {
const char *doctype;
const char *etag;
unsigned int flags;
- size_t bufsz;
+ int bufsz;
apr_hash_t *links;
apr_array_header_t *events;
const char *charset_out;
@@ -660,7 +679,7 @@ static meta *metafix(request_rec *r, const char *buf, apr_size_t len)
while (!apr_isalpha(*++p));
for (q = p; apr_isalnum(*q) || (*q == '-'); ++q);
header = apr_pstrmemdup(r->pool, p, q-p);
- if (strncasecmp(header, "Content-", 8)) {
+ if (ap_cstr_casecmpn(header, "Content-", 8)) {
/* find content=... string */
p = apr_strmatch(seek_content, buf+offs+pmatch[0].rm_so,
pmatch[0].rm_eo - pmatch[0].rm_so);
@@ -677,7 +696,7 @@ static meta *metafix(request_rec *r, const char *buf, apr_size_t len)
if ((*p == '\'') || (*p == '"')) {
delim = *p++;
for (q = p; *q && *q != delim; ++q);
- /* No terminating delimiter found? Skip the boggus directive */
+ /* No terminating delimiter found? Skip the bogus directive */
if (*q != delim)
break;
} else {
@@ -688,7 +707,7 @@ static meta *metafix(request_rec *r, const char *buf, apr_size_t len)
}
}
}
- else if (!strncasecmp(header, "Content-Type", 12)) {
+ else if (!ap_cstr_casecmpn(header, "Content-Type", 12)) {
ret = apr_palloc(r->pool, sizeof(meta));
ret->start = offs+pmatch[0].rm_so;
ret->end = offs+pmatch[0].rm_eo;
@@ -817,8 +836,8 @@ static saxctxt *check_filter_init (ap_filter_t *f)
else if (!f->r->content_type) {
errmsg = "No content-type; bailing out of proxy-html filter";
}
- else if (strncasecmp(f->r->content_type, "text/html", 9) &&
- strncasecmp(f->r->content_type,
+ else if (ap_cstr_casecmpn(f->r->content_type, "text/html", 9) &&
+ ap_cstr_casecmpn(f->r->content_type,
"application/xhtml+xml", 21)) {
errmsg = "Non-HTML content; not inserting proxy-html filter";
}
diff --git a/modules/filters/mod_reflector.c b/modules/filters/mod_reflector.c
index 961092d..5979cb8 100644
--- a/modules/filters/mod_reflector.c
+++ b/modules/filters/mod_reflector.c
@@ -91,11 +91,16 @@ static int reflector_handler(request_rec * r)
/* reflect the content length, if present */
if ((content_length = apr_table_get(r->headers_in, "Content-Length"))) {
- apr_off_t offset;
+ apr_off_t clen;
- apr_strtoff(&offset, content_length, NULL, 10);
- ap_set_content_length(r, offset);
+ if (!ap_parse_strict_length(&clen, content_length)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10243)
+ "reflector_handler: invalid content-length '%s'",
+ content_length);
+ return HTTP_BAD_REQUEST;
+ }
+ ap_set_content_length(r, clen);
}
/* reflect the content type, if present */
diff --git a/modules/filters/mod_reqtimeout.c b/modules/filters/mod_reqtimeout.c
index 538e9b1..0ebd78a 100644
--- a/modules/filters/mod_reqtimeout.c
+++ b/modules/filters/mod_reqtimeout.c
@@ -29,23 +29,29 @@
module AP_MODULE_DECLARE_DATA reqtimeout_module;
#define UNSET -1
-#define MRT_DEFAULT_HEADER_TIMEOUT 20
-#define MRT_DEFAULT_HEADER_MAX_TIMEOUT 40
-#define MRT_DEFAULT_HEADER_MIN_RATE 500
-#define MRT_DEFAULT_BODY_TIMEOUT 20
-#define MRT_DEFAULT_BODY_MAX_TIMEOUT 0
-#define MRT_DEFAULT_BODY_MIN_RATE 500
+#define MRT_DEFAULT_handshake_TIMEOUT 0 /* disabled */
+#define MRT_DEFAULT_handshake_MAX_TIMEOUT 0
+#define MRT_DEFAULT_handshake_MIN_RATE 0
+#define MRT_DEFAULT_header_TIMEOUT 20
+#define MRT_DEFAULT_header_MAX_TIMEOUT 40
+#define MRT_DEFAULT_header_MIN_RATE 500
+#define MRT_DEFAULT_body_TIMEOUT 20
+#define MRT_DEFAULT_body_MAX_TIMEOUT 0
+#define MRT_DEFAULT_body_MIN_RATE 500
typedef struct
{
- int header_timeout; /* timeout for reading the req hdrs in secs */
- int header_max_timeout; /* max timeout for req hdrs in secs */
- int header_min_rate; /* min rate for reading req hdrs in bytes/s */
- apr_time_t header_rate_factor;
- int body_timeout; /* timeout for reading the req body in secs */
- int body_max_timeout; /* max timeout for req body in secs */
- int body_min_rate; /* min rate for reading req body in bytes/s */
- apr_time_t body_rate_factor;
+ int timeout; /* timeout in secs */
+ int max_timeout; /* max timeout in secs */
+ int min_rate; /* min rate in bytes/s */
+ apr_time_t rate_factor; /* scale factor (#usecs per min_rate) */
+} reqtimeout_stage_t;
+
+typedef struct
+{
+ reqtimeout_stage_t handshake; /* Handshaking (TLS) */
+ reqtimeout_stage_t header; /* Reading the HTTP header */
+ reqtimeout_stage_t body; /* Reading the HTTP body */
} reqtimeout_srv_cfg;
/* this struct is used both as conn_config and as filter context */
@@ -53,17 +59,15 @@ typedef struct
{
apr_time_t timeout_at;
apr_time_t max_timeout_at;
- int min_rate;
- int new_timeout;
- int new_max_timeout;
+ reqtimeout_stage_t cur_stage;
int in_keep_alive;
char *type;
apr_socket_t *socket;
- apr_time_t rate_factor;
apr_bucket_brigade *tmpbb;
} reqtimeout_con_cfg;
static const char *const reqtimeout_filter_name = "reqtimeout";
+static int default_handshake_rate_factor;
static int default_header_rate_factor;
static int default_body_rate_factor;
@@ -75,7 +79,7 @@ static void extend_timeout(reqtimeout_con_cfg *ccfg, apr_bucket_brigade *bb)
if (apr_brigade_length(bb, 0, &len) != APR_SUCCESS || len <= 0)
return;
- new_timeout_at = ccfg->timeout_at + len * ccfg->rate_factor;
+ new_timeout_at = ccfg->timeout_at + len * ccfg->cur_stage.rate_factor;
if (ccfg->max_timeout_at > 0 && new_timeout_at > ccfg->max_timeout_at) {
ccfg->timeout_at = ccfg->max_timeout_at;
}
@@ -190,14 +194,14 @@ static apr_status_t reqtimeout_filter(ap_filter_t *f,
apr_brigade_cleanup(bb);
}
- if (ccfg->new_timeout > 0) {
+ if (ccfg->cur_stage.timeout > 0) {
/* set new timeout */
now = apr_time_now();
- ccfg->timeout_at = now + apr_time_from_sec(ccfg->new_timeout);
- ccfg->new_timeout = 0;
- if (ccfg->new_max_timeout > 0) {
- ccfg->max_timeout_at = now + apr_time_from_sec(ccfg->new_max_timeout);
- ccfg->new_max_timeout = 0;
+ ccfg->timeout_at = now + apr_time_from_sec(ccfg->cur_stage.timeout);
+ ccfg->cur_stage.timeout = 0;
+ if (ccfg->cur_stage.max_timeout > 0) {
+ ccfg->max_timeout_at = now + apr_time_from_sec(ccfg->cur_stage.max_timeout);
+ ccfg->cur_stage.max_timeout = 0;
}
}
else if (ccfg->timeout_at == 0) {
@@ -216,7 +220,7 @@ static apr_status_t reqtimeout_filter(ap_filter_t *f,
if (block == APR_NONBLOCK_READ || mode == AP_MODE_INIT
|| mode == AP_MODE_EATCRLF) {
rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
- if (ccfg->min_rate > 0 && rv == APR_SUCCESS) {
+ if (ccfg->cur_stage.rate_factor && rv == APR_SUCCESS) {
extend_timeout(ccfg, bb);
}
return rv;
@@ -250,7 +254,7 @@ static apr_status_t reqtimeout_filter(ap_filter_t *f,
}
if (!APR_BRIGADE_EMPTY(bb)) {
- if (ccfg->min_rate > 0) {
+ if (ccfg->cur_stage.rate_factor) {
extend_timeout(ccfg, bb);
}
@@ -309,9 +313,9 @@ static apr_status_t reqtimeout_filter(ap_filter_t *f,
rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
/* Don't extend the timeout in speculative mode, wait for
* the real (relevant) bytes to be asked later, within the
- * currently alloted time.
+ * currently allotted time.
*/
- if (ccfg->min_rate > 0 && rv == APR_SUCCESS
+ if (ccfg->cur_stage.rate_factor && rv == APR_SUCCESS
&& mode != AP_MODE_SPECULATIVE) {
extend_timeout(ccfg, bb);
}
@@ -350,6 +354,19 @@ static apr_status_t reqtimeout_eor(ap_filter_t *f, apr_bucket_brigade *bb)
return ap_pass_brigade(f->next, bb);
}
+#define INIT_STAGE(cfg, ccfg, stage) do { \
+ if (cfg->stage.timeout != UNSET) { \
+ ccfg->cur_stage.timeout = cfg->stage.timeout; \
+ ccfg->cur_stage.max_timeout = cfg->stage.max_timeout; \
+ ccfg->cur_stage.rate_factor = cfg->stage.rate_factor; \
+ } \
+ else { \
+ ccfg->cur_stage.timeout = MRT_DEFAULT_##stage##_TIMEOUT; \
+ ccfg->cur_stage.max_timeout = MRT_DEFAULT_##stage##_MAX_TIMEOUT; \
+ ccfg->cur_stage.rate_factor = default_##stage##_rate_factor; \
+ } \
+} while (0)
+
static int reqtimeout_init(conn_rec *c)
{
reqtimeout_con_cfg *ccfg;
@@ -358,7 +375,11 @@ static int reqtimeout_init(conn_rec *c)
cfg = ap_get_module_config(c->base_server->module_config,
&reqtimeout_module);
AP_DEBUG_ASSERT(cfg != NULL);
- if (cfg->header_timeout == 0 && cfg->body_timeout == 0) {
+
+ /* For compatibility, handshake timeout is disabled when UNSET (< 0) */
+ if (cfg->handshake.timeout <= 0
+ && cfg->header.timeout == 0
+ && cfg->body.timeout == 0) {
/* disabled for this vhost */
return DECLINED;
}
@@ -369,6 +390,11 @@ static int reqtimeout_init(conn_rec *c)
ap_set_module_config(c->conn_config, &reqtimeout_module, ccfg);
ap_add_output_filter(reqtimeout_filter_name, ccfg, NULL, c);
ap_add_input_filter(reqtimeout_filter_name, ccfg, NULL, c);
+
+ ccfg->type = "handshake";
+ if (cfg->handshake.timeout > 0) {
+ INIT_STAGE(cfg, ccfg, handshake);
+ }
}
/* we are not handling the connection, we just do initialization */
@@ -393,22 +419,11 @@ static void reqtimeout_before_header(request_rec *r, conn_rec *c)
/* (Re)set the state for this new request, but ccfg->socket and
* ccfg->tmpbb which have the lifetime of the connection.
*/
+ ccfg->type = "header";
ccfg->timeout_at = 0;
ccfg->max_timeout_at = 0;
ccfg->in_keep_alive = (c->keepalives > 0);
- ccfg->type = "header";
- if (cfg->header_timeout != UNSET) {
- ccfg->new_timeout = cfg->header_timeout;
- ccfg->new_max_timeout = cfg->header_max_timeout;
- ccfg->min_rate = cfg->header_min_rate;
- ccfg->rate_factor = cfg->header_rate_factor;
- }
- else {
- ccfg->new_timeout = MRT_DEFAULT_HEADER_TIMEOUT;
- ccfg->new_max_timeout = MRT_DEFAULT_HEADER_MAX_TIMEOUT;
- ccfg->min_rate = MRT_DEFAULT_HEADER_MIN_RATE;
- ccfg->rate_factor = default_header_rate_factor;
- }
+ INIT_STAGE(cfg, ccfg, header);
}
static int reqtimeout_before_body(request_rec *r)
@@ -421,64 +436,61 @@ static int reqtimeout_before_body(request_rec *r)
/* not configured for this connection */
return OK;
}
- cfg = ap_get_module_config(r->connection->base_server->module_config,
- &reqtimeout_module);
+ cfg = ap_get_module_config(r->server->module_config,
+ &reqtimeout_module);
AP_DEBUG_ASSERT(cfg != NULL);
+ ccfg->type = "body";
ccfg->timeout_at = 0;
ccfg->max_timeout_at = 0;
- ccfg->type = "body";
if (r->method_number == M_CONNECT) {
/* disabled for a CONNECT request */
- ccfg->new_timeout = 0;
- }
- else if (cfg->body_timeout != UNSET) {
- ccfg->new_timeout = cfg->body_timeout;
- ccfg->new_max_timeout = cfg->body_max_timeout;
- ccfg->min_rate = cfg->body_min_rate;
- ccfg->rate_factor = cfg->body_rate_factor;
+ ccfg->cur_stage.timeout = 0;
}
else {
- ccfg->new_timeout = MRT_DEFAULT_BODY_TIMEOUT;
- ccfg->new_max_timeout = MRT_DEFAULT_BODY_MAX_TIMEOUT;
- ccfg->min_rate = MRT_DEFAULT_BODY_MIN_RATE;
- ccfg->rate_factor = default_body_rate_factor;
+ INIT_STAGE(cfg, ccfg, body);
}
return OK;
}
+#define UNSET_STAGE(cfg, stage) do { \
+ cfg->stage.timeout = UNSET; \
+ cfg->stage.max_timeout = UNSET; \
+ cfg->stage.min_rate = UNSET; \
+} while (0)
+
static void *reqtimeout_create_srv_config(apr_pool_t *p, server_rec *s)
{
reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg));
- cfg->header_timeout = UNSET;
- cfg->header_max_timeout = UNSET;
- cfg->header_min_rate = UNSET;
- cfg->body_timeout = UNSET;
- cfg->body_max_timeout = UNSET;
- cfg->body_min_rate = UNSET;
+ UNSET_STAGE(cfg, handshake);
+ UNSET_STAGE(cfg, header);
+ UNSET_STAGE(cfg, body);
return cfg;
}
-#define MERGE_INT(cfg, b, a, val) cfg->val = (a->val == UNSET) ? b->val : a->val;
+#define MERGE_INT(cfg, base, add, val) \
+ cfg->val = (add->val == UNSET) ? base->val : add->val
+#define MERGE_STAGE(cfg, base, add, stage) do { \
+ MERGE_INT(cfg, base, add, stage.timeout); \
+ MERGE_INT(cfg, base, add, stage.max_timeout); \
+ MERGE_INT(cfg, base, add, stage.min_rate); \
+ cfg->stage.rate_factor = (cfg->stage.min_rate == UNSET) \
+ ? base->stage.rate_factor \
+ : add->stage.rate_factor; \
+} while (0)
+
static void *reqtimeout_merge_srv_config(apr_pool_t *p, void *base_, void *add_)
{
reqtimeout_srv_cfg *base = base_;
reqtimeout_srv_cfg *add = add_;
reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg));
- MERGE_INT(cfg, base, add, header_timeout);
- MERGE_INT(cfg, base, add, header_max_timeout);
- MERGE_INT(cfg, base, add, header_min_rate);
- MERGE_INT(cfg, base, add, body_timeout);
- MERGE_INT(cfg, base, add, body_max_timeout);
- MERGE_INT(cfg, base, add, body_min_rate);
-
- cfg->header_rate_factor = (cfg->header_min_rate == UNSET) ?
- base->header_rate_factor : add->header_rate_factor;
- cfg->body_rate_factor = (cfg->body_min_rate == UNSET) ?
- base->body_rate_factor : add->body_rate_factor;
+ MERGE_STAGE(cfg, base, add, handshake);
+ MERGE_STAGE(cfg, base, add, header);
+ MERGE_STAGE(cfg, base, add, body);
+
return cfg;
}
@@ -506,66 +518,59 @@ static const char *set_reqtimeout_param(reqtimeout_srv_cfg *conf,
{
const char *ret = NULL;
char *rate_str = NULL, *initial_str, *max_str = NULL;
- int rate = 0, initial = 0, max = 0;
- enum { PARAM_HEADER, PARAM_BODY } type;
+ reqtimeout_stage_t *stage;
- if (!strcasecmp(key, "header")) {
- type = PARAM_HEADER;
+ if (!strcasecmp(key, "handshake")) {
+ stage = &conf->handshake;
+ }
+ else if (!strcasecmp(key, "header")) {
+ stage = &conf->header;
}
else if (!strcasecmp(key, "body")) {
- type = PARAM_BODY;
+ stage = &conf->body;
}
else {
return "Unknown RequestReadTimeout parameter";
}
+ memset(stage, 0, sizeof(*stage));
+
if ((rate_str = ap_strcasestr(val, ",minrate="))) {
initial_str = apr_pstrndup(p, val, rate_str - val);
rate_str += strlen(",minrate=");
- ret = parse_int(p, rate_str, &rate);
+ ret = parse_int(p, rate_str, &stage->min_rate);
if (ret)
return ret;
- if (rate == 0)
+ if (stage->min_rate == 0)
return "Minimum data rate must be larger than 0";
if ((max_str = strchr(initial_str, '-'))) {
*max_str++ = '\0';
- ret = parse_int(p, max_str, &max);
+ ret = parse_int(p, max_str, &stage->max_timeout);
if (ret)
return ret;
}
- ret = parse_int(p, initial_str, &initial);
+ ret = parse_int(p, initial_str, &stage->timeout);
}
else {
if (ap_strchr_c(val, '-'))
return "Must set MinRate option if using timeout range";
- ret = parse_int(p, val, &initial);
+ ret = parse_int(p, val, &stage->timeout);
}
-
if (ret)
return ret;
- if (max && initial >= max) {
+ if (stage->max_timeout && stage->timeout >= stage->max_timeout) {
return "Maximum timeout must be larger than initial timeout";
}
- if (type == PARAM_HEADER) {
- conf->header_timeout = initial;
- conf->header_max_timeout = max;
- conf->header_min_rate = rate;
- if (rate)
- conf->header_rate_factor = apr_time_from_sec(1) / rate;
- }
- else {
- conf->body_timeout = initial;
- conf->body_max_timeout = max;
- conf->body_min_rate = rate;
- if (rate)
- conf->body_rate_factor = apr_time_from_sec(1) / rate;
+ if (stage->min_rate) {
+ stage->rate_factor = apr_time_from_sec(1) / stage->min_rate;
}
- return ret;
+
+ return NULL;
}
static const char *set_reqtimeouts(cmd_parms *cmd, void *mconfig,
@@ -603,8 +608,7 @@ static void reqtimeout_hooks(apr_pool_t *pool)
{
/*
* mod_ssl is AP_FTYPE_CONNECTION + 5 and mod_reqtimeout needs to
- * be called before mod_ssl. Otherwise repeated reads during the ssl
- * handshake can prevent the timeout from triggering.
+ * be called before mod_ssl for the handshake stage to catch SSL traffic.
*/
ap_register_input_filter(reqtimeout_filter_name, reqtimeout_filter, NULL,
AP_FTYPE_CONNECTION + 8);
@@ -621,28 +625,37 @@ static void reqtimeout_hooks(apr_pool_t *pool)
* mod_reqtimeout needs to be called before ap_process_http_request (which
* is run at APR_HOOK_REALLY_LAST) but after all other protocol modules.
* This ensures that it only influences normal http connections and not
- * e.g. mod_ftp. Also, if mod_reqtimeout used the pre_connection hook, it
- * would be inserted on mod_proxy's backend connections.
+ * e.g. mod_ftp. We still process it first though, for the handshake stage
+ * to work with/before mod_ssl, but since it's disabled by default it won't
+ * influence non-HTTP modules unless configured explicitly. Also, if
+ * mod_reqtimeout used the pre_connection hook, it would be inserted on
+ * mod_proxy's backend connections, and we don't want this.
*/
- ap_hook_process_connection(reqtimeout_init, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_process_connection(reqtimeout_init, NULL, NULL, APR_HOOK_FIRST);
ap_hook_pre_read_request(reqtimeout_before_header, NULL, NULL,
APR_HOOK_MIDDLE);
ap_hook_post_read_request(reqtimeout_before_body, NULL, NULL,
APR_HOOK_MIDDLE);
-#if MRT_DEFAULT_HEADER_MIN_RATE > 0
- default_header_rate_factor = apr_time_from_sec(1) / MRT_DEFAULT_HEADER_MIN_RATE;
+#if MRT_DEFAULT_handshake_MIN_RATE
+ default_handshake_rate_factor = apr_time_from_sec(1) /
+ MRT_DEFAULT_handshake_MIN_RATE;
+#endif
+#if MRT_DEFAULT_header_MIN_RATE
+ default_header_rate_factor = apr_time_from_sec(1) /
+ MRT_DEFAULT_header_MIN_RATE;
#endif
-#if MRT_DEFAULT_BODY_MIN_RATE > 0
- default_body_rate_factor = apr_time_from_sec(1) / MRT_DEFAULT_BODY_MIN_RATE;
+#if MRT_DEFAULT_body_MIN_RATE
+ default_body_rate_factor = apr_time_from_sec(1) /
+ MRT_DEFAULT_body_MIN_RATE;
#endif
}
static const command_rec reqtimeout_cmds[] = {
AP_INIT_RAW_ARGS("RequestReadTimeout", set_reqtimeouts, NULL, RSRC_CONF,
- "Set various timeout parameters for reading request "
- "headers and body"),
+ "Set various timeout parameters for TLS handshake and/or "
+ "reading request headers and body"),
{NULL}
};
diff --git a/modules/filters/mod_request.c b/modules/filters/mod_request.c
index 21db7de..1768edc 100644
--- a/modules/filters/mod_request.c
+++ b/modules/filters/mod_request.c
@@ -73,10 +73,8 @@ static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
apr_bucket *bucket;
apr_off_t len = 0;
-
if (!ctx) {
const char *lenp;
- char *endstr = NULL;
request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config,
&request_module);
@@ -93,13 +91,12 @@ static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
if (lenp) {
/* Protects against over/underflow, non-digit chars in the
- * string (excluding leading space) (the endstr checks)
- * and a negative number. */
- if (apr_strtoff(&ctx->remaining, lenp, &endstr, 10)
- || endstr == lenp || *endstr || ctx->remaining < 0) {
-
+ * string, leading plus/minus signs, trailing characters and
+ * a negative number.
+ */
+ if (!ap_parse_strict_length(&ctx->remaining, lenp)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411)
- "Invalid Content-Length");
+ "Invalid Content-Length '%s'", lenp);
ap_remove_input_filter(f);
return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
@@ -121,7 +118,6 @@ static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
ctx->remaining = dconf->keep_body;
-
}
/* get the brigade from upstream, and read it in to get its length */
@@ -264,8 +260,8 @@ static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
ctx->remaining -= readbytes;
ctx->offset += readbytes;
- return APR_SUCCESS;
+ return APR_SUCCESS;
}
/**
@@ -311,18 +307,18 @@ static void ap_request_insert_filter(request_rec * r)
NULL, r, r->connection);
}
}
-
}
-/**
- * Remove the kept_body and keep body filters from this specific request.
+/*
+ * Remove the kept_body and keep_body filters from this specific request.
*/
-static void ap_request_remove_filter(request_rec * r)
+static void ap_request_remove_filter(request_rec *r)
{
- ap_filter_t * f = r->input_filters;
+ ap_filter_t *f = r->input_filters;
+
while (f) {
if (f->frec->filter_func.in_func == kept_body_filter ||
- f->frec->filter_func.in_func == keep_body_filter) {
+ f->frec->filter_func.in_func == keep_body_filter) {
ap_remove_input_filter(f);
}
f = f->next;
diff --git a/modules/filters/mod_sed.c b/modules/filters/mod_sed.c
index 346c210..12cb04a 100644
--- a/modules/filters/mod_sed.c
+++ b/modules/filters/mod_sed.c
@@ -51,7 +51,7 @@ typedef struct sed_filter_ctxt
apr_bucket_brigade *bbinp;
char *outbuf;
char *curoutbuf;
- int bufsize;
+ apr_size_t bufsize;
apr_pool_t *tpool;
int numbuckets;
} sed_filter_ctxt;
@@ -59,7 +59,7 @@ typedef struct sed_filter_ctxt
module AP_MODULE_DECLARE_DATA sed_module;
/* This function will be call back from libsed functions if there is any error
- * happend during execution of sed scripts
+ * happened during execution of sed scripts
*/
static apr_status_t log_sed_errf(void *data, const char *error)
{
@@ -100,7 +100,7 @@ static void alloc_outbuf(sed_filter_ctxt* ctx)
/* append_bucket
* Allocate a new bucket from buf and sz and append to ctx->bb
*/
-static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz)
+static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, apr_size_t sz)
{
apr_status_t status = APR_SUCCESS;
apr_bucket *b;
@@ -133,7 +133,7 @@ static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz)
*/
static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx)
{
- int size = ctx->curoutbuf - ctx->outbuf;
+ apr_size_t size = ctx->curoutbuf - ctx->outbuf;
char *out;
apr_status_t status = APR_SUCCESS;
if ((ctx->outbuf == NULL) || (size <=0))
@@ -147,12 +147,12 @@ static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx)
/* This is a call back function. When libsed wants to generate the output,
* this function will be invoked.
*/
-static apr_status_t sed_write_output(void *dummy, char *buf, int sz)
+static apr_status_t sed_write_output(void *dummy, char *buf, apr_size_t sz)
{
/* dummy is basically filter context. Context is passed during invocation
* of sed_eval_buffer
*/
- int remainbytes = 0;
+ apr_size_t remainbytes = 0;
apr_status_t status = APR_SUCCESS;
sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy;
if (ctx->outbuf == NULL) {
@@ -168,21 +168,29 @@ static apr_status_t sed_write_output(void *dummy, char *buf, int sz)
}
/* buffer is now full */
status = append_bucket(ctx, ctx->outbuf, ctx->bufsize);
- /* old buffer is now used so allocate new buffer */
- alloc_outbuf(ctx);
- /* if size is bigger than the allocated buffer directly add to output
- * brigade */
- if ((status == APR_SUCCESS) && (sz >= ctx->bufsize)) {
- char* newbuf = apr_pmemdup(ctx->tpool, buf, sz);
- status = append_bucket(ctx, newbuf, sz);
- /* pool might get clear after append_bucket */
- if (ctx->outbuf == NULL) {
+ if (status == APR_SUCCESS) {
+ /* if size is bigger than the allocated buffer directly add to output
+ * brigade */
+ if (sz >= ctx->bufsize) {
+ char* newbuf = apr_pmemdup(ctx->tpool, buf, sz);
+ status = append_bucket(ctx, newbuf, sz);
+ if (status == APR_SUCCESS) {
+ /* old buffer is now used so allocate new buffer */
+ alloc_outbuf(ctx);
+ }
+ else {
+ clear_ctxpool(ctx);
+ }
+ }
+ else {
+ /* old buffer is now used so allocate new buffer */
alloc_outbuf(ctx);
+ memcpy(ctx->curoutbuf, buf, sz);
+ ctx->curoutbuf += sz;
}
}
else {
- memcpy(ctx->curoutbuf, buf, sz);
- ctx->curoutbuf += sz;
+ clear_ctxpool(ctx);
}
}
else {
@@ -254,6 +262,7 @@ static apr_status_t init_context(ap_filter_t *f, sed_expr_config *sed_cfg, int u
ctx->bufsize = MODSED_OUTBUF_SIZE;
if (usetpool) {
apr_pool_create(&(ctx->tpool), r->pool);
+ apr_pool_tag(ctx->tpool, "sed_tpool");
}
else {
ctx->tpool = r->pool;
@@ -268,7 +277,7 @@ static apr_status_t sed_response_filter(ap_filter_t *f,
apr_bucket_brigade *bb)
{
apr_bucket *b;
- apr_status_t status;
+ apr_status_t status = APR_SUCCESS;
sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
&sed_module);
sed_filter_ctxt *ctx = f->ctx;
@@ -293,9 +302,9 @@ static apr_status_t sed_response_filter(ap_filter_t *f,
return status;
ctx = f->ctx;
apr_table_unset(f->r->headers_out, "Content-Length");
- }
- ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ }
/* Here is the main logic. Iterate through all the buckets, read the
* content of the bucket, call sed_eval_buffer on the data.
@@ -317,63 +326,52 @@ static apr_status_t sed_response_filter(ap_filter_t *f,
* in sed's internal buffer which can't be flushed until new line
* character is arrived.
*/
- for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);) {
- const char *buf = NULL;
- apr_size_t bytes = 0;
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
if (APR_BUCKET_IS_EOS(b)) {
- apr_bucket *b1 = APR_BUCKET_NEXT(b);
/* Now clean up the internal sed buffer */
sed_finalize_eval(&ctx->eval, ctx);
status = flush_output_buffer(ctx);
if (status != APR_SUCCESS) {
- clear_ctxpool(ctx);
- return status;
+ break;
}
+ /* Move the eos bucket to ctx->bb brigade */
APR_BUCKET_REMOVE(b);
- /* Insert the eos bucket to ctx->bb brigade */
APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
- b = b1;
}
else if (APR_BUCKET_IS_FLUSH(b)) {
- apr_bucket *b1 = APR_BUCKET_NEXT(b);
- APR_BUCKET_REMOVE(b);
status = flush_output_buffer(ctx);
if (status != APR_SUCCESS) {
- clear_ctxpool(ctx);
- return status;
+ break;
}
+ /* Move the flush bucket to ctx->bb brigade */
+ APR_BUCKET_REMOVE(b);
APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
- b = b1;
}
- else if (APR_BUCKET_IS_METADATA(b)) {
- b = APR_BUCKET_NEXT(b);
- }
- else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
- == APR_SUCCESS) {
- apr_bucket *b1 = APR_BUCKET_NEXT(b);
- status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
- if (status != APR_SUCCESS) {
- clear_ctxpool(ctx);
- return status;
+ else {
+ if (!APR_BUCKET_IS_METADATA(b)) {
+ const char *buf = NULL;
+ apr_size_t bytes = 0;
+
+ status = apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ);
+ if (status == APR_SUCCESS) {
+ status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
+ }
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10394) "error evaluating sed on output");
+ break;
+ }
}
- APR_BUCKET_REMOVE(b);
apr_bucket_delete(b);
- b = b1;
- }
- else {
- apr_bucket *b1 = APR_BUCKET_NEXT(b);
- APR_BUCKET_REMOVE(b);
- b = b1;
}
}
- apr_brigade_cleanup(bb);
- status = flush_output_buffer(ctx);
- if (status != APR_SUCCESS) {
- clear_ctxpool(ctx);
- return status;
+ if (status == APR_SUCCESS) {
+ status = flush_output_buffer(ctx);
}
if (!APR_BRIGADE_EMPTY(ctx->bb)) {
- status = ap_pass_brigade(f->next, ctx->bb);
+ if (status == APR_SUCCESS) {
+ status = ap_pass_brigade(f->next, ctx->bb);
+ }
apr_brigade_cleanup(ctx->bb);
}
clear_ctxpool(ctx);
@@ -424,7 +422,7 @@ static apr_status_t sed_request_filter(ap_filter_t *f,
* the buckets in bbinp and read the data from buckets and invoke
* sed_eval_buffer on the data. libsed will generate its output using
* sed_write_output which will add data in ctx->bb. Do it until it have
- * atleast one bucket in ctx->bb. At the end of data eos bucket
+ * at least one bucket in ctx->bb. At the end of data eos bucket
* should be there.
*
* Once eos bucket is seen, then invoke sed_finalize_eval to clear the
@@ -466,8 +464,10 @@ static apr_status_t sed_request_filter(ap_filter_t *f,
if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
== APR_SUCCESS) {
status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
- if (status != APR_SUCCESS)
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10395) "error evaluating sed on input");
return status;
+ }
flush_output_buffer(ctx);
}
}
diff --git a/modules/filters/mod_substitute.c b/modules/filters/mod_substitute.c
index b7d5296..d454bf3 100644
--- a/modules/filters/mod_substitute.c
+++ b/modules/filters/mod_substitute.c
@@ -306,7 +306,7 @@ static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
}
else {
apr_size_t repl_len;
- /* acount for string before the match */
+ /* account for string before the match */
if (space_left <= regm[0].rm_so)
return APR_ENOMEM;
space_left -= regm[0].rm_so;
@@ -402,6 +402,7 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
ctx->passbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
/* Create our temporary pool only once */
apr_pool_create(&(ctx->tpool), f->r->pool);
+ apr_pool_tag(ctx->tpool, "substitute_tpool");
apr_table_unset(f->r->headers_out, "Content-Length");
}
@@ -667,8 +668,10 @@ static const char *set_pattern(cmd_parms *cmd, void *cfg, const char *line)
/* first see if we can compile the regex */
if (!is_pattern) {
- r = ap_pregcomp(cmd->pool, from, AP_REG_EXTENDED |
- (ignore_case ? AP_REG_ICASE : 0));
+ int flags = AP_REG_NO_DEFAULT
+ | (ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY)
+ | (ignore_case ? AP_REG_ICASE : 0);
+ r = ap_pregcomp(cmd->pool, from, flags);
if (!r)
return "Substitute could not compile regex";
}
diff --git a/modules/filters/mod_xml2enc.c b/modules/filters/mod_xml2enc.c
index 05a4e9a..eb05c18 100644
--- a/modules/filters/mod_xml2enc.c
+++ b/modules/filters/mod_xml2enc.c
@@ -23,9 +23,28 @@
#include
+/* libxml2 includes unicode/[...].h files which uses C++ comments */
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wcomment"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wcomment"
+#endif
+#endif
+
/* libxml2 */
#include
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
#include "http_protocol.h"
#include "http_config.h"
#include "http_log.h"
@@ -51,7 +70,7 @@ module AP_MODULE_DECLARE_DATA xml2enc_module;
(((enc)!=XML_CHAR_ENCODING_NONE)&&((enc)!=XML_CHAR_ENCODING_ERROR))
/*
- * XXX: Check all those ap_assert()s ans replace those that should not happen
+ * XXX: Check all those ap_assert()s and replace those that should not happen
* XXX: with AP_DEBUG_ASSERT and those that may happen with proper error
* XXX: handling.
*/
@@ -187,11 +206,11 @@ static void sniff_encoding(request_rec* r, xml2ctx* ctx)
}
}
}
-
+
/* to sniff, first we look for BOM */
if (ctx->xml2enc == XML_CHAR_ENCODING_NONE) {
- ctx->xml2enc = xmlDetectCharEncoding((const xmlChar*)ctx->buf,
- ctx->bytes);
+ ctx->xml2enc = xmlDetectCharEncoding((const unsigned char*)ctx->buf,
+ ctx->bytes);
if (HAVE_ENCODING(ctx->xml2enc)) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01432)
"Got charset from XML rules.") ;
@@ -304,7 +323,7 @@ static apr_status_t xml2enc_ffunc(ap_filter_t* f, apr_bucket_brigade* bb)
apr_bucket* bstart;
apr_size_t insz = 0;
int pending_meta = 0;
- char *ctype;
+ char *mtype;
char *p;
if (!ctx || !f->r->content_type) {
@@ -313,13 +332,17 @@ static apr_status_t xml2enc_ffunc(ap_filter_t* f, apr_bucket_brigade* bb)
return ap_pass_brigade(f->next, bb) ;
}
- ctype = apr_pstrdup(f->r->pool, f->r->content_type);
- for (p = ctype; *p; ++p)
- if (isupper(*p))
- *p = tolower(*p);
-
- /* only act if starts-with "text/" or contains "xml" */
- if (strncmp(ctype, "text/", 5) && !strstr(ctype, "xml")) {
+ /* Extract the media type, ignoring parameters in content-type. */
+ mtype = apr_pstrdup(f->r->pool, f->r->content_type);
+ if ((p = ap_strchr(mtype, ';')) != NULL) *p = '\0';
+ ap_str_tolower(mtype);
+
+ /* Accept text/ types, plus any XML media type per RFC 7303. */
+ if (!(strncmp(mtype, "text/", 5) == 0
+ || strcmp(mtype, "application/xml") == 0
+ || (strlen(mtype) > 7 /* minimum 'a/b+xml' length */
+ && (p = strstr(mtype, "+xml")) != NULL
+ && strlen(p) == 4 /* ensures +xml is a suffix */))) {
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb) ;
}
diff --git a/modules/filters/regexp.h b/modules/filters/regexp.h
index 6af8912..dc4993a 100644
--- a/modules/filters/regexp.h
+++ b/modules/filters/regexp.h
@@ -93,8 +93,8 @@ extern void command_errf(sed_commands_t *commands, const char *fmt, ...)
#define SEDERR_COMES "cannot open %s"
#define SEDERR_CCMES "cannot create %s"
#define SEDERR_TMLNMES "too many line numbers"
-#define SEDERR_TMAMES "too many appends after line %lld"
-#define SEDERR_TMRMES "too many reads after line %lld"
+#define SEDERR_TMAMES "too many appends after line %" APR_INT64_T_FMT
+#define SEDERR_TMRMES "too many reads after line %" APR_INT64_T_FMT
#define SEDERR_DOORNG "``\\digit'' out of range: %s"
#define SEDERR_EDMOSUB "ending delimiter missing on substitution: %s"
#define SEDERR_EDMOSTR "ending delimiter missing on string: %s"
diff --git a/modules/filters/sed1.c b/modules/filters/sed1.c
index be03506..047f49b 100644
--- a/modules/filters/sed1.c
+++ b/modules/filters/sed1.c
@@ -71,7 +71,7 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2);
static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
step_vars_storage *step_vars);
-static apr_status_t wline(sed_eval_t *eval, char *buf, int sz);
+static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz);
static apr_status_t arout(sed_eval_t *eval);
static void eval_errf(sed_eval_t *eval, const char *fmt, ...)
@@ -87,18 +87,20 @@ static void eval_errf(sed_eval_t *eval, const char *fmt, ...)
}
#define INIT_BUF_SIZE 1024
+#define MAX_BUF_SIZE 1024*8192
/*
* grow_buffer
*/
-static void grow_buffer(apr_pool_t *pool, char **buffer,
- char **spend, unsigned int *cursize,
- unsigned int newsize)
+static apr_status_t grow_buffer(apr_pool_t *pool, char **buffer,
+ char **spend, apr_size_t *cursize,
+ apr_size_t newsize)
{
char* newbuffer = NULL;
- int spendsize = 0;
- if (*cursize >= newsize)
- return;
+ apr_size_t spendsize = 0;
+ if (*cursize >= newsize) {
+ return APR_SUCCESS;
+ }
/* Avoid number of times realloc is called. It could cause huge memory
* requirement if line size is huge e.g 2 MB */
if (newsize < *cursize * 2) {
@@ -107,6 +109,9 @@ static void grow_buffer(apr_pool_t *pool, char **buffer,
/* Align it to 4 KB boundary */
newsize = (newsize + ((1 << 12) - 1)) & ~((1 << 12) - 1);
+ if (newsize > MAX_BUF_SIZE) {
+ return APR_ENOMEM;
+ }
newbuffer = apr_pcalloc(pool, newsize);
if (*spend && *buffer && (*cursize > 0)) {
spendsize = *spend - *buffer;
@@ -119,123 +124,168 @@ static void grow_buffer(apr_pool_t *pool, char **buffer,
if (spend != buffer) {
*spend = *buffer + spendsize;
}
+ return APR_SUCCESS;
}
/*
* grow_line_buffer
*/
-static void grow_line_buffer(sed_eval_t *eval, int newsize)
+static apr_status_t grow_line_buffer(sed_eval_t *eval, apr_size_t newsize)
{
- grow_buffer(eval->pool, &eval->linebuf, &eval->lspend,
+ return grow_buffer(eval->pool, &eval->linebuf, &eval->lspend,
&eval->lsize, newsize);
}
/*
* grow_hold_buffer
*/
-static void grow_hold_buffer(sed_eval_t *eval, int newsize)
+static apr_status_t grow_hold_buffer(sed_eval_t *eval, apr_size_t newsize)
{
- grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend,
+ return grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend,
&eval->hsize, newsize);
}
/*
* grow_gen_buffer
*/
-static void grow_gen_buffer(sed_eval_t *eval, int newsize,
+static apr_status_t grow_gen_buffer(sed_eval_t *eval, apr_size_t newsize,
char **gspend)
{
+ apr_status_t rc = 0;
if (gspend == NULL) {
gspend = &eval->genbuf;
}
- grow_buffer(eval->pool, &eval->genbuf, gspend,
- &eval->gsize, newsize);
- eval->lcomend = &eval->genbuf[71];
+ rc = grow_buffer(eval->pool, &eval->genbuf, gspend,
+ &eval->gsize, newsize);
+ if (rc == APR_SUCCESS) {
+ eval->lcomend = &eval->genbuf[71];
+ }
+ return rc;
}
/*
* appendmem_to_linebuf
*/
-static void appendmem_to_linebuf(sed_eval_t *eval, const char* sz, int len)
+static apr_status_t appendmem_to_linebuf(sed_eval_t *eval, const char* sz, apr_size_t len)
{
- unsigned int reqsize = (eval->lspend - eval->linebuf) + len;
+ apr_status_t rc = 0;
+ apr_size_t reqsize = (eval->lspend - eval->linebuf) + len;
if (eval->lsize < reqsize) {
- grow_line_buffer(eval, reqsize);
+ rc = grow_line_buffer(eval, reqsize);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
}
memcpy(eval->lspend, sz, len);
eval->lspend += len;
+ return APR_SUCCESS;
}
/*
* append_to_linebuf
*/
-static void append_to_linebuf(sed_eval_t *eval, const char* sz)
+static apr_status_t append_to_linebuf(sed_eval_t *eval, const char* sz,
+ step_vars_storage *step_vars)
{
- int len = strlen(sz);
+ apr_size_t len = strlen(sz);
+ char *old_linebuf = eval->linebuf;
+ apr_status_t rc = 0;
/* Copy string including null character */
- appendmem_to_linebuf(eval, sz, len + 1);
+ rc = appendmem_to_linebuf(eval, sz, len + 1);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
--eval->lspend; /* lspend will now point to NULL character */
+ /* Sync step_vars after a possible linebuf expansion */
+ if (step_vars && old_linebuf != eval->linebuf) {
+ if (step_vars->loc1) {
+ step_vars->loc1 = step_vars->loc1 - old_linebuf + eval->linebuf;
+ }
+ if (step_vars->loc2) {
+ step_vars->loc2 = step_vars->loc2 - old_linebuf + eval->linebuf;
+ }
+ if (step_vars->locs) {
+ step_vars->locs = step_vars->locs - old_linebuf + eval->linebuf;
+ }
+ }
+ return APR_SUCCESS;
}
/*
* copy_to_linebuf
*/
-static void copy_to_linebuf(sed_eval_t *eval, const char* sz)
+static apr_status_t copy_to_linebuf(sed_eval_t *eval, const char* sz,
+ step_vars_storage *step_vars)
{
eval->lspend = eval->linebuf;
- append_to_linebuf(eval, sz);
+ return append_to_linebuf(eval, sz, step_vars);
}
/*
* append_to_holdbuf
*/
-static void append_to_holdbuf(sed_eval_t *eval, const char* sz)
+static apr_status_t append_to_holdbuf(sed_eval_t *eval, const char* sz)
{
- int len = strlen(sz);
- unsigned int reqsize = (eval->hspend - eval->holdbuf) + len + 1;
+ apr_size_t len = strlen(sz);
+ apr_size_t reqsize = (eval->hspend - eval->holdbuf) + len + 1;
+ apr_status_t rc = 0;
if (eval->hsize <= reqsize) {
- grow_hold_buffer(eval, reqsize);
+ rc = grow_hold_buffer(eval, reqsize);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
}
memcpy(eval->hspend, sz, len + 1);
/* hspend will now point to NULL character */
eval->hspend += len;
+ return APR_SUCCESS;
}
/*
* copy_to_holdbuf
*/
-static void copy_to_holdbuf(sed_eval_t *eval, const char* sz)
+static apr_status_t copy_to_holdbuf(sed_eval_t *eval, const char* sz)
{
eval->hspend = eval->holdbuf;
- append_to_holdbuf(eval, sz);
+ return append_to_holdbuf(eval, sz);
}
/*
* append_to_genbuf
*/
-static void append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend)
+static apr_status_t append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend)
{
- int len = strlen(sz);
- unsigned int reqsize = (*gspend - eval->genbuf) + len + 1;
+ apr_size_t len = strlen(sz);
+ apr_size_t reqsize = (*gspend - eval->genbuf) + len + 1;
+ apr_status_t rc = 0;
if (eval->gsize < reqsize) {
- grow_gen_buffer(eval, reqsize, gspend);
+ rc = grow_gen_buffer(eval, reqsize, gspend);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
}
memcpy(*gspend, sz, len + 1);
/* *gspend will now point to NULL character */
*gspend += len;
+ return APR_SUCCESS;
}
/*
* copy_to_genbuf
*/
-static void copy_to_genbuf(sed_eval_t *eval, const char* sz)
+static apr_status_t copy_to_genbuf(sed_eval_t *eval, const char* sz)
{
- int len = strlen(sz);
- unsigned int reqsize = len + 1;
+ apr_size_t len = strlen(sz);
+ apr_size_t reqsize = len + 1;
+ apr_status_t rc = APR_SUCCESS;;
if (eval->gsize < reqsize) {
- grow_gen_buffer(eval, reqsize, NULL);
+ rc = grow_gen_buffer(eval, reqsize, NULL);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
}
memcpy(eval->genbuf, sz, len + 1);
+ return rc;
}
/*
@@ -353,7 +403,7 @@ apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout)
/*
* sed_eval_buffer
*/
-apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void *fout)
+apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout)
{
apr_status_t rv;
@@ -382,8 +432,9 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void
}
while (bufsz) {
+ apr_status_t rc = 0;
char *n;
- int llen;
+ apr_size_t llen;
n = memchr(buf, '\n', bufsz);
if (n == NULL)
@@ -396,7 +447,10 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void
break;
}
- appendmem_to_linebuf(eval, buf, llen + 1);
+ rc = appendmem_to_linebuf(eval, buf, llen + 1);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
--eval->lspend;
/* replace new line character with NULL */
*eval->lspend = '\0';
@@ -411,7 +465,10 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void
/* Save the leftovers for later */
if (bufsz) {
- appendmem_to_linebuf(eval, buf, bufsz);
+ apr_status_t rc = appendmem_to_linebuf(eval, buf, bufsz);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
}
return APR_SUCCESS;
@@ -433,6 +490,7 @@ apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout)
/* Process leftovers */
if (eval->lspend > eval->linebuf) {
apr_status_t rv;
+ apr_status_t rc = 0;
if (eval->lreadyflag) {
eval->lreadyflag = 0;
@@ -442,7 +500,10 @@ apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout)
* buffer is not a newline.
*/
/* Assure space for NULL */
- append_to_linebuf(eval, "");
+ rc = append_to_linebuf(eval, "", NULL);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
}
*eval->lspend = '\0';
@@ -640,11 +701,15 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
sp = eval->genbuf;
rp = rhsbuf;
sp = place(eval, sp, lp, step_vars->loc1);
+ if (sp == NULL) {
+ return APR_EGENERAL;
+ }
while ((c = *rp++) != 0) {
if (c == '&') {
sp = place(eval, sp, step_vars->loc1, step_vars->loc2);
- if (sp == NULL)
+ if (sp == NULL) {
return APR_EGENERAL;
+ }
}
else if (c == '\\') {
c = *rp++;
@@ -660,13 +725,19 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
*sp++ = c;
if (sp >= eval->genbuf + eval->gsize) {
/* expand genbuf and set the sp appropriately */
- grow_gen_buffer(eval, eval->gsize + 1024, &sp);
+ rv = grow_gen_buffer(eval, eval->gsize + 1024, &sp);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
}
}
lp = step_vars->loc2;
step_vars->loc2 = sp - eval->genbuf + eval->linebuf;
- append_to_genbuf(eval, lp, &sp);
- copy_to_linebuf(eval, eval->genbuf);
+ rv = append_to_genbuf(eval, lp, &sp);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ rv = copy_to_linebuf(eval, eval->genbuf, step_vars);
return rv;
}
@@ -676,11 +747,14 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2)
{
char *sp = asp;
- int n = al2 - al1;
- unsigned int reqsize = (sp - eval->genbuf) + n + 1;
+ apr_size_t n = al2 - al1;
+ apr_size_t reqsize = (sp - eval->genbuf) + n + 1;
if (eval->gsize < reqsize) {
- grow_gen_buffer(eval, reqsize, &sp);
+ apr_status_t rc = grow_gen_buffer(eval, reqsize, &sp);
+ if (rc != APR_SUCCESS) {
+ return NULL;
+ }
}
memcpy(sp, al1, n);
return sp + n;
@@ -735,7 +809,8 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
}
p1++;
- copy_to_linebuf(eval, p1);
+ rv = copy_to_linebuf(eval, p1, step_vars);
+ if (rv != APR_SUCCESS) return rv;
eval->jflag++;
break;
@@ -745,21 +820,27 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
break;
case GCOM:
- copy_to_linebuf(eval, eval->holdbuf);
+ rv = copy_to_linebuf(eval, eval->holdbuf, step_vars);
+ if (rv != APR_SUCCESS) return rv;
break;
case CGCOM:
- append_to_linebuf(eval, "\n");
- append_to_linebuf(eval, eval->holdbuf);
+ rv = append_to_linebuf(eval, "\n", step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ rv = append_to_linebuf(eval, eval->holdbuf, step_vars);
+ if (rv != APR_SUCCESS) return rv;
break;
case HCOM:
- copy_to_holdbuf(eval, eval->linebuf);
+ rv = copy_to_holdbuf(eval, eval->linebuf);
+ if (rv != APR_SUCCESS) return rv;
break;
case CHCOM:
- append_to_holdbuf(eval, "\n");
- append_to_holdbuf(eval, eval->linebuf);
+ rv = append_to_holdbuf(eval, "\n");
+ if (rv != APR_SUCCESS) return rv;
+ rv = append_to_holdbuf(eval, eval->linebuf);
+ if (rv != APR_SUCCESS) return rv;
break;
case ICOM:
@@ -881,7 +962,8 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
if (rv != APR_SUCCESS)
return rv;
}
- append_to_linebuf(eval, "\n");
+ rv = append_to_linebuf(eval, "\n", step_vars);
+ if (rv != APR_SUCCESS) return rv;
eval->pending = ipc->next;
break;
@@ -955,9 +1037,12 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
break;
case XCOM:
- copy_to_genbuf(eval, eval->linebuf);
- copy_to_linebuf(eval, eval->holdbuf);
- copy_to_holdbuf(eval, eval->genbuf);
+ rv = copy_to_genbuf(eval, eval->linebuf);
+ if (rv != APR_SUCCESS) return rv;
+ rv = copy_to_linebuf(eval, eval->holdbuf, step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ rv = copy_to_holdbuf(eval, eval->genbuf);
+ if (rv != APR_SUCCESS) return rv;
break;
case YCOM:
@@ -1013,7 +1098,7 @@ static apr_status_t arout(sed_eval_t *eval)
/*
* wline
*/
-static apr_status_t wline(sed_eval_t *eval, char *buf, int sz)
+static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz)
{
apr_status_t rv = APR_SUCCESS;
rv = eval->writefn(eval->fout, buf, sz);
diff --git a/modules/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c
index 9094e30..cb44603 100644
--- a/modules/generators/mod_autoindex.c
+++ b/modules/generators/mod_autoindex.c
@@ -1070,7 +1070,7 @@ static void emit_head(request_rec *r, char *header_fname, int suppress_amble,
emit_H1 = 1;
}
}
- else if (!strncasecmp("text/", rr->content_type, 5)) {
+ else if (!ap_cstr_casecmpn("text/", rr->content_type, 5)) {
/*
* If we can open the file, prefix it with the preamble
* regardless; since we'll be sending a block around
@@ -1165,7 +1165,7 @@ static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble)
suppress_post = suppress_amble;
}
}
- else if (!strncasecmp("text/", rr->content_type, 5)) {
+ else if (!ap_cstr_casecmpn("text/", rr->content_type, 5)) {
/*
* If we can open the file, suppress the signature.
*/
@@ -1266,8 +1266,9 @@ static struct ent *make_parent_entry(apr_int32_t autoindex_opts,
if (!(p->name = ap_make_full_path(r->pool, r->uri, "../"))) {
return (NULL);
}
- ap_getparents(p->name);
- if (!*p->name) {
+ if (!ap_normalize_path(p->name, AP_NORMALIZE_ALLOW_RELATIVE |
+ AP_NORMALIZE_NOT_ABOVE_ROOT)
+ || p->name[0] == '\0') {
return (NULL);
}
@@ -1517,6 +1518,7 @@ static void output_directories(struct ent **ar, int n,
char *breakrow = "";
apr_pool_create(&scratch, r->pool);
+ apr_pool_tag(scratch, "autoindex_scratch");
name_width = d->name_width;
desc_width = d->desc_width;
diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c
index 8c4a2c6..1f77786 100644
--- a/modules/generators/mod_cgi.c
+++ b/modules/generators/mod_cgi.c
@@ -92,6 +92,10 @@ typedef struct {
apr_size_t bufbytes;
} cgi_server_conf;
+typedef struct {
+ apr_interval_time_t timeout;
+} cgi_dirconf;
+
static void *create_cgi_config(apr_pool_t *p, server_rec *s)
{
cgi_server_conf *c =
@@ -112,6 +116,12 @@ static void *merge_cgi_config(apr_pool_t *p, void *basev, void *overridesv)
return overrides->logname ? overrides : base;
}
+static void *create_cgi_dirconf(apr_pool_t *p, char *dummy)
+{
+ cgi_dirconf *c = (cgi_dirconf *) apr_pcalloc(p, sizeof(cgi_dirconf));
+ return c;
+}
+
static const char *set_scriptlog(cmd_parms *cmd, void *dummy, const char *arg)
{
server_rec *s = cmd->server;
@@ -150,6 +160,17 @@ static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy,
return NULL;
}
+static const char *set_script_timeout(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ cgi_dirconf *dc = dummy;
+
+ if (ap_timeout_parameter_parse(arg, &dc->timeout, "s") != APR_SUCCESS) {
+ return "CGIScriptTimeout has wrong format";
+ }
+
+ return NULL;
+}
+
static const command_rec cgi_cmds[] =
{
AP_INIT_TAKE1("ScriptLog", set_scriptlog, NULL, RSRC_CONF,
@@ -158,6 +179,9 @@ AP_INIT_TAKE1("ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF,
"the maximum length (in bytes) of the script debug log"),
AP_INIT_TAKE1("ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF,
"the maximum size (in bytes) to record of a POST request"),
+AP_INIT_TAKE1("CGIScriptTimeout", set_script_timeout, NULL, RSRC_CONF | ACCESS_CONF,
+ "The amount of time to wait between successful reads from "
+ "the CGI script, in seconds."),
{NULL}
};
@@ -466,23 +490,26 @@ static apr_status_t run_cgi_child(apr_file_t **script_out,
apr_filepath_name_get(r->filename));
}
else {
+ cgi_dirconf *dc = ap_get_module_config(r->per_dir_config, &cgi_module);
+ apr_interval_time_t timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout;
+
apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
*script_in = procnew->out;
if (!*script_in)
return APR_EBADF;
- apr_file_pipe_timeout_set(*script_in, r->server->timeout);
+ apr_file_pipe_timeout_set(*script_in, timeout);
if (e_info->prog_type == RUN_AS_CGI) {
*script_out = procnew->in;
if (!*script_out)
return APR_EBADF;
- apr_file_pipe_timeout_set(*script_out, r->server->timeout);
+ apr_file_pipe_timeout_set(*script_out, timeout);
*script_err = procnew->err;
if (!*script_err)
return APR_EBADF;
- apr_file_pipe_timeout_set(*script_err, r->server->timeout);
+ apr_file_pipe_timeout_set(*script_err, timeout);
}
}
}
@@ -541,19 +568,15 @@ static void discard_script_output(apr_bucket_brigade *bb)
apr_bucket *e;
const char *buf;
apr_size_t len;
- apr_status_t rv;
for (e = APR_BRIGADE_FIRST(bb);
- e != APR_BRIGADE_SENTINEL(bb);
- e = APR_BUCKET_NEXT(e))
+ e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e);
+ e = APR_BRIGADE_FIRST(bb))
{
- if (APR_BUCKET_IS_EOS(e)) {
- break;
- }
- rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
+ if (apr_bucket_read(e, &buf, &len, APR_BLOCK_READ)) {
break;
}
+ apr_bucket_delete(e);
}
}
@@ -679,11 +702,14 @@ static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str,
apr_size_t *len, apr_read_type_e block)
{
struct cgi_bucket_data *data = b->data;
- apr_interval_time_t timeout;
+ apr_interval_time_t timeout = 0;
apr_status_t rv;
int gotdata = 0;
+ cgi_dirconf *dc = ap_get_module_config(data->r->per_dir_config, &cgi_module);
- timeout = block == APR_NONBLOCK_READ ? 0 : data->r->server->timeout;
+ if (block != APR_NONBLOCK_READ) {
+ timeout = dc->timeout > 0 ? dc->timeout : data->r->server->timeout;
+ }
do {
const apr_pollfd_t *results;
@@ -761,6 +787,8 @@ static int cgi_handler(request_rec *r)
apr_status_t rv;
cgi_exec_info_t e_info;
conn_rec *c;
+ cgi_dirconf *dc = ap_get_module_config(r->per_dir_config, &cgi_module);
+ apr_interval_time_t timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout;
if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) {
return DECLINED;
@@ -939,9 +967,18 @@ static int cgi_handler(request_rec *r)
char sbuf[MAX_STRING_LEN];
int ret;
- if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
- APLOG_MODULE_INDEX)))
- {
+ ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
+ APLOG_MODULE_INDEX);
+
+ /* xCGI has its own body framing mechanism which we don't
+ * match against any provided Content-Length, so let the
+ * core determine C-L vs T-E based on what's actually sent.
+ */
+ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+
+ if (ret != OK) {
ret = log_script(r, conf, ret, dbuf, sbuf, bb, script_err);
/*
@@ -980,7 +1017,7 @@ static int cgi_handler(request_rec *r)
* stderr output, as normal. */
discard_script_output(bb);
apr_brigade_destroy(bb);
- apr_file_pipe_timeout_set(script_err, r->server->timeout);
+ apr_file_pipe_timeout_set(script_err, timeout);
log_script_err(r, script_err);
}
@@ -1031,7 +1068,7 @@ static int cgi_handler(request_rec *r)
* connection drops or we stopped sending output for some other
* reason */
if (rv == APR_SUCCESS && !r->connection->aborted) {
- apr_file_pipe_timeout_set(script_err, r->server->timeout);
+ apr_file_pipe_timeout_set(script_err, timeout);
log_script_err(r, script_err);
}
@@ -1272,7 +1309,7 @@ static void register_hooks(apr_pool_t *p)
AP_DECLARE_MODULE(cgi) =
{
STANDARD20_MODULE_STUFF,
- NULL, /* dir config creater */
+ create_cgi_dirconf, /* dir config creater */
NULL, /* dir merger --- default is to override */
create_cgi_config, /* server config */
merge_cgi_config, /* merge server config */
diff --git a/modules/generators/mod_cgid.c b/modules/generators/mod_cgid.c
index b827ed6..4bab59f 100644
--- a/modules/generators/mod_cgid.c
+++ b/modules/generators/mod_cgid.c
@@ -608,6 +608,7 @@ static int cgid_server(void *data)
apr_status_t rv;
apr_pool_create(&ptrans, pcgi);
+ apr_pool_tag(ptrans, "cgid_ptrans");
apr_signal(SIGCHLD, SIG_IGN);
apr_signal(SIGHUP, daemon_signal_handler);
@@ -626,6 +627,9 @@ static int cgid_server(void *data)
return errno;
}
+ apr_pool_cleanup_register(pcgi, (void *)((long)sd),
+ close_unix_socket, close_unix_socket);
+
omask = umask(0077); /* so that only Apache can use socket */
rc = bind(sd, (struct sockaddr *)server_addr, server_addr_len);
umask(omask); /* can't fail, so can't clobber errno */
@@ -660,9 +664,6 @@ static int cgid_server(void *data)
}
}
- apr_pool_cleanup_register(pcgi, (void *)((long)sd),
- close_unix_socket, close_unix_socket);
-
/* if running as root, switch to configured user/group */
if ((rc = ap_run_drop_privileges(pcgi, ap_server_conf)) != 0) {
return rc;
@@ -879,6 +880,7 @@ static int cgid_start(apr_pool_t *p, server_rec *main_server,
else if (daemon_pid == 0) {
if (pcgi == NULL) {
apr_pool_create(&pcgi, p);
+ apr_pool_tag(pcgi, "cgid_pcgi");
}
exit(cgid_server(main_server) > 0 ? DAEMON_STARTUP_ERROR : -1);
}
@@ -1275,19 +1277,15 @@ static void discard_script_output(apr_bucket_brigade *bb)
apr_bucket *e;
const char *buf;
apr_size_t len;
- apr_status_t rv;
for (e = APR_BRIGADE_FIRST(bb);
- e != APR_BRIGADE_SENTINEL(bb);
- e = APR_BUCKET_NEXT(e))
+ e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e);
+ e = APR_BRIGADE_FIRST(bb))
{
- if (APR_BUCKET_IS_EOS(e)) {
- break;
- }
- rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
+ if (apr_bucket_read(e, &buf, &len, APR_BLOCK_READ)) {
break;
}
+ apr_bucket_delete(e);
}
}
@@ -1618,9 +1616,18 @@ static int cgid_handler(request_rec *r)
b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
- if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
- APLOG_MODULE_INDEX)))
- {
+ ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
+ APLOG_MODULE_INDEX);
+
+ /* xCGI has its own body framing mechanism which we don't
+ * match against any provided Content-Length, so let the
+ * core determine C-L vs T-E based on what's actually sent.
+ */
+ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+
+ if (ret != OK) {
ret = log_script(r, conf, ret, dbuf, sbuf, bb, NULL);
/*
diff --git a/modules/generators/mod_info.c b/modules/generators/mod_info.c
index e5e63de..1662242 100644
--- a/modules/generators/mod_info.c
+++ b/modules/generators/mod_info.c
@@ -80,9 +80,9 @@ typedef struct
module AP_MODULE_DECLARE_DATA info_module;
/* current file name when doing -DDUMP_CONFIG */
-const char *dump_config_fn_info;
+static const char *dump_config_fn_info;
/* file handle when doing -DDUMP_CONFIG */
-apr_file_t *out = NULL;
+static apr_file_t *out = NULL;
static void *create_info_config(apr_pool_t * p, server_rec * s)
{
@@ -230,7 +230,7 @@ static int mod_info_has_cmd(const command_rec * cmds, ap_directive_t * dir)
if (cmds == NULL)
return 1;
for (cmd = cmds; cmd->name; ++cmd) {
- if (strcasecmp(cmd->name, dir->directive) == 0)
+ if (ap_cstr_casecmp(cmd->name, dir->directive) == 0)
return 1;
}
return 0;
@@ -297,7 +297,7 @@ typedef struct
hook_get_t get;
} hook_lookup_t;
-static hook_lookup_t startup_hooks[] = {
+static const hook_lookup_t startup_hooks[] = {
{"Pre-Config", ap_hook_get_pre_config},
{"Check Configuration", ap_hook_get_check_config},
{"Test Configuration", ap_hook_get_test_config},
@@ -311,7 +311,7 @@ static hook_lookup_t startup_hooks[] = {
{NULL},
};
-static hook_lookup_t request_hooks[] = {
+static const hook_lookup_t request_hooks[] = {
{"Pre-Connection", ap_hook_get_pre_connection},
{"Create Connection", ap_hook_get_create_connection},
{"Process Connection", ap_hook_get_process_connection},
@@ -322,6 +322,7 @@ static hook_lookup_t request_hooks[] = {
{"HTTP Scheme", ap_hook_get_http_scheme},
{"Default Port", ap_hook_get_default_port},
{"Quick Handler", ap_hook_get_quick_handler},
+ {"Pre-Translate Name", ap_hook_get_pre_translate_name},
{"Translate Name", ap_hook_get_translate_name},
{"Map to Storage", ap_hook_get_map_to_storage},
{"Check Access", ap_hook_get_access_checker_ex},
@@ -339,7 +340,7 @@ static hook_lookup_t request_hooks[] = {
{NULL},
};
-static hook_lookup_t other_hooks[] = {
+static const hook_lookup_t other_hooks[] = {
{"Monitor", ap_hook_get_monitor},
{"Child Status", ap_hook_get_child_status},
{"End Generation", ap_hook_get_end_generation},
@@ -378,7 +379,7 @@ static int module_find_hook(module * modp, hook_get_t hook_get)
static void module_participate(request_rec * r,
module * modp,
- hook_lookup_t * lookup, int *comma)
+ const hook_lookup_t *lookup, int *comma)
{
if (module_find_hook(modp, lookup->get)) {
if (*comma) {
@@ -453,6 +454,12 @@ static int show_server_settings(request_rec * r)
"
Compiled with APU Version: "
"%s\n", APU_VERSION_STRING);
#endif
+ ap_rprintf(r,
+ "Server loaded PCRE Version: "
+ "%s\n", ap_pcre_version_string(AP_REG_PCRE_LOADED));
+ ap_rprintf(r,
+ "Compiled with PCRE Version: "
+ "%s\n", ap_pcre_version_string(AP_REG_PCRE_COMPILED));
ap_rprintf(r,
"Module Magic Number: "
"%d:%d\n", MODULE_MAGIC_NUMBER_MAJOR,
@@ -577,7 +584,7 @@ static int show_server_settings(request_rec * r)
#ifdef BUFFERED_LOGS
ap_rputs(" -D BUFFERED_LOGS\n", r);
#ifdef PIPE_BUF
- ap_rputs(" -D PIPE_BUF=%ld\n", (long) PIPE_BUF, r);
+ ap_rprintf(r, " -D PIPE_BUF=%ld\n", (long) PIPE_BUF);
#endif
#endif
@@ -785,7 +792,7 @@ static int display_info(request_rec * r)
" Server Information\n" "\n", r);
ap_rputs(""
"Apache Server Information
\n", r);
- if (!r->args || strcasecmp(r->args, "list")) {
+ if (!r->args || ap_cstr_casecmp(r->args, "list")) {
if (!r->args) {
ap_rputs("- Subpages:
", r);
ap_rputs("Configuration Files, "
@@ -819,19 +826,19 @@ static int display_info(request_rec * r)
ap_rputs("
", r);
}
- if (!r->args || !strcasecmp(r->args, "server")) {
+ if (!r->args || !ap_cstr_casecmp(r->args, "server")) {
show_server_settings(r);
}
- if (!r->args || !strcasecmp(r->args, "hooks")) {
+ if (!r->args || !ap_cstr_casecmp(r->args, "hooks")) {
show_active_hooks(r);
}
- if (!r->args || !strcasecmp(r->args, "providers")) {
+ if (!r->args || !ap_cstr_casecmp(r->args, "providers")) {
show_providers(r);
}
- if (r->args && 0 == strcasecmp(r->args, "config")) {
+ if (r->args && 0 == ap_cstr_casecmp(r->args, "config")) {
ap_rputs("- Configuration:\n", r);
mod_info_module_cmds(r, NULL, ap_conftree, 0, 0);
ap_rputs("
", r);
@@ -842,7 +849,7 @@ static int display_info(request_rec * r)
modules = get_sorted_modules(r->pool);
for (i = 0; i < modules->nelts; i++) {
modp = APR_ARRAY_IDX(modules, i, module *);
- if (!r->args || !strcasecmp(modp->name, r->args)) {
+ if (!r->args || !ap_cstr_casecmp(modp->name, r->args)) {
ap_rprintf(r,
"- Module Name: "
"%s
\n",
@@ -940,7 +947,7 @@ static int display_info(request_rec * r)
}
}
}
- if (!modp && r->args && strcasecmp(r->args, "server")) {
+ if (!modp && r->args && ap_cstr_casecmp(r->args, "server")) {
ap_rputs("No such module
\n", r);
}
}
diff --git a/modules/generators/mod_status.c b/modules/generators/mod_status.c
index 5917953..5bada07 100644
--- a/modules/generators/mod_status.c
+++ b/modules/generators/mod_status.c
@@ -186,7 +186,8 @@ static int status_handler(request_rec *r)
apr_uint32_t up_time;
ap_loadavg_t t;
int j, i, res, written;
- int ready;
+ int idle;
+ int graceful;
int busy;
unsigned long count;
unsigned long lres, my_lres, conn_lres;
@@ -203,6 +204,7 @@ static int status_handler(request_rec *r)
char *stat_buffer;
pid_t *pid_buffer, worker_pid;
int *thread_idle_buffer = NULL;
+ int *thread_graceful_buffer = NULL;
int *thread_busy_buffer = NULL;
clock_t tu, ts, tcu, tcs;
clock_t gu, gs, gcu, gcs;
@@ -231,7 +233,8 @@ static int status_handler(request_rec *r)
#endif
#endif
- ready = 0;
+ idle = 0;
+ graceful = 0;
busy = 0;
count = 0;
bcount = 0;
@@ -250,6 +253,7 @@ static int status_handler(request_rec *r)
stat_buffer = apr_palloc(r->pool, server_limit * thread_limit * sizeof(char));
if (is_async) {
thread_idle_buffer = apr_palloc(r->pool, server_limit * sizeof(int));
+ thread_graceful_buffer = apr_palloc(r->pool, server_limit * sizeof(int));
thread_busy_buffer = apr_palloc(r->pool, server_limit * sizeof(int));
}
@@ -318,6 +322,7 @@ static int status_handler(request_rec *r)
ps_record = ap_get_scoreboard_process(i);
if (is_async) {
thread_idle_buffer[i] = 0;
+ thread_graceful_buffer[i] = 0;
thread_busy_buffer[i] = 0;
}
for (j = 0; j < thread_limit; ++j) {
@@ -336,18 +341,20 @@ static int status_handler(request_rec *r)
&& ps_record->pid) {
if (res == SERVER_READY) {
if (ps_record->generation == mpm_generation)
- ready++;
+ idle++;
if (is_async)
thread_idle_buffer[i]++;
}
else if (res != SERVER_DEAD &&
res != SERVER_STARTING &&
res != SERVER_IDLE_KILL) {
- busy++;
- if (is_async) {
- if (res == SERVER_GRACEFUL)
- thread_idle_buffer[i]++;
- else
+ if (res == SERVER_GRACEFUL) {
+ graceful++;
+ if (is_async)
+ thread_graceful_buffer[i]++;
+ } else {
+ busy++;
+ if (is_async)
thread_busy_buffer[i]++;
}
}
@@ -548,10 +555,10 @@ static int status_handler(request_rec *r)
} /* ap_extended_status */
if (!short_report)
- ap_rprintf(r, "- %d requests currently being processed, "
- "%d idle workers
\n", busy, ready);
+ ap_rprintf(r, "- %d requests currently being processed, %d workers gracefully restarting, "
+ "%d idle workers
\n", busy, graceful, idle);
else
- ap_rprintf(r, "BusyWorkers: %d\nIdleWorkers: %d\n", busy, ready);
+ ap_rprintf(r, "BusyWorkers: %d\nGracefulWorkers: %d\nIdleWorkers: %d\n", busy, graceful, idle);
if (!short_report)
ap_rputs("
", r);
@@ -559,11 +566,6 @@ static int status_handler(request_rec *r)
if (is_async) {
int write_completion = 0, lingering_close = 0, keep_alive = 0,
connections = 0, stopping = 0, procs = 0;
- /*
- * These differ from 'busy' and 'ready' in how gracefully finishing
- * threads are counted. XXX: How to make this clear in the html?
- */
- int busy_workers = 0, idle_workers = 0;
if (!short_report)
ap_rputs("\n\n\n"
"Slot | "
@@ -573,7 +575,7 @@ static int status_handler(request_rec *r)
"Threads | "
"Async connections |
\n"
"total | accepting | "
- "busy | idle | "
+ "busy | graceful | idle | "
"writing | keep-alive | closing |
\n", r);
for (i = 0; i < server_limit; ++i) {
ps_record = ap_get_scoreboard_process(i);
@@ -582,8 +584,6 @@ static int status_handler(request_rec *r)
write_completion += ps_record->write_completion;
keep_alive += ps_record->keep_alive;
lingering_close += ps_record->lingering_close;
- busy_workers += thread_busy_buffer[i];
- idle_workers += thread_idle_buffer[i];
procs++;
if (ps_record->quiescing) {
stopping++;
@@ -599,7 +599,7 @@ static int status_handler(request_rec *r)
ap_rprintf(r, "%u | %" APR_PID_T_FMT " | "
"%s%s | "
"%u | %s | "
- "%u | %u | "
+ "%u | %u | %u | "
"%u | %u | %u | "
"
\n",
i, ps_record->pid,
@@ -607,6 +607,7 @@ static int status_handler(request_rec *r)
ps_record->connections,
ps_record->not_accepting ? "no" : "yes",
thread_busy_buffer[i],
+ thread_graceful_buffer[i],
thread_idle_buffer[i],
ps_record->write_completion,
ps_record->keep_alive,
@@ -618,25 +619,22 @@ static int status_handler(request_rec *r)
ap_rprintf(r, "Sum | "
"%d | %d | "
"%d | | "
- "%d | %d | "
+ "%d | %d | %d | "
"%d | %d | %d | "
"
\n
\n",
procs, stopping,
connections,
- busy_workers, idle_workers,
+ busy, graceful, idle,
write_completion, keep_alive, lingering_close);
}
else {
ap_rprintf(r, "Processes: %d\n"
"Stopping: %d\n"
- "BusyWorkers: %d\n"
- "IdleWorkers: %d\n"
"ConnsTotal: %d\n"
"ConnsAsyncWriting: %d\n"
"ConnsAsyncKeepAlive: %d\n"
"ConnsAsyncClosing: %d\n",
procs, stopping,
- busy_workers, idle_workers,
connections,
write_completion, keep_alive, lingering_close);
}
diff --git a/modules/http/byterange_filter.c b/modules/http/byterange_filter.c
index de585c5..5ebe853 100644
--- a/modules/http/byterange_filter.c
+++ b/modules/http/byterange_filter.c
@@ -152,7 +152,6 @@ static int ap_set_byterange(request_rec *r, apr_off_t clength,
*indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
while ((cur = ap_getword(r->pool, &range, ','))) {
char *dash;
- char *errp;
apr_off_t number, start, end;
if (!*cur)
@@ -169,7 +168,7 @@ static int ap_set_byterange(request_rec *r, apr_off_t clength,
if (dash == cur) {
/* In the form "-5" */
- if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
+ if (!ap_parse_strict_length(&number, dash+1)) {
return 0;
}
if (number < 1) {
@@ -180,12 +179,12 @@ static int ap_set_byterange(request_rec *r, apr_off_t clength,
}
else {
*dash++ = '\0';
- if (apr_strtoff(&number, cur, &errp, 10) || *errp) {
+ if (!ap_parse_strict_length(&number, cur)) {
return 0;
}
start = number;
if (*dash) {
- if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
+ if (!ap_parse_strict_length(&number, dash)) {
return 0;
}
end = number;
diff --git a/modules/http/http_core.c b/modules/http/http_core.c
index 35869b4..c6cb473 100644
--- a/modules/http/http_core.c
+++ b/modules/http/http_core.c
@@ -140,16 +140,17 @@ static int ap_process_http_async_connection(conn_rec *c)
AP_DEBUG_ASSERT(cs != NULL);
AP_DEBUG_ASSERT(cs->state == CONN_STATE_READ_REQUEST_LINE);
- while (cs->state == CONN_STATE_READ_REQUEST_LINE) {
+ if (cs->state == CONN_STATE_READ_REQUEST_LINE) {
ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
-
+ if (ap_extended_status) {
+ ap_set_conn_count(c->sbh, r, c->keepalives);
+ }
if ((r = ap_read_request(c))) {
-
- c->keepalive = AP_CONN_UNKNOWN;
- /* process the request if it was read without error */
-
if (r->status == HTTP_OK) {
cs->state = CONN_STATE_HANDLER;
+ if (ap_extended_status) {
+ ap_set_conn_count(c->sbh, r, c->keepalives + 1);
+ }
ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
ap_process_async_request(r);
/* After the call to ap_process_request, the
@@ -200,9 +201,6 @@ static int ap_process_http_sync_connection(conn_rec *c)
keep_alive_timeout = c->base_server->keep_alive_timeout;
}
- c->keepalive = AP_CONN_UNKNOWN;
- /* process the request if it was read without error */
-
if (r->status == HTTP_OK) {
if (cs)
cs->state = CONN_STATE_HANDLER;
diff --git a/modules/http/http_etag.c b/modules/http/http_etag.c
index 7f3c6d9..af74549 100644
--- a/modules/http/http_etag.c
+++ b/modules/http/http_etag.c
@@ -16,6 +16,9 @@
#include "apr_strings.h"
#include "apr_thread_proc.h" /* for RLIMIT stuff */
+#include "apr_sha1.h"
+#include "apr_base64.h"
+#include "apr_buckets.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"
@@ -24,9 +27,16 @@
#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"
+#if APR_HAS_MMAP
+#include "apr_mmap.h"
+#endif /* APR_HAS_MMAP */
+
+#define SHA1_DIGEST_BASE64_LEN 4*(APR_SHA1_DIGESTSIZE/3)
+
/* Generate the human-readable hex representation of an apr_uint64_t
* (basically a faster version of 'sprintf("%llx")')
*/
@@ -53,19 +63,159 @@ static char *etag_uint64_to_hex(char *next, apr_uint64_t u)
#define ETAG_WEAK "W/"
#define CHARS_PER_UINT64 (sizeof(apr_uint64_t) * 2)
+
+static void etag_start(char *etag, const char *weak, char **next)
+{
+ if (weak) {
+ while (*weak) {
+ *etag++ = *weak++;
+ }
+ }
+ *etag++ = '"';
+
+ *next = etag;
+}
+
+static void etag_end(char *next, const char *vlv, apr_size_t vlv_len)
+{
+ if (vlv) {
+ *next++ = ';';
+ apr_cpystrn(next, vlv, vlv_len);
+ }
+ else {
+ *next++ = '"';
+ *next = '\0';
+ }
+}
+
+/*
+ * Construct a strong ETag by creating a SHA1 hash across the file content.
+ */
+static char *make_digest_etag(request_rec *r, etag_rec *er, char *vlv,
+ apr_size_t vlv_len, char *weak, apr_size_t weak_len)
+{
+ apr_sha1_ctx_t context;
+ unsigned char digest[APR_SHA1_DIGESTSIZE];
+ apr_file_t *fd = NULL;
+ core_dir_config *cfg;
+ char *etag, *next;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+
+ apr_size_t nbytes;
+ apr_off_t offset = 0, zero = 0, len = 0;
+ apr_status_t status;
+
+ cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
+
+ if (er->fd) {
+ fd = er->fd;
+ }
+ else if (er->pathname) {
+ if ((status = apr_file_open(&fd, er->pathname, APR_READ | APR_BINARY,
+ 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10251)
+ "Make etag: could not open %s", er->pathname);
+ return "";
+ }
+ }
+ if (!fd) {
+ return "";
+ }
+
+ if ((status = apr_file_seek(fd, APR_CUR, &offset)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10252)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ if ((status = apr_file_seek(fd, APR_END, &len)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10258)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ if ((status = apr_file_seek(fd, APR_SET, &zero)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10253)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ e = apr_brigade_insert_file(bb, fd, 0, len, r->pool);
+
+#if APR_HAS_MMAP
+ if (cfg->enable_mmap == ENABLE_MMAP_OFF) {
+ (void)apr_bucket_file_enable_mmap(e, 0);
+ }
+#endif
+
+ apr_sha1_init(&context);
+ while (!APR_BRIGADE_EMPTY(bb))
+ {
+ const char *str;
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ if ((status = apr_bucket_read(e, &str, &nbytes, APR_BLOCK_READ)) != APR_SUCCESS) {
+ apr_brigade_destroy(bb);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10254)
+ "Make etag: could not read");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ apr_sha1_update(&context, str, nbytes);
+ apr_bucket_delete(e);
+ }
+
+ if ((status = apr_file_seek(fd, APR_SET, &offset)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10255)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+ apr_sha1_final(digest, &context);
+
+ etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
+ SHA1_DIGEST_BASE64_LEN + vlv_len + 4);
+
+ etag_start(etag, weak, &next);
+ next += apr_base64_encode_binary(next, digest, APR_SHA1_DIGESTSIZE) - 1;
+ etag_end(next, vlv, vlv_len);
+
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+
+ return etag;
+}
+
/*
* Construct an entity tag (ETag) from resource information. If it's a real
* file, build in some of the file characteristics. If the modification time
* is newer than (request-time minus 1 second), mark the ETag as weak - it
- * could be modified again in as short an interval. We rationalize the
- * modification time we're given to keep it from being in the future.
+ * could be modified again in as short an interval.
*/
-AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
+AP_DECLARE(char *) ap_make_etag_ex(request_rec *r, etag_rec *er)
{
- char *weak;
- apr_size_t weak_len;
- char *etag;
- char *next;
+ char *weak = NULL;
+ apr_size_t weak_len = 0, vlv_len = 0;
+ char *etag, *next, *vlv;
core_dir_config *cfg;
etag_components_t etag_bits;
etag_components_t bits_added;
@@ -73,13 +223,62 @@ AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
+ if (er->force_weak) {
+ weak = ETAG_WEAK;
+ weak_len = sizeof(ETAG_WEAK);
+ }
+
+ if (r->vlist_validator) {
+
+ /* If we have a variant list validator (vlv) due to the
+ * response being negotiated, then we create a structured
+ * entity tag which merges the variant etag with the variant
+ * list validator (vlv). This merging makes revalidation
+ * somewhat safer, ensures that caches which can deal with
+ * Vary will (eventually) be updated if the set of variants is
+ * changed, and is also a protocol requirement for transparent
+ * content negotiation.
+ */
+
+ /* if the variant list validator is weak, we make the whole
+ * structured etag weak. If we would not, then clients could
+ * have problems merging range responses if we have different
+ * variants with the same non-globally-unique strong etag.
+ */
+
+ vlv = r->vlist_validator;
+ if (vlv[0] == 'W') {
+ vlv += 3;
+ weak = ETAG_WEAK;
+ weak_len = sizeof(ETAG_WEAK);
+ }
+ else {
+ vlv++;
+ }
+ vlv_len = strlen(vlv);
+
+ }
+ else {
+ vlv = NULL;
+ vlv_len = 0;
+ }
+
+ /*
+ * Did a module flag the need for a strong etag, or did the
+ * configuration tell us to generate a digest?
+ */
+ if (er->finfo->filetype == APR_REG &&
+ (AP_REQUEST_IS_STRONG_ETAG(r) || (etag_bits & ETAG_DIGEST))) {
+
+ return make_digest_etag(r, er, vlv, vlv_len, weak, weak_len);
+ }
+
/*
* If it's a file (or we wouldn't be here) and no ETags
* should be set for files, return an empty string and
* note it for the header-sender to ignore.
*/
if (etag_bits & ETAG_NONE) {
- apr_table_setn(r->notes, "no-etag", "omit");
return "";
}
@@ -98,123 +297,117 @@ AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
* be modified again later in the second, and the validation
* would be incorrect.
*/
- if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) &&
- !force_weak) {
- weak = NULL;
- weak_len = 0;
- }
- else {
+ if ((er->request_time - er->finfo->mtime < (1 * APR_USEC_PER_SEC))) {
weak = ETAG_WEAK;
weak_len = sizeof(ETAG_WEAK);
}
- if (r->finfo.filetype != APR_NOFILE) {
+ if (er->finfo->filetype != APR_NOFILE) {
/*
* ETag gets set to [W/]"inode-size-mtime", modulo any
* FileETag keywords.
*/
etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") +
- 3 * CHARS_PER_UINT64 + 1);
- next = etag;
- if (weak) {
- while (*weak) {
- *next++ = *weak++;
- }
- }
- *next++ = '"';
+ 3 * CHARS_PER_UINT64 + vlv_len + 2);
+
+ etag_start(etag, weak, &next);
+
bits_added = 0;
if (etag_bits & ETAG_INODE) {
- next = etag_uint64_to_hex(next, r->finfo.inode);
+ next = etag_uint64_to_hex(next, er->finfo->inode);
bits_added |= ETAG_INODE;
}
if (etag_bits & ETAG_SIZE) {
if (bits_added != 0) {
*next++ = '-';
}
- next = etag_uint64_to_hex(next, r->finfo.size);
+ next = etag_uint64_to_hex(next, er->finfo->size);
bits_added |= ETAG_SIZE;
}
if (etag_bits & ETAG_MTIME) {
if (bits_added != 0) {
*next++ = '-';
}
- next = etag_uint64_to_hex(next, r->mtime);
+ next = etag_uint64_to_hex(next, er->finfo->mtime);
}
- *next++ = '"';
- *next = '\0';
+
+ etag_end(next, vlv, vlv_len);
+
}
else {
/*
* Not a file document, so just use the mtime: [W/]"mtime"
*/
etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
- CHARS_PER_UINT64 + 1);
- next = etag;
- if (weak) {
- while (*weak) {
- *next++ = *weak++;
- }
- }
- *next++ = '"';
- next = etag_uint64_to_hex(next, r->mtime);
- *next++ = '"';
- *next = '\0';
+ CHARS_PER_UINT64 + vlv_len + 2);
+
+ etag_start(etag, weak, &next);
+ next = etag_uint64_to_hex(next, er->finfo->mtime);
+ etag_end(next, vlv, vlv_len);
+
}
return etag;
}
+AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
+{
+ etag_rec er;
+
+ er.vlist_validator = NULL;
+ er.request_time = r->request_time;
+ er.finfo = &r->finfo;
+ er.pathname = r->filename;
+ er.fd = NULL;
+ er.force_weak = force_weak;
+
+ return ap_make_etag_ex(r, &er);
+}
+
AP_DECLARE(void) ap_set_etag(request_rec *r)
{
char *etag;
- char *variant_etag, *vlv;
- int vlv_weak;
- if (!r->vlist_validator) {
- etag = ap_make_etag(r, 0);
+ etag_rec er;
- /* If we get a blank etag back, don't set the header. */
- if (!etag[0]) {
- return;
- }
+ er.vlist_validator = r->vlist_validator;
+ er.request_time = r->request_time;
+ er.finfo = &r->finfo;
+ er.pathname = r->filename;
+ er.fd = NULL;
+ er.force_weak = 0;
+
+ etag = ap_make_etag_ex(r, &er);
+
+ if (etag && etag[0]) {
+ apr_table_setn(r->headers_out, "ETag", etag);
}
else {
- /* If we have a variant list validator (vlv) due to the
- * response being negotiated, then we create a structured
- * entity tag which merges the variant etag with the variant
- * list validator (vlv). This merging makes revalidation
- * somewhat safer, ensures that caches which can deal with
- * Vary will (eventually) be updated if the set of variants is
- * changed, and is also a protocol requirement for transparent
- * content negotiation.
- */
+ apr_table_setn(r->notes, "no-etag", "omit");
+ }
- /* if the variant list validator is weak, we make the whole
- * structured etag weak. If we would not, then clients could
- * have problems merging range responses if we have different
- * variants with the same non-globally-unique strong etag.
- */
+}
- vlv = r->vlist_validator;
- vlv_weak = (vlv[0] == 'W');
+AP_DECLARE(void) ap_set_etag_fd(request_rec *r, apr_file_t *fd)
+{
+ char *etag;
- variant_etag = ap_make_etag(r, vlv_weak);
+ etag_rec er;
- /* If we get a blank etag back, don't append vlv and stop now. */
- if (!variant_etag[0]) {
- return;
- }
+ er.vlist_validator = r->vlist_validator;
+ er.request_time = r->request_time;
+ er.finfo = &r->finfo;
+ er.pathname = NULL;
+ er.fd = fd;
+ er.force_weak = 0;
- /* merge variant_etag and vlv into a structured etag */
- variant_etag[strlen(variant_etag) - 1] = '\0';
- if (vlv_weak) {
- vlv += 3;
- }
- else {
- vlv++;
- }
- etag = apr_pstrcat(r->pool, variant_etag, ";", vlv, NULL);
+ etag = ap_make_etag_ex(r, &er);
+
+ if (etag && etag[0]) {
+ apr_table_setn(r->headers_out, "ETag", etag);
+ }
+ else {
+ apr_table_setn(r->notes, "no-etag", "omit");
}
- apr_table_setn(r->headers_out, "ETag", etag);
}
diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c
index 9828cdf..f20aee7 100644
--- a/modules/http/http_filters.c
+++ b/modules/http/http_filters.c
@@ -79,7 +79,8 @@ typedef struct http_filter_ctx
BODY_CHUNK_END_LF, /* got CR after data, expect LF */
BODY_CHUNK_TRAILER /* trailers */
} state;
- unsigned int eos_sent :1;
+ unsigned int eos_sent :1,
+ seen_data:1;
apr_bucket_brigade *bb;
} http_ctx_t;
@@ -348,7 +349,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
http_ctx_t *ctx = f->ctx;
apr_status_t rv;
int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE;
- apr_bucket_brigade *bb;
int again;
/* just get out of the way of things we don't want. */
@@ -361,7 +361,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
ctx->state = BODY_NONE;
ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
- bb = ctx->bb;
/* LimitRequestBody does not apply to proxied responses.
* Consider implementing this check in its own filter.
@@ -379,8 +378,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
lenp = apr_table_get(f->r->headers_in, "Content-Length");
if (tenc) {
- if (strcasecmp(tenc, "chunked") == 0 /* fast path */
- || ap_find_last_token(f->r->pool, tenc, "chunked")) {
+ if (ap_is_chunked(f->r->pool, tenc)) {
ctx->state = BODY_CHUNK;
}
else if (f->r->proxyreq == PROXYREQ_RESPONSE) {
@@ -406,16 +404,13 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
lenp = NULL;
}
if (lenp) {
- char *endstr;
-
ctx->state = BODY_LENGTH;
/* Protects against over/underflow, non-digit chars in the
- * string (excluding leading space) (the endstr checks)
- * and a negative number. */
- if (apr_strtoff(&ctx->remaining, lenp, &endstr, 10)
- || endstr == lenp || *endstr || ctx->remaining < 0) {
-
+ * string, leading plus/minus signs, trailing characters and
+ * a negative number.
+ */
+ if (!ap_parse_strict_length(&ctx->remaining, lenp)) {
ctx->remaining = 0;
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01587)
"Invalid Content-Length");
@@ -452,42 +447,46 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
ctx->eos_sent = 1;
return APR_SUCCESS;
}
+ }
- /* 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 ((ctx->state == BODY_CHUNK ||
- (ctx->state == BODY_LENGTH && ctx->remaining > 0)) &&
- f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1) &&
- !(f->r->eos_sent || f->r->bytes_sent)) {
- if (!ap_is_HTTP_SUCCESS(f->r->status)) {
- ctx->state = BODY_NONE;
- ctx->eos_sent = 1;
- }
- else {
- char *tmp;
- int len;
-
- /* if we send an interim response, we're no longer
- * in a state of expecting one.
- */
- f->r->expecting_100 = 0;
- tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL " ",
- ap_get_status_line(HTTP_CONTINUE), CRLF CRLF,
- NULL);
- len = strlen(tmp);
- ap_xlate_proto_to_ascii(tmp, len);
- apr_brigade_cleanup(bb);
- e = apr_bucket_pool_create(tmp, len, f->r->pool,
- f->c->bucket_alloc);
- APR_BRIGADE_INSERT_HEAD(bb, e);
- e = apr_bucket_flush_create(f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
-
- rv = ap_pass_brigade(f->c->output_filters, bb);
- if (rv != APR_SUCCESS) {
- return AP_FILTER_ERROR;
- }
- }
+ /* 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->eos_sent || f->r->eos_sent || f->r->bytes_sent)) {
+ if (!ap_is_HTTP_SUCCESS(f->r->status)) {
+ ctx->state = BODY_NONE;
+ ctx->eos_sent = 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 [...]
+ */
+ f->r->expecting_100 = 0;
}
}
@@ -538,9 +537,11 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
int parsing = 0;
rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ);
-
if (rv == APR_SUCCESS) {
parsing = 1;
+ if (len > 0) {
+ ctx->seen_data = 1;
+ }
rv = parse_chunk_size(ctx, buffer, len,
f->r->server->limit_req_fieldsize, strict);
}
@@ -602,6 +603,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
/* How many bytes did we just read? */
apr_brigade_length(b, 0, &totalread);
+ if (totalread > 0) {
+ ctx->seen_data = 1;
+ }
/* If this happens, we have a bucket of unknown length. Die because
* it means our assumptions have changed. */
@@ -774,6 +778,18 @@ static APR_INLINE int check_headers(request_rec *r)
struct check_header_ctx ctx;
core_server_config *conf =
ap_get_core_module_config(r->server->module_config);
+ const char *val;
+
+ if ((val = apr_table_get(r->headers_out, "Transfer-Encoding"))) {
+ if (apr_table_get(r->headers_out, "Content-Length")) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+ if (!ap_is_chunked(r->pool, val)) {
+ r->connection->keepalive = AP_CONN_CLOSE;
+ return 0;
+ }
+ }
ctx.r = r;
ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
@@ -872,7 +888,7 @@ static int uniq_field_values(void *d, const char *key, const char *val)
*/
for (i = 0, strpp = (char **) values->elts; i < values->nelts;
++i, ++strpp) {
- if (*strpp && strcasecmp(*strpp, start) == 0) {
+ if (*strpp && ap_cstr_casecmp(*strpp, start) == 0) {
break;
}
}
@@ -1290,6 +1306,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
request_rec *r = f->r;
conn_rec *c = r->connection;
const char *clheader;
+ int header_only = (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status));
const char *protocol = NULL;
apr_bucket *e;
apr_bucket_brigade *b2;
@@ -1307,7 +1324,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
}
else if (ctx->headers_sent) {
/* Eat body if response must not have one. */
- if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
+ if (header_only) {
/* Still next filters may be waiting for EOS, so pass it (alone)
* when encountered and be done with this filter.
*/
@@ -1348,6 +1365,9 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
*/
apr_table_clear(r->headers_out);
apr_table_clear(r->err_headers_out);
+ r->content_type = r->content_encoding = NULL;
+ r->content_languages = NULL;
+ r->clength = r->chunked = 0;
apr_brigade_cleanup(b);
/* Don't recall ap_die() if we come back here (from its own internal
@@ -1364,8 +1384,6 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
APR_BRIGADE_INSERT_TAIL(b, e);
e = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(b, e);
- r->content_type = r->content_encoding = NULL;
- r->content_languages = NULL;
ap_set_content_length(r, 0);
recursive_error = 1;
}
@@ -1392,6 +1410,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
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);
}
/*
@@ -1411,6 +1430,17 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
fixup_vary(r);
}
+
+ /*
+ * Control cachability for non-cacheable responses if not already set by
+ * some other part of the server configuration.
+ */
+ 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_addn(r->headers_out, "Expires", date);
+ }
+
/*
* Now remove any ETag response header field if earlier processing
* says so (such as a 'FileETag None' directive).
@@ -1423,6 +1453,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
basic_http_header_check(r, &protocol);
ap_set_keepalive(r);
+ /* 204/304 responses don't have content related headers */
if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
apr_table_unset(r->headers_out, "Transfer-Encoding");
apr_table_unset(r->headers_out, "Content-Length");
@@ -1453,7 +1484,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
for (i = 0; i < r->content_languages->nelts; ++i) {
- if (!strcasecmp(token, languages[i]))
+ if (!ap_cstr_casecmp(token, languages[i]))
break;
}
if (i == r->content_languages->nelts) {
@@ -1465,16 +1496,6 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
apr_table_setn(r->headers_out, "Content-Language", field);
}
- /*
- * Control cachability for non-cacheable responses if not already set by
- * some other part of the server configuration.
- */
- 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_addn(r->headers_out, "Expires", date);
- }
-
/* This is a hack, but I can't find anyway around it. The idea is that
* we don't want to send out 0 Content-Lengths if it is a head request.
* This happens when modules try to outsmart the server, and return
@@ -1499,37 +1520,25 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
h.pool = r->pool;
h.bb = b2;
- if (r->status == HTTP_NOT_MODIFIED) {
- apr_table_do((int (*)(void *, const char *, const char *)) form_header_field,
- (void *) &h, r->headers_out,
- "Connection",
- "Keep-Alive",
- "ETag",
- "Content-Location",
- "Expires",
- "Cache-Control",
- "Vary",
- "Warning",
- "WWW-Authenticate",
- "Proxy-Authenticate",
- "Set-Cookie",
- "Set-Cookie2",
- NULL);
- }
- else {
- send_all_header_fields(&h, r);
- }
+ send_all_header_fields(&h, r);
terminate_header(b2);
- rv = ap_pass_brigade(f->next, b2);
- if (rv != APR_SUCCESS) {
- goto out;
+ if (header_only) {
+ e = APR_BRIGADE_LAST(b);
+ if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(b2, e);
+ ap_remove_output_filter(f);
+ }
+ apr_brigade_cleanup(b);
}
+
+ rv = ap_pass_brigade(f->next, b2);
+ apr_brigade_cleanup(b2);
ctx->headers_sent = 1;
- if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
- apr_brigade_cleanup(b);
+ if (rv != APR_SUCCESS || header_only) {
goto out;
}
@@ -1605,9 +1614,9 @@ AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status)
*/
AP_DECLARE(int) ap_discard_request_body(request_rec *r)
{
+ int rc = OK;
+ conn_rec *c = r->connection;
apr_bucket_brigade *bb;
- int seen_eos;
- apr_status_t rv;
/* Sometimes we'll get in a state where the input handling has
* detected an error where we want to drop the connection, so if
@@ -1616,54 +1625,57 @@ AP_DECLARE(int) ap_discard_request_body(request_rec *r)
*
* This function is also a no-op on a subrequest.
*/
- if (r->main || r->connection->keepalive == AP_CONN_CLOSE ||
- ap_status_drops_connection(r->status)) {
+ if (r->main || c->keepalive == AP_CONN_CLOSE) {
+ return OK;
+ }
+ if (ap_status_drops_connection(r->status)) {
+ c->keepalive = AP_CONN_CLOSE;
return OK;
}
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
- seen_eos = 0;
- do {
- apr_bucket *bucket;
+ for (;;) {
+ apr_status_t rv;
rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
APR_BLOCK_READ, HUGE_STRING_LEN);
-
if (rv != APR_SUCCESS) {
- apr_brigade_destroy(bb);
- return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ rc = ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ goto cleanup;
}
- for (bucket = APR_BRIGADE_FIRST(bb);
- bucket != APR_BRIGADE_SENTINEL(bb);
- bucket = APR_BUCKET_NEXT(bucket))
- {
- const char *data;
- apr_size_t len;
-
- if (APR_BUCKET_IS_EOS(bucket)) {
- seen_eos = 1;
- break;
- }
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
- /* These are metadata buckets. */
- if (bucket->length == 0) {
- continue;
+ if (APR_BUCKET_IS_EOS(b)) {
+ goto cleanup;
}
- /* We MUST read because in case we have an unknown-length
- * bucket or one that morphs, we want to exhaust it.
+ /* There is no need to read empty or metadata buckets or
+ * buckets of known length, but we MUST read buckets of
+ * unknown length in order to exhaust them.
*/
- rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
- if (rv != APR_SUCCESS) {
- apr_brigade_destroy(bb);
- return HTTP_BAD_REQUEST;
+ if (b->length == (apr_size_t)-1) {
+ apr_size_t len;
+ const char *data;
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ rc = HTTP_BAD_REQUEST;
+ goto cleanup;
+ }
}
+
+ apr_bucket_delete(b);
}
- apr_brigade_cleanup(bb);
- } while (!seen_eos);
+ }
- return OK;
+cleanup:
+ apr_brigade_cleanup(bb);
+ if (rc != OK) {
+ c->keepalive = AP_CONN_CLOSE;
+ }
+ return rc;
}
/* Here we deal with getting the request message body from the client.
@@ -1707,13 +1719,14 @@ 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");
+ apr_off_t limit_req_body = ap_get_limit_req_body(r);
r->read_body = read_policy;
r->read_chunked = 0;
r->remaining = 0;
if (tenc) {
- if (strcasecmp(tenc, "chunked")) {
+ 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;
@@ -1727,13 +1740,10 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy)
r->read_chunked = 1;
}
else if (lenp) {
- char *endstr;
-
- if (apr_strtoff(&r->remaining, lenp, &endstr, 10)
- || *endstr || r->remaining < 0) {
+ if (!ap_parse_strict_length(&r->remaining, lenp)) {
r->remaining = 0;
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01594)
- "Invalid Content-Length");
+ "Invalid Content-Length '%s'", lenp);
return HTTP_BAD_REQUEST;
}
}
@@ -1745,6 +1755,11 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy)
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
+ if (limit_req_body > 0 && (r->remaining > limit_req_body)) {
+ /* will be logged when the body is discarded */
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
#ifdef AP_DEBUG
{
/* Make sure ap_getline() didn't leave any droppings. */
@@ -1852,6 +1867,7 @@ AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer,
/* Context struct for ap_http_outerror_filter */
typedef struct {
int seen_eoc;
+ int first_error;
} outerror_filter_ctx_t;
/* Filter to handle any error buckets on output */
@@ -1880,10 +1896,18 @@ apr_status_t ap_http_outerror_filter(ap_filter_t *f,
/* stream aborted and we have not ended it yet */
r->connection->keepalive = AP_CONN_CLOSE;
}
+ /*
+ * Memorize the status code of the first error bucket for possible
+ * later use.
+ */
+ if (!ctx->first_error) {
+ ctx->first_error = ((ap_bucket_error *)(e->data))->status;
+ }
continue;
}
/* Detect EOC buckets and memorize this in the context. */
if (AP_BUCKET_IS_EOC(e)) {
+ r->connection->keepalive = AP_CONN_CLOSE;
ctx->seen_eoc = 1;
}
}
@@ -1907,6 +1931,18 @@ apr_status_t ap_http_outerror_filter(ap_filter_t *f,
* EOS bucket.
*/
if (ctx->seen_eoc) {
+ /*
+ * Set the request status to the status of the first error bucket.
+ * This should ensure that we log an appropriate status code in
+ * the access log.
+ * We need to set r->status on each call after we noticed an EOC as
+ * data bucket generators like ap_die might have changed the status
+ * code. But we know better in this case and insist on the status
+ * code that we have seen in the error bucket.
+ */
+ if (ctx->first_error) {
+ r->status = ctx->first_error;
+ }
for (e = APR_BRIGADE_FIRST(b);
e != APR_BRIGADE_SENTINEL(b);
e = APR_BUCKET_NEXT(e))
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c
index e419eb6..d031f24 100644
--- a/modules/http/http_protocol.c
+++ b/modules/http/http_protocol.c
@@ -60,7 +60,7 @@
APLOG_USE_MODULE(http);
-/* New Apache routine to map status codes into array indicies
+/* New Apache routine to map status codes into array indices
* e.g. 100 -> 0, 101 -> 1, 200 -> 2 ...
* The number of status lines must equal the value of
* RESPONSE_CODES (httpd.h) and must be listed in order.
@@ -257,10 +257,9 @@ AP_DECLARE(int) ap_set_keepalive(request_rec *r)
&& (r->header_only
|| AP_STATUS_IS_HEADER_ONLY(r->status)
|| apr_table_get(r->headers_out, "Content-Length")
- || ap_find_last_token(r->pool,
+ || ap_is_chunked(r->pool,
apr_table_get(r->headers_out,
- "Transfer-Encoding"),
- "chunked")
+ "Transfer-Encoding"))
|| ((r->proto_num >= HTTP_VERSION(1,1))
&& (r->chunked = 1))) /* THIS CODE IS CORRECT, see above. */
&& r->server->keep_alive
@@ -987,14 +986,17 @@ AP_DECLARE(const char *) ap_method_name_of(apr_pool_t *p, int methnum)
* from status_lines[shortcut[i]] to status_lines[shortcut[i+1]-1];
* or use NULL to fill the gaps.
*/
-AP_DECLARE(int) ap_index_of_response(int status)
+static int index_of_response(int status)
{
- static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400,
- LEVEL_500, RESPONSE_CODES};
+ static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400, LEVEL_500,
+ RESPONSE_CODES};
int i, pos;
- if (status < 100) { /* Below 100 is illegal for HTTP status */
- return LEVEL_500;
+ if (status < 100) { /* Below 100 is illegal for HTTP status */
+ return -1;
+ }
+ if (status > 999) { /* Above 999 is also illegal for HTTP status */
+ return -1;
}
for (i = 0; i < 5; i++) {
@@ -1005,11 +1007,29 @@ AP_DECLARE(int) ap_index_of_response(int status)
return pos;
}
else {
- return LEVEL_500; /* status unknown (falls in gap) */
+ break;
}
}
}
- return LEVEL_500; /* 600 or above is also illegal */
+ return -2; /* Status unknown (falls in gap) or above 600 */
+}
+
+AP_DECLARE(int) ap_index_of_response(int status)
+{
+ int index = index_of_response(status);
+ return (index < 0) ? LEVEL_500 : index;
+}
+
+AP_DECLARE(const char *) ap_get_status_line_ex(apr_pool_t *p, int status)
+{
+ int index = index_of_response(status);
+ if (index >= 0) {
+ return status_lines[index];
+ }
+ else if (index == -2) {
+ return apr_psprintf(p, "%i Status %i", status, status);
+ }
+ return status_lines[LEVEL_500];
}
AP_DECLARE(const char *) ap_get_status_line(int status)
@@ -1132,13 +1152,10 @@ static const char *get_canned_error_string(int status,
"\">here.
\n",
NULL));
case HTTP_USE_PROXY:
- return(apr_pstrcat(p,
- "This resource is only accessible "
- "through the proxy\n",
- ap_escape_html(r->pool, location),
- "
\nYou will need to configure "
- "your client to use that proxy.
\n",
- NULL));
+ return("This resource is only accessible "
+ "through the proxy\n"
+ "
\nYou will need to configure "
+ "your client to use that proxy.
\n");
case HTTP_PROXY_AUTHENTICATION_REQUIRED:
case HTTP_UNAUTHORIZED:
return("This server could not verify that you\n"
@@ -1154,34 +1171,20 @@ static const char *get_canned_error_string(int status,
"error-notes",
"
\n"));
case HTTP_FORBIDDEN:
- s1 = apr_pstrcat(p,
- "You don't have permission to access ",
- ap_escape_html(r->pool, r->uri),
- "\non this server.
\n",
- NULL);
- return(add_optional_notes(r, s1, "error-notes", "
\n"));
+ return(add_optional_notes(r, "You don't have permission to access this resource.", "error-notes", "
\n"));
case HTTP_NOT_FOUND:
- return(apr_pstrcat(p,
- "The requested URL ",
- ap_escape_html(r->pool, r->uri),
- " was not found on this server.
\n",
- NULL));
+ return("The requested URL was not found on this server.
\n");
case HTTP_METHOD_NOT_ALLOWED:
return(apr_pstrcat(p,
"The requested method ",
ap_escape_html(r->pool, r->method),
- " is not allowed for the URL ",
- ap_escape_html(r->pool, r->uri),
- ".
\n",
+ " is not allowed for this URL.\n",
NULL));
case HTTP_NOT_ACCEPTABLE:
- s1 = apr_pstrcat(p,
- "An appropriate representation of the "
- "requested resource ",
- ap_escape_html(r->pool, r->uri),
- " could not be found on this server.
\n",
- NULL);
- return(add_optional_notes(r, s1, "variant-list", ""));
+ return(add_optional_notes(r,
+ "An appropriate representation of the requested resource "
+ "could not be found on this server.
\n",
+ "variant-list", ""));
case HTTP_MULTIPLE_CHOICES:
return(add_optional_notes(r, "", "variant-list", ""));
case HTTP_LENGTH_REQUIRED:
@@ -1192,18 +1195,13 @@ static const char *get_canned_error_string(int status,
NULL);
return(add_optional_notes(r, s1, "error-notes", "\n"));
case HTTP_PRECONDITION_FAILED:
- return(apr_pstrcat(p,
- "The precondition on the request "
- "for the URL ",
- ap_escape_html(r->pool, r->uri),
- " evaluated to false.
\n",
- NULL));
+ return("The precondition on the request "
+ "for this URL evaluated to false.
\n");
case HTTP_NOT_IMPLEMENTED:
s1 = apr_pstrcat(p,
"",
- ap_escape_html(r->pool, r->method), " to ",
- ap_escape_html(r->pool, r->uri),
- " not supported.
\n",
+ ap_escape_html(r->pool, r->method),
+ " not supported for current URL.
\n",
NULL);
return(add_optional_notes(r, s1, "error-notes", "
\n"));
case HTTP_BAD_GATEWAY:
@@ -1211,29 +1209,19 @@ static const char *get_canned_error_string(int status,
"response from an upstream server.
" CRLF;
return(add_optional_notes(r, s1, "error-notes", "\n"));
case HTTP_VARIANT_ALSO_VARIES:
- return(apr_pstrcat(p,
- "A variant for the requested "
- "resource\n
\n",
- ap_escape_html(r->pool, r->uri),
- "\n
\nis itself a negotiable resource. "
- "This indicates a configuration error.\n",
- NULL));
+ return("A variant for the requested "
+ "resource\n
\n"
+ "\n
\nis itself a negotiable resource. "
+ "This indicates a configuration error.\n");
case HTTP_REQUEST_TIME_OUT:
return("Server timeout waiting for the HTTP request from the client.
\n");
case HTTP_GONE:
- return(apr_pstrcat(p,
- "The requested resource
",
- ap_escape_html(r->pool, r->uri),
- "
\nis no longer available on this server "
- "and there is no forwarding address.\n"
- "Please remove all references to this "
- "resource.
\n",
- NULL));
+ return("The requested resource is no longer available on this server"
+ " and there is no forwarding address.\n"
+ "Please remove all references to this resource.
\n");
case HTTP_REQUEST_ENTITY_TOO_LARGE:
return(apr_pstrcat(p,
- "The requested resource
",
- ap_escape_html(r->pool, r->uri), "
\n",
- "does not allow request data with ",
+ "The requested resource does not allow request data with ",
ap_escape_html(r->pool, r->method),
" requests, or the amount of data provided in\n"
"the request exceeds the capacity limit.\n",
@@ -1317,11 +1305,9 @@ static const char *get_canned_error_string(int status,
"the Server Name Indication (SNI) in use for this\n"
"connection.\n");
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- s1 = apr_pstrcat(p,
- "Access to ", ap_escape_html(r->pool, r->uri),
- "\nhas been denied for legal reasons.
\n",
- NULL);
- return(add_optional_notes(r, s1, "error-notes", "
\n"));
+ return(add_optional_notes(r,
+ "Access to this URL has been denied for legal reasons.
\n",
+ "error-notes", "
\n"));
default: /* HTTP_INTERNAL_SERVER_ERROR */
/*
* This comparison to expose error-notes could be modified to
diff --git a/modules/http/http_request.c b/modules/http/http_request.c
index 9e7c4db..d59cfe2 100644
--- a/modules/http/http_request.c
+++ b/modules/http/http_request.c
@@ -249,7 +249,7 @@ AP_DECLARE(apr_status_t) ap_check_pipeline(conn_rec *c, apr_bucket_brigade *bb,
apr_brigade_cleanup(bb);
rv = ap_get_brigade(c->input_filters, bb, mode,
APR_NONBLOCK_READ, len);
- if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb) || !max_blank_lines) {
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
if (mode == AP_MODE_READBYTES) {
/* Unexpected error, stop with this connection */
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(02967)
@@ -257,23 +257,22 @@ AP_DECLARE(apr_status_t) ap_check_pipeline(conn_rec *c, apr_bucket_brigade *bb,
c->keepalive = AP_CONN_CLOSE;
rv = APR_EGENERAL;
}
- else if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
- if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
- /* Pipe is dead */
- c->keepalive = AP_CONN_CLOSE;
- }
- else {
- /* Pipe is up and empty */
- rv = APR_EAGAIN;
- }
+ else if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
+ /* Pipe is dead */
+ c->keepalive = AP_CONN_CLOSE;
}
else {
- apr_off_t n = 0;
- /* Single read asked, (non-meta-)data available? */
- rv = apr_brigade_length(bb, 0, &n);
- if (rv == APR_SUCCESS && n <= 0) {
- rv = APR_EAGAIN;
- }
+ /* Pipe is up and empty */
+ rv = APR_EAGAIN;
+ }
+ break;
+ }
+ if (!max_blank_lines) {
+ apr_off_t n = 0;
+ /* Single read asked, (non-meta-)data available? */
+ rv = apr_brigade_length(bb, 0, &n);
+ if (rv == APR_SUCCESS && n <= 0) {
+ rv = APR_EAGAIN;
}
break;
}
@@ -681,7 +680,7 @@ static request_rec *internal_internal_redirect(const char *new_uri,
* to do their thing on internal redirects as well. Perhaps this is a
* misnamed function.
*/
- if ((access_status = ap_run_post_read_request(new))) {
+ if ((access_status = ap_post_read_request(new))) {
ap_die(access_status, new);
return NULL;
}
diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c
index 28c53be..700f824 100644
--- a/modules/http/mod_mime.c
+++ b/modules/http/mod_mime.c
@@ -755,7 +755,7 @@ static int find_ct(request_rec *r)
mime_dir_config *conf;
apr_array_header_t *exception_list;
char *ext;
- const char *fn, *fntmp, *type, *charset = NULL, *resource_name;
+ const char *fn, *fntmp, *type, *charset = NULL, *resource_name, *qm;
int found_metadata = 0;
if (r->finfo.filetype == APR_DIR) {
@@ -775,6 +775,19 @@ static int find_ct(request_rec *r)
if (conf->use_path_info & 1) {
resource_name = apr_pstrcat(r->pool, r->filename, r->path_info, NULL);
}
+ /*
+ * In the reverse proxy case r->filename might contain a query string if
+ * the nocanon option was used with ProxyPass.
+ * If this is the case cut off the query string as the last parameter in
+ * this query string might end up on an extension we take care about, but
+ * we only want to match against path components not against query
+ * parameters.
+ */
+ else if ((r->proxyreq == PROXYREQ_REVERSE)
+ && (apr_table_get(r->notes, "proxy-nocanon"))
+ && ((qm = ap_strchr_c(r->filename, '?')) != NULL)) {
+ resource_name = apr_pstrmemdup(r->pool, r->filename, qm - r->filename);
+ }
else {
resource_name = r->filename;
}
@@ -989,9 +1002,7 @@ static int find_ct(request_rec *r)
if (!r->content_languages && conf->default_language) {
const char **new;
- if (!r->content_languages) {
- r->content_languages = apr_array_make(r->pool, 2, sizeof(char *));
- }
+ r->content_languages = apr_array_make(r->pool, 2, sizeof(char *));
new = (const char **)apr_array_push(r->content_languages);
*new = conf->default_language;
}
diff --git a/modules/http2/.gitignore b/modules/http2/.gitignore
deleted file mode 100644
index ca49620..0000000
--- a/modules/http2/.gitignore
+++ /dev/null
@@ -1,35 +0,0 @@
-*.xcuserstate
-sandbox/httpd/packages/httpd-2.4.x.tar.gz
-sandbox/test/conf/sites/mod-h2.greenbytes.de.conf
-*.o
-*.slo
-*.lo
-*.la
-*.pcap
-.libs
-.configured
-.deps
-compile
-aclocal.m4
-autom4te.cache
-autoscan.log
-config.guess
-config.log
-config.status
-config.sub
-config.h
-config.h.in
-config.h.in~
-configure
-configure.scan
-depcomp
-install-sh
-libtool
-ltmain.sh
-missing
-stamp-h1
-Makefile.in
-Makefile
-mod_h2-*.tar.gz
-mod_h2/h2_version.h
-m4
diff --git a/modules/http2/config2.m4 b/modules/http2/config2.m4
index e8cefe3..c4579c4 100644
--- a/modules/http2/config2.m4
+++ b/modules/http2/config2.m4
@@ -19,27 +19,25 @@ APACHE_MODPATH_INIT(http2)
dnl # list of module object files
http2_objs="dnl
mod_http2.lo dnl
-h2_alt_svc.lo dnl
h2_bucket_beam.lo dnl
h2_bucket_eos.lo dnl
+h2_c1.lo dnl
+h2_c1_io.lo dnl
+h2_c2.lo dnl
+h2_c2_filter.lo dnl
h2_config.lo dnl
-h2_conn.lo dnl
-h2_conn_io.lo dnl
-h2_ctx.lo dnl
-h2_filter.lo dnl
-h2_from_h1.lo dnl
-h2_h2.lo dnl
+h2_conn_ctx.lo dnl
h2_headers.lo dnl
h2_mplx.lo dnl
-h2_ngn_shed.lo dnl
+h2_protocol.lo dnl
h2_push.lo dnl
h2_request.lo dnl
h2_session.lo dnl
h2_stream.lo dnl
h2_switch.lo dnl
-h2_task.lo dnl
h2_util.lo dnl
h2_workers.lo dnl
+h2_ws.lo dnl
"
dnl
@@ -164,6 +162,12 @@ dnl # nghttp2 >= 1.14.0: invalid header callback
dnl # nghttp2 >= 1.15.0: get/set stream window sizes
AC_CHECK_FUNCS([nghttp2_session_get_stream_local_window_size],
[APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_LOCAL_WIN_SIZE"])], [])
+dnl # nghttp2 >= 1.15.0: don't keep info on closed streams
+ AC_CHECK_FUNCS([nghttp2_option_set_no_closed_streams],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_NO_CLOSED_STREAMS"])], [])
+dnl # nghttp2 >= 1.50.0: rfc9113 leading/trailing whitespec strictness
+ AC_CHECK_FUNCS([nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_RFC9113_STRICTNESS"])], [])
else
AC_MSG_WARN([nghttp2 version is too old])
fi
diff --git a/modules/http2/h2.h b/modules/http2/h2.h
index 38b4019..f496a6d 100644
--- a/modules/http2/h2.h
+++ b/modules/http2/h2.h
@@ -17,6 +17,38 @@
#ifndef __mod_h2__h2__
#define __mod_h2__h2__
+#include
+#include
+
+#include
+
+struct h2_session;
+struct h2_stream;
+
+/*
+ * When apr pollsets can poll file descriptors (e.g. pipes),
+ * we use it for polling stream input/output.
+ */
+#ifdef H2_NO_PIPES
+#define H2_USE_PIPES 0
+#else
+#define H2_USE_PIPES (APR_FILES_AS_SOCKETS && APR_VERSION_AT_LEAST(1,6,0))
+#endif
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 129)
+#define H2_USE_POLLFD_FROM_CONN 1
+#else
+#define H2_USE_POLLFD_FROM_CONN 0
+#endif
+
+/* WebSockets support requires apr 1.7.0 for apr_encode.h, plus the
+ * WebSockets features of nghttp2 1.34.0 and later. */
+#if H2_USE_PIPES && defined(NGHTTP2_VERSION_NUM) && NGHTTP2_VERSION_NUM >= 0x012200 && APR_VERSION_AT_LEAST(1,7,0)
+#define H2_USE_WEBSOCKETS 1
+#else
+#define H2_USE_WEBSOCKETS 0
+#endif
+
/**
* The magic PRIamble of RFC 7540 that is always sent when starting
* a h2 communication.
@@ -46,14 +78,16 @@ extern const char *H2_MAGIC_TOKEN;
#define H2_HEADER_AUTH_LEN 10
#define H2_HEADER_PATH ":path"
#define H2_HEADER_PATH_LEN 5
+#define H2_HEADER_PROTO ":protocol"
+#define H2_HEADER_PROTO_LEN 9
#define H2_CRLF "\r\n"
-/* Max data size to write so it fits inside a TLS record */
-#define H2_DATA_CHUNK_SIZE ((16*1024) - 100 - 9)
-
/* Size of the frame header itself in HTTP/2 */
#define H2_FRAME_HDR_LEN 9
+/* Max data size to write so it fits inside a TLS record */
+#define H2_DATA_CHUNK_SIZE ((16*1024) - 100 - H2_FRAME_HDR_LEN)
+
/* Maximum number of padding bytes in a frame, rfc7540 */
#define H2_MAX_PADLEN 256
/* Initial default window size, RFC 7540 ch. 6.5.2 */
@@ -89,7 +123,7 @@ typedef enum {
H2_SESSION_ST_DONE, /* finished, connection close */
H2_SESSION_ST_IDLE, /* nothing to write, expecting data inc */
H2_SESSION_ST_BUSY, /* read/write without stop */
- H2_SESSION_ST_WAIT, /* waiting for tasks reporting back */
+ H2_SESSION_ST_WAIT, /* waiting for c1 incoming + c2s output */
H2_SESSION_ST_CLEANUP, /* pool is being cleaned up */
} h2_session_state;
@@ -99,6 +133,7 @@ typedef struct h2_session_props {
int emitted_count; /* the number of local streams sent */
int emitted_max; /* the highest local stream id sent */
int error; /* the last session error encountered */
+ const char *error_msg; /* the short message given on the error */
unsigned int accepting : 1; /* if the session is accepting new streams */
unsigned int shutdown : 1; /* if the final GOAWAY has been sent */
} h2_session_props;
@@ -120,7 +155,9 @@ typedef enum {
H2_SEV_CLOSED_R,
H2_SEV_CANCELLED,
H2_SEV_EOS_SENT,
+ H2_SEV_IN_ERROR,
H2_SEV_IN_DATA_PENDING,
+ H2_SEV_OUT_C1_BLOCK,
} h2_stream_event_t;
@@ -129,38 +166,46 @@ typedef enum {
* become a request_rec to be handled by soemone.
*/
typedef struct h2_request h2_request;
-
struct h2_request {
const char *method; /* pseudo header values, see ch. 8.1.2.3 */
const char *scheme;
const char *authority;
const char *path;
+ const char *protocol;
apr_table_t *headers;
apr_time_t request_time;
- unsigned int chunked : 1; /* iff requst body needs to be forwarded as chunked */
- unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
apr_off_t raw_bytes; /* RAW network bytes that generated this request - if known. */
+ int http_status; /* Store a possible HTTP status code that gets
+ * defined before creating the dummy HTTP/1.1
+ * request e.g. due to an error already
+ * detected.
+ */
};
-typedef struct h2_headers h2_headers;
-
-struct h2_headers {
- int status;
- apr_table_t *headers;
- apr_table_t *notes;
- apr_off_t raw_bytes; /* RAW network bytes that generated this request - if known. */
-};
+/*
+ * A possible HTTP status code is not defined yet. See the http_status field
+ * in struct h2_request above for further explanation.
+ */
+#define H2_HTTP_STATUS_UNSET (0)
typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len);
-typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx);
-
-/* Note key to attach connection task id to conn_rec/request_rec instances */
+typedef int h2_stream_pri_cmp_fn(int stream_id1, int stream_id2, void *session);
+typedef struct h2_stream *h2_stream_get_fn(struct h2_session *session, int stream_id);
-#define H2_TASK_ID_NOTE "http2-task-id"
-#define H2_FILTER_DEBUG_NOTE "http2-debug"
+/* Note key to attach stream id to conn_rec/request_rec instances */
#define H2_HDR_CONFORMANCE "http2-hdr-conformance"
#define H2_HDR_CONFORMANCE_UNSAFE "unsafe"
+#define H2_PUSH_MODE_NOTE "http2-push-mode"
+
+
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 6)
+#define AP_HAS_RESPONSE_BUCKETS 1
+
+#else /* AP_MODULE_MAGIC_AT_LEAST(20211221, 6) */
+#define AP_HAS_RESPONSE_BUCKETS 0
+
+#endif /* else AP_MODULE_MAGIC_AT_LEAST(20211221, 6) */
#endif /* defined(__mod_h2__h2__) */
diff --git a/modules/http2/h2_alt_svc.c b/modules/http2/h2_alt_svc.c
deleted file mode 100644
index 295a16d..0000000
--- a/modules/http2/h2_alt_svc.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/* 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
-#include
-#include
-#include
-#include
-#include
-
-#include "h2_private.h"
-#include "h2_alt_svc.h"
-#include "h2_ctx.h"
-#include "h2_config.h"
-#include "h2_h2.h"
-#include "h2_util.h"
-
-static int h2_alt_svc_handler(request_rec *r);
-
-void h2_alt_svc_register_hooks(void)
-{
- ap_hook_post_read_request(h2_alt_svc_handler, NULL, NULL, APR_HOOK_MIDDLE);
-}
-
-/**
- * Parse an Alt-Svc specifier as described in "HTTP Alternative Services"
- * (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04)
- * with the following changes:
- * - do not percent encode token values
- * - do not use quotation marks
- */
-h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool)
-{
- const char *sep = ap_strchr_c(s, '=');
- if (sep) {
- const char *alpn = apr_pstrmemdup(pool, s, sep - s);
- const char *host = NULL;
- int port = 0;
- s = sep + 1;
- sep = ap_strchr_c(s, ':'); /* mandatory : */
- if (sep) {
- if (sep != s) { /* optional host */
- host = apr_pstrmemdup(pool, s, sep - s);
- }
- s = sep + 1;
- if (*s) { /* must be a port number */
- port = (int)apr_atoi64(s);
- if (port > 0 && port < (0x1 << 16)) {
- h2_alt_svc *as = apr_pcalloc(pool, sizeof(*as));
- as->alpn = alpn;
- as->host = host;
- as->port = port;
- return as;
- }
- }
- }
- }
- return NULL;
-}
-
-#define h2_alt_svc_IDX(list, i) ((h2_alt_svc**)(list)->elts)[i]
-
-static int h2_alt_svc_handler(request_rec *r)
-{
- const h2_config *cfg;
- int i;
-
- if (r->connection->keepalives > 0) {
- /* Only announce Alt-Svc on the first response */
- return DECLINED;
- }
-
- if (h2_ctx_rget(r)) {
- return DECLINED;
- }
-
- cfg = h2_config_sget(r->server);
- if (r->hostname && cfg && cfg->alt_svcs && cfg->alt_svcs->nelts > 0) {
- const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used");
- if (!alt_svc_used) {
- /* We have alt-svcs defined and client is not already using
- * one, announce the services that were configured and match.
- * The security of this connection determines if we allow
- * other host names or ports only.
- */
- const char *alt_svc = "";
- const char *svc_ma = "";
- int secure = h2_h2_is_tls(r->connection);
- int ma = h2_config_geti(cfg, H2_CONF_ALT_SVC_MAX_AGE);
- if (ma >= 0) {
- svc_ma = apr_psprintf(r->pool, "; ma=%d", ma);
- }
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03043)
- "h2_alt_svc: announce %s for %s:%d",
- (secure? "secure" : "insecure"),
- r->hostname, (int)r->server->port);
- for (i = 0; i < cfg->alt_svcs->nelts; ++i) {
- h2_alt_svc *as = h2_alt_svc_IDX(cfg->alt_svcs, i);
- const char *ahost = as->host;
- if (ahost && !apr_strnatcasecmp(ahost, r->hostname)) {
- ahost = NULL;
- }
- if (secure || !ahost) {
- alt_svc = apr_psprintf(r->pool, "%s%s%s=\"%s:%d\"%s",
- alt_svc,
- (*alt_svc? ", " : ""), as->alpn,
- ahost? ahost : "", as->port,
- svc_ma);
- }
- }
- if (*alt_svc) {
- apr_table_setn(r->headers_out, "Alt-Svc", alt_svc);
- }
- }
- }
-
- return DECLINED;
-}
diff --git a/modules/http2/h2_alt_svc.h b/modules/http2/h2_alt_svc.h
deleted file mode 100644
index 479e4d1..0000000
--- a/modules/http2/h2_alt_svc.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_alt_svc__
-#define __mod_h2__h2_alt_svc__
-
-typedef struct h2_alt_svc h2_alt_svc;
-
-struct h2_alt_svc {
- const char *alpn;
- const char *host;
- int port;
-};
-
-void h2_alt_svc_register_hooks(void);
-
-/**
- * Parse an Alt-Svc specifier as described in "HTTP Alternative Services"
- * (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04)
- * with the following changes:
- * - do not percent encode token values
- * - do not use quotation marks
- */
-h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool);
-
-
-#endif /* defined(__mod_h2__h2_alt_svc__) */
diff --git a/modules/http2/h2_bucket_beam.c b/modules/http2/h2_bucket_beam.c
index f79cbe3..6978254 100644
--- a/modules/http2/h2_bucket_beam.c
+++ b/modules/http2/h2_bucket_beam.c
@@ -24,261 +24,123 @@
#include
#include
+#include
#include
#include "h2_private.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
#include "h2_util.h"
#include "h2_bucket_beam.h"
-static void h2_beam_emitted(h2_bucket_beam *beam, h2_beam_proxy *proxy);
-#define H2_BPROXY_NEXT(e) APR_RING_NEXT((e), link)
-#define H2_BPROXY_PREV(e) APR_RING_PREV((e), link)
-#define H2_BPROXY_REMOVE(e) APR_RING_REMOVE((e), link)
-
-#define H2_BPROXY_LIST_INIT(b) APR_RING_INIT(&(b)->list, h2_beam_proxy, link);
-#define H2_BPROXY_LIST_SENTINEL(b) APR_RING_SENTINEL(&(b)->list, h2_beam_proxy, link)
-#define H2_BPROXY_LIST_EMPTY(b) APR_RING_EMPTY(&(b)->list, h2_beam_proxy, link)
-#define H2_BPROXY_LIST_FIRST(b) APR_RING_FIRST(&(b)->list)
-#define H2_BPROXY_LIST_LAST(b) APR_RING_LAST(&(b)->list)
-#define H2_PROXY_BLIST_INSERT_HEAD(b, e) do { \
- h2_beam_proxy *ap__b = (e); \
- APR_RING_INSERT_HEAD(&(b)->list, ap__b, h2_beam_proxy, link); \
+#define H2_BLIST_INIT(b) APR_RING_INIT(&(b)->list, apr_bucket, link);
+#define H2_BLIST_SENTINEL(b) APR_RING_SENTINEL(&(b)->list, apr_bucket, link)
+#define H2_BLIST_EMPTY(b) APR_RING_EMPTY(&(b)->list, apr_bucket, link)
+#define H2_BLIST_FIRST(b) APR_RING_FIRST(&(b)->list)
+#define H2_BLIST_LAST(b) APR_RING_LAST(&(b)->list)
+#define H2_BLIST_INSERT_HEAD(b, e) do { \
+ apr_bucket *ap__b = (e); \
+ APR_RING_INSERT_HEAD(&(b)->list, ap__b, apr_bucket, link); \
} while (0)
-#define H2_BPROXY_LIST_INSERT_TAIL(b, e) do { \
- h2_beam_proxy *ap__b = (e); \
- APR_RING_INSERT_TAIL(&(b)->list, ap__b, h2_beam_proxy, link); \
+#define H2_BLIST_INSERT_TAIL(b, e) do { \
+ apr_bucket *ap__b = (e); \
+ APR_RING_INSERT_TAIL(&(b)->list, ap__b, apr_bucket, link); \
} while (0)
-#define H2_BPROXY_LIST_CONCAT(a, b) do { \
- APR_RING_CONCAT(&(a)->list, &(b)->list, h2_beam_proxy, link); \
+#define H2_BLIST_CONCAT(a, b) do { \
+ APR_RING_CONCAT(&(a)->list, &(b)->list, apr_bucket, link); \
} while (0)
-#define H2_BPROXY_LIST_PREPEND(a, b) do { \
- APR_RING_PREPEND(&(a)->list, &(b)->list, h2_beam_proxy, link); \
+#define H2_BLIST_PREPEND(a, b) do { \
+ APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link); \
} while (0)
-/*******************************************************************************
- * beam bucket with reference to beam and bucket it represents
- ******************************************************************************/
-
-const apr_bucket_type_t h2_bucket_type_beam;
-
-#define H2_BUCKET_IS_BEAM(e) (e->type == &h2_bucket_type_beam)
-
-struct h2_beam_proxy {
- apr_bucket_refcount refcount;
- APR_RING_ENTRY(h2_beam_proxy) link;
- h2_bucket_beam *beam;
- apr_bucket *bsender;
- apr_size_t n;
-};
-
-static const char Dummy = '\0';
-
-static apr_status_t beam_bucket_read(apr_bucket *b, const char **str,
- apr_size_t *len, apr_read_type_e block)
-{
- h2_beam_proxy *d = b->data;
- if (d->bsender) {
- const char *data;
- apr_status_t status = apr_bucket_read(d->bsender, &data, len, block);
- if (status == APR_SUCCESS) {
- *str = data + b->start;
- *len = b->length;
- }
- return status;
- }
- *str = &Dummy;
- *len = 0;
- return APR_ECONNRESET;
-}
-
-static void beam_bucket_destroy(void *data)
-{
- h2_beam_proxy *d = data;
-
- if (apr_bucket_shared_destroy(d)) {
- /* When the beam gets destroyed before this bucket, it will
- * NULLify its reference here. This is not protected by a mutex,
- * so it will not help with race conditions.
- * But it lets us shut down memory pool with circulare beam
- * references. */
- if (d->beam) {
- h2_beam_emitted(d->beam, d);
- }
- apr_bucket_free(d);
- }
-}
-
-static apr_bucket * h2_beam_bucket_make(apr_bucket *b,
- h2_bucket_beam *beam,
- apr_bucket *bsender, apr_size_t n)
-{
- h2_beam_proxy *d;
-
- d = apr_bucket_alloc(sizeof(*d), b->list);
- H2_BPROXY_LIST_INSERT_TAIL(&beam->proxies, d);
- d->beam = beam;
- d->bsender = bsender;
- d->n = n;
-
- b = apr_bucket_shared_make(b, d, 0, bsender? bsender->length : 0);
- b->type = &h2_bucket_type_beam;
-
- return b;
-}
-
-static apr_bucket *h2_beam_bucket_create(h2_bucket_beam *beam,
- apr_bucket *bsender,
- apr_bucket_alloc_t *list,
- apr_size_t n)
-{
- apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
-
- APR_BUCKET_INIT(b);
- b->free = apr_bucket_free;
- b->list = list;
- return h2_beam_bucket_make(b, beam, bsender, n);
-}
-
-const apr_bucket_type_t h2_bucket_type_beam = {
- "BEAM", 5, APR_BUCKET_DATA,
- beam_bucket_destroy,
- beam_bucket_read,
- apr_bucket_setaside_noop,
- apr_bucket_shared_split,
- apr_bucket_shared_copy
-};
-
-/*******************************************************************************
- * h2_blist, a brigade without allocations
- ******************************************************************************/
-
-static apr_array_header_t *beamers;
+static int buffer_is_empty(h2_bucket_beam *beam);
+static apr_off_t get_buffered_data_len(h2_bucket_beam *beam);
-static apr_status_t cleanup_beamers(void *dummy)
+static int h2_blist_count(h2_blist *blist)
{
- (void)dummy;
- beamers = NULL;
- return APR_SUCCESS;
-}
-
-void h2_register_bucket_beamer(h2_bucket_beamer *beamer)
-{
- if (!beamers) {
- apr_pool_cleanup_register(apr_hook_global_pool, NULL,
- cleanup_beamers, apr_pool_cleanup_null);
- beamers = apr_array_make(apr_hook_global_pool, 10,
- sizeof(h2_bucket_beamer*));
- }
- APR_ARRAY_PUSH(beamers, h2_bucket_beamer*) = beamer;
-}
-
-static apr_bucket *h2_beam_bucket(h2_bucket_beam *beam,
- apr_bucket_brigade *dest,
- const apr_bucket *src)
-{
- apr_bucket *b = NULL;
- int i;
- if (beamers) {
- for (i = 0; i < beamers->nelts && b == NULL; ++i) {
- h2_bucket_beamer *beamer;
-
- beamer = APR_ARRAY_IDX(beamers, i, h2_bucket_beamer*);
- b = beamer(beam, dest, src);
- }
- }
- return b;
-}
-
-
-/*******************************************************************************
- * bucket beam that can transport buckets across threads
- ******************************************************************************/
-
-static void mutex_leave(void *ctx, apr_thread_mutex_t *lock)
-{
- apr_thread_mutex_unlock(lock);
-}
+ apr_bucket *b;
+ int count = 0;
-static apr_status_t mutex_enter(void *ctx, h2_beam_lock *pbl)
-{
- h2_bucket_beam *beam = ctx;
- pbl->mutex = beam->lock;
- pbl->leave = mutex_leave;
- return apr_thread_mutex_lock(pbl->mutex);
-}
+ for (b = H2_BLIST_FIRST(blist); b != H2_BLIST_SENTINEL(blist);
+ b = APR_BUCKET_NEXT(b)) {
+ ++count;
+ }
+ return count;
+}
+
+#define H2_BEAM_LOG(beam, c, level, rv, msg, bb) \
+ do { \
+ if (APLOG_C_IS_LEVEL((c),(level))) { \
+ char buffer[4 * 1024]; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = bb? h2_util_bb_print(buffer, bmax, "", "", bb) : 0; \
+ ap_log_cerror(APLOG_MARK, (level), rv, (c), \
+ "BEAM[%s,%s%sdata=%ld,buckets(send/consumed)=%d/%d]: %s %s", \
+ (beam)->name, \
+ (beam)->aborted? "aborted," : "", \
+ buffer_is_empty(beam)? "empty," : "", \
+ (long)get_buffered_data_len(beam), \
+ h2_blist_count(&(beam)->buckets_to_send), \
+ h2_blist_count(&(beam)->buckets_consumed), \
+ (msg), len? buffer : ""); \
+ } \
+ } while (0)
-static apr_status_t enter_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
-{
- return mutex_enter(beam, pbl);
-}
-static void leave_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
+static int bucket_is_mmap(apr_bucket *b)
{
- if (pbl->leave) {
- pbl->leave(pbl->leave_ctx, pbl->mutex);
- }
+#if APR_HAS_MMAP
+ return APR_BUCKET_IS_MMAP(b);
+#else
+ /* if it is not defined as enabled, it should always be no */
+ return 0;
+#endif
}
static apr_off_t bucket_mem_used(apr_bucket *b)
{
- if (APR_BUCKET_IS_FILE(b)) {
+ if (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b)) {
return 0;
}
else {
/* should all have determinate length */
- return b->length;
+ return (apr_off_t)b->length;
}
}
-static int report_consumption(h2_bucket_beam *beam, h2_beam_lock *pbl)
+static int report_consumption(h2_bucket_beam *beam, int locked)
{
int rv = 0;
- apr_off_t len = beam->received_bytes - beam->cons_bytes_reported;
+ apr_off_t len = beam->recv_bytes - beam->recv_bytes_reported;
h2_beam_io_callback *cb = beam->cons_io_cb;
if (len > 0) {
if (cb) {
void *ctx = beam->cons_ctx;
- if (pbl) leave_yellow(beam, pbl);
+ if (locked) apr_thread_mutex_unlock(beam->lock);
cb(ctx, beam, len);
- if (pbl) enter_yellow(beam, pbl);
+ if (locked) apr_thread_mutex_lock(beam->lock);
rv = 1;
}
- beam->cons_bytes_reported += len;
+ beam->recv_bytes_reported += len;
}
return rv;
}
-static void report_prod_io(h2_bucket_beam *beam, int force, h2_beam_lock *pbl)
-{
- apr_off_t len = beam->sent_bytes - beam->prod_bytes_reported;
- if (force || len > 0) {
- h2_beam_io_callback *cb = beam->prod_io_cb;
- if (cb) {
- void *ctx = beam->prod_ctx;
-
- leave_yellow(beam, pbl);
- cb(ctx, beam, len);
- enter_yellow(beam, pbl);
- }
- beam->prod_bytes_reported += len;
- }
-}
-
static apr_size_t calc_buffered(h2_bucket_beam *beam)
{
apr_size_t len = 0;
apr_bucket *b;
- for (b = H2_BLIST_FIRST(&beam->send_list);
- b != H2_BLIST_SENTINEL(&beam->send_list);
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
b = APR_BUCKET_NEXT(b)) {
if (b->length == ((apr_size_t)-1)) {
/* do not count */
}
- else if (APR_BUCKET_IS_FILE(b)) {
+ else if (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b)) {
/* if unread, has no real mem footprint. */
}
else {
@@ -288,13 +150,30 @@ static apr_size_t calc_buffered(h2_bucket_beam *beam)
return len;
}
-static void r_purge_sent(h2_bucket_beam *beam)
+static void purge_consumed_buckets(h2_bucket_beam *beam)
+{
+ apr_bucket *b;
+ /* delete all sender buckets in purge brigade, needs to be called
+ * from sender thread only */
+ while (!H2_BLIST_EMPTY(&beam->buckets_consumed)) {
+ b = H2_BLIST_FIRST(&beam->buckets_consumed);
+ if(AP_BUCKET_IS_EOR(b)) {
+ APR_BUCKET_REMOVE(b);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_eor, b);
+ }
+ else {
+ apr_bucket_delete(b);
+ }
+ }
+}
+
+static void purge_eor_buckets(h2_bucket_beam *beam)
{
apr_bucket *b;
/* delete all sender buckets in purge brigade, needs to be called
* from sender thread only */
- while (!H2_BLIST_EMPTY(&beam->purge_list)) {
- b = H2_BLIST_FIRST(&beam->purge_list);
+ while (!H2_BLIST_EMPTY(&beam->buckets_eor)) {
+ b = H2_BLIST_FIRST(&beam->buckets_eor);
apr_bucket_delete(b);
}
}
@@ -302,7 +181,7 @@ static void r_purge_sent(h2_bucket_beam *beam)
static apr_size_t calc_space_left(h2_bucket_beam *beam)
{
if (beam->max_buf_size > 0) {
- apr_off_t len = calc_buffered(beam);
+ apr_size_t len = calc_buffered(beam);
return (beam->max_buf_size > len? (beam->max_buf_size - len) : 0);
}
return APR_SIZE_MAX;
@@ -310,31 +189,10 @@ static apr_size_t calc_space_left(h2_bucket_beam *beam)
static int buffer_is_empty(h2_bucket_beam *beam)
{
- return ((!beam->recv_buffer || APR_BRIGADE_EMPTY(beam->recv_buffer))
- && H2_BLIST_EMPTY(&beam->send_list));
+ return H2_BLIST_EMPTY(&beam->buckets_to_send);
}
-static apr_status_t wait_empty(h2_bucket_beam *beam, apr_read_type_e block,
- apr_thread_mutex_t *lock)
-{
- apr_status_t rv = APR_SUCCESS;
-
- while (!buffer_is_empty(beam) && APR_SUCCESS == rv) {
- if (APR_BLOCK_READ != block || !lock) {
- rv = APR_EAGAIN;
- }
- else if (beam->timeout > 0) {
- rv = apr_thread_cond_timedwait(beam->change, lock, beam->timeout);
- }
- else {
- rv = apr_thread_cond_wait(beam->change, lock);
- }
- }
- return rv;
-}
-
-static apr_status_t wait_not_empty(h2_bucket_beam *beam, apr_read_type_e block,
- apr_thread_mutex_t *lock)
+static apr_status_t wait_not_empty(h2_bucket_beam *beam, conn_rec *c, apr_read_type_e block)
{
apr_status_t rv = APR_SUCCESS;
@@ -345,21 +203,24 @@ static apr_status_t wait_not_empty(h2_bucket_beam *beam, apr_read_type_e block,
else if (beam->closed) {
rv = APR_EOF;
}
- else if (APR_BLOCK_READ != block || !lock) {
+ else if (APR_BLOCK_READ != block) {
rv = APR_EAGAIN;
}
else if (beam->timeout > 0) {
- rv = apr_thread_cond_timedwait(beam->change, lock, beam->timeout);
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_empty, timeout", NULL);
+ rv = apr_thread_cond_timedwait(beam->change, beam->lock, beam->timeout);
}
else {
- rv = apr_thread_cond_wait(beam->change, lock);
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_empty, forever", NULL);
+ rv = apr_thread_cond_wait(beam->change, beam->lock);
}
}
return rv;
}
-static apr_status_t wait_not_full(h2_bucket_beam *beam, apr_read_type_e block,
- apr_size_t *pspace_left, h2_beam_lock *bl)
+static apr_status_t wait_not_full(h2_bucket_beam *beam, conn_rec *c,
+ apr_read_type_e block,
+ apr_size_t *pspace_left)
{
apr_status_t rv = APR_SUCCESS;
apr_size_t left;
@@ -368,15 +229,17 @@ static apr_status_t wait_not_full(h2_bucket_beam *beam, apr_read_type_e block,
if (beam->aborted) {
rv = APR_ECONNABORTED;
}
- else if (block != APR_BLOCK_READ || !bl->mutex) {
+ else if (block != APR_BLOCK_READ) {
rv = APR_EAGAIN;
}
else {
if (beam->timeout > 0) {
- rv = apr_thread_cond_timedwait(beam->change, bl->mutex, beam->timeout);
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_full, timeout", NULL);
+ rv = apr_thread_cond_timedwait(beam->change, beam->lock, beam->timeout);
}
else {
- rv = apr_thread_cond_wait(beam->change, bl->mutex);
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_full, forever", NULL);
+ rv = apr_thread_cond_wait(beam->change, beam->lock);
}
}
}
@@ -384,73 +247,6 @@ static apr_status_t wait_not_full(h2_bucket_beam *beam, apr_read_type_e block,
return rv;
}
-static void h2_beam_emitted(h2_bucket_beam *beam, h2_beam_proxy *proxy)
-{
- h2_beam_lock bl;
- apr_bucket *b, *next;
-
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- /* even when beam buckets are split, only the one where
- * refcount drops to 0 will call us */
- H2_BPROXY_REMOVE(proxy);
- /* invoked from receiver thread, the last beam bucket for the send
- * bucket is about to be destroyed.
- * remove it from the hold, where it should be now */
- if (proxy->bsender) {
- for (b = H2_BLIST_FIRST(&beam->hold_list);
- b != H2_BLIST_SENTINEL(&beam->hold_list);
- b = APR_BUCKET_NEXT(b)) {
- if (b == proxy->bsender) {
- break;
- }
- }
- if (b != H2_BLIST_SENTINEL(&beam->hold_list)) {
- /* bucket is in hold as it should be, mark this one
- * and all before it for purging. We might have placed meta
- * buckets without a receiver proxy into the hold before it
- * and schedule them for purging now */
- for (b = H2_BLIST_FIRST(&beam->hold_list);
- b != H2_BLIST_SENTINEL(&beam->hold_list);
- b = next) {
- next = APR_BUCKET_NEXT(b);
- if (b == proxy->bsender) {
- APR_BUCKET_REMOVE(b);
- H2_BLIST_INSERT_TAIL(&beam->purge_list, b);
- break;
- }
- else if (APR_BUCKET_IS_METADATA(b)) {
- APR_BUCKET_REMOVE(b);
- H2_BLIST_INSERT_TAIL(&beam->purge_list, b);
- }
- else {
- /* another data bucket before this one in hold. this
- * is normal since DATA buckets need not be destroyed
- * in order */
- }
- }
-
- proxy->bsender = NULL;
- }
- else {
- /* it should be there unless we screwed up */
- ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, beam->send_pool,
- APLOGNO(03384) "h2_beam(%d-%s): emitted bucket not "
- "in hold, n=%d", beam->id, beam->tag,
- (int)proxy->n);
- ap_assert(!proxy->bsender);
- }
- }
- /* notify anyone waiting on space to become available */
- if (!bl.mutex) {
- r_purge_sent(beam);
- }
- else {
- apr_thread_cond_broadcast(beam->change);
- }
- leave_yellow(beam, &bl);
- }
-}
-
static void h2_blist_cleanup(h2_blist *bl)
{
apr_bucket *e;
@@ -461,335 +257,203 @@ static void h2_blist_cleanup(h2_blist *bl)
}
}
-static apr_status_t beam_close(h2_bucket_beam *beam)
+static void beam_shutdown(h2_bucket_beam *beam, apr_shutdown_how_e how)
{
- if (!beam->closed) {
- beam->closed = 1;
- apr_thread_cond_broadcast(beam->change);
+ if (!beam->pool) {
+ /* pool being cleared already */
+ return;
}
- return APR_SUCCESS;
-}
-
-int h2_beam_is_closed(h2_bucket_beam *beam)
-{
- return beam->closed;
-}
-static int pool_register(h2_bucket_beam *beam, apr_pool_t *pool,
- apr_status_t (*cleanup)(void *))
-{
- if (pool && pool != beam->pool) {
- apr_pool_pre_cleanup_register(pool, beam, cleanup);
- return 1;
+ /* shutdown both receiver and sender? */
+ if (how == APR_SHUTDOWN_READWRITE) {
+ beam->cons_io_cb = NULL;
+ beam->recv_cb = NULL;
+ beam->eagain_cb = NULL;
}
- return 0;
-}
-static int pool_kill(h2_bucket_beam *beam, apr_pool_t *pool,
- apr_status_t (*cleanup)(void *)) {
- if (pool && pool != beam->pool) {
- apr_pool_cleanup_kill(pool, beam, cleanup);
- return 1;
+ /* shutdown sender (or both)? */
+ if (how != APR_SHUTDOWN_READ) {
+ purge_consumed_buckets(beam);
+ h2_blist_cleanup(&beam->buckets_to_send);
}
- return 0;
}
-static apr_status_t beam_recv_cleanup(void *data)
+static apr_status_t beam_cleanup(void *data)
{
h2_bucket_beam *beam = data;
- /* receiver pool has gone away, clear references */
- beam->recv_buffer = NULL;
- beam->recv_pool = NULL;
+ beam_shutdown(beam, APR_SHUTDOWN_READWRITE);
+ purge_eor_buckets(beam);
+ beam->pool = NULL; /* the pool is clearing now */
return APR_SUCCESS;
}
-static apr_status_t beam_send_cleanup(void *data)
+apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c)
{
- h2_bucket_beam *beam = data;
- /* sender is going away, clear up all references to its memory */
- r_purge_sent(beam);
- h2_blist_cleanup(&beam->send_list);
- report_consumption(beam, NULL);
- while (!H2_BPROXY_LIST_EMPTY(&beam->proxies)) {
- h2_beam_proxy *proxy = H2_BPROXY_LIST_FIRST(&beam->proxies);
- H2_BPROXY_REMOVE(proxy);
- proxy->beam = NULL;
- proxy->bsender = NULL;
+ if (beam->pool) {
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, 0, "destroy", NULL);
+ apr_pool_cleanup_run(beam->pool, beam, beam_cleanup);
}
- h2_blist_cleanup(&beam->purge_list);
- h2_blist_cleanup(&beam->hold_list);
- beam->send_pool = NULL;
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, 0, "destroyed", NULL);
return APR_SUCCESS;
}
-static void beam_set_send_pool(h2_bucket_beam *beam, apr_pool_t *pool)
-{
- if (beam->send_pool != pool) {
- if (beam->send_pool && beam->send_pool != beam->pool) {
- pool_kill(beam, beam->send_pool, beam_send_cleanup);
- beam_send_cleanup(beam);
- }
- beam->send_pool = pool;
- pool_register(beam, beam->send_pool, beam_send_cleanup);
- }
-}
-
-static void recv_buffer_cleanup(h2_bucket_beam *beam, h2_beam_lock *bl)
-{
- if (beam->recv_buffer && !APR_BRIGADE_EMPTY(beam->recv_buffer)) {
- apr_bucket_brigade *bb = beam->recv_buffer;
- apr_off_t bblen = 0;
-
- beam->recv_buffer = NULL;
- apr_brigade_length(bb, 0, &bblen);
- beam->received_bytes += bblen;
-
- /* need to do this unlocked since bucket destroy might
- * call this beam again. */
- if (bl) leave_yellow(beam, bl);
- apr_brigade_destroy(bb);
- if (bl) enter_yellow(beam, bl);
-
- apr_thread_cond_broadcast(beam->change);
- if (beam->cons_ev_cb) {
- beam->cons_ev_cb(beam->cons_ctx, beam);
- }
- }
-}
-
-static apr_status_t beam_cleanup(h2_bucket_beam *beam, int from_pool)
-{
- apr_status_t status = APR_SUCCESS;
- int safe_send = (beam->owner == H2_BEAM_OWNER_SEND);
- int safe_recv = (beam->owner == H2_BEAM_OWNER_RECV);
-
- /*
- * Owner of the beam is going away, depending on which side it owns,
- * cleanup strategies will differ.
- *
- * In general, receiver holds references to memory from sender.
- * Clean up receiver first, if safe, then cleanup sender, if safe.
- */
-
- /* When called from pool destroy, io callbacks are disabled */
- if (from_pool) {
- beam->cons_io_cb = NULL;
- }
-
- /* When modify send is not safe, this means we still have multi-thread
- * protection and the owner is receiving the buckets. If the sending
- * side has not gone away, this means we could have dangling buckets
- * in our lists that never get destroyed. This should not happen. */
- ap_assert(safe_send || !beam->send_pool);
- if (!H2_BLIST_EMPTY(&beam->send_list)) {
- ap_assert(beam->send_pool);
- }
-
- if (safe_recv) {
- if (beam->recv_pool) {
- pool_kill(beam, beam->recv_pool, beam_recv_cleanup);
- beam->recv_pool = NULL;
- }
- recv_buffer_cleanup(beam, NULL);
- }
- else {
- beam->recv_buffer = NULL;
- beam->recv_pool = NULL;
- }
-
- if (safe_send && beam->send_pool) {
- pool_kill(beam, beam->send_pool, beam_send_cleanup);
- status = beam_send_cleanup(beam);
- }
-
- if (safe_recv) {
- ap_assert(H2_BPROXY_LIST_EMPTY(&beam->proxies));
- ap_assert(H2_BLIST_EMPTY(&beam->send_list));
- ap_assert(H2_BLIST_EMPTY(&beam->hold_list));
- ap_assert(H2_BLIST_EMPTY(&beam->purge_list));
- }
- return status;
-}
-
-static apr_status_t beam_pool_cleanup(void *data)
-{
- return beam_cleanup(data, 1);
-}
-
-apr_status_t h2_beam_destroy(h2_bucket_beam *beam)
-{
- apr_pool_cleanup_kill(beam->pool, beam, beam_pool_cleanup);
- return beam_cleanup(beam, 0);
-}
-
-apr_status_t h2_beam_create(h2_bucket_beam **pbeam, apr_pool_t *pool,
- int id, const char *tag,
- h2_beam_owner_t owner,
+apr_status_t h2_beam_create(h2_bucket_beam **pbeam, conn_rec *from,
+ apr_pool_t *pool, int id, const char *tag,
apr_size_t max_buf_size,
apr_interval_time_t timeout)
{
h2_bucket_beam *beam;
- apr_status_t rv = APR_SUCCESS;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(from);
+ apr_status_t rv;
beam = apr_pcalloc(pool, sizeof(*beam));
- if (!beam) {
- return APR_ENOMEM;
- }
-
- beam->id = id;
- beam->tag = tag;
beam->pool = pool;
- beam->owner = owner;
- H2_BLIST_INIT(&beam->send_list);
- H2_BLIST_INIT(&beam->hold_list);
- H2_BLIST_INIT(&beam->purge_list);
- H2_BPROXY_LIST_INIT(&beam->proxies);
+ beam->from = from;
+ beam->id = id;
+ beam->name = apr_psprintf(pool, "%s-%d-%s",
+ conn_ctx->id, id, tag);
+
+ H2_BLIST_INIT(&beam->buckets_to_send);
+ H2_BLIST_INIT(&beam->buckets_consumed);
+ H2_BLIST_INIT(&beam->buckets_eor);
beam->tx_mem_limits = 1;
beam->max_buf_size = max_buf_size;
beam->timeout = timeout;
rv = apr_thread_mutex_create(&beam->lock, APR_THREAD_MUTEX_DEFAULT, pool);
- if (APR_SUCCESS == rv) {
- rv = apr_thread_cond_create(&beam->change, pool);
- if (APR_SUCCESS == rv) {
- apr_pool_pre_cleanup_register(pool, beam, beam_pool_cleanup);
- *pbeam = beam;
- }
- }
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = apr_thread_cond_create(&beam->change, pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_pool_pre_cleanup_register(pool, beam, beam_cleanup);
+
+cleanup:
+ H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "created", NULL);
+ *pbeam = (APR_SUCCESS == rv)? beam : NULL;
return rv;
}
void h2_beam_buffer_size_set(h2_bucket_beam *beam, apr_size_t buffer_size)
{
- h2_beam_lock bl;
-
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- beam->max_buf_size = buffer_size;
- leave_yellow(beam, &bl);
- }
+ apr_thread_mutex_lock(beam->lock);
+ beam->max_buf_size = buffer_size;
+ apr_thread_mutex_unlock(beam->lock);
}
-apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam)
+void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled)
{
- h2_beam_lock bl;
- apr_size_t buffer_size = 0;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- buffer_size = beam->max_buf_size;
- leave_yellow(beam, &bl);
- }
- return buffer_size;
+ apr_thread_mutex_lock(beam->lock);
+ beam->copy_files = enabled;
+ apr_thread_mutex_unlock(beam->lock);
}
-void h2_beam_timeout_set(h2_bucket_beam *beam, apr_interval_time_t timeout)
+apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam)
{
- h2_beam_lock bl;
+ apr_size_t buffer_size = 0;
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- beam->timeout = timeout;
- leave_yellow(beam, &bl);
- }
+ apr_thread_mutex_lock(beam->lock);
+ buffer_size = beam->max_buf_size;
+ apr_thread_mutex_unlock(beam->lock);
+ return buffer_size;
}
apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam)
{
- h2_beam_lock bl;
- apr_interval_time_t timeout = 0;
-
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- timeout = beam->timeout;
- leave_yellow(beam, &bl);
- }
+ apr_interval_time_t timeout;
+
+ apr_thread_mutex_lock(beam->lock);
+ timeout = beam->timeout;
+ apr_thread_mutex_unlock(beam->lock);
return timeout;
}
-void h2_beam_abort(h2_bucket_beam *beam)
+void h2_beam_timeout_set(h2_bucket_beam *beam, apr_interval_time_t timeout)
{
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- beam->aborted = 1;
- r_purge_sent(beam);
- h2_blist_cleanup(&beam->send_list);
- report_consumption(beam, &bl);
- apr_thread_cond_broadcast(beam->change);
- leave_yellow(beam, &bl);
- }
+ apr_thread_mutex_lock(beam->lock);
+ beam->timeout = timeout;
+ apr_thread_mutex_unlock(beam->lock);
}
-apr_status_t h2_beam_close(h2_bucket_beam *beam)
+void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c)
{
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- r_purge_sent(beam);
- beam_close(beam);
- report_consumption(beam, &bl);
- leave_yellow(beam, &bl);
- }
- return beam->aborted? APR_ECONNABORTED : APR_SUCCESS;
-}
+ apr_thread_mutex_lock(beam->lock);
+ beam->aborted = 1;
+ if (c == beam->from) {
+ /* sender aborts */
+ if (beam->send_cb) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (beam->was_empty_cb && buffer_is_empty(beam)) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ /* no more consumption reporting to sender */
+ report_consumption(beam, 1);
+ beam->cons_ctx = NULL;
-apr_status_t h2_beam_leave(h2_bucket_beam *beam)
-{
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- recv_buffer_cleanup(beam, &bl);
- beam->aborted = 1;
- beam_close(beam);
- leave_yellow(beam, &bl);
+ beam_shutdown(beam, APR_SHUTDOWN_WRITE);
}
- return APR_SUCCESS;
-}
-
-apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block)
-{
- apr_status_t status;
- h2_beam_lock bl;
-
- if ((status = enter_yellow(beam, &bl)) == APR_SUCCESS) {
- status = wait_empty(beam, block, bl.mutex);
- leave_yellow(beam, &bl);
+ else {
+ /* receiver aborts */
+ beam_shutdown(beam, APR_SHUTDOWN_READ);
}
- return status;
+ apr_thread_cond_broadcast(beam->change);
+ apr_thread_mutex_unlock(beam->lock);
}
-static void move_to_hold(h2_bucket_beam *beam,
- apr_bucket_brigade *sender_bb)
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c)
{
- apr_bucket *b;
- while (sender_bb && !APR_BRIGADE_EMPTY(sender_bb)) {
- b = APR_BRIGADE_FIRST(sender_bb);
- APR_BUCKET_REMOVE(b);
- H2_BLIST_INSERT_TAIL(&beam->send_list, b);
+ apr_thread_mutex_lock(beam->lock);
+ if (!beam->closed) {
+ /* should only be called from sender */
+ ap_assert(c == beam->from);
+ beam->closed = 1;
+ if (beam->send_cb) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (beam->was_empty_cb && buffer_is_empty(beam)) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ apr_thread_cond_broadcast(beam->change);
}
+ apr_thread_mutex_unlock(beam->lock);
}
-static apr_status_t append_bucket(h2_bucket_beam *beam,
- apr_bucket *b,
+static apr_status_t append_bucket(h2_bucket_beam *beam,
+ apr_bucket_brigade *bb,
apr_read_type_e block,
apr_size_t *pspace_left,
- h2_beam_lock *pbl)
+ apr_off_t *pwritten)
{
+ apr_bucket *b;
const char *data;
apr_size_t len;
- apr_status_t status;
- int can_beam = 0, check_len;
+ apr_status_t rv = APR_SUCCESS;
+ int can_beam = 0;
+ (void)block;
if (beam->aborted) {
- return APR_ECONNABORTED;
+ rv = APR_ECONNABORTED;
+ goto cleanup;
}
-
+
+ ap_assert(beam->pool);
+
+ b = APR_BRIGADE_FIRST(bb);
if (APR_BUCKET_IS_METADATA(b)) {
- if (APR_BUCKET_IS_EOS(b)) {
- beam->closed = 1;
- }
APR_BUCKET_REMOVE(b);
- H2_BLIST_INSERT_TAIL(&beam->send_list, b);
- return APR_SUCCESS;
+ apr_bucket_setaside(b, beam->pool);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_to_send, b);
+ goto cleanup;
+ }
+ /* non meta bucket */
+
+ /* in case of indeterminate length, we need to read the bucket,
+ * so that it transforms itself into something stable. */
+ if (b->length == ((apr_size_t)-1)) {
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) goto cleanup;
}
- else if (APR_BUCKET_IS_FILE(b)) {
+
+ if (APR_BUCKET_IS_FILE(b)) {
/* For file buckets the problem is their internal readpool that
* is used on the first read to allocate buffer/mmap.
* Since setting aside a file bucket will de-register the
@@ -806,478 +470,414 @@ static apr_status_t append_bucket(h2_bucket_beam *beam,
* of open file handles and rather use a less efficient beam
* transport. */
apr_bucket_file *bf = b->data;
- apr_file_t *fd = bf->fd;
- can_beam = (bf->refcount.refcount == 1);
- if (can_beam && beam->can_beam_fn) {
- can_beam = beam->can_beam_fn(beam->can_beam_ctx, beam, fd);
- }
- check_len = !can_beam;
+ can_beam = !beam->copy_files && (bf->refcount.refcount == 1);
}
- else {
- if (b->length == ((apr_size_t)-1)) {
- const char *data;
- status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
- if (status != APR_SUCCESS) {
- return status;
- }
- }
- check_len = 1;
+ else if (bucket_is_mmap(b)) {
+ can_beam = !beam->copy_files;
}
-
- if (check_len) {
- if (b->length > *pspace_left) {
- apr_bucket_split(b, *pspace_left);
- }
- *pspace_left -= b->length;
+
+ if (b->length == 0) {
+ apr_bucket_delete(b);
+ rv = APR_SUCCESS;
+ goto cleanup;
}
- /* The fundamental problem is that reading a sender bucket from
- * a receiver thread is a total NO GO, because the bucket might use
- * its pool/bucket_alloc from a foreign thread and that will
- * corrupt. */
- status = APR_ENOTIMPL;
- if (APR_BUCKET_IS_TRANSIENT(b)) {
- /* this takes care of transient buckets and converts them
- * into heap ones. Other bucket types might or might not be
- * affected by this. */
- status = apr_bucket_setaside(b, beam->send_pool);
+ if (!*pspace_left) {
+ rv = APR_EAGAIN;
+ goto cleanup;
}
- else if (APR_BUCKET_IS_HEAP(b)) {
- /* For heap buckets read from a receiver thread is fine. The
+
+ /* bucket is accepted and added to beam->buckets_to_send */
+ if (APR_BUCKET_IS_HEAP(b)) {
+ /* For heap buckets, a read from a receiver thread is fine. The
* data will be there and live until the bucket itself is
* destroyed. */
- status = APR_SUCCESS;
+ rv = apr_bucket_setaside(b, beam->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
}
- else if (APR_BUCKET_IS_POOL(b)) {
- /* pool buckets are bastards that register at pool cleanup
- * to morph themselves into heap buckets. That may happen anytime,
- * even after the bucket data pointer has been read. So at
- * any time inside the receiver thread, the pool bucket memory
- * may disappear. yikes. */
- status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
- if (status == APR_SUCCESS) {
- apr_bucket_heap_make(b, data, len, NULL);
- }
+ else if (can_beam && (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b))) {
+ rv = apr_bucket_setaside(b, beam->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
}
- else if (APR_BUCKET_IS_FILE(b) && can_beam) {
- status = apr_bucket_setaside(b, beam->send_pool);
+ else {
+ /* we know of no special shortcut to transfer the bucket to
+ * another pool without copying. So we make it a heap bucket. */
+ apr_bucket *b2;
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) goto cleanup;
+ /* this allocates and copies data */
+ b2 = apr_bucket_heap_create(data, len, NULL, bb->bucket_alloc);
+ apr_bucket_delete(b);
+ b = b2;
+ APR_BRIGADE_INSERT_HEAD(bb, b);
}
- if (status == APR_ENOTIMPL) {
- /* we have no knowledge about the internals of this bucket,
- * but hope that after read, its data stays immutable for the
- * lifetime of the bucket. (see pool bucket handling above for
- * a counter example).
- * We do the read while in the sender thread, so that the bucket may
- * use pools/allocators safely. */
- status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
- if (status == APR_SUCCESS) {
- status = apr_bucket_setaside(b, beam->send_pool);
- }
+ APR_BUCKET_REMOVE(b);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_to_send, b);
+ *pwritten += (apr_off_t)b->length;
+ if (b->length > *pspace_left) {
+ *pspace_left = 0;
}
-
- if (status != APR_SUCCESS && status != APR_ENOTIMPL) {
- return status;
+ else {
+ *pspace_left -= b->length;
}
-
- APR_BUCKET_REMOVE(b);
- H2_BLIST_INSERT_TAIL(&beam->send_list, b);
- beam->sent_bytes += b->length;
-
- return APR_SUCCESS;
-}
-void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p)
-{
- h2_beam_lock bl;
- /* Called from the sender thread to add buckets to the beam */
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- r_purge_sent(beam);
- beam_set_send_pool(beam, p);
- leave_yellow(beam, &bl);
- }
+cleanup:
+ return rv;
}
-apr_status_t h2_beam_send(h2_bucket_beam *beam,
+apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
apr_bucket_brigade *sender_bb,
- apr_read_type_e block)
+ apr_read_type_e block,
+ apr_off_t *pwritten)
{
- apr_bucket *b;
apr_status_t rv = APR_SUCCESS;
apr_size_t space_left = 0;
- h2_beam_lock bl;
+ int was_empty;
+
+ ap_assert(beam->pool);
/* Called from the sender thread to add buckets to the beam */
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- ap_assert(beam->send_pool);
- r_purge_sent(beam);
-
+ apr_thread_mutex_lock(beam->lock);
+ ap_assert(beam->from == from);
+ ap_assert(sender_bb);
+ H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "start send", sender_bb);
+ purge_consumed_buckets(beam);
+ *pwritten = 0;
+ was_empty = buffer_is_empty(beam);
+
+ space_left = calc_space_left(beam);
+ while (!APR_BRIGADE_EMPTY(sender_bb) && APR_SUCCESS == rv) {
+ rv = append_bucket(beam, sender_bb, block, &space_left, pwritten);
if (beam->aborted) {
- move_to_hold(beam, sender_bb);
- rv = APR_ECONNABORTED;
- }
- else if (sender_bb) {
- int force_report = !APR_BRIGADE_EMPTY(sender_bb);
-
- space_left = calc_space_left(beam);
- while (!APR_BRIGADE_EMPTY(sender_bb) && APR_SUCCESS == rv) {
- if (space_left <= 0) {
- report_prod_io(beam, force_report, &bl);
- r_purge_sent(beam);
- rv = wait_not_full(beam, block, &space_left, &bl);
- if (APR_SUCCESS != rv) {
- break;
- }
- }
- b = APR_BRIGADE_FIRST(sender_bb);
- rv = append_bucket(beam, b, block, &space_left, &bl);
+ goto cleanup;
+ }
+ else if (APR_EAGAIN == rv) {
+ /* bucket was not added, as beam buffer has no space left.
+ * Trigger event callbacks, so receiver can know there is something
+ * to receive before we do a conditional wait. */
+ purge_consumed_buckets(beam);
+ if (beam->send_cb) {
+ beam->send_cb(beam->send_ctx, beam);
}
-
- report_prod_io(beam, force_report, &bl);
- apr_thread_cond_broadcast(beam->change);
+ if (was_empty && beam->was_empty_cb) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ rv = wait_not_full(beam, from, block, &space_left);
+ if (APR_SUCCESS != rv) {
+ break;
+ }
+ was_empty = buffer_is_empty(beam);
}
- report_consumption(beam, &bl);
- leave_yellow(beam, &bl);
}
+
+cleanup:
+ if (beam->send_cb && !buffer_is_empty(beam)) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (was_empty && beam->was_empty_cb && !buffer_is_empty(beam)) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ apr_thread_cond_broadcast(beam->change);
+
+ report_consumption(beam, 1);
+ if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ }
+ H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "end send", sender_bb);
+ if(rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv) && sender_bb != NULL) {
+ apr_brigade_cleanup(sender_bb);
+ }
+ apr_thread_mutex_unlock(beam->lock);
return rv;
}
-apr_status_t h2_beam_receive(h2_bucket_beam *beam,
+apr_status_t h2_beam_receive(h2_bucket_beam *beam,
+ conn_rec *to,
apr_bucket_brigade *bb,
apr_read_type_e block,
apr_off_t readbytes)
{
- h2_beam_lock bl;
apr_bucket *bsender, *brecv, *ng;
int transferred = 0;
- apr_status_t status = APR_SUCCESS;
+ apr_status_t rv = APR_SUCCESS;
apr_off_t remain;
- int transferred_buckets = 0;
-
- /* Called from the receiver thread to take buckets from the beam */
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- if (readbytes <= 0) {
- readbytes = APR_SIZE_MAX;
- }
- remain = readbytes;
-
+ int consumed_buckets = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ H2_BEAM_LOG(beam, to, APLOG_TRACE2, 0, "start receive", bb);
+ if (readbytes <= 0) {
+ readbytes = (apr_off_t)APR_SIZE_MAX;
+ }
+ remain = readbytes;
+
transfer:
- if (beam->aborted) {
- recv_buffer_cleanup(beam, &bl);
- status = APR_ECONNABORTED;
- goto leave;
- }
+ if (beam->aborted) {
+ beam_shutdown(beam, APR_SHUTDOWN_READ);
+ rv = APR_ECONNABORTED;
+ goto leave;
+ }
- /* transfer enough buckets from our receiver brigade, if we have one */
- while (remain >= 0
- && beam->recv_buffer
- && !APR_BRIGADE_EMPTY(beam->recv_buffer)) {
-
- brecv = APR_BRIGADE_FIRST(beam->recv_buffer);
- if (brecv->length > 0 && remain <= 0) {
- break;
- }
- APR_BUCKET_REMOVE(brecv);
- APR_BRIGADE_INSERT_TAIL(bb, brecv);
- remain -= brecv->length;
- ++transferred;
+ ap_assert(beam->pool);
+
+ /* transfer from our sender brigade, transforming sender buckets to
+ * receiver ones until we have enough */
+ while (remain >= 0 && !H2_BLIST_EMPTY(&beam->buckets_to_send)) {
+
+ brecv = NULL;
+ bsender = H2_BLIST_FIRST(&beam->buckets_to_send);
+ if (bsender->length > 0 && remain <= 0) {
+ break;
}
- /* transfer from our sender brigade, transforming sender buckets to
- * receiver ones until we have enough */
- while (remain >= 0 && !H2_BLIST_EMPTY(&beam->send_list)) {
-
- brecv = NULL;
- bsender = H2_BLIST_FIRST(&beam->send_list);
- if (bsender->length > 0 && remain <= 0) {
- break;
+ if (APR_BUCKET_IS_METADATA(bsender)) {
+ /* we need a real copy into the receivers bucket_alloc */
+ if (APR_BUCKET_IS_EOS(bsender)) {
+ /* this closes the beam */
+ beam->closed = 1;
+ brecv = apr_bucket_eos_create(bb->bucket_alloc);
}
-
- if (APR_BUCKET_IS_METADATA(bsender)) {
- if (APR_BUCKET_IS_EOS(bsender)) {
- brecv = apr_bucket_eos_create(bb->bucket_alloc);
- beam->close_sent = 1;
- }
- else if (APR_BUCKET_IS_FLUSH(bsender)) {
- brecv = apr_bucket_flush_create(bb->bucket_alloc);
- }
- else if (AP_BUCKET_IS_ERROR(bsender)) {
- ap_bucket_error *eb = (ap_bucket_error *)bsender;
- brecv = ap_bucket_error_create(eb->status, eb->data,
- bb->p, bb->bucket_alloc);
- }
+ else if (APR_BUCKET_IS_FLUSH(bsender)) {
+ brecv = apr_bucket_flush_create(bb->bucket_alloc);
}
- else if (bsender->length == 0) {
- APR_BUCKET_REMOVE(bsender);
- H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
- continue;
+#if AP_HAS_RESPONSE_BUCKETS
+ else if (AP_BUCKET_IS_RESPONSE(bsender)) {
+ brecv = ap_bucket_response_clone(bsender, bb->p, bb->bucket_alloc);
}
- else if (APR_BUCKET_IS_FILE(bsender)) {
- /* This is set aside into the target brigade pool so that
- * any read operation messes with that pool and not
- * the sender one. */
- apr_bucket_file *f = (apr_bucket_file *)bsender->data;
- apr_file_t *fd = f->fd;
- int setaside = (f->readpool != bb->p);
-
- if (setaside) {
- status = apr_file_setaside(&fd, fd, bb->p);
- if (status != APR_SUCCESS) {
- goto leave;
- }
- ++beam->files_beamed;
- }
- ng = apr_brigade_insert_file(bb, fd, bsender->start, bsender->length,
- bb->p);
-#if APR_HAS_MMAP
- /* disable mmap handling as this leads to segfaults when
- * the underlying file is changed while memory pointer has
- * been handed out. See also PR 59348 */
- apr_bucket_file_enable_mmap(ng, 0);
-#endif
- APR_BUCKET_REMOVE(bsender);
- H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
-
- remain -= bsender->length;
- ++transferred;
- ++transferred_buckets;
- continue;
+ else if (AP_BUCKET_IS_REQUEST(bsender)) {
+ brecv = ap_bucket_request_clone(bsender, bb->p, bb->bucket_alloc);
}
- else {
- /* create a "receiver" standin bucket. we took care about the
- * underlying sender bucket and its data when we placed it into
- * the sender brigade.
- * the beam bucket will notify us on destruction that bsender is
- * no longer needed. */
- brecv = h2_beam_bucket_create(beam, bsender, bb->bucket_alloc,
- beam->buckets_sent++);
+ else if (AP_BUCKET_IS_HEADERS(bsender)) {
+ brecv = ap_bucket_headers_clone(bsender, bb->p, bb->bucket_alloc);
}
-
- /* Place the sender bucket into our hold, to be destroyed when no
- * receiver bucket references it any more. */
- APR_BUCKET_REMOVE(bsender);
- H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
-
- beam->received_bytes += bsender->length;
- ++transferred_buckets;
-
- if (brecv) {
- APR_BRIGADE_INSERT_TAIL(bb, brecv);
- remain -= brecv->length;
- ++transferred;
+#else
+ else if (H2_BUCKET_IS_HEADERS(bsender)) {
+ brecv = h2_bucket_headers_clone(bsender, bb->p, bb->bucket_alloc);
}
- else {
- /* let outside hook determine how bucket is beamed */
- leave_yellow(beam, &bl);
- brecv = h2_beam_bucket(beam, bb, bsender);
- enter_yellow(beam, &bl);
-
- while (brecv && brecv != APR_BRIGADE_SENTINEL(bb)) {
- ++transferred;
- remain -= brecv->length;
- brecv = APR_BUCKET_NEXT(brecv);
- }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+ else if (AP_BUCKET_IS_ERROR(bsender)) {
+ ap_bucket_error *eb = bsender->data;
+ brecv = ap_bucket_error_create(eb->status, eb->data,
+ bb->p, bb->bucket_alloc);
}
}
-
- if (remain < 0) {
- /* too much, put some back into out recv_buffer */
- remain = readbytes;
- for (brecv = APR_BRIGADE_FIRST(bb);
- brecv != APR_BRIGADE_SENTINEL(bb);
- brecv = APR_BUCKET_NEXT(brecv)) {
- remain -= (beam->tx_mem_limits? bucket_mem_used(brecv)
- : brecv->length);
- if (remain < 0) {
- apr_bucket_split(brecv, brecv->length+remain);
- beam->recv_buffer = apr_brigade_split_ex(bb,
- APR_BUCKET_NEXT(brecv),
- beam->recv_buffer);
- break;
- }
- }
+ else if (bsender->length == 0) {
+ /* nop */
}
-
- if (beam->closed && buffer_is_empty(beam)) {
- /* beam is closed and we have nothing more to receive */
- if (!beam->close_sent) {
- apr_bucket *b = apr_bucket_eos_create(bb->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- beam->close_sent = 1;
- ++transferred;
- status = APR_SUCCESS;
- }
+#if APR_HAS_MMAP
+ else if (APR_BUCKET_IS_MMAP(bsender)) {
+ apr_bucket_mmap *bmmap = bsender->data;
+ apr_mmap_t *mmap;
+ rv = apr_mmap_dup(&mmap, bmmap->mmap, bb->p);
+ if (rv != APR_SUCCESS) goto leave;
+ brecv = apr_bucket_mmap_create(mmap, bsender->start, bsender->length, bb->bucket_alloc);
}
-
- if (transferred_buckets > 0) {
- if (beam->cons_ev_cb) {
- beam->cons_ev_cb(beam->cons_ctx, beam);
+#endif
+ else if (APR_BUCKET_IS_FILE(bsender)) {
+ /* This is setaside into the target brigade pool so that
+ * any read operation messes with that pool and not
+ * the sender one. */
+ apr_bucket_file *f = (apr_bucket_file *)bsender->data;
+ apr_file_t *fd = f->fd;
+ int setaside = (f->readpool != bb->p);
+
+ if (setaside) {
+ rv = apr_file_setaside(&fd, fd, bb->p);
+ if (rv != APR_SUCCESS) goto leave;
}
- }
-
- if (transferred) {
- apr_thread_cond_broadcast(beam->change);
- status = APR_SUCCESS;
+ ng = apr_brigade_insert_file(bb, fd, bsender->start, (apr_off_t)bsender->length,
+ bb->p);
+#if APR_HAS_MMAP
+ /* disable mmap handling as this leads to segfaults when
+ * the underlying file is changed while memory pointer has
+ * been handed out. See also PR 59348 */
+ apr_bucket_file_enable_mmap(ng, 0);
+#endif
+ remain -= bsender->length;
+ ++transferred;
}
else {
- status = wait_not_empty(beam, block, bl.mutex);
- if (status != APR_SUCCESS) {
- goto leave;
- }
- goto transfer;
+ const char *data;
+ apr_size_t dlen;
+ /* we did that when the bucket was added, so this should
+ * give us the same data as before without changing the bucket
+ * or anything (pool) connected to it. */
+ rv = apr_bucket_read(bsender, &data, &dlen, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) goto leave;
+ rv = apr_brigade_write(bb, NULL, NULL, data, dlen);
+ if (rv != APR_SUCCESS) goto leave;
+
+ remain -= dlen;
+ ++transferred;
+ }
+
+ if (brecv) {
+ /* we have a proxy that we can give the receiver */
+ APR_BRIGADE_INSERT_TAIL(bb, brecv);
+ remain -= brecv->length;
+ ++transferred;
+ }
+ APR_BUCKET_REMOVE(bsender);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_consumed, bsender);
+ beam->recv_bytes += bsender->length;
+ ++consumed_buckets;
+ }
+
+ if (beam->recv_cb && consumed_buckets > 0) {
+ beam->recv_cb(beam->recv_ctx, beam);
+ }
+
+ if (transferred) {
+ apr_thread_cond_broadcast(beam->change);
+ rv = APR_SUCCESS;
+ }
+ else if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ }
+ else if (beam->closed) {
+ rv = APR_EOF;
+ }
+ else {
+ rv = wait_not_empty(beam, to, block);
+ if (rv != APR_SUCCESS) {
+ goto leave;
}
-leave:
- leave_yellow(beam, &bl);
+ goto transfer;
+ }
+
+leave:
+ H2_BEAM_LOG(beam, to, APLOG_TRACE2, rv, "end receive", bb);
+ if (rv == APR_EAGAIN && beam->eagain_cb) {
+ beam->eagain_cb(beam->eagain_ctx, beam);
}
- return status;
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
}
void h2_beam_on_consumed(h2_bucket_beam *beam,
- h2_beam_ev_callback *ev_cb,
h2_beam_io_callback *io_cb, void *ctx)
{
- h2_beam_lock bl;
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- beam->cons_ev_cb = ev_cb;
- beam->cons_io_cb = io_cb;
- beam->cons_ctx = ctx;
- leave_yellow(beam, &bl);
- }
+ apr_thread_mutex_lock(beam->lock);
+ beam->cons_io_cb = io_cb;
+ beam->cons_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
}
-void h2_beam_on_produced(h2_bucket_beam *beam,
- h2_beam_io_callback *io_cb, void *ctx)
+void h2_beam_on_received(h2_bucket_beam *beam,
+ h2_beam_ev_callback *recv_cb, void *ctx)
{
- h2_beam_lock bl;
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- beam->prod_io_cb = io_cb;
- beam->prod_ctx = ctx;
- leave_yellow(beam, &bl);
- }
+ apr_thread_mutex_lock(beam->lock);
+ beam->recv_cb = recv_cb;
+ beam->recv_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
}
-void h2_beam_on_file_beam(h2_bucket_beam *beam,
- h2_beam_can_beam_callback *cb, void *ctx)
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+ h2_beam_ev_callback *eagain_cb, void *ctx)
{
- h2_beam_lock bl;
-
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- beam->can_beam_fn = cb;
- beam->can_beam_ctx = ctx;
- leave_yellow(beam, &bl);
- }
+ apr_thread_mutex_lock(beam->lock);
+ beam->eagain_cb = eagain_cb;
+ beam->eagain_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
}
+void h2_beam_on_send(h2_bucket_beam *beam,
+ h2_beam_ev_callback *send_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->send_cb = send_cb;
+ beam->send_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
-apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam)
+void h2_beam_on_was_empty(h2_bucket_beam *beam,
+ h2_beam_ev_callback *was_empty_cb, void *ctx)
{
- apr_bucket *b;
- apr_off_t l = 0;
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- for (b = H2_BLIST_FIRST(&beam->send_list);
- b != H2_BLIST_SENTINEL(&beam->send_list);
- b = APR_BUCKET_NEXT(b)) {
- /* should all have determinate length */
- l += b->length;
- }
- leave_yellow(beam, &bl);
- }
- return l;
+ apr_thread_mutex_lock(beam->lock);
+ beam->was_empty_cb = was_empty_cb;
+ beam->was_empty_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
}
-apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam)
+
+static apr_off_t get_buffered_data_len(h2_bucket_beam *beam)
{
apr_bucket *b;
apr_off_t l = 0;
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- for (b = H2_BLIST_FIRST(&beam->send_list);
- b != H2_BLIST_SENTINEL(&beam->send_list);
- b = APR_BUCKET_NEXT(b)) {
- l += bucket_mem_used(b);
- }
- leave_yellow(beam, &bl);
+
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ /* should all have determinate length */
+ l += b->length;
}
return l;
}
-int h2_beam_empty(h2_bucket_beam *beam)
+apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam)
{
- int empty = 1;
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- empty = (H2_BLIST_EMPTY(&beam->send_list)
- && (!beam->recv_buffer || APR_BRIGADE_EMPTY(beam->recv_buffer)));
- leave_yellow(beam, &bl);
- }
- return empty;
-}
+ apr_off_t l = 0;
-int h2_beam_holds_proxies(h2_bucket_beam *beam)
-{
- int has_proxies = 1;
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- has_proxies = !H2_BPROXY_LIST_EMPTY(&beam->proxies);
- leave_yellow(beam, &bl);
- }
- return has_proxies;
+ apr_thread_mutex_lock(beam->lock);
+ l = get_buffered_data_len(beam);
+ apr_thread_mutex_unlock(beam->lock);
+ return l;
}
-int h2_beam_was_received(h2_bucket_beam *beam)
+apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam)
{
- int happend = 0;
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- happend = (beam->received_bytes > 0);
- leave_yellow(beam, &bl);
- }
- return happend;
-}
+ apr_bucket *b;
+ apr_off_t l = 0;
-apr_size_t h2_beam_get_files_beamed(h2_bucket_beam *beam)
-{
- apr_size_t n = 0;
- h2_beam_lock bl;
-
- if (beam && enter_yellow(beam, &bl) == APR_SUCCESS) {
- n = beam->files_beamed;
- leave_yellow(beam, &bl);
+ apr_thread_mutex_lock(beam->lock);
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ l += bucket_mem_used(b);
}
- return n;
+ apr_thread_mutex_unlock(beam->lock);
+ return l;
}
-int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file)
+int h2_beam_empty(h2_bucket_beam *beam)
{
- return 0;
+ int empty = 1;
+
+ apr_thread_mutex_lock(beam->lock);
+ empty = buffer_is_empty(beam);
+ apr_thread_mutex_unlock(beam->lock);
+ return empty;
}
int h2_beam_report_consumption(h2_bucket_beam *beam)
{
- h2_beam_lock bl;
int rv = 0;
- if (enter_yellow(beam, &bl) == APR_SUCCESS) {
- rv = report_consumption(beam, &bl);
- leave_yellow(beam, &bl);
- }
+
+ apr_thread_mutex_lock(beam->lock);
+ rv = report_consumption(beam, 1);
+ apr_thread_mutex_unlock(beam->lock);
return rv;
}
-void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg)
+int h2_beam_is_complete(h2_bucket_beam *beam)
{
- if (beam && APLOG_C_IS_LEVEL(c,level)) {
- ap_log_cerror(APLOG_MARK, level, 0, c,
- "beam(%ld-%d,%s,closed=%d,aborted=%d,empty=%d,buf=%ld): %s",
- (c->master? c->master->id : c->id), beam->id, beam->tag,
- beam->closed, beam->aborted, h2_beam_empty(beam),
- (long)h2_beam_get_buffered(beam), msg);
+ int rv = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ if (beam->closed)
+ rv = 1;
+ else {
+ apr_bucket *b;
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ rv = 1;
+ break;
+ }
+ }
}
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
}
-
-
diff --git a/modules/http2/h2_bucket_beam.h b/modules/http2/h2_bucket_beam.h
index f260762..c58ce98 100644
--- a/modules/http2/h2_bucket_beam.h
+++ b/modules/http2/h2_bucket_beam.h
@@ -17,191 +17,63 @@
#ifndef h2_bucket_beam_h
#define h2_bucket_beam_h
+#include "h2_conn_ctx.h"
+
struct apr_thread_mutex_t;
struct apr_thread_cond_t;
-/*******************************************************************************
- * apr_bucket list without bells and whistles
- ******************************************************************************/
-
-/**
- * h2_blist can hold a list of buckets just like apr_bucket_brigade, but
- * does not to any allocations or related features.
- */
-typedef struct {
- APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
-} h2_blist;
-
-#define H2_BLIST_INIT(b) APR_RING_INIT(&(b)->list, apr_bucket, link);
-#define H2_BLIST_SENTINEL(b) APR_RING_SENTINEL(&(b)->list, apr_bucket, link)
-#define H2_BLIST_EMPTY(b) APR_RING_EMPTY(&(b)->list, apr_bucket, link)
-#define H2_BLIST_FIRST(b) APR_RING_FIRST(&(b)->list)
-#define H2_BLIST_LAST(b) APR_RING_LAST(&(b)->list)
-#define H2_BLIST_INSERT_HEAD(b, e) do { \
- apr_bucket *ap__b = (e); \
- APR_RING_INSERT_HEAD(&(b)->list, ap__b, apr_bucket, link); \
- } while (0)
-#define H2_BLIST_INSERT_TAIL(b, e) do { \
- apr_bucket *ap__b = (e); \
- APR_RING_INSERT_TAIL(&(b)->list, ap__b, apr_bucket, link); \
- } while (0)
-#define H2_BLIST_CONCAT(a, b) do { \
- APR_RING_CONCAT(&(a)->list, &(b)->list, apr_bucket, link); \
- } while (0)
-#define H2_BLIST_PREPEND(a, b) do { \
- APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link); \
- } while (0)
-
-/*******************************************************************************
- * h2_bucket_beam
- ******************************************************************************/
-
/**
* A h2_bucket_beam solves the task of transferring buckets, esp. their data,
- * across threads with zero buffer copies.
- *
- * When a thread, let's call it the sender thread, wants to send buckets to
- * another, the green thread, it creates a h2_bucket_beam and adds buckets
- * via the h2_beam_send(). It gives the beam to the green thread which then
- * can receive buckets into its own brigade via h2_beam_receive().
- *
- * Sending and receiving can happen concurrently.
- *
- * The beam can limit the amount of data it accepts via the buffer_size. This
- * can also be adjusted during its lifetime. Sends and receives can be done blocking.
- * A timeout can be set for such blocks.
- *
- * Care needs to be taken when terminating the beam. The beam registers at
- * the pool it was created with and will cleanup after itself. However, if
- * received buckets do still exist, already freed memory might be accessed.
- * The beam does a assertion on this condition.
- *
- * The proper way of shutting down a beam is to first make sure there are no
- * more green buckets out there, then cleanup the beam to purge eventually
- * still existing sender buckets and then, possibly, terminate the beam itself
- * (or the pool it was created with).
- *
- * The following restrictions apply to bucket transport:
- * - only EOS and FLUSH meta buckets are copied through. All other meta buckets
- * are kept in the beams hold.
- * - all kind of data buckets are transported through:
- * - transient buckets are converted to heap ones on send
- * - heap and pool buckets require no extra handling
- * - buckets with indeterminate length are read on send
- * - file buckets will transfer the file itself into a new bucket, if allowed
- * - all other buckets are read on send to make sure data is present
- *
- * This assures that when the sender thread sends its sender buckets, the data
- * is made accessible while still on the sender side. The sender bucket then enters
- * the beams hold storage.
- * When the green thread calls receive, sender buckets in the hold are wrapped
- * into special beam buckets. Beam buckets on read present the data directly
- * from the internal sender one, but otherwise live on the green side. When a
- * beam bucket gets destroyed, it notifies its beam that the corresponding
- * sender bucket from the hold may be destroyed.
- * Since the destruction of green buckets happens in the green thread, any
- * corresponding sender bucket can not immediately be destroyed, as that would
- * result in race conditions.
- * Instead, the beam transfers such sender buckets from the hold to the purge
- * storage. Next time there is a call from the sender side, the buckets in
- * purge will be deleted.
- *
- * There are callbacks that can be registesender with a beam:
- * - a "consumed" callback that gets called on the sender side with the
- * amount of data that has been received by the green side. The amount
- * is a delta from the last callback invocation. The sender side can trigger
- * these callbacks by calling h2_beam_send() with a NULL brigade.
- * - a "can_beam_file" callback that can prohibit the transfer of file handles
- * through the beam. This will cause file buckets to be read on send and
- * its data buffer will then be transports just like a heap bucket would.
- * When no callback is registered, no restrictions apply and all files are
- * passed through.
- * File handles transfersender to the green side will stay there until the
- * receiving brigade's pool is destroyed/cleared. If the pool lives very
- * long or if many different files are beamed, the process might run out
- * of available file handles.
- *
- * The name "beam" of course is inspired by good old transporter
- * technology where humans are kept inside the transporter's memory
- * buffers until the transmission is complete. Star gates use a similar trick.
+ * across threads with as little copying as possible.
*/
-typedef void h2_beam_mutex_leave(void *ctx, struct apr_thread_mutex_t *lock);
-
-typedef struct {
- apr_thread_mutex_t *mutex;
- h2_beam_mutex_leave *leave;
- void *leave_ctx;
-} h2_beam_lock;
-
typedef struct h2_bucket_beam h2_bucket_beam;
-typedef apr_status_t h2_beam_mutex_enter(void *ctx, h2_beam_lock *pbl);
-
typedef void h2_beam_io_callback(void *ctx, h2_bucket_beam *beam,
apr_off_t bytes);
typedef void h2_beam_ev_callback(void *ctx, h2_bucket_beam *beam);
-typedef struct h2_beam_proxy h2_beam_proxy;
-typedef struct {
- APR_RING_HEAD(h2_beam_proxy_list, h2_beam_proxy) list;
-} h2_bproxy_list;
-
-typedef int h2_beam_can_beam_callback(void *ctx, h2_bucket_beam *beam,
- apr_file_t *file);
-
-typedef enum {
- H2_BEAM_OWNER_SEND,
- H2_BEAM_OWNER_RECV
-} h2_beam_owner_t;
-
/**
- * Will deny all transfer of apr_file_t across the beam and force
- * a data copy instead.
+ * h2_blist can hold a list of buckets just like apr_bucket_brigade, but
+ * does not to any allocations or related features.
*/
-int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file);
+typedef struct {
+ APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
+} h2_blist;
struct h2_bucket_beam {
int id;
- const char *tag;
+ const char *name;
+ conn_rec *from;
apr_pool_t *pool;
- h2_beam_owner_t owner;
- h2_blist send_list;
- h2_blist hold_list;
- h2_blist purge_list;
- apr_bucket_brigade *recv_buffer;
- h2_bproxy_list proxies;
- apr_pool_t *send_pool;
- apr_pool_t *recv_pool;
-
+ h2_blist buckets_to_send;
+ h2_blist buckets_consumed;
+ h2_blist buckets_eor;
+
apr_size_t max_buf_size;
apr_interval_time_t timeout;
- apr_off_t sent_bytes; /* amount of bytes send */
- apr_off_t received_bytes; /* amount of bytes received */
-
- apr_size_t buckets_sent; /* # of beam buckets sent */
- apr_size_t files_beamed; /* how many file handles have been set aside */
-
- unsigned int aborted : 1;
- unsigned int closed : 1;
- unsigned int close_sent : 1;
- unsigned int tx_mem_limits : 1; /* only memory size counts on transfers */
+ int aborted;
+ int closed;
+ int tx_mem_limits; /* only memory size counts on transfers */
+ int copy_files;
struct apr_thread_mutex_t *lock;
struct apr_thread_cond_t *change;
- apr_off_t cons_bytes_reported; /* amount of bytes reported as consumed */
- h2_beam_ev_callback *cons_ev_cb;
- h2_beam_io_callback *cons_io_cb;
+ h2_beam_ev_callback *was_empty_cb; /* event: beam changed to non-empty in h2_beam_send() */
+ void *was_empty_ctx;
+ h2_beam_ev_callback *recv_cb; /* event: buckets were transfered in h2_beam_receive() */
+ void *recv_ctx;
+ h2_beam_ev_callback *send_cb; /* event: buckets were added in h2_beam_send() */
+ void *send_ctx;
+ h2_beam_ev_callback *eagain_cb; /* event: a receive results in ARP_EAGAIN */
+ void *eagain_ctx;
+
+ apr_off_t recv_bytes; /* amount of bytes transferred in h2_beam_receive() */
+ apr_off_t recv_bytes_reported; /* amount of bytes reported as received via callback */
+ h2_beam_io_callback *cons_io_cb; /* report: recv_bytes deltas for sender */
void *cons_ctx;
-
- apr_off_t prod_bytes_reported; /* amount of bytes reported as produced */
- h2_beam_io_callback *prod_io_cb;
- void *prod_ctx;
-
- h2_beam_can_beam_callback *can_beam_fn;
- void *can_beam_ctx;
};
/**
@@ -212,56 +84,66 @@ struct h2_bucket_beam {
* that is only used inside that same mutex.
*
* @param pbeam will hold the created beam on return
+ * @param c_from connection from which buchets are sent
* @param pool pool owning the beam, beam will cleanup when pool released
* @param id identifier of the beam
* @param tag tag identifying beam for logging
- * @param owner if the beam is owned by the sender or receiver, e.g. if
- * the pool owner is using this beam for sending or receiving
* @param buffer_size maximum memory footprint of buckets buffered in beam, or
* 0 for no limitation
* @param timeout timeout for blocking operations
*/
apr_status_t h2_beam_create(h2_bucket_beam **pbeam,
+ conn_rec *from,
apr_pool_t *pool,
int id, const char *tag,
- h2_beam_owner_t owner,
apr_size_t buffer_size,
apr_interval_time_t timeout);
/**
* Destroys the beam immediately without cleanup.
*/
-apr_status_t h2_beam_destroy(h2_bucket_beam *beam);
+apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c);
/**
- * Send buckets from the given brigade through the beam. Will hold buckets
- * internally as long as they have not been processed by the receiving side.
- * All accepted buckets are removed from the given brigade. Will return with
- * APR_EAGAIN on non-blocking sends when not all buckets could be accepted.
- *
- * Call from the sender side only.
+ * Switch copying of file buckets on/off.
*/
-apr_status_t h2_beam_send(h2_bucket_beam *beam,
- apr_bucket_brigade *bb,
- apr_read_type_e block);
+void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled);
/**
- * Register the pool from which future buckets are send. This defines
- * the lifetime of the buckets, e.g. the pool should not be cleared/destroyed
- * until the data is no longer needed (or has been received).
+ * Send buckets from the given brigade through the beam.
+ * This can block of the amount of bucket data is above the buffer limit.
+ * @param beam the beam to add buckets to
+ * @param from the connection the sender operates on, must be the same as
+ * used to create the beam
+ * @param bb the brigade to take buckets from
+ * @param block if the sending should block when the buffer is full
+ * @param pwritten on return, contains the number of data bytes sent
+ * @return APR_SUCCESS when buckets were added to the beam. This can be
+ * a partial transfer and other buckets may still remain in bb
+ * APR_EAGAIN on non-blocking send when the buffer is full
+ * APR_TIMEUP on blocking semd that time out
+ * APR_ECONNABORTED when beam has been aborted
*/
-void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p);
+apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
+ apr_bucket_brigade *bb,
+ apr_read_type_e block,
+ apr_off_t *pwritten);
/**
- * Receive buckets from the beam into the given brigade. Will return APR_EOF
- * when reading past an EOS bucket. Reads can be blocking until data is
- * available or the beam has been closed. Non-blocking calls return APR_EAGAIN
- * if no data is available.
- *
- * Call from the receiver side only.
+ * Receive buckets from the beam into the given brigade. The caller is
+ * operating on connection `to`.
+ * @param beam the beam to receive buckets from
+ * @param to the connection the receiver is working with
+ * @param bb the bucket brigade to append to
+ * @param block if the read should block when buckets are unavailable
+ * @param readbytes the amount of data the receiver wants
+ * @return APR_SUCCESS when buckets were appended
+ * APR_EAGAIN on non-blocking read when no buckets are available
+ * APR_TIMEUP on blocking reads that time out
+ * APR_ECONNABORTED when beam has been aborted
*/
-apr_status_t h2_beam_receive(h2_bucket_beam *beam,
- apr_bucket_brigade *green_buckets,
+apr_status_t h2_beam_receive(h2_bucket_beam *beam, conn_rec *to,
+ apr_bucket_brigade *bb,
apr_read_type_e block,
apr_off_t readbytes);
@@ -271,53 +153,27 @@ apr_status_t h2_beam_receive(h2_bucket_beam *beam,
int h2_beam_empty(h2_bucket_beam *beam);
/**
- * Determine if beam has handed out proxy buckets that are not destroyed.
- */
-int h2_beam_holds_proxies(h2_bucket_beam *beam);
-
-/**
- * Abort the beam. Will cleanup any buffered buckets and answer all send
- * and receives with APR_ECONNABORTED.
- *
- * Call from the sender side only.
- */
-void h2_beam_abort(h2_bucket_beam *beam);
-
-/**
- * Close the beam. Sending an EOS bucket serves the same purpose.
- *
- * Call from the sender side only.
- */
-apr_status_t h2_beam_close(h2_bucket_beam *beam);
-
-/**
- * Receives leaves the beam, e.g. will no longer read. This will
- * interrupt any sender blocked writing and fail future send.
- *
- * Call from the receiver side only.
+ * Abort the beam, either from receiving or sending side.
+ *
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
*/
-apr_status_t h2_beam_leave(h2_bucket_beam *beam);
-
-int h2_beam_is_closed(h2_bucket_beam *beam);
+void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c);
/**
- * Return APR_SUCCESS when all buckets in transit have been handled.
- * When called with APR_BLOCK_READ and a mutex set, will wait until the green
- * side has consumed all data. Otherwise APR_EAGAIN is returned.
- * With clear_buffers set, any queued data is discarded.
- * If a timeout is set on the beam, waiting might also time out and
- * return APR_ETIMEUP.
+ * Close the beam. Make certain an EOS is sent.
*
- * Call from the sender side only.
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
*/
-apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block);
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c);
-/**
- * Set/get the timeout for blocking read/write operations. Only works
- * if a mutex has been set for the beam.
+/**
+ * Set/get the timeout for blocking sebd/receive operations.
*/
void h2_beam_timeout_set(h2_bucket_beam *beam,
apr_interval_time_t timeout);
+
apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam);
/**
@@ -332,7 +188,6 @@ apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam);
* amount of bytes that have been consumed by the receiver, since the
* last callback invocation or reset.
* @param beam the beam to set the callback on
- * @param ev_cb the callback or NULL, called when bytes are consumed
* @param io_cb the callback or NULL, called on sender with bytes consumed
* @param ctx the context to use in callback invocation
*
@@ -340,43 +195,58 @@ apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam);
* from any side.
*/
void h2_beam_on_consumed(h2_bucket_beam *beam,
- h2_beam_ev_callback *ev_cb,
h2_beam_io_callback *io_cb, void *ctx);
/**
- * Call any registered consumed handler, if any changes have happened
- * since the last invocation.
- * @return !=0 iff a handler has been called
- *
- * Needs to be invoked from the sending side.
+ * Register a callback to be invoked on the receiver side whenever
+ * buckets have been transfered in a h2_beam_receive() call.
+ * @param beam the beam to set the callback on
+ * @param recv_cb the callback or NULL, called when buckets are received
+ * @param ctx the context to use in callback invocation
*/
-int h2_beam_report_consumption(h2_bucket_beam *beam);
+void h2_beam_on_received(h2_bucket_beam *beam,
+ h2_beam_ev_callback *recv_cb, void *ctx);
/**
- * Register a callback to be invoked on the receiver side with the
- * amount of bytes that have been produces by the sender, since the
- * last callback invocation or reset.
+ * Register a callback to be invoked on the receiver side whenever
+ * APR_EAGAIN is being returned in h2_beam_receive().
* @param beam the beam to set the callback on
- * @param io_cb the callback or NULL, called on receiver with bytes produced
+ * @param egain_cb the callback or NULL, called before APR_EAGAIN is returned
* @param ctx the context to use in callback invocation
- *
- * Call from the receiver side, callbacks invoked on either side.
*/
-void h2_beam_on_produced(h2_bucket_beam *beam,
- h2_beam_io_callback *io_cb, void *ctx);
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+ h2_beam_ev_callback *eagain_cb, void *ctx);
/**
- * Register a callback that may prevent a file from being beam as
- * file handle, forcing the file content to be copied. Then no callback
- * is set (NULL), file handles are transferred directly.
+ * Register a call back from the sender side to be invoked when send
+ * has added buckets to the beam.
+ * Unregister by passing a NULL on_send_cb.
* @param beam the beam to set the callback on
- * @param io_cb the callback or NULL, called on receiver with bytes produced
+ * @param on_send_cb the callback to invoke after buckets were added
* @param ctx the context to use in callback invocation
- *
- * Call from the receiver side, callbacks invoked on either side.
*/
-void h2_beam_on_file_beam(h2_bucket_beam *beam,
- h2_beam_can_beam_callback *cb, void *ctx);
+void h2_beam_on_send(h2_bucket_beam *beam,
+ h2_beam_ev_callback *on_send_cb, void *ctx);
+
+/**
+ * Register a call back from the sender side to be invoked when send
+ * has added to a previously empty beam.
+ * Unregister by passing a NULL was_empty_cb.
+ * @param beam the beam to set the callback on
+ * @param was_empty_cb the callback to invoke on blocked send
+ * @param ctx the context to use in callback invocation
+ */
+void h2_beam_on_was_empty(h2_bucket_beam *beam,
+ h2_beam_ev_callback *was_empty_cb, void *ctx);
+
+/**
+ * Call any registered consumed handler, if any changes have happened
+ * since the last invocation.
+ * @return !=0 iff a handler has been called
+ *
+ * Needs to be invoked from the sending side.
+ */
+int h2_beam_report_consumption(h2_bucket_beam *beam);
/**
* Get the amount of bytes currently buffered in the beam (unread).
@@ -389,18 +259,9 @@ apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam);
apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
/**
- * Return != 0 iff (some) data from the beam has been received.
+ * @return != 0 iff beam has been closed or has an EOS bucket buffered
+ * waiting to be received.
*/
-int h2_beam_was_received(h2_bucket_beam *beam);
-
-apr_size_t h2_beam_get_files_beamed(h2_bucket_beam *beam);
-
-typedef apr_bucket *h2_bucket_beamer(h2_bucket_beam *beam,
- apr_bucket_brigade *dest,
- const apr_bucket *src);
-
-void h2_register_bucket_beamer(h2_bucket_beamer *beamer);
-
-void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg);
+int h2_beam_is_complete(h2_bucket_beam *beam);
#endif /* h2_bucket_beam_h */
diff --git a/modules/http2/h2_bucket_eos.c b/modules/http2/h2_bucket_eos.c
index c89d499..fa46a30 100644
--- a/modules/http2/h2_bucket_eos.c
+++ b/modules/http2/h2_bucket_eos.c
@@ -13,22 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-/* 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
#include
@@ -37,6 +21,7 @@
#include
#include
#include
+#include
#include "h2_private.h"
#include "h2.h"
diff --git a/modules/http2/h2_c1.c b/modules/http2/h2_c1.c
new file mode 100644
index 0000000..afb26fc
--- /dev/null
+++ b/modules/http2/h2_c1.c
@@ -0,0 +1,323 @@
+/* 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
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_protocol.h"
+#include "h2_workers.h"
+#include "h2_c1.h"
+#include "h2_version.h"
+#include "h2_util.h"
+
+static struct h2_workers *workers;
+
+static int async_mpm;
+
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
+
+apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s)
+{
+ apr_status_t status = APR_SUCCESS;
+ int minw, maxw;
+ apr_time_t idle_limit;
+
+ status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
+ if (status != APR_SUCCESS) {
+ /* some MPMs do not implemnent this */
+ async_mpm = 0;
+ status = APR_SUCCESS;
+ }
+
+ h2_config_init(pool);
+
+ h2_get_workers_config(s, &minw, &maxw, &idle_limit);
+ workers = h2_workers_create(s, pool, maxw, minw, idle_limit);
+
+ h2_c_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
+ h2_c_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
+
+ return h2_mplx_c1_child_init(pool, s);
+}
+
+void h2_c1_child_stopping(apr_pool_t *pool, int graceful)
+{
+ if (workers) {
+ h2_workers_shutdown(workers, graceful);
+ }
+}
+
+
+apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s)
+{
+ h2_session *session;
+ h2_conn_ctx_t *ctx;
+ apr_status_t rv;
+
+ if (!workers) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911)
+ "workers not initialized");
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+
+ rv = h2_session_create(&session, c, r, s, workers);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ctx = h2_conn_ctx_get(c);
+ ap_assert(ctx);
+ h2_conn_ctx_assign_session(ctx, session);
+ /* remove the input filter of mod_reqtimeout, now that the connection
+ * is established and we have switched to h2. reqtimeout has supervised
+ * possibly configured handshake timeouts and needs to get out of the way
+ * now since the rest of its state handling assumes http/1.x to take place. */
+ ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+
+cleanup:
+ return rv;
+}
+
+apr_status_t h2_c1_run(conn_rec *c)
+{
+ apr_status_t status;
+ int mpm_state = 0;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ ap_assert(conn_ctx);
+ ap_assert(conn_ctx->session);
+ do {
+ if (c->cs) {
+ c->cs->sense = CONN_SENSE_DEFAULT;
+ c->cs->state = CONN_STATE_HANDLER;
+ }
+
+ status = h2_session_process(conn_ctx->session, async_mpm);
+
+ if (APR_STATUS_IS_EOF(status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(03045), conn_ctx->session,
+ "process, closing conn"));
+ c->keepalive = AP_CONN_CLOSE;
+ }
+ else {
+ c->keepalive = AP_CONN_KEEPALIVE;
+ }
+
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+ break;
+ }
+ } while (!async_mpm
+ && c->keepalive == AP_CONN_KEEPALIVE
+ && mpm_state != AP_MPMQ_STOPPING);
+
+ if (c->cs) {
+ switch (conn_ctx->session->state) {
+ case H2_SESSION_ST_INIT:
+ case H2_SESSION_ST_IDLE:
+ case H2_SESSION_ST_BUSY:
+ case H2_SESSION_ST_WAIT:
+ c->cs->state = CONN_STATE_WRITE_COMPLETION;
+ if (c->cs && !conn_ctx->session->remote.emitted_count) {
+ /* let the MPM know that we are not done and want
+ * the Timeout behaviour instead of a KeepAliveTimeout
+ * See PR 63534.
+ */
+ c->cs->sense = CONN_SENSE_WANT_READ;
+ }
+ break;
+ case H2_SESSION_ST_CLEANUP:
+ case H2_SESSION_ST_DONE:
+ default:
+ c->cs->state = CONN_STATE_LINGER;
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->session) {
+ apr_status_t status = h2_session_pre_close(conn_ctx->session, async_mpm);
+ return (status == APR_SUCCESS)? DONE : status;
+ }
+ return DONE;
+}
+
+int h2_c1_allows_direct(conn_rec *c)
+{
+ if (!c->master) {
+ int is_tls = ap_ssl_conn_is_ssl(c);
+ const char *needed_protocol = is_tls? "h2" : "h2c";
+ int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT);
+
+ if (h2_direct < 0) {
+ h2_direct = is_tls? 0 : 1;
+ }
+ return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
+ }
+ return 0;
+}
+
+int h2_c1_can_upgrade(request_rec *r)
+{
+ if (!r->connection->master) {
+ int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE);
+ return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection));
+ }
+ return 0;
+}
+
+static int h2_c1_hook_process_connection(conn_rec* c)
+{
+ apr_status_t status;
+ h2_conn_ctx_t *ctx;
+
+ if (c->master) goto declined;
+ ctx = h2_conn_ctx_get(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
+ if (!ctx && c->keepalives == 0) {
+ const char *proto = ap_get_protocol(c);
+
+ if (APLOGctrace1(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
+ "new connection using protocol '%s', direct=%d, "
+ "tls acceptable=%d", proto, h2_c1_allows_direct(c),
+ h2_protocol_is_acceptable_c1(c, NULL, 1));
+ }
+
+ if (!strcmp(AP_PROTOCOL_HTTP1, proto)
+ && h2_c1_allows_direct(c)
+ && h2_protocol_is_acceptable_c1(c, NULL, 1)) {
+ /* Fresh connection still is on http/1.1 and H2Direct is enabled.
+ * Otherwise connection is in a fully acceptable state.
+ * -> peek at the first 24 incoming bytes
+ */
+ apr_bucket_brigade *temp;
+ char *peek = NULL;
+ apr_size_t peeklen;
+
+ temp = apr_brigade_create(c->pool, c->bucket_alloc);
+ status = ap_get_brigade(c->input_filters, temp,
+ AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
+
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
+ "h2_h2, error reading 24 bytes speculative");
+ apr_brigade_destroy(temp);
+ return DECLINED;
+ }
+
+ apr_brigade_pflatten(temp, &peek, &peeklen, c->pool);
+ if ((peeklen >= 24) && !memcmp(H2_MAGIC_TOKEN, peek, 24)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_h2, direct mode detected");
+ ctx = h2_conn_ctx_create_for_c1(c, c->base_server,
+ ap_ssl_conn_is_ssl(c)? "h2" : "h2c");
+ }
+ else if (APLOGctrace2(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_h2, not detected in %d bytes(base64): %s",
+ (int)peeklen, h2_util_base64url_encode(peek, peeklen, c->pool));
+ }
+ apr_brigade_destroy(temp);
+ }
+ }
+
+ if (!ctx) goto declined;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
+ if (!ctx->session) {
+ status = h2_c1_setup(c, NULL, ctx->server? ctx->server : c->base_server);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
+ if (status != APR_SUCCESS) {
+ h2_conn_ctx_detach(c);
+ return !OK;
+ }
+ }
+ h2_c1_run(c);
+ return OK;
+
+declined:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
+ return DECLINED;
+}
+
+static int h2_c1_hook_pre_close(conn_rec *c)
+{
+ h2_conn_ctx_t *ctx;
+
+ /* secondary connection? */
+ if (c->master) {
+ return DECLINED;
+ }
+
+ ctx = h2_conn_ctx_get(c);
+ if (ctx) {
+ /* If the session has been closed correctly already, we will not
+ * find a h2_conn_ctx_there. The presence indicates that the session
+ * is still ongoing. */
+ return h2_c1_pre_close(ctx, c);
+ }
+ return DECLINED;
+}
+
+static const char* const mod_ssl[] = { "mod_ssl.c", NULL};
+static const char* const mod_reqtimeout[] = { "mod_ssl.c", "mod_reqtimeout.c", NULL};
+
+void h2_c1_register_hooks(void)
+{
+ /* Our main processing needs to run quite late. Definitely after mod_ssl,
+ * as we need its connection filters, but also before reqtimeout as its
+ * method of timeouts is specific to HTTP/1.1 (as of now).
+ * The core HTTP/1 processing run as REALLY_LAST, so we will have
+ * a chance to take over before it.
+ */
+ ap_hook_process_connection(h2_c1_hook_process_connection,
+ mod_reqtimeout, NULL, APR_HOOK_LAST);
+
+ /* One last chance to properly say goodbye if we have not done so
+ * already. */
+ ap_hook_pre_close_connection(h2_c1_hook_pre_close, NULL, mod_ssl, APR_HOOK_LAST);
+}
+
diff --git a/modules/http2/h2_c1.h b/modules/http2/h2_c1.h
new file mode 100644
index 0000000..41527f6
--- /dev/null
+++ b/modules/http2/h2_c1.h
@@ -0,0 +1,83 @@
+/* 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.
+ */
+
+#ifndef __mod_h2__h2_c1__
+#define __mod_h2__h2_c1__
+
+#include
+
+struct h2_conn_ctx_t;
+
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
+
+/* Initialize this child process for h2 primary connection work,
+ * to be called once during child init before multi processing
+ * starts.
+ */
+apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Setup the primary connection and our context for HTTP/2 processing
+ *
+ * @param c the connection HTTP/2 is starting on
+ * @param r the upgrade request that still awaits an answer, optional
+ * @param s the server selected for this connection (can be != c->base_server)
+ */
+apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s);
+
+/**
+ * Run the HTTP/2 primary connection in synchronous fashion.
+ * Return when the HTTP/2 session is done
+ * and the connection will close or a fatal error occurred.
+ *
+ * @param c the http2 connection to run
+ * @return APR_SUCCESS when session is done.
+ */
+apr_status_t h2_c1_run(conn_rec *c);
+
+/**
+ * The primary connection is about to close. If we have not send a GOAWAY
+ * yet, this is the last chance.
+ */
+apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c);
+
+/**
+ * Check if the connection allows a direct detection of HTTPP/2,
+ * as configurable by the H2Direct directive.
+ * @param c the connection to check on
+ * @return != 0 if direct detection is enabled
+ */
+int h2_c1_allows_direct(conn_rec *c);
+
+/**
+ * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
+ * for the given request.
+ * @param r the request to check
+ * @return != 0 iff Upgrade switching is enabled
+ */
+int h2_c1_can_upgrade(request_rec *r);
+
+/* Register hooks for h2 handling on primary connections.
+ */
+void h2_c1_register_hooks(void);
+
+/**
+ * Child is about to be stopped, release unused resources
+ */
+void h2_c1_child_stopping(apr_pool_t *pool, int graceful);
+
+#endif /* defined(__mod_h2__h2_c1__) */
diff --git a/modules/http2/h2_c1_io.c b/modules/http2/h2_c1_io.c
new file mode 100644
index 0000000..5ed4ee8
--- /dev/null
+++ b/modules/http2/h2_c1_io.c
@@ -0,0 +1,559 @@
+/* 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
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "h2_private.h"
+#include "h2_bucket_eos.h"
+#include "h2_config.h"
+#include "h2_c1.h"
+#include "h2_c1_io.h"
+#include "h2_protocol.h"
+#include "h2_session.h"
+#include "h2_util.h"
+
+#define TLS_DATA_MAX (16*1024)
+
+/* Calculated like this: assuming MTU 1500 bytes
+ * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options)
+ * - TLS overhead (60-100)
+ * ~= 1300 bytes */
+#define WRITE_SIZE_INITIAL 1300
+
+/* The maximum we'd like to write in one chunk is
+ * the max size of a TLS record. When pushing
+ * many frames down the h2 connection, this might
+ * align differently because of headers and other
+ * frames or simply as not sufficient data is
+ * in a response body.
+ * However keeping frames at or below this limit
+ * should make optimizations at the layer that writes
+ * to TLS easier.
+ */
+#define WRITE_SIZE_MAX (TLS_DATA_MAX)
+
+#define BUF_REMAIN ((apr_size_t)(bmax-off))
+
+static void h2_c1_io_bb_log(conn_rec *c, int stream_id, int level,
+ const char *tag, apr_bucket_brigade *bb)
+{
+ char buffer[16 * 1024];
+ const char *line = "(null)";
+ int bmax = sizeof(buffer)/sizeof(buffer[0]);
+ int off = 0;
+ apr_bucket *b;
+
+ (void)stream_id;
+ if (bb) {
+ memset(buffer, 0, bmax--);
+ for (b = APR_BRIGADE_FIRST(bb);
+ bmax && (b != APR_BRIGADE_SENTINEL(bb));
+ b = APR_BUCKET_NEXT(b)) {
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "eos ");
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "flush ");
+ }
+ else if (AP_BUCKET_IS_EOR(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "eor ");
+ }
+ else if (H2_BUCKET_IS_H2EOS(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "h2eos ");
+ }
+ else {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "meta(unknown) ");
+ }
+ }
+ else {
+ const char *btype = "data";
+ if (APR_BUCKET_IS_FILE(b)) {
+ btype = "file";
+ }
+ else if (APR_BUCKET_IS_PIPE(b)) {
+ btype = "pipe";
+ }
+ else if (APR_BUCKET_IS_SOCKET(b)) {
+ btype = "socket";
+ }
+ else if (APR_BUCKET_IS_HEAP(b)) {
+ btype = "heap";
+ }
+ else if (APR_BUCKET_IS_TRANSIENT(b)) {
+ btype = "transient";
+ }
+ else if (APR_BUCKET_IS_IMMORTAL(b)) {
+ btype = "immortal";
+ }
+#if APR_HAS_MMAP
+ else if (APR_BUCKET_IS_MMAP(b)) {
+ btype = "mmap";
+ }
+#endif
+ else if (APR_BUCKET_IS_POOL(b)) {
+ btype = "pool";
+ }
+
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "%s[%ld] ",
+ btype,
+ (long)(b->length == ((apr_size_t)-1)? -1UL : b->length));
+ }
+ }
+ line = *buffer? buffer : "(empty)";
+ }
+ /* Intentional no APLOGNO */
+ ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s",
+ c->id, tag, line);
+
+}
+#define C1_IO_BB_LOG(c, stream_id, level, tag, bb) \
+ if (APLOG_C_IS_LEVEL(c, level)) { \
+ h2_c1_io_bb_log((c), (stream_id), (level), (tag), (bb)); \
+ }
+
+
+apr_status_t h2_c1_io_init(h2_c1_io *io, h2_session *session)
+{
+ conn_rec *c = session->c1;
+
+ io->session = session;
+ io->output = apr_brigade_create(c->pool, c->bucket_alloc);
+ io->is_tls = ap_ssl_conn_is_ssl(session->c1);
+ io->buffer_output = io->is_tls;
+ io->flush_threshold = 4 * (apr_size_t)h2_config_sgeti64(session->s, H2_CONF_STREAM_MAX_MEM);
+
+ if (io->buffer_output) {
+ /* This is what we start with,
+ * see https://issues.apache.org/jira/browse/TS-2503
+ */
+ io->warmup_size = h2_config_sgeti64(session->s, H2_CONF_TLS_WARMUP_SIZE);
+ io->cooldown_usecs = (h2_config_sgeti(session->s, H2_CONF_TLS_COOLDOWN_SECS)
+ * APR_USEC_PER_SEC);
+ io->cooldown_usecs = 0;
+ io->write_size = (io->cooldown_usecs > 0?
+ WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
+ }
+ else {
+ io->warmup_size = 0;
+ io->cooldown_usecs = 0;
+ io->write_size = 0;
+ }
+
+ if (APLOGctrace1(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c,
+ "h2_c1_io(%ld): init, buffering=%d, warmup_size=%ld, "
+ "cd_secs=%f", c->id, io->buffer_output,
+ (long)io->warmup_size,
+ ((double)io->cooldown_usecs/APR_USEC_PER_SEC));
+ }
+
+ return APR_SUCCESS;
+}
+
+static void append_scratch(h2_c1_io *io)
+{
+ if (io->scratch && io->slen > 0) {
+ apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
+ apr_bucket_free,
+ io->session->c1->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ io->buffered_len += io->slen;
+ io->scratch = NULL;
+ io->slen = io->ssize = 0;
+ }
+}
+
+static apr_size_t assure_scratch_space(h2_c1_io *io) {
+ apr_size_t remain = io->ssize - io->slen;
+ if (io->scratch && remain == 0) {
+ append_scratch(io);
+ }
+ if (!io->scratch) {
+ /* we control the size and it is larger than what buckets usually
+ * allocate. */
+ io->scratch = apr_bucket_alloc(io->write_size, io->session->c1->bucket_alloc);
+ io->ssize = io->write_size;
+ io->slen = 0;
+ remain = io->ssize;
+ }
+ return remain;
+}
+
+static apr_status_t read_to_scratch(h2_c1_io *io, apr_bucket *b)
+{
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ if (!b->length) {
+ return APR_SUCCESS;
+ }
+
+ ap_assert(b->length <= (io->ssize - io->slen));
+ if (APR_BUCKET_IS_FILE(b)) {
+ apr_bucket_file *f = (apr_bucket_file *)b->data;
+ apr_file_t *fd = f->fd;
+ apr_off_t offset = b->start;
+
+ len = b->length;
+ /* file buckets will read 8000 byte chunks and split
+ * themselves. However, we do know *exactly* how many
+ * bytes we need where. So we read the file directly to
+ * where we need it.
+ */
+ status = apr_file_seek(fd, APR_SET, &offset);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ status = apr_file_read(fd, io->scratch + io->slen, &len);
+ if (status != APR_SUCCESS && status != APR_EOF) {
+ return status;
+ }
+ io->slen += len;
+ }
+ else if (APR_BUCKET_IS_MMAP(b)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, io->session->c1,
+ "h2_c1_io(%ld): seeing mmap bucket of size %ld, scratch remain=%ld",
+ io->session->c1->id, (long)b->length, (long)(io->ssize - io->slen));
+ status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (status == APR_SUCCESS) {
+ memcpy(io->scratch+io->slen, data, len);
+ io->slen += len;
+ }
+ }
+ else {
+ status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (status == APR_SUCCESS) {
+ memcpy(io->scratch+io->slen, data, len);
+ io->slen += len;
+ }
+ }
+ return status;
+}
+
+static apr_status_t pass_output(h2_c1_io *io, int flush)
+{
+ conn_rec *c = io->session->c1;
+ apr_off_t bblen = 0;
+ apr_status_t rv;
+
+ if (io->is_passing) {
+ /* recursive call, may be triggered by an H2EOS bucket
+ * being destroyed and triggering sending more data? */
+ AP_DEBUG_ASSERT(0);
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10456)
+ "h2_c1_io(%ld): recursive call of h2_c1_io_pass. "
+ "Denied to prevent output corruption. This "
+ "points to a bug in the HTTP/2 implementation.",
+ c->id);
+ return APR_EGENERAL;
+ }
+ io->is_passing = 1;
+
+ append_scratch(io);
+ if (flush) {
+ if (!APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output))) {
+ apr_bucket *b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ }
+ }
+ if (APR_BRIGADE_EMPTY(io->output)) {
+ rv = APR_SUCCESS;
+ goto cleanup;
+ }
+
+ io->unflushed = !APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output));
+ apr_brigade_length(io->output, 0, &bblen);
+ C1_IO_BB_LOG(c, 0, APLOG_TRACE2, "out", io->output);
+
+ rv = ap_pass_brigade(c->output_filters, io->output);
+ if (APR_SUCCESS != rv) goto cleanup;
+ io->bytes_written += (apr_size_t)bblen;
+
+ if (io->write_size < WRITE_SIZE_MAX
+ && io->bytes_written >= io->warmup_size) {
+ /* connection is hot, use max size */
+ io->write_size = WRITE_SIZE_MAX;
+ }
+ else if (io->cooldown_usecs > 0
+ && io->write_size > WRITE_SIZE_INITIAL) {
+ apr_time_t now = apr_time_now();
+ if ((now - io->last_write) >= io->cooldown_usecs) {
+ /* long time not written, reset write size */
+ io->write_size = WRITE_SIZE_INITIAL;
+ io->bytes_written = 0;
+ }
+ else {
+ io->last_write = now;
+ }
+ }
+
+cleanup:
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(03044)
+ "h2_c1_io(%ld): pass_out brigade %ld bytes",
+ c->id, (long)bblen);
+ }
+ apr_brigade_cleanup(io->output);
+ io->buffered_len = 0;
+ io->is_passing = 0;
+ return rv;
+}
+
+int h2_c1_io_needs_flush(h2_c1_io *io)
+{
+ return io->buffered_len >= io->flush_threshold;
+}
+
+int h2_c1_io_pending(h2_c1_io *io)
+{
+ return !APR_BRIGADE_EMPTY(io->output) || (io->scratch && io->slen > 0);
+}
+
+apr_status_t h2_c1_io_pass(h2_c1_io *io)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (h2_c1_io_pending(io)) {
+ rv = pass_output(io, 0);
+ }
+ return rv;
+}
+
+apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (h2_c1_io_pending(io) || io->unflushed) {
+ rv = pass_output(io, 1);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+cleanup:
+ return rv;
+}
+
+apr_status_t h2_c1_io_add_data(h2_c1_io *io, const char *data, size_t length)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_size_t remain;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->session->c1,
+ "h2_c1_io(%ld): adding %ld data bytes",
+ io->session->c1->id, (long)length);
+ if (io->buffer_output) {
+ while (length > 0) {
+ remain = assure_scratch_space(io);
+ if (remain >= length) {
+ memcpy(io->scratch + io->slen, data, length);
+ io->slen += length;
+ length = 0;
+ }
+ else {
+ memcpy(io->scratch + io->slen, data, remain);
+ io->slen += remain;
+ data += remain;
+ length -= remain;
+ }
+ }
+ }
+ else {
+ status = apr_brigade_write(io->output, NULL, NULL, data, length);
+ io->buffered_len += length;
+ }
+ return status;
+}
+
+apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ apr_status_t rv = APR_SUCCESS;
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_METADATA(b) || APR_BUCKET_IS_MMAP(b)) {
+ /* need to finish any open scratch bucket, as meta data
+ * needs to be forward "in order". */
+ append_scratch(io);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ }
+ else if (io->buffer_output) {
+ apr_size_t remain = assure_scratch_space(io);
+ if (b->length > remain) {
+ apr_bucket_split(b, remain);
+ if (io->slen == 0) {
+ /* complete write_size bucket, append unchanged */
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ io->buffered_len += b->length;
+ continue;
+ }
+ }
+ else {
+ /* bucket fits in remain, copy to scratch */
+ rv = read_to_scratch(io, b);
+ apr_bucket_delete(b);
+ if (APR_SUCCESS != rv) goto cleanup;
+ continue;
+ }
+ }
+ else {
+ /* no buffering, forward buckets setaside on flush */
+ apr_bucket_setaside(b, io->session->c1->pool);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ io->buffered_len += b->length;
+ }
+ }
+cleanup:
+ return rv;
+}
+
+static apr_status_t c1_in_feed_bucket(h2_session *session,
+ apr_bucket *b, apr_ssize_t *inout_len)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t len;
+ const char *data;
+ ssize_t n;
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ while (APR_SUCCESS == rv && len > 0) {
+ n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, session->c1,
+ H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
+ (long)len, (long)n);
+ if (n < 0) {
+ if (nghttp2_is_fatal((int)n)) {
+ h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
+ (int)n, nghttp2_strerror((int)n));
+ rv = APR_EGENERAL;
+ }
+ }
+ else {
+ *inout_len += n;
+ if ((apr_ssize_t)len <= n) {
+ break;
+ }
+ len -= (apr_size_t)n;
+ data += n;
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t c1_in_feed_brigade(h2_session *session,
+ apr_bucket_brigade *bb,
+ apr_ssize_t *inout_len)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_bucket* b;
+
+ *inout_len = 0;
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ if (!APR_BUCKET_IS_METADATA(b)) {
+ rv = c1_in_feed_bucket(session, b, inout_len);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ apr_bucket_delete(b);
+ }
+cleanup:
+ apr_brigade_cleanup(bb);
+ return rv;
+}
+
+static apr_status_t read_and_feed(h2_session *session)
+{
+ apr_ssize_t bytes_fed, bytes_requested;
+ apr_status_t rv;
+
+ bytes_requested = H2MAX(APR_BUCKET_BUFF_SIZE, session->max_stream_mem * 4);
+ rv = ap_get_brigade(session->c1->input_filters,
+ session->bbtmp, AP_MODE_READBYTES,
+ APR_NONBLOCK_READ, bytes_requested);
+
+ if (APR_SUCCESS == rv) {
+ if (!APR_BRIGADE_EMPTY(session->bbtmp)) {
+ h2_util_bb_log(session->c1, session->id, APLOG_TRACE2, "c1 in",
+ session->bbtmp);
+ rv = c1_in_feed_brigade(session, session->bbtmp, &bytes_fed);
+ session->io.bytes_read += bytes_fed;
+ }
+ else {
+ rv = APR_EAGAIN;
+ }
+ }
+ return rv;
+}
+
+apr_status_t h2_c1_read(h2_session *session)
+{
+ apr_status_t rv;
+
+ /* H2_IN filter handles all incoming data against the session.
+ * We just pull at the filter chain to make it happen */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_MSG(session, "session_read start"));
+ rv = read_and_feed(session);
+
+ if (APR_SUCCESS == rv) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_PENDING, 0, NULL);
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* Signal that we have exhausted the input momentarily.
+ * This might switch to polling the socket */
+ h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_EXHAUSTED, 0, NULL);
+ }
+ else if (APR_SUCCESS != rv) {
+ if (APR_STATUS_IS_ETIMEDOUT(rv)
+ || APR_STATUS_IS_ECONNABORTED(rv)
+ || APR_STATUS_IS_ECONNRESET(rv)
+ || APR_STATUS_IS_EOF(rv)
+ || APR_STATUS_IS_EBADF(rv)) {
+ /* common status for a client that has left */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
+ H2_SSSN_MSG(session, "input gone"));
+ }
+ else {
+ /* uncommon status, log on INFO so that we see this */
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, rv, session->c1,
+ H2_SSSN_LOG(APLOGNO(02950), session,
+ "error reading, terminating"));
+ }
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ }
+
+ apr_brigade_cleanup(session->bbtmp);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
+ H2_SSSN_MSG(session, "session_read done"));
+ return rv;
+}
diff --git a/modules/http2/h2_c1_io.h b/modules/http2/h2_c1_io.h
new file mode 100644
index 0000000..c4dac38
--- /dev/null
+++ b/modules/http2/h2_c1_io.h
@@ -0,0 +1,101 @@
+/* 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.
+ */
+
+#ifndef __mod_h2__h2_c1_io__
+#define __mod_h2__h2_c1_io__
+
+struct h2_config;
+struct h2_session;
+
+/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
+ * one for input, one for output and works with the installed connection
+ * filters.
+ * The read is done via a callback function, so that input can be processed
+ * directly without copying.
+ */
+typedef struct {
+ struct h2_session *session;
+ apr_bucket_brigade *output;
+
+ int is_tls;
+ int unflushed;
+ apr_time_t cooldown_usecs;
+ apr_int64_t warmup_size;
+
+ apr_size_t write_size;
+ apr_time_t last_write;
+ apr_int64_t bytes_read;
+ apr_int64_t bytes_written;
+
+ int buffer_output;
+ apr_off_t buffered_len;
+ apr_off_t flush_threshold;
+ unsigned int is_flushed : 1;
+ unsigned int is_passing : 1;
+
+ char *scratch;
+ apr_size_t ssize;
+ apr_size_t slen;
+} h2_c1_io;
+
+apr_status_t h2_c1_io_init(h2_c1_io *io, struct h2_session *session);
+
+/**
+ * Append data to the buffered output.
+ * @param buf the data to append
+ * @param length the length of the data to append
+ */
+apr_status_t h2_c1_io_add_data(h2_c1_io *io,
+ const char *buf,
+ size_t length);
+
+apr_status_t h2_c1_io_add(h2_c1_io *io, apr_bucket *b);
+
+apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb);
+
+/**
+ * Pass any buffered data on to the connection output filters.
+ * @param io the connection io
+ */
+apr_status_t h2_c1_io_pass(h2_c1_io *io);
+
+/**
+ * if there is any data pendiong or was any data send
+ * since the last FLUSH, send out a FLUSH now.
+ */
+apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io);
+
+/**
+ * Check if the buffered amount of data needs flushing.
+ */
+int h2_c1_io_needs_flush(h2_c1_io *io);
+
+/**
+ * Check if we have output pending.
+ */
+int h2_c1_io_pending(h2_c1_io *io);
+
+struct h2_session;
+
+/**
+ * Read c1 input and pass it on to nghttp2.
+ * @param session the session
+ * @param when_pending != 0 if only pending input (sitting in filters)
+ * needs to be read
+ */
+apr_status_t h2_c1_read(struct h2_session *session);
+
+#endif /* defined(__mod_h2__h2_c1_io__) */
diff --git a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c
new file mode 100644
index 0000000..a955200
--- /dev/null
+++ b/modules/http2/h2_c2.c
@@ -0,0 +1,942 @@
+/* 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
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_c1.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c2_filter.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_ws.h"
+#include "h2_c2.h"
+#include "h2_util.h"
+#include "mod_http2.h"
+
+
+static module *mpm_module;
+static int mpm_supported = 1;
+static apr_socket_t *dummy_socket;
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static ap_filter_rec_t *c2_net_in_filter_handle;
+static ap_filter_rec_t *c2_net_out_filter_handle;
+static ap_filter_rec_t *c2_request_in_filter_handle;
+static ap_filter_rec_t *c2_notes_out_filter_handle;
+
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+
+static void check_modules(int force)
+{
+ static int checked = 0;
+ int i;
+
+ if (force || !checked) {
+ for (i = 0; ap_loaded_modules[i]; ++i) {
+ module *m = ap_loaded_modules[i];
+
+ if (!strcmp("event.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("motorz.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("mpm_netware.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("prefork.c", m->name)) {
+ mpm_module = m;
+ /* While http2 can work really well on prefork, it collides
+ * today's use case for prefork: running single-thread app engines
+ * like php. If we restrict h2_workers to 1 per process, php will
+ * work fine, but browser will be limited to 1 active request at a
+ * time. */
+ mpm_supported = 0;
+ break;
+ }
+ else if (!strcmp("simple_api.c", m->name)) {
+ mpm_module = m;
+ mpm_supported = 0;
+ break;
+ }
+ else if (!strcmp("mpm_winnt.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("worker.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ }
+ checked = 1;
+ }
+}
+
+const char *h2_conn_mpm_name(void)
+{
+ check_modules(0);
+ return mpm_module? mpm_module->name : "unknown";
+}
+
+int h2_mpm_supported(void)
+{
+ check_modules(0);
+ return mpm_supported;
+}
+
+apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s)
+{
+ check_modules(1);
+ return apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
+ APR_PROTO_TCP, pool);
+}
+
+static void h2_c2_log_io(conn_rec *c2, apr_off_t bytes_sent)
+{
+ if (bytes_sent && h2_c_logio_add_bytes_out) {
+ h2_c_logio_add_bytes_out(c2, bytes_sent);
+ }
+}
+
+void h2_c2_destroy(conn_rec *c2)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
+ "h2_c2(%s): destroy", c2->log_id);
+ if(!c2->aborted && conn_ctx && conn_ctx->bytes_sent) {
+ h2_c2_log_io(c2, conn_ctx->bytes_sent);
+ }
+ apr_pool_destroy(c2->pool);
+}
+
+void h2_c2_abort(conn_rec *c2, conn_rec *from)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+ AP_DEBUG_ASSERT(conn_ctx);
+ AP_DEBUG_ASSERT(conn_ctx->stream_id);
+ if(!c2->aborted && conn_ctx->bytes_sent) {
+ h2_c2_log_io(c2, conn_ctx->bytes_sent);
+ }
+
+ if (conn_ctx->beam_in) {
+ h2_beam_abort(conn_ctx->beam_in, from);
+ }
+ if (conn_ctx->beam_out) {
+ h2_beam_abort(conn_ctx->beam_out, from);
+ }
+ c2->aborted = 1;
+}
+
+typedef struct {
+ apr_bucket_brigade *bb; /* c2: data in holding area */
+ unsigned did_upgrade_eos:1; /* for Upgrade, we added an extra EOS */
+} h2_c2_fctx_in_t;
+
+static apr_status_t h2_c2_filter_in(ap_filter_t* f,
+ apr_bucket_brigade* bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ h2_conn_ctx_t *conn_ctx;
+ h2_c2_fctx_in_t *fctx = f->ctx;
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket *b;
+ apr_off_t bblen;
+ apr_size_t rmax = (readbytes < APR_INT32_MAX)?
+ (apr_size_t)readbytes : APR_INT32_MAX;
+
+ conn_ctx = h2_conn_ctx_get(f->c);
+ AP_DEBUG_ASSERT(conn_ctx);
+
+ if (mode == AP_MODE_INIT) {
+ return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
+ }
+
+ if (f->c->aborted) {
+ return APR_ECONNABORTED;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c,
+ "h2_c2_in(%s-%d): read, mode=%d, block=%d, readbytes=%ld",
+ conn_ctx->id, conn_ctx->stream_id, mode, block,
+ (long)readbytes);
+ }
+
+ if (!fctx) {
+ fctx = apr_pcalloc(f->c->pool, sizeof(*fctx));
+ f->ctx = fctx;
+ fctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ if (!conn_ctx->beam_in) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
+ }
+ }
+
+ /* If this is a HTTP Upgrade, it means the request we process
+ * has not Content, although the stream is not necessarily closed.
+ * On first read, we insert an EOS to signal processing that it
+ * has the complete body. */
+ if (conn_ctx->is_upgrade && !fctx->did_upgrade_eos) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
+ fctx->did_upgrade_eos = 1;
+ }
+
+ while (APR_BRIGADE_EMPTY(fctx->bb)) {
+ /* Get more input data for our request. */
+ if (APLOGctrace2(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
+ "h2_c2_in(%s-%d): get more data from mplx, block=%d, "
+ "readbytes=%ld",
+ conn_ctx->id, conn_ctx->stream_id, block, (long)readbytes);
+ }
+ if (conn_ctx->beam_in) {
+ if (conn_ctx->pipe_in[H2_PIPE_OUT]) {
+receive:
+ status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, APR_NONBLOCK_READ,
+ conn_ctx->mplx->stream_max_mem);
+ if (APR_STATUS_IS_EAGAIN(status) && APR_BLOCK_READ == block) {
+ status = h2_util_wait_on_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
+ if (APR_SUCCESS == status) {
+ goto receive;
+ }
+ }
+ }
+ else {
+ status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, block,
+ conn_ctx->mplx->stream_max_mem);
+ }
+ }
+ else {
+ status = APR_EOF;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+ "h2_c2_in(%s-%d): read returned",
+ conn_ctx->id, conn_ctx->stream_id);
+ }
+ if (APR_STATUS_IS_EAGAIN(status)
+ && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
+ /* chunked input handling does not seem to like it if we
+ * return with APR_EAGAIN from a GETLINE read...
+ * upload 100k test on test-ser.example.org hangs */
+ status = APR_SUCCESS;
+ }
+ else if (APR_STATUS_IS_EOF(status)) {
+ break;
+ }
+ else if (status != APR_SUCCESS) {
+ conn_ctx->last_err = status;
+ return status;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3,
+ "c2 input recv raw", fctx->bb);
+ }
+ if (h2_c_logio_add_bytes_in) {
+ apr_brigade_length(bb, 0, &bblen);
+ h2_c_logio_add_bytes_in(f->c, bblen);
+ }
+ }
+
+ /* Nothing there, no more data to get. Return. */
+ if (status == APR_EOF && APR_BRIGADE_EMPTY(fctx->bb)) {
+ return status;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3,
+ "c2 input.bb", fctx->bb);
+ }
+
+ if (APR_BRIGADE_EMPTY(fctx->bb)) {
+ if (APLOGctrace3(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c,
+ "h2_c2_in(%s-%d): no data",
+ conn_ctx->id, conn_ctx->stream_id);
+ }
+ return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
+ }
+
+ if (mode == AP_MODE_EXHAUSTIVE) {
+ /* return all we have */
+ APR_BRIGADE_CONCAT(bb, fctx->bb);
+ }
+ else if (mode == AP_MODE_READBYTES) {
+ status = h2_brigade_concat_length(bb, fctx->bb, rmax);
+ }
+ else if (mode == AP_MODE_SPECULATIVE) {
+ status = h2_brigade_copy_length(bb, fctx->bb, rmax);
+ }
+ 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->bb, block,
+ HUGE_STRING_LEN);
+ if (APLOGctrace3(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_TRACE3, status, f->c,
+ "h2_c2_in(%s-%d): getline: %s",
+ conn_ctx->id, conn_ctx->stream_id, buffer);
+ }
+ }
+ 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(03472)
+ "h2_c2_in(%s-%d), unsupported READ mode %d",
+ conn_ctx->id, conn_ctx->stream_id, mode);
+ status = APR_ENOTIMPL;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ apr_brigade_length(bb, 0, &bblen);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+ "h2_c2_in(%s-%d): %ld data bytes",
+ conn_ctx->id, conn_ctx->stream_id, (long)bblen);
+ }
+ return status;
+}
+
+static apr_status_t beam_out(conn_rec *c2, h2_conn_ctx_t *conn_ctx, apr_bucket_brigade* bb)
+{
+ apr_off_t written = 0;
+ apr_status_t rv;
+
+ rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ, &written);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ rv = APR_SUCCESS;
+ }
+ return rv;
+}
+
+static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ apr_status_t rv;
+
+ ap_assert(conn_ctx);
+#if AP_HAS_RESPONSE_BUCKETS
+ if (!conn_ctx->has_final_response) {
+ apr_bucket *e;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (AP_BUCKET_IS_RESPONSE(e)) {
+ ap_bucket_response *resp = e->data;
+ if (resp->status >= 200) {
+ conn_ctx->has_final_response = 1;
+ break;
+ }
+ }
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+ }
+ }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+ rv = beam_out(f->c, conn_ctx, bb);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
+ "h2_c2(%s-%d): output leave",
+ conn_ctx->id, conn_ctx->stream_id);
+ if (APR_SUCCESS != rv) {
+ h2_c2_abort(f->c, f->c);
+ }
+ return rv;
+}
+
+static int addn_headers(void *udata, const char *name, const char *value)
+{
+ apr_table_t *dest = udata;
+ apr_table_addn(dest, name, value);
+ return 1;
+}
+
+static void check_early_hints(request_rec *r, const char *tag)
+{
+ apr_array_header_t *push_list = h2_config_push_list(r);
+ apr_table_t *early_headers = h2_config_early_headers(r);
+
+ if (!r->expecting_100 &&
+ ((push_list && push_list->nelts > 0) ||
+ (early_headers && !apr_is_empty_table(early_headers)))) {
+ int have_hints = 0, i;
+
+ if (push_list && push_list->nelts > 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "%s, early announcing %d resources for push",
+ tag, push_list->nelts);
+ for (i = 0; i < push_list->nelts; ++i) {
+ h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
+ apr_table_add(r->headers_out, "Link",
+ apr_psprintf(r->pool, "<%s>; rel=preload%s",
+ push->uri_ref, push->critical? "; critical" : ""));
+ }
+ have_hints = 1;
+ }
+ if (early_headers && !apr_is_empty_table(early_headers)) {
+ apr_table_do(addn_headers, r->headers_out, early_headers, NULL);
+ have_hints = 1;
+ }
+
+ if (have_hints) {
+ int old_status;
+ const char *old_line;
+
+ if (h2_config_rgeti(r, H2_CONF_PUSH) == 0 &&
+ h2_config_sgeti(r->server, H2_CONF_PUSH) != 0) {
+ apr_table_setn(r->connection->notes, H2_PUSH_MODE_NOTE, "0");
+ }
+ old_status = r->status;
+ old_line = r->status_line;
+ r->status = 103;
+ r->status_line = "103 Early Hints";
+ ap_send_interim_response(r, 1);
+ r->status = old_status;
+ r->status_line = old_line;
+ }
+ }
+}
+
+static int c2_hook_fixups(request_rec *r)
+{
+ conn_rec *c2 = r->connection;
+ h2_conn_ctx_t *conn_ctx;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return DECLINED;
+ }
+
+ check_early_hints(r, "late_fixup");
+
+ return DECLINED;
+}
+
+static apr_status_t http2_get_pollfd_from_conn(conn_rec *c,
+ struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout)
+{
+#if H2_USE_PIPES
+ if (c->master) {
+ h2_conn_ctx_t *ctx = h2_conn_ctx_get(c);
+ if (ctx) {
+ if (ctx->beam_in && ctx->pipe_in[H2_PIPE_OUT]) {
+ pfd->desc_type = APR_POLL_FILE;
+ pfd->desc.f = ctx->pipe_in[H2_PIPE_OUT];
+ if (ptimeout)
+ *ptimeout = h2_beam_timeout_get(ctx->beam_in);
+ }
+ else {
+ /* no input */
+ pfd->desc_type = APR_NO_DESC;
+ if (ptimeout)
+ *ptimeout = -1;
+ }
+ return APR_SUCCESS;
+ }
+ }
+#else
+ (void)c;
+ (void)pfd;
+ (void)ptimeout;
+#endif /* H2_USE_PIPES */
+ return APR_ENOTIMPL;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static void c2_pre_read_request(request_rec *r, conn_rec *c2)
+{
+ h2_conn_ctx_t *conn_ctx;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "h2_c2(%s-%d): adding request filters",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_add_input_filter_handle(c2_request_in_filter_handle, NULL, r, r->connection);
+ ap_add_output_filter_handle(c2_notes_out_filter_handle, NULL, r, r->connection);
+}
+
+static int c2_post_read_request(request_rec *r)
+{
+ h2_conn_ctx_t *conn_ctx;
+ conn_rec *c2 = r->connection;
+ apr_time_t timeout;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return DECLINED;
+ }
+ /* Now that the request_rec is fully initialized, set relevant params */
+ conn_ctx->server = r->server;
+ timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
+ if (timeout <= 0) {
+ timeout = r->server->timeout;
+ }
+ h2_conn_ctx_set_timeout(conn_ctx, 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_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "h2_mplx(%s-%d): copy_files in output",
+ conn_ctx->id, conn_ctx->stream_id);
+ h2_beam_set_copy_files(conn_ctx->beam_out, 1);
+ }
+
+ /* Add the raw bytes of the request (e.g. header frame lengths to
+ * the logio for this request. */
+ if (conn_ctx->request->raw_bytes && h2_c_logio_add_bytes_in) {
+ h2_c_logio_add_bytes_in(c2, conn_ctx->request->raw_bytes);
+ }
+ return OK;
+}
+
+static int c2_hook_pre_connection(conn_rec *c2, void *csd)
+{
+ h2_conn_ctx_t *conn_ctx;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return DECLINED;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_c2(%s-%d), adding filters",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_add_input_filter_handle(c2_net_in_filter_handle, NULL, NULL, c2);
+ ap_add_output_filter_handle(c2_net_out_filter_handle, NULL, NULL, c2);
+ if (c2->keepalives == 0) {
+ /* Simulate that we had already a request on this connection. Some
+ * hooks trigger special behaviour when keepalives is 0.
+ * (Not necessarily in pre_connection, but later. Set it here, so it
+ * is in place.) */
+ c2->keepalives = 1;
+ /* We signal that this connection will be closed after the request.
+ * Which is true in that sense that we throw away all traffic data
+ * on this c2 connection after each requests. Although we might
+ * reuse internal structures like memory pools.
+ * The wanted effect of this is that httpd does not try to clean up
+ * any dangling data on this connection when a request is done. Which
+ * is unnecessary on a h2 stream.
+ */
+ c2->keepalive = AP_CONN_CLOSE;
+ }
+ return OK;
+}
+
+void h2_c2_register_hooks(void)
+{
+ /* When the connection processing actually starts, we might
+ * take over, if the connection is for a h2 stream.
+ */
+ ap_hook_pre_connection(c2_hook_pre_connection,
+ NULL, NULL, APR_HOOK_MIDDLE);
+
+ /* We need to manipulate the standard HTTP/1.1 protocol filters and
+ * install our own. This needs to be done very early. */
+ ap_hook_pre_read_request(c2_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(c2_post_read_request, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+ ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if H2_USE_POLLFD_FROM_CONN
+ ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL,
+ APR_HOOK_MIDDLE);
+#endif
+ APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn);
+
+ c2_net_in_filter_handle =
+ ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
+ NULL, AP_FTYPE_NETWORK);
+ c2_net_out_filter_handle =
+ ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
+ NULL, AP_FTYPE_NETWORK);
+ c2_request_in_filter_handle =
+ ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
+ NULL, AP_FTYPE_PROTOCOL);
+ c2_notes_out_filter_handle =
+ ap_register_output_filter("H2_C2_NOTES_OUT", h2_c2_filter_notes_out,
+ NULL, AP_FTYPE_PROTOCOL);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+static apr_status_t c2_run_pre_connection(conn_rec *c2, apr_socket_t *csd)
+{
+ if (c2->keepalives == 0) {
+ /* Simulate that we had already a request on this connection. Some
+ * hooks trigger special behaviour when keepalives is 0.
+ * (Not necessarily in pre_connection, but later. Set it here, so it
+ * is in place.) */
+ c2->keepalives = 1;
+ /* We signal that this connection will be closed after the request.
+ * Which is true in that sense that we throw away all traffic data
+ * on this c2 connection after each requests. Although we might
+ * reuse internal structures like memory pools.
+ * The wanted effect of this is that httpd does not try to clean up
+ * any dangling data on this connection when a request is done. Which
+ * is unnecessary on a h2 stream.
+ */
+ c2->keepalive = AP_CONN_CLOSE;
+ return ap_run_pre_connection(c2, csd);
+ }
+ ap_assert(c2->output_filters);
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_c2_process(conn_rec *c2, apr_thread_t *thread, int worker_id)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+ ap_assert(conn_ctx);
+ ap_assert(conn_ctx->mplx);
+
+ /* See the discussion at
+ *
+ * Each conn_rec->id is supposed to be unique at a point in time. Since
+ * some modules (and maybe external code) uses this id as an identifier
+ * for the request_rec they handle, it needs to be unique for secondary
+ * connections also.
+ *
+ * The MPM module assigns the connection ids and mod_unique_id is using
+ * that one to generate identifier for requests. While the implementation
+ * works for HTTP/1.x, the parallel execution of several requests per
+ * connection will generate duplicate identifiers on load.
+ *
+ * The original implementation for secondary connection identifiers used
+ * to shift the master connection id up and assign the stream id to the
+ * lower bits. This was cramped on 32 bit systems, but on 64bit there was
+ * enough space.
+ *
+ * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
+ * connection id, even on 64bit systems. Therefore collisions in request ids.
+ *
+ * The way master connection ids are generated, there is some space "at the
+ * top" of the lower 32 bits on allmost all systems. If you have a setup
+ * with 64k threads per child and 255 child processes, you live on the edge.
+ *
+ * The new implementation shifts 8 bits and XORs in the worker
+ * id. This will experience collisions with > 256 h2 workers and heavy
+ * load still. There seems to be no way to solve this in all possible
+ * configurations by mod_h2 alone.
+ */
+ c2->id = (c2->master->id << 8)^worker_id;
+
+ if (!conn_ctx->pre_conn_done) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_c2(%s-%d), adding filters",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_add_input_filter("H2_C2_NET_IN", NULL, NULL, c2);
+ ap_add_output_filter("H2_C2_NET_CATCH_H1", NULL, NULL, c2);
+ ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
+
+ c2_run_pre_connection(c2, ap_get_conn_socket(c2));
+ conn_ctx->pre_conn_done = 1;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): process connection",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ c2->current_thread = thread;
+ ap_run_process_connection(c2);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): processing done",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c)
+{
+ const h2_request *req = conn_ctx->request;
+ conn_state_t *cs = c->cs;
+ request_rec *r = NULL;
+ const char *tenc;
+ apr_time_t timeout;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (req->protocol && !strcmp("websocket", req->protocol)) {
+ req = h2_ws_rewrite_request(req, c, conn_ctx->beam_in == NULL);
+ if (!req) {
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+ }
+
+ r = h2_create_request_rec(req, c, conn_ctx->beam_in == NULL);
+
+ if (!r) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): create request_rec failed, r=NULL",
+ conn_ctx->id, conn_ctx->stream_id);
+ goto cleanup;
+ }
+ if (r->status != HTTP_OK) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): create request_rec failed, r->status=%d",
+ conn_ctx->id, conn_ctx->stream_id, r->status);
+ goto cleanup;
+ }
+
+ tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ conn_ctx->input_chunked = tenc && ap_is_chunked(r->pool, tenc);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): created request_rec for %s",
+ conn_ctx->id, conn_ctx->stream_id, r->the_request);
+ conn_ctx->server = r->server;
+ timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
+ if (timeout <= 0) {
+ timeout = r->server->timeout;
+ }
+ h2_conn_ctx_set_timeout(conn_ctx, timeout);
+
+ if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_mplx(%s-%d): copy_files in output",
+ conn_ctx->id, conn_ctx->stream_id);
+ h2_beam_set_copy_files(conn_ctx->beam_out, 1);
+ }
+
+ ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
+ if (cs) {
+ cs->state = CONN_STATE_HANDLER;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): start process_request",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ /* Add the raw bytes of the request (e.g. header frame lengths to
+ * the logio for this request. */
+ if (req->raw_bytes && h2_c_logio_add_bytes_in) {
+ h2_c_logio_add_bytes_in(c, req->raw_bytes);
+ }
+
+ ap_process_request(r);
+ /* After the call to ap_process_request, the
+ * request pool may have been deleted. */
+ r = NULL;
+ if (conn_ctx->beam_out) {
+ h2_beam_close(conn_ctx->beam_out, c);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): process_request done",
+ conn_ctx->id, conn_ctx->stream_id);
+ if (cs)
+ cs->state = CONN_STATE_WRITE_COMPLETION;
+
+cleanup:
+ return rv;
+}
+
+conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
+ apr_bucket_alloc_t *buckt_alloc)
+{
+ apr_pool_t *pool;
+ conn_rec *c2;
+ void *cfg;
+
+ ap_assert(c1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c1,
+ "h2_c2: create for c1(%ld)", c1->id);
+
+ /* We create a pool with its own allocator to be used for
+ * processing a request. This is the only way to have the processing
+ * independent of its parent pool in the sense that it can work in
+ * another thread.
+ */
+ apr_pool_create(&pool, parent);
+ apr_pool_tag(pool, "h2_c2_conn");
+
+ c2 = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
+ memcpy(c2, c1, sizeof(conn_rec));
+
+ c2->master = c1;
+ c2->pool = pool;
+ c2->conn_config = ap_create_conn_config(pool);
+ c2->notes = apr_table_make(pool, 5);
+ c2->input_filters = NULL;
+ c2->output_filters = NULL;
+ c2->keepalives = 0;
+#if AP_MODULE_MAGIC_AT_LEAST(20180903, 1)
+ c2->filter_conn_ctx = NULL;
+#endif
+ c2->bucket_alloc = apr_bucket_alloc_create(pool);
+#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1)
+ c2->data_in_input_filters = 0;
+ c2->data_in_output_filters = 0;
+#endif
+ /* prevent mpm_event from making wrong assumptions about this connection,
+ * like e.g. using its socket for an async read check. */
+ c2->clogging_input_filters = 1;
+ c2->log = NULL;
+ c2->aborted = 0;
+ /* We cannot install the master connection socket on the secondary, as
+ * modules mess with timeouts/blocking of the socket, with
+ * unwanted side effects to the master connection processing.
+ * Fortunately, since we never use the secondary socket, we can just install
+ * a single, process-wide dummy and everyone is happy.
+ */
+ ap_set_module_config(c2->conn_config, &core_module, dummy_socket);
+ /* TODO: these should be unique to this thread */
+ c2->sbh = NULL; /*c1->sbh;*/
+ /* TODO: not all mpm modules have learned about secondary connections yet.
+ * copy their config from master to secondary.
+ */
+ if (mpm_module) {
+ cfg = ap_get_module_config(c1->conn_config, mpm_module);
+ ap_set_module_config(c2->conn_config, mpm_module, cfg);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
+ "h2_c2(%s): created", c2->log_id);
+ return c2;
+}
+
+static int h2_c2_hook_post_read_request(request_rec *r)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+
+ if (conn_ctx && conn_ctx->stream_id && ap_is_initial_req(r)) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "h2_c2(%s-%d): adding request filters",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ /* setup the correct filters to process the request for h2 */
+ ap_add_input_filter("H2_C2_REQUEST_IN", NULL, r, r->connection);
+
+ /* replace the core http filter that formats response headers
+ * in HTTP/1 with our own that collects status and headers */
+ ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
+
+ ap_add_output_filter("H2_C2_RESPONSE_OUT", NULL, r, r->connection);
+ ap_add_output_filter("H2_C2_TRAILERS_OUT", NULL, r, r->connection);
+ }
+ return DECLINED;
+}
+
+static int h2_c2_hook_process(conn_rec* c)
+{
+ h2_conn_ctx_t *ctx;
+
+ if (!c->master) {
+ return DECLINED;
+ }
+
+ ctx = h2_conn_ctx_get(c);
+ if (ctx->stream_id) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_h2, processing request directly");
+ c2_process(ctx, c);
+ return DONE;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "secondary_conn(%ld): no h2 stream assing?", c->id);
+ }
+ return DECLINED;
+}
+
+void h2_c2_register_hooks(void)
+{
+ /* When the connection processing actually starts, we might
+ * take over, if the connection is for a h2 stream.
+ */
+ ap_hook_process_connection(h2_c2_hook_process,
+ NULL, NULL, APR_HOOK_FIRST);
+ /* We need to manipulate the standard HTTP/1.1 protocol filters and
+ * install our own. This needs to be done very early. */
+ ap_hook_post_read_request(h2_c2_hook_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if H2_USE_POLLFD_FROM_CONN
+ ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL,
+ APR_HOOK_MIDDLE);
+#endif
+ APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn);
+
+ ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
+ NULL, AP_FTYPE_NETWORK);
+ ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
+ NULL, AP_FTYPE_NETWORK);
+ ap_register_output_filter("H2_C2_NET_CATCH_H1", h2_c2_filter_catch_h1_out,
+ NULL, AP_FTYPE_NETWORK);
+
+ ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_register_output_filter("H2_C2_RESPONSE_OUT", h2_c2_filter_response_out,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_register_output_filter("H2_C2_TRAILERS_OUT", h2_c2_filter_trailers_out,
+ NULL, AP_FTYPE_PROTOCOL);
+}
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_c2.h b/modules/http2/h2_c2.h
new file mode 100644
index 0000000..f278382
--- /dev/null
+++ b/modules/http2/h2_c2.h
@@ -0,0 +1,57 @@
+/* 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.
+ */
+
+#ifndef __mod_h2__h2_c2__
+#define __mod_h2__h2_c2__
+
+#include
+
+#include "h2.h"
+
+const char *h2_conn_mpm_name(void);
+int h2_mpm_supported(void);
+
+/* Initialize this child process for h2 secondary connection work,
+ * to be called once during child init before multi processing
+ * starts.
+ */
+apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s);
+
+#if !AP_HAS_RESPONSE_BUCKETS
+
+conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
+ apr_bucket_alloc_t *buckt_alloc);
+
+/**
+ * Process a secondary connection for a HTTP/2 stream request.
+ */
+apr_status_t h2_c2_process(conn_rec *c, apr_thread_t *thread, int worker_id);
+
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+
+void h2_c2_destroy(conn_rec *c2);
+
+/**
+ * Abort the I/O processing of a secondary connection. And
+ * in-/output beams will return errors and c2->aborted is set.
+ * @param c2 the secondary connection to abort
+ * @param from the connection this is invoked from
+ */
+void h2_c2_abort(conn_rec *c2, conn_rec *from);
+
+void h2_c2_register_hooks(void);
+
+#endif /* defined(__mod_h2__h2_c2__) */
diff --git a/modules/http2/h2_c2_filter.c b/modules/http2/h2_c2_filter.c
new file mode 100644
index 0000000..554f88b
--- /dev/null
+++ b/modules/http2/h2_c2_filter.c
@@ -0,0 +1,1056 @@
+/* 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
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_c1.h"
+#include "h2_c2_filter.h"
+#include "h2_c2.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_ws.h"
+#include "h2_util.h"
+
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ request_rec *r_prev;
+ ap_bucket_response *resp;
+ const char *err;
+
+ if (!f->r) {
+ goto pass;
+ }
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ resp = b->data;
+ if (resp->status >= 400 && f->r->prev) {
+ /* Error responses are commonly handled via internal
+ * redirects to error documents. That creates a new
+ * request_rec with 'prev' set to the original.
+ * Each of these has its onw 'notes'.
+ * We'd like to copy interesting ones into the current 'r->notes'
+ * as we reset HTTP/2 stream with H2 specific error codes then.
+ */
+ for (r_prev = f->r; r_prev != NULL; r_prev = r_prev->prev) {
+ if ((err = apr_table_get(r_prev->notes, "ssl-renegotiate-forbidden"))) {
+ if (r_prev != f->r) {
+ apr_table_setn(resp->notes, "ssl-renegotiate-forbidden", err);
+ }
+ break;
+ }
+ }
+ }
+ else if (h2_config_rgeti(f->r, H2_CONF_PUSH) == 0
+ && h2_config_sgeti(f->r->server, H2_CONF_PUSH) != 0) {
+ /* location configuration turns off H2 PUSH handling */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_notes_out, turning PUSH off");
+ apr_table_setn(resp->notes, H2_PUSH_MODE_NOTE, "0");
+ }
+ }
+ }
+pass:
+ return ap_pass_brigade(f->next, bb);
+}
+
+apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ h2_conn_ctx_t *conn_ctx;
+ apr_bucket *b;
+
+ /* just get out of the way for things we don't want to handle. */
+ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* This filter is a one-time wonder */
+ ap_remove_input_filter(f);
+
+ if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) &&
+ conn_ctx->stream_id) {
+ const h2_request *req = conn_ctx->request;
+
+ if (req->http_status == H2_HTTP_STATUS_UNSET &&
+ req->protocol && !strcmp("websocket", req->protocol)) {
+ req = h2_ws_rewrite_request(req, f->c, conn_ctx->beam_in == NULL);
+ if (!req)
+ return APR_EGENERAL;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_request_in(%s): adding request bucket",
+ conn_ctx->id);
+ b = h2_request_create_bucket(req, f->r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ /* error was encountered preparing this request */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_request_in(%s): adding error bucket %d",
+ conn_ctx->id, req->http_status);
+ b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ return APR_SUCCESS;
+ }
+
+ if (!conn_ctx->beam_in) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+
+ return APR_SUCCESS;
+ }
+
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+#define H2_FILTER_LOG(name, c, level, rv, msg, bb) \
+ do { \
+ if (APLOG_C_IS_LEVEL((c),(level))) { \
+ char buffer[4 * 1024]; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = h2_util_bb_print(buffer, bmax, "", "", (bb)); \
+ ap_log_cerror(APLOG_MARK, (level), rv, (c), \
+ "FILTER[%s]: %s %s", \
+ (name), (msg), len? buffer : ""); \
+ } \
+ } while (0)
+
+
+/* This routine is called by apr_table_do and merges all instances of
+ * the passed field values into a single array that will be further
+ * processed by some later routine. Originally intended to help split
+ * and recombine multiple Vary fields, though it is generic to any field
+ * consisting of comma/space-separated tokens.
+ */
+static int uniq_field_values(void *d, const char *key, const char *val)
+{
+ apr_array_header_t *values;
+ char *start;
+ char *e;
+ char **strpp;
+ int i;
+
+ (void)key;
+ values = (apr_array_header_t *)d;
+
+ e = apr_pstrdup(values->pool, val);
+
+ do {
+ /* Find a non-empty fieldname */
+
+ 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';
+ }
+
+ /* Now add it to values if it isn't already represented.
+ * Could be replaced by a ap_array_strcasecmp() if we had one.
+ */
+ for (i = 0, strpp = (char **) values->elts; i < values->nelts;
+ ++i, ++strpp) {
+ if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
+ break;
+ }
+ }
+ if (i == values->nelts) { /* if not found */
+ *(char **)apr_array_push(values) = start;
+ }
+ } while (*e != '\0');
+
+ return 1;
+}
+
+/*
+ * Since some clients choke violently on multiple Vary fields, or
+ * Vary fields with duplicate tokens, combine any multiples and remove
+ * any duplicates.
+ */
+static void fix_vary(request_rec *r)
+{
+ apr_array_header_t *varies;
+
+ varies = apr_array_make(r->pool, 5, sizeof(char *));
+
+ /* Extract all Vary fields from the headers_out, separate each into
+ * its comma-separated fieldname values, and then add them to varies
+ * if not already present in the array.
+ */
+ apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
+
+ /* If we found any, replace old Vary fields with unique-ified value */
+
+ if (varies->nelts > 0) {
+ apr_table_setn(r->headers_out, "Vary",
+ apr_array_pstrcat(r->pool, varies, ','));
+ }
+}
+
+static h2_headers *create_response(request_rec *r)
+{
+ const char *clheader;
+ const char *ctype;
+
+ /*
+ * Now that we are ready to send a response, we need to combine the two
+ * header field tables into a single table. If we don't do this, our
+ * later attempts to set or unset a given fieldname might be bypassed.
+ */
+ 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);
+ }
+
+ /*
+ * Remove the 'Vary' header field if the client can't handle it.
+ * Since this will have nasty effects on HTTP/1.1 caches, force
+ * the response into HTTP/1.0 mode.
+ */
+ if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+ apr_table_unset(r->headers_out, "Vary");
+ r->proto_num = HTTP_VERSION(1,0);
+ apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
+ }
+ else {
+ fix_vary(r);
+ }
+
+ /*
+ * Now remove any ETag response header field if earlier processing
+ * says so (such as a 'FileETag None' directive).
+ */
+ if (apr_table_get(r->notes, "no-etag") != NULL) {
+ apr_table_unset(r->headers_out, "ETag");
+ }
+
+ /* determine the protocol and whether we should use keepalives. */
+ ap_set_keepalive(r);
+
+ 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;
+ }
+ else if (r->chunked) {
+ apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ 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)) {
+ int i;
+ char *token;
+ char **languages = (char **)(r->content_languages->elts);
+ const char *field = apr_table_get(r->headers_out, "Content-Language");
+
+ while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
+ for (i = 0; i < r->content_languages->nelts; ++i) {
+ if (!apr_strnatcasecmp(token, languages[i]))
+ break;
+ }
+ if (i == r->content_languages->nelts) {
+ *((char **) apr_array_push(r->content_languages)) = token;
+ }
+ }
+
+ field = apr_array_pstrcat(r->pool, r->content_languages, ',');
+ apr_table_setn(r->headers_out, "Content-Language", field);
+ }
+
+ /*
+ * Control cachability for non-cachable responses if not already set by
+ * some other part of the server configuration.
+ */
+ 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);
+ }
+
+ /* This is a hack, but I can't find anyway around it. The idea is that
+ * we don't want to send out 0 Content-Lengths if it is a head request.
+ * This happens when modules try to outsmart the server, and return
+ * if they see a HEAD request. Apache 1.3 handlers were supposed to
+ * just return in that situation, and the core handled the HEAD. In
+ * 2.0, if a handler returns, then the core sends an EOS bucket down
+ * the filter stack, and the content-length filter computes a C-L of
+ * zero and that gets put in the headers, and we end up sending a
+ * zero C-L to the client. We can't just remove the C-L filter,
+ * because well behaved 2.0 handlers will send their data down the stack,
+ * and we will compute a real C-L for the head request. RBB
+ */
+ if (r->header_only
+ && (clheader = apr_table_get(r->headers_out, "Content-Length"))
+ && !strcmp(clheader, "0")) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ /*
+ * keep the set-by-proxy server and date headers, otherwise
+ * generate a new server header / date header
+ */
+ 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);
+ }
+ }
+
+ return h2_headers_rcreate(r, r->status, r->headers_out, r->pool);
+}
+
+typedef enum {
+ H2_RP_STATUS_LINE,
+ H2_RP_HEADER_LINE,
+ H2_RP_DONE
+} h2_rp_state_t;
+
+typedef struct h2_response_parser h2_response_parser;
+struct h2_response_parser {
+ const char *id;
+ h2_rp_state_t state;
+ conn_rec *c;
+ apr_pool_t *pool;
+ int http_status;
+ apr_array_header_t *hlines;
+ apr_bucket_brigade *tmp;
+ apr_bucket_brigade *saveto;
+};
+
+static apr_status_t parse_header(h2_response_parser *parser, char *line) {
+ const char *hline;
+ if (line[0] == ' ' || line[0] == '\t') {
+ char **plast;
+ /* continuation line from the header before this */
+ while (line[0] == ' ' || line[0] == '\t') {
+ ++line;
+ }
+
+ plast = apr_array_pop(parser->hlines);
+ if (plast == NULL) {
+ /* not well formed */
+ return APR_EINVAL;
+ }
+ hline = apr_psprintf(parser->pool, "%s %s", *plast, line);
+ }
+ else {
+ /* new header line */
+ hline = apr_pstrdup(parser->pool, line);
+ }
+ APR_ARRAY_PUSH(parser->hlines, const char*) = hline;
+ return APR_SUCCESS;
+}
+
+static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
+ char *line, apr_size_t len)
+{
+ apr_status_t status;
+
+ if (!parser->tmp) {
+ parser->tmp = apr_brigade_create(parser->pool, parser->c->bucket_alloc);
+ }
+ status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ,
+ len);
+ if (status == APR_SUCCESS) {
+ --len;
+ status = apr_brigade_flatten(parser->tmp, line, &len);
+ if (status == APR_SUCCESS) {
+ /* we assume a non-0 containing line and remove trailing crlf. */
+ line[len] = '\0';
+ /*
+ * XXX: What to do if there is an LF but no CRLF?
+ * Should we error out?
+ */
+ if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
+ len -= 2;
+ line[len] = '\0';
+ apr_brigade_cleanup(parser->tmp);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+ "h2_c2(%s): read response line: %s",
+ parser->id, line);
+ }
+ else {
+ apr_off_t brigade_length;
+
+ /*
+ * If the brigade parser->tmp becomes longer than our buffer
+ * for flattening we never have a chance to get a complete
+ * line. This can happen if we are called multiple times after
+ * previous calls did not find a H2_CRLF and we returned
+ * APR_EAGAIN. In this case parser->tmp (correctly) grows
+ * with each call to apr_brigade_split_line.
+ *
+ * XXX: Currently a stack based buffer of HUGE_STRING_LEN is
+ * used. This means we cannot cope with lines larger than
+ * HUGE_STRING_LEN which might be an issue.
+ */
+ status = apr_brigade_length(parser->tmp, 0, &brigade_length);
+ if ((status != APR_SUCCESS) || (brigade_length > (apr_off_t)len)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, parser->c, APLOGNO(10257)
+ "h2_c2(%s): read response, line too long",
+ parser->id);
+ return APR_ENOSPC;
+ }
+ /* this does not look like a complete line yet */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+ "h2_c2(%s): read response, incomplete line: %s",
+ parser->id, line);
+ if (!parser->saveto) {
+ parser->saveto = apr_brigade_create(parser->pool,
+ parser->c->bucket_alloc);
+ }
+ /*
+ * Be on the save side and save the parser->tmp brigade
+ * as it could contain transient buckets which could be
+ * invalid next time we are here.
+ *
+ * NULL for the filter parameter is ok since we
+ * provide our own brigade as second parameter
+ * and ap_save_brigade does not need to create one.
+ */
+ ap_save_brigade(NULL, &(parser->saveto), &(parser->tmp),
+ parser->tmp->p);
+ APR_BRIGADE_CONCAT(parser->tmp, parser->saveto);
+ return APR_EAGAIN;
+ }
+ }
+ }
+ apr_brigade_cleanup(parser->tmp);
+ return status;
+}
+
+static apr_table_t *make_table(h2_response_parser *parser)
+{
+ apr_array_header_t *hlines = parser->hlines;
+ if (hlines) {
+ apr_table_t *headers = apr_table_make(parser->pool, hlines->nelts);
+ int i;
+
+ for (i = 0; i < hlines->nelts; ++i) {
+ char *hline = ((char **)hlines->elts)[i];
+ char *sep = ap_strchr(hline, ':');
+ if (!sep) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, parser->c,
+ APLOGNO(02955) "h2_c2(%s): invalid header[%d] '%s'",
+ parser->id, i, (char*)hline);
+ /* not valid format, abort */
+ return NULL;
+ }
+ (*sep++) = '\0';
+ while (*sep == ' ' || *sep == '\t') {
+ ++sep;
+ }
+
+ if (!h2_util_ignore_resp_header(hline)) {
+ apr_table_merge(headers, hline, sep);
+ }
+ }
+ return headers;
+ }
+ else {
+ return apr_table_make(parser->pool, 0);
+ }
+}
+
+static apr_status_t pass_response(h2_conn_ctx_t *conn_ctx, ap_filter_t *f,
+ h2_response_parser *parser)
+{
+ apr_bucket *b;
+ apr_status_t status;
+ h2_headers *response = h2_headers_create(parser->http_status,
+ make_table(parser),
+ parser->c->notes,
+ 0, parser->pool);
+ apr_brigade_cleanup(parser->tmp);
+ b = h2_bucket_headers_create(parser->c->bucket_alloc, response);
+ APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+ b = apr_bucket_flush_create(parser->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+ status = ap_pass_brigade(f->next, parser->tmp);
+ apr_brigade_cleanup(parser->tmp);
+
+ /* reset parser for possible next response */
+ parser->state = H2_RP_STATUS_LINE;
+ apr_array_clear(parser->hlines);
+
+ if (response->status >= 200) {
+ conn_ctx->has_final_response = 1;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+ APLOGNO(03197) "h2_c2(%s): passed response %d",
+ parser->id, response->status);
+ return status;
+}
+
+static apr_status_t parse_status(h2_response_parser *parser, char *line)
+{
+ int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
+ (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
+ if (sindex > 0) {
+ int k = sindex + 3;
+ char keepchar = line[k];
+ line[k] = '\0';
+ parser->http_status = atoi(&line[sindex]);
+ line[k] = keepchar;
+ parser->state = H2_RP_HEADER_LINE;
+
+ return APR_SUCCESS;
+ }
+ /* Seems like there is garbage on the connection. May be a leftover
+ * from a previous proxy request.
+ * This should only happen if the H2_RESPONSE filter is not yet in
+ * place (post_read_request has not been reached and the handler wants
+ * to write something. Probably just the interim response we are
+ * waiting for. But if there is other data hanging around before
+ * that, this needs to fail. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c, APLOGNO(03467)
+ "h2_c2(%s): unable to parse status line: %s",
+ parser->id, line);
+ return APR_EINVAL;
+}
+
+static apr_status_t parse_response(h2_response_parser *parser,
+ h2_conn_ctx_t *conn_ctx,
+ ap_filter_t* f, apr_bucket_brigade *bb)
+{
+ char line[HUGE_STRING_LEN];
+ apr_status_t status = APR_SUCCESS;
+
+ while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
+ switch (parser->state) {
+ case H2_RP_STATUS_LINE:
+ case H2_RP_HEADER_LINE:
+ status = get_line(parser, bb, line, sizeof(line));
+ if (status == APR_EAGAIN) {
+ /* need more data */
+ return APR_SUCCESS;
+ }
+ else if (status != APR_SUCCESS) {
+ return status;
+ }
+ if (parser->state == H2_RP_STATUS_LINE) {
+ /* instead of parsing, just take it directly */
+ status = parse_status(parser, line);
+ }
+ else if (line[0] == '\0') {
+ /* end of headers, pass response onward */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
+ "h2_c2(%s): end of response", parser->id);
+ return pass_response(conn_ctx, f, parser);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
+ "h2_c2(%s): response header %s", parser->id, line);
+ status = parse_header(parser, line);
+ }
+ break;
+
+ default:
+ return status;
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ h2_response_parser *parser = f->ctx;
+ apr_status_t rv;
+
+ ap_assert(conn_ctx);
+ H2_FILTER_LOG("c2_catch_h1_out", f->c, APLOG_TRACE2, 0, "check", bb);
+
+ if (!f->c->aborted && !conn_ctx->has_final_response) {
+ if (!parser) {
+ parser = apr_pcalloc(f->c->pool, sizeof(*parser));
+ parser->id = apr_psprintf(f->c->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
+ parser->pool = f->c->pool;
+ parser->c = f->c;
+ parser->state = H2_RP_STATUS_LINE;
+ parser->hlines = apr_array_make(parser->pool, 10, sizeof(char *));
+ f->ctx = parser;
+ }
+
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
+ if (AP_BUCKET_IS_EOR(b)) {
+ /* TODO: Yikes, this happens when errors are encountered on input
+ * before anything from the repsonse has been processed. The
+ * ap_die_r() call will do nothing in certain conditions.
+ */
+ int result = ap_map_http_request_error(conn_ctx->last_err,
+ HTTP_INTERNAL_SERVER_ERROR);
+ request_rec *r = h2_create_request_rec(conn_ctx->request, f->c, 1);
+ if (r) {
+ ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
+ b = ap_bucket_eor_create(f->c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+ }
+ }
+ /* There are cases where we need to parse a serialized http/1.1 response.
+ * One example is a 100-continue answer via a mod_proxy setup. */
+ while (bb && !f->c->aborted && !conn_ctx->has_final_response) {
+ rv = parse_response(parser, conn_ctx, f, bb);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
+ "h2_c2(%s): parsed response", parser->id);
+ if (APR_BRIGADE_EMPTY(bb) || APR_SUCCESS != rv) {
+ return rv;
+ }
+ }
+ }
+
+ return ap_pass_brigade(f->next, bb);
+}
+
+apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ request_rec *r = f->r;
+ apr_bucket *b, *bresp, *body_bucket = NULL, *next;
+ ap_bucket_error *eb = NULL;
+ h2_headers *response = NULL;
+ int headers_passing = 0;
+
+ H2_FILTER_LOG("c2_response_out", f->c, APLOG_TRACE1, 0, "called with", bb);
+
+ if (f->c->aborted || !conn_ctx || conn_ctx->has_final_response) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (!conn_ctx->has_final_response) {
+ /* check, if we need to send the response now. Until we actually
+ * see a DATA bucket or some EOS/EOR, we do not do so. */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (AP_BUCKET_IS_ERROR(b) && !eb) {
+ eb = b->data;
+ }
+ else if (AP_BUCKET_IS_EOC(b)) {
+ /* If we see an EOC bucket it is a signal that we should get out
+ * of the way doing nothing.
+ */
+ ap_remove_output_filter(f);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2(%s): eoc bucket passed", conn_ctx->id);
+ return ap_pass_brigade(f->next, bb);
+ }
+ else if (H2_BUCKET_IS_HEADERS(b)) {
+ headers_passing = 1;
+ }
+ else if (!APR_BUCKET_IS_FLUSH(b)) {
+ body_bucket = b;
+ break;
+ }
+ }
+
+ if (eb) {
+ int st = eb->status;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
+ "h2_c2(%s): err bucket status=%d",
+ conn_ctx->id, st);
+ /* throw everything away and replace it with the error response
+ * generated by ap_die() */
+ apr_brigade_cleanup(bb);
+ ap_die(st, r);
+ return AP_FILTER_ERROR;
+ }
+
+ if (body_bucket || !headers_passing) {
+ /* time to insert the response bucket before the body or if
+ * no h2_headers is passed, e.g. the response is empty */
+ response = create_response(r);
+ if (response == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
+ "h2_c2(%s): unable to create response", conn_ctx->id);
+ return APR_ENOMEM;
+ }
+
+ bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
+ if (body_bucket) {
+ APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
+ }
+ else {
+ APR_BRIGADE_INSERT_HEAD(bb, bresp);
+ }
+ conn_ctx->has_final_response = 1;
+ r->sent_bodyct = 1;
+ ap_remove_output_filter_byhandle(f->r->output_filters, "H2_C2_NET_CATCH_H1");
+ }
+ }
+
+ if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_c2(%s): headers only, cleanup output brigade", conn_ctx->id);
+ b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
+ while (b != APR_BRIGADE_SENTINEL(bb)) {
+ next = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
+ break;
+ }
+ if (!H2_BUCKET_IS_HEADERS(b)) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ b = next;
+ }
+ }
+ if (conn_ctx->has_final_response) {
+ /* lets get out of the way, our task is done */
+ ap_remove_output_filter(f);
+ }
+ return ap_pass_brigade(f->next, 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,
+ apr_read_type_e block,
+ 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;
+ core_server_config *conf =
+ (core_server_config *) ap_get_module_config(r->server->module_config,
+ &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, mode=%d, block=%d, "
+ "readbytes=%ld, exp=%d",
+ conn_ctx->id, conn_ctx->stream_id, mode, block,
+ (long)readbytes, r->expecting_100);
+ if (!conn_ctx->input_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;
+ }
+ }
+ return status;
+ }
+
+ /* 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);
+ }
+ }
+ 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;
+}
+
+apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ request_rec *r = f->r;
+ apr_bucket *b, *e;
+
+ if (conn_ctx && r) {
+ /* Detect the EOS/EOR bucket and forward any trailers that may have
+ * been set to our h2_headers.
+ */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
+ && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
+ h2_headers *headers;
+ apr_table_t *trailers;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
+ "h2_c2(%s-%d): sending trailers",
+ conn_ctx->id, conn_ctx->stream_id);
+ trailers = apr_table_clone(r->pool, r->trailers_out);
+ headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
+ e = h2_bucket_headers_create(bb->bucket_alloc, headers);
+ APR_BUCKET_INSERT_BEFORE(b, e);
+ apr_table_clear(r->trailers_out);
+ ap_remove_output_filter(f);
+ break;
+ }
+ }
+ }
+
+ return ap_pass_brigade(f->next, bb);
+}
+
+#endif /* else #if AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_c2_filter.h b/modules/http2/h2_c2_filter.h
new file mode 100644
index 0000000..c6f50dd
--- /dev/null
+++ b/modules/http2/h2_c2_filter.h
@@ -0,0 +1,68 @@
+/* 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.
+ */
+
+#ifndef __mod_h2__h2_c2_filter__
+#define __mod_h2__h2_c2_filter__
+
+#include "h2.h"
+
+/**
+ * Input filter on secondary connections that insert the REQUEST bucket
+ * with the request to perform and then removes itself.
+ */
+apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes);
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+/**
+ * Output filter that inspects the request_rec->notes of the request
+ * itself and possible internal redirects to detect conditions that
+ * merit specific HTTP/2 response codes, such as 421.
+ */
+apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+/**
+ * h2_from_h1 parses a HTTP/1.1 response into
+ * - response status
+ * - a list of header values
+ * - a series of bytes that represent the response body alone, without
+ * any meta data, such as inserted by chunked transfer encoding.
+ *
+ * All data is allocated from the stream memory pool.
+ *
+ * Again, see comments in h2_request: ideally we would take the headers
+ * and status from the httpd structures instead of parsing them here, but
+ * we need to have all handlers and filters involved in request/response
+ * processing, so this seems to be the way for now.
+ */
+struct h2_headers;
+struct h2_response_parser;
+
+apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb);
+
+apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+
+#endif /* defined(__mod_h2__h2_c2_filter__) */
diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c
index 8766355..22653d4 100644
--- a/modules/http2/h2_config.c
+++ b/modules/http2/h2_config.c
@@ -30,11 +30,10 @@
#include
#include "h2.h"
-#include "h2_alt_svc.h"
-#include "h2_ctx.h"
-#include "h2_conn.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
#include "h2_config.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
#include "h2_private.h"
#define DEF_VAL (-1)
@@ -42,17 +41,65 @@
#define H2_CONFIG_GET(a, b, n) \
(((a)->n == DEF_VAL)? (b) : (a))->n
+#define H2_CONFIG_SET(a, n, v) \
+ ((a)->n = v)
+
+#define CONFIG_CMD_SET(cmd,dir,var,val) \
+ h2_config_seti(((cmd)->path? (dir) : NULL), h2_config_sget((cmd)->server), var, val)
+
+#define CONFIG_CMD_SET64(cmd,dir,var,val) \
+ h2_config_seti64(((cmd)->path? (dir) : NULL), h2_config_sget((cmd)->server), var, val)
+
+/* Apache httpd module configuration for h2. */
+typedef struct h2_config {
+ const char *name;
+ int h2_max_streams; /* max concurrent # streams (http2) */
+ int h2_window_size; /* stream window size (http2) */
+ int min_workers; /* min # of worker threads/child */
+ int max_workers; /* max # of worker threads/child */
+ apr_interval_time_t idle_limit; /* max duration for idle workers */
+ int stream_max_mem_size; /* max # bytes held in memory/stream */
+ int h2_direct; /* if mod_h2 is active directly */
+ int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
+ int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
+ apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
+ int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */
+ int h2_push; /* if HTTP/2 server push is enabled */
+ struct apr_hash_t *priorities; /* map of content-type to h2_priority records */
+
+ int push_diary_size; /* # of entries in push diary */
+ int copy_files; /* if files shall be copied vs setaside on output */
+ apr_array_header_t *push_list; /* list of h2_push_res configurations */
+ apr_table_t *early_headers; /* HTTP headers for a 103 response */
+ int early_hints; /* support status code 103 */
+ int padding_bits;
+ int padding_always;
+ int output_buffered;
+ apr_interval_time_t stream_timeout;/* beam timeout */
+ int max_data_frame_len; /* max # bytes in a single h2 DATA frame */
+ int proxy_requests; /* act as forward proxy */
+ int h2_websockets; /* if mod_h2 negotiating WebSockets */
+} h2_config;
+
+typedef struct h2_dir_config {
+ const char *name;
+ int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
+ int h2_push; /* if HTTP/2 server push is enabled */
+ apr_array_header_t *push_list; /* list of h2_push_res configurations */
+ apr_table_t *early_headers; /* HTTP headers for a 103 response */
+ int early_hints; /* support status code 103 */
+ apr_interval_time_t stream_timeout;/* beam timeout */
+} h2_dir_config;
+
+
static h2_config defconf = {
"default",
100, /* max_streams */
H2_INITIAL_WINDOW_SIZE, /* window_size */
-1, /* min workers */
-1, /* max workers */
- 10 * 60, /* max workers idle secs */
+ apr_time_from_sec(10 * 60), /* workers idle limit */
32 * 1024, /* stream max mem size */
- NULL, /* no alt-svcs */
- -1, /* alt-svc max age */
- 0, /* serialize headers */
-1, /* h2 direct mode */
1, /* modern TLS only */
-1, /* HTTP/1 Upgrade support */
@@ -63,7 +110,25 @@ static h2_config defconf = {
256, /* push diary size */
0, /* copy files across threads */
NULL, /* push list */
+ NULL, /* early headers */
0, /* early hints, http status 103 */
+ 0, /* padding bits */
+ 1, /* padding always */
+ 1, /* stream output buffered */
+ -1, /* beam timeout */
+ 0, /* max DATA frame len, 0 == no extra limit */
+ 0, /* forward proxy */
+ 0, /* WebSockets negotiation, enabled */
+};
+
+static h2_dir_config defdconf = {
+ "default",
+ -1, /* HTTP/1 Upgrade support */
+ -1, /* HTTP/2 server push enabled */
+ NULL, /* push list */
+ NULL, /* early headers */
+ -1, /* early hints, http status 103 */
+ -1, /* beam timeout */
};
void h2_config_init(apr_pool_t *pool)
@@ -71,22 +136,18 @@ void h2_config_init(apr_pool_t *pool)
(void)pool;
}
-static void *h2_config_create(apr_pool_t *pool,
- const char *prefix, const char *x)
+void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
{
h2_config *conf = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
- const char *s = x? x : "unknown";
- char *name = apr_pstrcat(pool, prefix, "[", s, "]", NULL);
+ char *name = apr_pstrcat(pool, "srv[", s->defn_name, "]", NULL);
conf->name = name;
conf->h2_max_streams = DEF_VAL;
conf->h2_window_size = DEF_VAL;
conf->min_workers = DEF_VAL;
conf->max_workers = DEF_VAL;
- conf->max_worker_idle_secs = DEF_VAL;
+ conf->idle_limit = DEF_VAL;
conf->stream_max_mem_size = DEF_VAL;
- conf->alt_svc_max_age = DEF_VAL;
- conf->serialize_headers = DEF_VAL;
conf->h2_direct = DEF_VAL;
conf->modern_tls_only = DEF_VAL;
conf->h2_upgrade = DEF_VAL;
@@ -97,20 +158,18 @@ static void *h2_config_create(apr_pool_t *pool,
conf->push_diary_size = DEF_VAL;
conf->copy_files = DEF_VAL;
conf->push_list = NULL;
+ conf->early_headers = NULL;
conf->early_hints = DEF_VAL;
+ conf->padding_bits = DEF_VAL;
+ conf->padding_always = DEF_VAL;
+ conf->output_buffered = DEF_VAL;
+ conf->stream_timeout = DEF_VAL;
+ conf->max_data_frame_len = DEF_VAL;
+ conf->proxy_requests = DEF_VAL;
+ conf->h2_websockets = DEF_VAL;
return conf;
}
-void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
-{
- return h2_config_create(pool, "srv", s->defn_name);
-}
-
-void *h2_config_create_dir(apr_pool_t *pool, char *x)
-{
- return h2_config_create(pool, "dir", x);
-}
-
static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
{
h2_config *base = (h2_config *)basev;
@@ -123,11 +182,8 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
n->min_workers = H2_CONFIG_GET(add, base, min_workers);
n->max_workers = H2_CONFIG_GET(add, base, max_workers);
- n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
+ n->idle_limit = H2_CONFIG_GET(add, base, idle_limit);
n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
- n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
- n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
- n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only);
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
@@ -142,32 +198,75 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
}
n->push_diary_size = H2_CONFIG_GET(add, base, push_diary_size);
n->copy_files = H2_CONFIG_GET(add, base, copy_files);
+ n->output_buffered = H2_CONFIG_GET(add, base, output_buffered);
if (add->push_list && base->push_list) {
n->push_list = apr_array_append(pool, base->push_list, add->push_list);
}
else {
n->push_list = add->push_list? add->push_list : base->push_list;
}
+ if (add->early_headers && base->early_headers) {
+ n->early_headers = apr_table_overlay(pool, add->early_headers, base->early_headers);
+ }
+ else {
+ n->early_headers = add->early_headers? add->early_headers : base->early_headers;
+ }
n->early_hints = H2_CONFIG_GET(add, base, early_hints);
+ n->padding_bits = H2_CONFIG_GET(add, base, padding_bits);
+ n->padding_always = H2_CONFIG_GET(add, base, padding_always);
+ n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
+ n->max_data_frame_len = H2_CONFIG_GET(add, base, max_data_frame_len);
+ n->proxy_requests = H2_CONFIG_GET(add, base, proxy_requests);
+ n->h2_websockets = H2_CONFIG_GET(add, base, h2_websockets);
return n;
}
-void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
+void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
{
return h2_config_merge(pool, basev, addv);
}
-void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
+void *h2_config_create_dir(apr_pool_t *pool, char *x)
{
- return h2_config_merge(pool, basev, addv);
+ h2_dir_config *conf = (h2_dir_config *)apr_pcalloc(pool, sizeof(h2_dir_config));
+ const char *s = x? x : "unknown";
+ char *name = apr_pstrcat(pool, "dir[", s, "]", NULL);
+
+ conf->name = name;
+ conf->h2_upgrade = DEF_VAL;
+ conf->h2_push = DEF_VAL;
+ conf->early_hints = DEF_VAL;
+ conf->stream_timeout = DEF_VAL;
+ return conf;
}
-int h2_config_geti(const h2_config *conf, h2_config_var_t var)
+void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
{
- return (int)h2_config_geti64(conf, var);
+ h2_dir_config *base = (h2_dir_config *)basev;
+ h2_dir_config *add = (h2_dir_config *)addv;
+ h2_dir_config *n = (h2_dir_config *)apr_pcalloc(pool, sizeof(h2_dir_config));
+
+ n->name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
+ n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
+ n->h2_push = H2_CONFIG_GET(add, base, h2_push);
+ if (add->push_list && base->push_list) {
+ n->push_list = apr_array_append(pool, base->push_list, add->push_list);
+ }
+ else {
+ n->push_list = add->push_list? add->push_list : base->push_list;
+ }
+ if (add->early_headers && base->early_headers) {
+ n->early_headers = apr_table_overlay(pool, add->early_headers, base->early_headers);
+ }
+ else {
+ n->early_headers = add->early_headers? add->early_headers : base->early_headers;
+ }
+ n->early_hints = H2_CONFIG_GET(add, base, early_hints);
+ n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
+ return n;
}
-apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var)
+static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t var)
{
switch(var) {
case H2_CONF_MAX_STREAMS:
@@ -178,14 +277,10 @@ apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var)
return H2_CONFIG_GET(conf, &defconf, min_workers);
case H2_CONF_MAX_WORKERS:
return H2_CONFIG_GET(conf, &defconf, max_workers);
- case H2_CONF_MAX_WORKER_IDLE_SECS:
- return H2_CONFIG_GET(conf, &defconf, max_worker_idle_secs);
+ case H2_CONF_MAX_WORKER_IDLE_LIMIT:
+ return H2_CONFIG_GET(conf, &defconf, idle_limit);
case H2_CONF_STREAM_MAX_MEM:
return H2_CONFIG_GET(conf, &defconf, stream_max_mem_size);
- case H2_CONF_ALT_SVC_MAX_AGE:
- return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
- case H2_CONF_SER_HEADERS:
- return H2_CONFIG_GET(conf, &defconf, serialize_headers);
case H2_CONF_MODERN_TLS_ONLY:
return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
case H2_CONF_UPGRADE:
@@ -204,12 +299,112 @@ apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var)
return H2_CONFIG_GET(conf, &defconf, copy_files);
case H2_CONF_EARLY_HINTS:
return H2_CONFIG_GET(conf, &defconf, early_hints);
+ case H2_CONF_PADDING_BITS:
+ return H2_CONFIG_GET(conf, &defconf, padding_bits);
+ case H2_CONF_PADDING_ALWAYS:
+ return H2_CONFIG_GET(conf, &defconf, padding_always);
+ case H2_CONF_OUTPUT_BUFFER:
+ return H2_CONFIG_GET(conf, &defconf, output_buffered);
+ case H2_CONF_STREAM_TIMEOUT:
+ return H2_CONFIG_GET(conf, &defconf, stream_timeout);
+ case H2_CONF_MAX_DATA_FRAME_LEN:
+ return H2_CONFIG_GET(conf, &defconf, max_data_frame_len);
+ case H2_CONF_PROXY_REQUESTS:
+ return H2_CONFIG_GET(conf, &defconf, proxy_requests);
+ case H2_CONF_WEBSOCKETS:
+ return H2_CONFIG_GET(conf, &defconf, h2_websockets);
default:
return DEF_VAL;
}
}
-const h2_config *h2_config_sget(server_rec *s)
+static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
+{
+ switch(var) {
+ case H2_CONF_MAX_STREAMS:
+ H2_CONFIG_SET(conf, h2_max_streams, val);
+ break;
+ case H2_CONF_WIN_SIZE:
+ H2_CONFIG_SET(conf, h2_window_size, val);
+ break;
+ case H2_CONF_MIN_WORKERS:
+ H2_CONFIG_SET(conf, min_workers, val);
+ break;
+ case H2_CONF_MAX_WORKERS:
+ H2_CONFIG_SET(conf, max_workers, val);
+ break;
+ case H2_CONF_STREAM_MAX_MEM:
+ H2_CONFIG_SET(conf, stream_max_mem_size, val);
+ break;
+ case H2_CONF_MODERN_TLS_ONLY:
+ H2_CONFIG_SET(conf, modern_tls_only, val);
+ break;
+ case H2_CONF_UPGRADE:
+ H2_CONFIG_SET(conf, h2_upgrade, val);
+ break;
+ case H2_CONF_DIRECT:
+ H2_CONFIG_SET(conf, h2_direct, val);
+ break;
+ case H2_CONF_TLS_WARMUP_SIZE:
+ H2_CONFIG_SET(conf, tls_warmup_size, val);
+ break;
+ case H2_CONF_TLS_COOLDOWN_SECS:
+ H2_CONFIG_SET(conf, tls_cooldown_secs, val);
+ break;
+ case H2_CONF_PUSH:
+ H2_CONFIG_SET(conf, h2_push, val);
+ break;
+ case H2_CONF_PUSH_DIARY_SIZE:
+ H2_CONFIG_SET(conf, push_diary_size, val);
+ break;
+ case H2_CONF_COPY_FILES:
+ H2_CONFIG_SET(conf, copy_files, val);
+ break;
+ case H2_CONF_EARLY_HINTS:
+ H2_CONFIG_SET(conf, early_hints, val);
+ break;
+ case H2_CONF_PADDING_BITS:
+ H2_CONFIG_SET(conf, padding_bits, val);
+ break;
+ case H2_CONF_PADDING_ALWAYS:
+ H2_CONFIG_SET(conf, padding_always, val);
+ break;
+ case H2_CONF_OUTPUT_BUFFER:
+ H2_CONFIG_SET(conf, output_buffered, val);
+ break;
+ case H2_CONF_MAX_DATA_FRAME_LEN:
+ H2_CONFIG_SET(conf, max_data_frame_len, val);
+ break;
+ case H2_CONF_PROXY_REQUESTS:
+ H2_CONFIG_SET(conf, proxy_requests, val);
+ break;
+ case H2_CONF_WEBSOCKETS:
+ H2_CONFIG_SET(conf, h2_websockets, val);
+ break;
+ default:
+ break;
+ }
+}
+
+static void h2_srv_config_seti64(h2_config *conf, h2_config_var_t var, apr_int64_t val)
+{
+ switch(var) {
+ case H2_CONF_TLS_WARMUP_SIZE:
+ H2_CONFIG_SET(conf, tls_warmup_size, val);
+ break;
+ case H2_CONF_STREAM_TIMEOUT:
+ H2_CONFIG_SET(conf, stream_timeout, val);
+ break;
+ case H2_CONF_MAX_WORKER_IDLE_LIMIT:
+ H2_CONFIG_SET(conf, idle_limit, val);
+ break;
+ default:
+ h2_srv_config_seti(conf, var, (int)val);
+ break;
+ }
+}
+
+static h2_config *h2_config_sget(server_rec *s)
{
h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config,
&http2_module);
@@ -217,177 +412,310 @@ const h2_config *h2_config_sget(server_rec *s)
return cfg;
}
-const struct h2_priority *h2_config_get_priority(const h2_config *conf,
- const char *content_type)
+static const h2_dir_config *h2_config_rget(request_rec *r)
+{
+ h2_dir_config *cfg = (h2_dir_config *)ap_get_module_config(r->per_dir_config,
+ &http2_module);
+ ap_assert(cfg);
+ return cfg;
+}
+
+static apr_int64_t h2_dir_config_geti64(const h2_dir_config *conf, h2_config_var_t var)
+{
+ switch(var) {
+ case H2_CONF_UPGRADE:
+ return H2_CONFIG_GET(conf, &defdconf, h2_upgrade);
+ case H2_CONF_PUSH:
+ return H2_CONFIG_GET(conf, &defdconf, h2_push);
+ case H2_CONF_EARLY_HINTS:
+ return H2_CONFIG_GET(conf, &defdconf, early_hints);
+ case H2_CONF_STREAM_TIMEOUT:
+ return H2_CONFIG_GET(conf, &defdconf, stream_timeout);
+
+ default:
+ return DEF_VAL;
+ }
+}
+
+static void h2_config_seti(h2_dir_config *dconf, h2_config *conf, h2_config_var_t var, int val)
+{
+ int set_srv = !dconf;
+ if (dconf) {
+ switch(var) {
+ case H2_CONF_UPGRADE:
+ H2_CONFIG_SET(dconf, h2_upgrade, val);
+ break;
+ case H2_CONF_PUSH:
+ H2_CONFIG_SET(dconf, h2_push, val);
+ break;
+ case H2_CONF_EARLY_HINTS:
+ H2_CONFIG_SET(dconf, early_hints, val);
+ break;
+ default:
+ /* not handled in dir_conf */
+ set_srv = 1;
+ break;
+ }
+ }
+
+ if (set_srv) {
+ h2_srv_config_seti(conf, var, val);
+ }
+}
+
+static void h2_config_seti64(h2_dir_config *dconf, h2_config *conf, h2_config_var_t var, apr_int64_t val)
+{
+ int set_srv = !dconf;
+ if (dconf) {
+ switch(var) {
+ case H2_CONF_STREAM_TIMEOUT:
+ H2_CONFIG_SET(dconf, stream_timeout, val);
+ break;
+ default:
+ /* not handled in dir_conf */
+ set_srv = 1;
+ break;
+ }
+ }
+
+ if (set_srv) {
+ h2_srv_config_seti64(conf, var, val);
+ }
+}
+
+static const h2_config *h2_config_get(conn_rec *c)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->server) {
+ return h2_config_sget(conn_ctx->server);
+ }
+ return h2_config_sget(c->base_server);
+}
+
+int h2_config_cgeti(conn_rec *c, h2_config_var_t var)
{
+ return (int)h2_srv_config_geti64(h2_config_get(c), var);
+}
+
+apr_int64_t h2_config_cgeti64(conn_rec *c, h2_config_var_t var)
+{
+ return h2_srv_config_geti64(h2_config_get(c), var);
+}
+
+int h2_config_sgeti(server_rec *s, h2_config_var_t var)
+{
+ return (int)h2_srv_config_geti64(h2_config_sget(s), var);
+}
+
+apr_int64_t h2_config_sgeti64(server_rec *s, h2_config_var_t var)
+{
+ return h2_srv_config_geti64(h2_config_sget(s), var);
+}
+
+int h2_config_geti(request_rec *r, server_rec *s, h2_config_var_t var)
+{
+ return (int)h2_config_geti64(r, s, var);
+}
+
+apr_int64_t h2_config_geti64(request_rec *r, server_rec *s, h2_config_var_t var)
+{
+ apr_int64_t mode = r? (int)h2_dir_config_geti64(h2_config_rget(r), var) : DEF_VAL;
+ return (mode != DEF_VAL)? mode : h2_config_sgeti64(s, var);
+}
+
+int h2_config_rgeti(request_rec *r, h2_config_var_t var)
+{
+ return h2_config_geti(r, r->server, var);
+}
+
+apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var)
+{
+ return h2_config_geti64(r, r->server, var);
+}
+
+apr_array_header_t *h2_config_push_list(request_rec *r)
+{
+ const h2_config *sconf;
+ const h2_dir_config *conf = h2_config_rget(r);
+
+ if (conf && conf->push_list) {
+ return conf->push_list;
+ }
+ sconf = h2_config_sget(r->server);
+ return sconf? sconf->push_list : NULL;
+}
+
+apr_table_t *h2_config_early_headers(request_rec *r)
+{
+ const h2_config *sconf;
+ const h2_dir_config *conf = h2_config_rget(r);
+
+ if (conf && conf->early_headers) {
+ return conf->early_headers;
+ }
+ sconf = h2_config_sget(r->server);
+ return sconf? sconf->early_headers : NULL;
+}
+
+const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type)
+{
+ const h2_config *conf = h2_config_get(c);
if (content_type && conf->priorities) {
- size_t len = strcspn(content_type, "; \t");
+ apr_ssize_t len = (apr_ssize_t)strcspn(content_type, "; \t");
h2_priority *prio = apr_hash_get(conf->priorities, content_type, len);
return prio? prio : apr_hash_get(conf->priorities, "*", 1);
}
return NULL;
}
-static const char *h2_conf_set_max_streams(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_max_streams(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->h2_max_streams = (int)apr_atoi64(value);
- (void)arg;
- if (cfg->h2_max_streams < 1) {
+ apr_int64_t ival = (int)apr_atoi64(value);
+ if (ival < 1) {
return "value must be > 0";
}
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_MAX_STREAMS, ival);
return NULL;
}
-static const char *h2_conf_set_window_size(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_window_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->h2_window_size = (int)apr_atoi64(value);
- (void)arg;
- if (cfg->h2_window_size < 1024) {
+ int val = (int)apr_atoi64(value);
+ if (val < 1024) {
return "value must be >= 1024";
}
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WIN_SIZE, val);
return NULL;
}
-static const char *h2_conf_set_min_workers(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_min_workers(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->min_workers = (int)apr_atoi64(value);
- (void)arg;
- if (cfg->min_workers < 1) {
+ int val = (int)apr_atoi64(value);
+ if (val < 1) {
return "value must be > 0";
}
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MIN_WORKERS, val);
return NULL;
}
-static const char *h2_conf_set_max_workers(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_max_workers(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->max_workers = (int)apr_atoi64(value);
- (void)arg;
- if (cfg->max_workers < 1) {
+ int val = (int)apr_atoi64(value);
+ if (val < 1) {
return "value must be > 0";
}
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_WORKERS, val);
return NULL;
}
-static const char *h2_conf_set_max_worker_idle_secs(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_max_worker_idle_limit(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->max_worker_idle_secs = (int)apr_atoi64(value);
- (void)arg;
- if (cfg->max_worker_idle_secs < 1) {
- return "value must be > 0";
+ apr_interval_time_t timeout;
+ apr_status_t rv = ap_timeout_parameter_parse(value, &timeout, "s");
+ if (rv != APR_SUCCESS) {
+ return "Invalid idle limit value";
}
+ if (timeout <= 0) {
+ timeout = DEF_VAL;
+ }
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_MAX_WORKER_IDLE_LIMIT, timeout);
return NULL;
}
-static const char *h2_conf_set_stream_max_mem_size(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_stream_max_mem_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
-
-
- cfg->stream_max_mem_size = (int)apr_atoi64(value);
- (void)arg;
- if (cfg->stream_max_mem_size < 1024) {
+ int val = (int)apr_atoi64(value);
+ if (val < 1024) {
return "value must be >= 1024";
}
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_STREAM_MAX_MEM, val);
return NULL;
}
-static const char *h2_add_alt_svc(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_max_data_frame_len(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- if (value && *value) {
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- h2_alt_svc *as = h2_alt_svc_parse(value, parms->pool);
- if (!as) {
- return "unable to parse alt-svc specifier";
- }
- if (!cfg->alt_svcs) {
- cfg->alt_svcs = apr_array_make(parms->pool, 5, sizeof(h2_alt_svc*));
- }
- APR_ARRAY_PUSH(cfg->alt_svcs, h2_alt_svc*) = as;
+ int val = (int)apr_atoi64(value);
+ if (val < 0) {
+ return "value must be 0 or larger";
}
- (void)arg;
- return NULL;
-}
-
-static const char *h2_conf_set_alt_svc_max_age(cmd_parms *parms,
- void *arg, const char *value)
-{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->alt_svc_max_age = (int)apr_atoi64(value);
- (void)arg;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_DATA_FRAME_LEN, val);
return NULL;
}
-static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
/* deprecated, ignore */
- (void)arg;
+ (void)dirconf;
(void)value;
- ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, parms->pool, /* NO LOGNO */
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool, /* NO LOGNO */
"H2SessionExtraFiles is obsolete and will be ignored");
return NULL;
}
static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
- void *arg, const char *value)
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
- cfg->serialize_headers = 1;
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, parms->server, APLOGNO(10307)
+ "%s: this feature has been disabled and the directive "
+ "to enable it is ignored.", parms->cmd->name);
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_direct(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_DIRECT, 1);
return NULL;
}
else if (!strcasecmp(value, "Off")) {
- cfg->serialize_headers = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_DIRECT, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
-static const char *h2_conf_set_direct(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_push(cmd_parms *cmd, void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
- cfg->h2_direct = 1;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PUSH, 1);
return NULL;
}
else if (!strcasecmp(value, "Off")) {
- cfg->h2_direct = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PUSH, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
-static const char *h2_conf_set_push(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_websockets(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
- cfg->h2_push = 1;
+#if H2_USE_WEBSOCKETS
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
return NULL;
+#elif !H2_USE_PIPES
+ return "HTTP/2 WebSockets are not supported on this platform";
+#else
+ return "HTTP/2 WebSockets are not supported in this server version";
+#endif
}
else if (!strcasecmp(value, "Off")) {
- cfg->h2_push = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
@@ -400,7 +728,8 @@ static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
h2_dependency dependency;
h2_priority *priority;
int weight;
-
+
+ (void)_cfg;
if (!*ctype) {
return "1st argument must be a mime-type, like 'text/css' or '*'";
}
@@ -419,7 +748,7 @@ static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
else if (!strcasecmp("BEFORE", sdependency)) {
dependency = H2_DEPENDANT_BEFORE;
if (sweight) {
- return "dependecy 'Before' does not allow a weight";
+ return "dependency 'Before' does not allow a weight";
}
}
else if (!strcasecmp("INTERLEAVED", sdependency)) {
@@ -443,104 +772,92 @@ static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
if (!cfg->priorities) {
cfg->priorities = apr_hash_make(cmd->pool);
}
- apr_hash_set(cfg->priorities, ctype, strlen(ctype), priority);
+ apr_hash_set(cfg->priorities, ctype, (apr_ssize_t)strlen(ctype), priority);
return NULL;
}
-static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_modern_tls_only(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
- cfg->modern_tls_only = 1;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MODERN_TLS_ONLY, 1);
return NULL;
}
else if (!strcasecmp(value, "Off")) {
- cfg->modern_tls_only = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MODERN_TLS_ONLY, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
-static const char *h2_conf_set_upgrade(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_upgrade(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
- cfg->h2_upgrade = 1;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_UPGRADE, 1);
return NULL;
}
else if (!strcasecmp(value, "Off")) {
- cfg->h2_upgrade = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_UPGRADE, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
-static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_tls_warmup_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->tls_warmup_size = apr_atoi64(value);
- (void)arg;
+ apr_int64_t val = apr_atoi64(value);
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_TLS_WARMUP_SIZE, val);
return NULL;
}
-static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- cfg->tls_cooldown_secs = (int)apr_atoi64(value);
- (void)arg;
+ apr_int64_t val = (int)apr_atoi64(value);
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_TLS_COOLDOWN_SECS, val);
return NULL;
}
-static const char *h2_conf_set_push_diary_size(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_push_diary_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- (void)arg;
- cfg->push_diary_size = (int)apr_atoi64(value);
- if (cfg->push_diary_size < 0) {
+ int val = (int)apr_atoi64(value);
+ if (val < 0) {
return "value must be >= 0";
}
- if (cfg->push_diary_size > 0 && (cfg->push_diary_size & (cfg->push_diary_size-1))) {
+ if (val > 0 && (val & (val-1))) {
return "value must a power of 2";
}
- if (cfg->push_diary_size > (1 << 15)) {
+ if (val > (1 << 15)) {
return "value must <= 65536";
}
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PUSH_DIARY_SIZE, val);
return NULL;
}
-static const char *h2_conf_set_copy_files(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_copy_files(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)arg;
if (!strcasecmp(value, "On")) {
- cfg->copy_files = 1;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_COPY_FILES, 1);
return NULL;
}
else if (!strcasecmp(value, "Off")) {
- cfg->copy_files = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_COPY_FILES, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
-static void add_push(apr_pool_t *pool, h2_config *conf, h2_push_res *push)
+static void add_push(apr_array_header_t **plist, apr_pool_t *pool, h2_push_res *push)
{
h2_push_res *new;
- if (!conf->push_list) {
- conf->push_list = apr_array_make(pool, 10, sizeof(*push));
+ if (!*plist) {
+ *plist = apr_array_make(pool, 10, sizeof(*push));
}
- new = apr_array_push(conf->push_list);
+ new = apr_array_push(*plist);
new->uri_ref = push->uri_ref;
new->critical = push->critical;
}
@@ -549,8 +866,6 @@ static const char *h2_conf_add_push_res(cmd_parms *cmd, void *dirconf,
const char *arg1, const char *arg2,
const char *arg3)
{
- h2_config *dconf = (h2_config*)dirconf ;
- h2_config *sconf = (h2_config*)h2_config_sget(cmd->server);
h2_push_res push;
const char *last = arg3;
@@ -575,57 +890,136 @@ static const char *h2_conf_add_push_res(cmd_parms *cmd, void *dirconf,
}
}
- /* server command? set both */
- if (cmd->path == NULL) {
- add_push(cmd->pool, sconf, &push);
- add_push(cmd->pool, dconf, &push);
+ if (cmd->path) {
+ add_push(&(((h2_dir_config*)dirconf)->push_list), cmd->pool, &push);
}
else {
- add_push(cmd->pool, dconf, &push);
+ add_push(&(h2_config_sget(cmd->server)->push_list), cmd->pool, &push);
}
+ return NULL;
+}
+
+static const char *h2_conf_add_early_hint(cmd_parms *cmd, void *dirconf,
+ const char *name, const char *value)
+{
+ apr_table_t *hds, **phds;
+
+ if(!name || !*name)
+ return "Early Hint header name must not be empty";
+ if(!value)
+ return "Early Hint header value must not be empty";
+ while (apr_isspace(*value))
+ ++value;
+ if(!*value)
+ return "Early Hint header value must not be empty/only space";
+ if (*ap_scan_http_field_content(value))
+ return "Early Hint header value contains invalid characters";
+
+ if (cmd->path) {
+ phds = &((h2_dir_config*)dirconf)->early_headers;
+ }
+ else {
+ phds = &(h2_config_sget(cmd->server))->early_headers;
+ }
+ hds = *phds;
+ if (!hds) {
+ *phds = hds = apr_table_make(cmd->pool, 10);
+ }
+ apr_table_add(hds, name, value);
return NULL;
}
-static const char *h2_conf_set_early_hints(cmd_parms *parms,
- void *arg, const char *value)
+static const char *h2_conf_set_early_hints(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val;
+
+ if (!strcasecmp(value, "On")) val = 1;
+ else if (!strcasecmp(value, "Off")) val = 0;
+ else return "value must be On or Off";
+
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_EARLY_HINTS, val);
+ if (cmd->path) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool,
+ "H2EarlyHints = %d on path %s", val, cmd->path);
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_padding(cmd_parms *cmd, void *dirconf, const char *value)
+{
+ int val;
+
+ val = (int)apr_atoi64(value);
+ if (val < 0) {
+ return "number of bits must be >= 0";
+ }
+ if (val > 8) {
+ return "number of bits must be <= 8";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PADDING_BITS, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_output_buffer(cmd_parms *cmd,
+ void *dirconf, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
- cfg->early_hints = 1;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_OUTPUT_BUFFER, 1);
return NULL;
}
else if (!strcasecmp(value, "Off")) {
- cfg->early_hints = 0;
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_OUTPUT_BUFFER, 0);
return NULL;
}
-
- (void)arg;
return "value must be On or Off";
}
-void h2_get_num_workers(server_rec *s, int *minw, int *maxw)
+static const char *h2_conf_set_stream_timeout(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ apr_status_t rv;
+ apr_interval_time_t timeout;
+
+ rv = ap_timeout_parameter_parse(value, &timeout, "s");
+ if (rv != APR_SUCCESS) {
+ return "Invalid timeout value";
+ }
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_STREAM_TIMEOUT, timeout);
+ return NULL;
+}
+
+static const char *h2_conf_set_proxy_requests(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
+ apr_time_t *pidle_limit)
{
int threads_per_child = 0;
- const h2_config *config = h2_config_sget(s);
- *minw = h2_config_geti(config, H2_CONF_MIN_WORKERS);
- *maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS);
- ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads_per_child);
+ *pminw = h2_config_sgeti(s, H2_CONF_MIN_WORKERS);
+ *pmaxw = h2_config_sgeti(s, H2_CONF_MAX_WORKERS);
- if (*minw <= 0) {
- *minw = threads_per_child;
- }
- if (*maxw <= 0) {
- /* As a default, this seems to work quite well under mpm_event.
- * For people enabling http2 under mpm_prefork, start 4 threads unless
- * configured otherwise. People get unhappy if their http2 requests are
- * blocking each other. */
- *maxw = 3 * (*minw) / 2;
- if (*maxw < 4) {
- *maxw = 4;
- }
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads_per_child);
+ if (*pminw <= 0) {
+ *pminw = threads_per_child;
+ }
+ if (*pmaxw <= 0) {
+ *pmaxw = H2MAX(4, 3 * (*pminw) / 2);
}
+ *pidle_limit = h2_config_sgeti64(s, H2_CONF_MAX_WORKER_IDLE_LIMIT);
}
#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
@@ -639,20 +1033,16 @@ const command_rec h2_cmds[] = {
RSRC_CONF, "minimum number of worker threads per child"),
AP_INIT_TAKE1("H2MaxWorkers", h2_conf_set_max_workers, NULL,
RSRC_CONF, "maximum number of worker threads per child"),
- AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_secs, NULL,
+ AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_limit, NULL,
RSRC_CONF, "maximum number of idle seconds before a worker shuts down"),
AP_INIT_TAKE1("H2StreamMaxMemSize", h2_conf_set_stream_max_mem_size, NULL,
RSRC_CONF, "maximum number of bytes buffered in memory for a stream"),
- AP_INIT_TAKE1("H2AltSvc", h2_add_alt_svc, NULL,
- RSRC_CONF, "adds an Alt-Svc for this server"),
- AP_INIT_TAKE1("H2AltSvcMaxAge", h2_conf_set_alt_svc_max_age, NULL,
- RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
- RSRC_CONF, "on to enable header serialization for compatibility"),
+ RSRC_CONF, "disabled, this directive has no longer an effect."),
AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
- RSRC_CONF, "on to allow HTTP/1 Upgrades to h2/h2c"),
+ RSRC_CONF|OR_AUTHCFG, "on to allow HTTP/1 Upgrades to h2/h2c"),
AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
RSRC_CONF, "on to enable direct HTTP/2 mode"),
AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
@@ -662,7 +1052,7 @@ const command_rec h2_cmds[] = {
AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
AP_INIT_TAKE1("H2Push", h2_conf_set_push, NULL,
- RSRC_CONF, "off to disable HTTP/2 server push"),
+ RSRC_CONF|OR_AUTHCFG, "off to disable HTTP/2 server push"),
AP_INIT_TAKE23("H2PushPriority", h2_conf_add_push_priority, NULL,
RSRC_CONF, "define priority of PUSHed resources per content type"),
AP_INIT_TAKE1("H2PushDiarySize", h2_conf_set_push_diary_size, NULL,
@@ -670,33 +1060,24 @@ const command_rec h2_cmds[] = {
AP_INIT_TAKE1("H2CopyFiles", h2_conf_set_copy_files, NULL,
OR_FILEINFO, "on to perform copy of file data"),
AP_INIT_TAKE123("H2PushResource", h2_conf_add_push_res, NULL,
- OR_FILEINFO, "add a resource to be pushed in this location/on this server."),
+ OR_FILEINFO|OR_AUTHCFG, "add a resource to be pushed in this location/on this server."),
AP_INIT_TAKE1("H2EarlyHints", h2_conf_set_early_hints, NULL,
RSRC_CONF, "on to enable interim status 103 responses"),
+ AP_INIT_TAKE1("H2Padding", h2_conf_set_padding, NULL,
+ RSRC_CONF, "set payload padding"),
+ AP_INIT_TAKE1("H2OutputBuffering", h2_conf_set_output_buffer, NULL,
+ RSRC_CONF, "set stream output buffer on/off"),
+ AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL,
+ RSRC_CONF, "set stream timeout"),
+ AP_INIT_TAKE1("H2MaxDataFrameLen", h2_conf_set_max_data_frame_len, NULL,
+ RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
+ AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
+ OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
+ AP_INIT_TAKE1("H2ProxyRequests", h2_conf_set_proxy_requests, NULL,
+ OR_FILEINFO, "Enables forward proxy requests via HTTP/2"),
+ AP_INIT_TAKE1("H2WebSockets", h2_conf_set_websockets, NULL,
+ RSRC_CONF, "off to disable WebSockets over HTTP/2"),
AP_END_CMD
};
-const h2_config *h2_config_rget(request_rec *r)
-{
- h2_config *cfg = (h2_config *)ap_get_module_config(r->per_dir_config,
- &http2_module);
- return cfg? cfg : h2_config_sget(r->server);
-}
-
-const h2_config *h2_config_get(conn_rec *c)
-{
- h2_ctx *ctx = h2_ctx_get(c, 0);
-
- if (ctx) {
- if (ctx->config) {
- return ctx->config;
- }
- else if (ctx->server) {
- ctx->config = h2_config_sget(ctx->server);
- return ctx->config;
- }
- }
-
- return h2_config_sget(c->base_server);
-}
diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h
index 17d75d6..15242db 100644
--- a/modules/http2/h2_config.h
+++ b/modules/http2/h2_config.h
@@ -28,11 +28,8 @@ typedef enum {
H2_CONF_WIN_SIZE,
H2_CONF_MIN_WORKERS,
H2_CONF_MAX_WORKERS,
- H2_CONF_MAX_WORKER_IDLE_SECS,
+ H2_CONF_MAX_WORKER_IDLE_LIMIT,
H2_CONF_STREAM_MAX_MEM,
- H2_CONF_ALT_SVCS,
- H2_CONF_ALT_SVC_MAX_AGE,
- H2_CONF_SER_HEADERS,
H2_CONF_DIRECT,
H2_CONF_MODERN_TLS_ONLY,
H2_CONF_UPGRADE,
@@ -42,6 +39,13 @@ typedef enum {
H2_CONF_PUSH_DIARY_SIZE,
H2_CONF_COPY_FILES,
H2_CONF_EARLY_HINTS,
+ H2_CONF_PADDING_BITS,
+ H2_CONF_PADDING_ALWAYS,
+ H2_CONF_OUTPUT_BUFFER,
+ H2_CONF_STREAM_TIMEOUT,
+ H2_CONF_MAX_DATA_FRAME_LEN,
+ H2_CONF_PROXY_REQUESTS,
+ H2_CONF_WEBSOCKETS,
} h2_config_var_t;
struct apr_hash_t;
@@ -53,33 +57,6 @@ typedef struct h2_push_res {
int critical;
} h2_push_res;
-/* Apache httpd module configuration for h2. */
-typedef struct h2_config {
- const char *name;
- int h2_max_streams; /* max concurrent # streams (http2) */
- int h2_window_size; /* stream window size (http2) */
- int min_workers; /* min # of worker threads/child */
- int max_workers; /* max # of worker threads/child */
- int max_worker_idle_secs; /* max # of idle seconds for worker */
- int stream_max_mem_size; /* max # bytes held in memory/stream */
- apr_array_header_t *alt_svcs; /* h2_alt_svc specs for this server */
- int alt_svc_max_age; /* seconds clients can rely on alt-svc info*/
- int serialize_headers; /* Use serialized HTTP/1.1 headers for
- processing, better compatibility */
- int h2_direct; /* if mod_h2 is active directly */
- int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
- int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
- apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
- int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */
- int h2_push; /* if HTTP/2 server push is enabled */
- struct apr_hash_t *priorities;/* map of content-type to h2_priority records */
-
- int push_diary_size; /* # of entries in push diary */
- int copy_files; /* if files shall be copied vs setaside on output */
- apr_array_header_t *push_list;/* list of h2_push_res configurations */
- int early_hints; /* support status code 103 */
-} h2_config;
-
void *h2_config_create_dir(apr_pool_t *pool, char *x);
void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv);
@@ -88,19 +65,38 @@ void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
extern const command_rec h2_cmds[];
-const h2_config *h2_config_get(conn_rec *c);
-const h2_config *h2_config_sget(server_rec *s);
-const h2_config *h2_config_rget(request_rec *r);
+int h2_config_geti(request_rec *r, server_rec *s, h2_config_var_t var);
+apr_int64_t h2_config_geti64(request_rec *r, server_rec *s, h2_config_var_t var);
+
+/**
+ * Get the configured value for variable at the given connection.
+ */
+int h2_config_cgeti(conn_rec *c, h2_config_var_t var);
+apr_int64_t h2_config_cgeti64(conn_rec *c, h2_config_var_t var);
+
+/**
+ * Get the configured value for variable at the given server.
+ */
+int h2_config_sgeti(server_rec *s, h2_config_var_t var);
+apr_int64_t h2_config_sgeti64(server_rec *s, h2_config_var_t var);
+
+/**
+ * Get the configured value for variable at the given request,
+ * if configured for the request location.
+ * Fallback to request server config otherwise.
+ */
+int h2_config_rgeti(request_rec *r, h2_config_var_t var);
+apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var);
-int h2_config_geti(const h2_config *conf, h2_config_var_t var);
-apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var);
+apr_array_header_t *h2_config_push_list(request_rec *r);
+apr_table_t *h2_config_early_headers(request_rec *r);
-void h2_get_num_workers(server_rec *s, int *minw, int *maxw);
+void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
+ apr_time_t *pidle_limit);
void h2_config_init(apr_pool_t *pool);
-const struct h2_priority *h2_config_get_priority(const h2_config *conf,
- const char *content_type);
+const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type);
#endif /* __mod_h2__h2_config_h__ */
diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c
deleted file mode 100644
index 88da2ba..0000000
--- a/modules/http2/h2_conn.c
+++ /dev/null
@@ -1,370 +0,0 @@
-/* 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
-#include
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_filter.h"
-#include "h2_mplx.h"
-#include "h2_session.h"
-#include "h2_stream.h"
-#include "h2_h2.h"
-#include "h2_task.h"
-#include "h2_workers.h"
-#include "h2_conn.h"
-#include "h2_version.h"
-
-static struct h2_workers *workers;
-
-static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
-static module *mpm_module;
-static int async_mpm;
-static int mpm_supported = 1;
-static apr_socket_t *dummy_socket;
-
-static void check_modules(int force)
-{
- static int checked = 0;
- int i;
-
- if (force || !checked) {
- for (i = 0; ap_loaded_modules[i]; ++i) {
- module *m = ap_loaded_modules[i];
-
- if (!strcmp("event.c", m->name)) {
- mpm_type = H2_MPM_EVENT;
- mpm_module = m;
- break;
- }
- else if (!strcmp("motorz.c", m->name)) {
- mpm_type = H2_MPM_MOTORZ;
- mpm_module = m;
- break;
- }
- else if (!strcmp("mpm_netware.c", m->name)) {
- mpm_type = H2_MPM_NETWARE;
- mpm_module = m;
- break;
- }
- else if (!strcmp("prefork.c", m->name)) {
- mpm_type = H2_MPM_PREFORK;
- mpm_module = m;
- /* While http2 can work really well on prefork, it collides
- * today's use case for prefork: runnning single-thread app engines
- * like php. If we restrict h2_workers to 1 per process, php will
- * work fine, but browser will be limited to 1 active request at a
- * time. */
- mpm_supported = 0;
- break;
- }
- else if (!strcmp("simple_api.c", m->name)) {
- mpm_type = H2_MPM_SIMPLE;
- mpm_module = m;
- mpm_supported = 0;
- break;
- }
- else if (!strcmp("mpm_winnt.c", m->name)) {
- mpm_type = H2_MPM_WINNT;
- mpm_module = m;
- break;
- }
- else if (!strcmp("worker.c", m->name)) {
- mpm_type = H2_MPM_WORKER;
- mpm_module = m;
- break;
- }
- }
- checked = 1;
- }
-}
-
-apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s)
-{
- const h2_config *config = h2_config_sget(s);
- apr_status_t status = APR_SUCCESS;
- int minw, maxw;
- int max_threads_per_child = 0;
- int idle_secs = 0;
-
- check_modules(1);
- ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
-
- status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
- if (status != APR_SUCCESS) {
- /* some MPMs do not implemnent this */
- async_mpm = 0;
- status = APR_SUCCESS;
- }
-
- h2_config_init(pool);
-
- h2_get_num_workers(s, &minw, &maxw);
-
- idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS);
- ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
- "h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d",
- minw, maxw, max_threads_per_child, idle_secs);
- workers = h2_workers_create(s, pool, minw, maxw, idle_secs);
-
- ap_register_input_filter("H2_IN", h2_filter_core_input,
- NULL, AP_FTYPE_CONNECTION);
-
- status = h2_mplx_child_init(pool, s);
-
- if (status == APR_SUCCESS) {
- status = apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
- APR_PROTO_TCP, pool);
- }
-
- return status;
-}
-
-h2_mpm_type_t h2_conn_mpm_type(void)
-{
- check_modules(0);
- return mpm_type;
-}
-
-const char *h2_conn_mpm_name(void)
-{
- check_modules(0);
- return mpm_module? mpm_module->name : "unknown";
-}
-
-int h2_mpm_supported(void)
-{
- check_modules(0);
- return mpm_supported;
-}
-
-static module *h2_conn_mpm_module(void)
-{
- check_modules(0);
- return mpm_module;
-}
-
-apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r)
-{
- h2_session *session;
- apr_status_t status;
-
- if (!workers) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911)
- "workers not initialized");
- return APR_EGENERAL;
- }
-
- if (r) {
- status = h2_session_rcreate(&session, r, ctx, workers);
- }
- else {
- status = h2_session_create(&session, c, ctx, workers);
- }
-
- if (status == APR_SUCCESS) {
- h2_ctx_session_set(ctx, session);
- }
- return status;
-}
-
-apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c)
-{
- apr_status_t status;
- int mpm_state = 0;
- h2_session *session = h2_ctx_session_get(ctx);
-
- ap_assert(session);
- do {
- if (c->cs) {
- c->cs->sense = CONN_SENSE_DEFAULT;
- c->cs->state = CONN_STATE_HANDLER;
- }
-
- status = h2_session_process(session, async_mpm);
-
- if (APR_STATUS_IS_EOF(status)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
- H2_SSSN_LOG(APLOGNO(03045), session,
- "process, closing conn"));
- c->keepalive = AP_CONN_CLOSE;
- }
- else {
- c->keepalive = AP_CONN_KEEPALIVE;
- }
-
- if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
- break;
- }
- } while (!async_mpm
- && c->keepalive == AP_CONN_KEEPALIVE
- && mpm_state != AP_MPMQ_STOPPING);
-
- if (c->cs) {
- switch (session->state) {
- case H2_SESSION_ST_INIT:
- case H2_SESSION_ST_IDLE:
- case H2_SESSION_ST_BUSY:
- case H2_SESSION_ST_WAIT:
- c->cs->state = CONN_STATE_WRITE_COMPLETION;
- break;
- case H2_SESSION_ST_CLEANUP:
- case H2_SESSION_ST_DONE:
- default:
- c->cs->state = CONN_STATE_LINGER;
- break;
- }
- }
-
- return APR_SUCCESS;
-}
-
-apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c)
-{
- h2_session *session = h2_ctx_session_get(ctx);
- if (session) {
- apr_status_t status = h2_session_pre_close(session, async_mpm);
- return (status == APR_SUCCESS)? DONE : status;
- }
- return DONE;
-}
-
-conn_rec *h2_slave_create(conn_rec *master, int slave_id, apr_pool_t *parent)
-{
- apr_allocator_t *allocator;
- apr_status_t status;
- apr_pool_t *pool;
- conn_rec *c;
- void *cfg;
- module *mpm;
-
- ap_assert(master);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, master,
- "h2_stream(%ld-%d): create slave", master->id, slave_id);
-
- /* We create a pool with its own allocator to be used for
- * processing a request. This is the only way to have the processing
- * independant of its parent pool in the sense that it can work in
- * another thread. Also, the new allocator needs its own mutex to
- * synchronize sub-pools.
- */
- apr_allocator_create(&allocator);
- apr_allocator_max_free_set(allocator, ap_max_mem_free);
- status = apr_pool_create_ex(&pool, parent, NULL, allocator);
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, master,
- APLOGNO(10004) "h2_session(%ld-%d): create slave pool",
- master->id, slave_id);
- return NULL;
- }
- apr_allocator_owner_set(allocator, pool);
- apr_pool_tag(pool, "h2_slave_conn");
-
- c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
- if (c == NULL) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, master,
- APLOGNO(02913) "h2_session(%ld-%d): create slave",
- master->id, slave_id);
- apr_pool_destroy(pool);
- return NULL;
- }
-
- memcpy(c, master, sizeof(conn_rec));
-
- c->master = master;
- c->pool = pool;
- c->conn_config = ap_create_conn_config(pool);
- c->notes = apr_table_make(pool, 5);
- c->input_filters = NULL;
- c->output_filters = NULL;
- c->bucket_alloc = apr_bucket_alloc_create(pool);
- c->data_in_input_filters = 0;
- c->data_in_output_filters = 0;
- /* prevent mpm_event from making wrong assumptions about this connection,
- * like e.g. using its socket for an async read check. */
- c->clogging_input_filters = 1;
- c->log = NULL;
- c->log_id = apr_psprintf(pool, "%ld-%d",
- master->id, slave_id);
- c->aborted = 0;
- /* We cannot install the master connection socket on the slaves, as
- * modules mess with timeouts/blocking of the socket, with
- * unwanted side effects to the master connection processing.
- * Fortunately, since we never use the slave socket, we can just install
- * a single, process-wide dummy and everyone is happy.
- */
- ap_set_module_config(c->conn_config, &core_module, dummy_socket);
- /* TODO: these should be unique to this thread */
- c->sbh = master->sbh;
- /* TODO: not all mpm modules have learned about slave connections yet.
- * copy their config from master to slave.
- */
- if ((mpm = h2_conn_mpm_module()) != NULL) {
- cfg = ap_get_module_config(master->conn_config, mpm);
- ap_set_module_config(c->conn_config, mpm, cfg);
- }
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
- "h2_stream(%ld-%d): created slave", master->id, slave_id);
- return c;
-}
-
-void h2_slave_destroy(conn_rec *slave)
-{
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, slave,
- "h2_stream(%s): destroy slave",
- apr_table_get(slave->notes, H2_TASK_ID_NOTE));
- slave->sbh = NULL;
- apr_pool_destroy(slave->pool);
-}
-
-apr_status_t h2_slave_run_pre_connection(conn_rec *slave, apr_socket_t *csd)
-{
- if (slave->keepalives == 0) {
- /* Simulate that we had already a request on this connection. Some
- * hooks trigger special behaviour when keepalives is 0.
- * (Not necessarily in pre_connection, but later. Set it here, so it
- * is in place.) */
- slave->keepalives = 1;
- /* We signal that this connection will be closed after the request.
- * Which is true in that sense that we throw away all traffic data
- * on this slave connection after each requests. Although we might
- * reuse internal structures like memory pools.
- * The wanted effect of this is that httpd does not try to clean up
- * any dangling data on this connection when a request is done. Which
- * is unneccessary on a h2 stream.
- */
- slave->keepalive = AP_CONN_CLOSE;
- return ap_run_pre_connection(slave, csd);
- }
- return APR_SUCCESS;
-}
-
diff --git a/modules/http2/h2_conn.h b/modules/http2/h2_conn.h
deleted file mode 100644
index e45ff31..0000000
--- a/modules/http2/h2_conn.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_conn__
-#define __mod_h2__h2_conn__
-
-struct h2_ctx;
-struct h2_task;
-
-/**
- * Setup the connection and our context for HTTP/2 processing
- *
- * @param ctx the http2 context to setup
- * @param c the connection HTTP/2 is starting on
- * @param r the upgrade request that still awaits an answer, optional
- */
-apr_status_t h2_conn_setup(struct h2_ctx *ctx, conn_rec *c, request_rec *r);
-
-/**
- * Run the HTTP/2 connection in synchronous fashion.
- * Return when the HTTP/2 session is done
- * and the connection will close or a fatal error occurred.
- *
- * @param ctx the http2 context to run
- * @return APR_SUCCESS when session is done.
- */
-apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c);
-
-/**
- * The connection is about to close. If we have not send a GOAWAY
- * yet, this is the last chance.
- */
-apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c);
-
-/* Initialize this child process for h2 connection work,
- * to be called once during child init before multi processing
- * starts.
- */
-apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s);
-
-
-typedef enum {
- H2_MPM_UNKNOWN,
- H2_MPM_WORKER,
- H2_MPM_EVENT,
- H2_MPM_PREFORK,
- H2_MPM_MOTORZ,
- H2_MPM_SIMPLE,
- H2_MPM_NETWARE,
- H2_MPM_WINNT,
-} h2_mpm_type_t;
-
-/* Returns the type of MPM module detected */
-h2_mpm_type_t h2_conn_mpm_type(void);
-const char *h2_conn_mpm_name(void);
-int h2_mpm_supported(void);
-
-conn_rec *h2_slave_create(conn_rec *master, int slave_id, apr_pool_t *parent);
-void h2_slave_destroy(conn_rec *slave);
-
-apr_status_t h2_slave_run_pre_connection(conn_rec *slave, apr_socket_t *csd);
-void h2_slave_run_connection(conn_rec *slave);
-
-#endif /* defined(__mod_h2__h2_conn__) */
diff --git a/modules/http2/h2_conn_ctx.c b/modules/http2/h2_conn_ctx.c
new file mode 100644
index 0000000..b8a0fb3
--- /dev/null
+++ b/modules/http2/h2_conn_ctx.c
@@ -0,0 +1,123 @@
+/* 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
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "h2_private.h"
+#include "h2_session.h"
+#include "h2_bucket_beam.h"
+#include "h2_c2.h"
+#include "h2_mplx.h"
+#include "h2_stream.h"
+#include "h2_util.h"
+#include "h2_conn_ctx.h"
+
+
+void h2_conn_ctx_detach(conn_rec *c)
+{
+ ap_set_module_config(c->conn_config, &http2_module, NULL);
+}
+
+static h2_conn_ctx_t *ctx_create(conn_rec *c, const char *id)
+{
+ h2_conn_ctx_t *conn_ctx = apr_pcalloc(c->pool, sizeof(*conn_ctx));
+ conn_ctx->id = id;
+ conn_ctx->server = c->base_server;
+ apr_atomic_set32(&conn_ctx->started, 1);
+ conn_ctx->started_at = apr_time_now();
+
+ ap_set_module_config(c->conn_config, &http2_module, conn_ctx);
+ return conn_ctx;
+}
+
+h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c1, server_rec *s, const char *protocol)
+{
+ h2_conn_ctx_t *ctx;
+
+ ctx = ctx_create(c1, apr_psprintf(c1->pool, "%ld", c1->id));
+ ctx->server = s;
+ ctx->protocol = apr_pstrdup(c1->pool, protocol);
+
+ ctx->pfd.desc_type = APR_POLL_SOCKET;
+ ctx->pfd.desc.s = ap_get_conn_socket(c1);
+ ctx->pfd.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
+ ctx->pfd.client_data = ctx;
+ apr_socket_opt_set(ctx->pfd.desc.s, APR_SO_NONBLOCK, 1);
+
+ return ctx;
+}
+
+void h2_conn_ctx_assign_session(h2_conn_ctx_t *ctx, struct h2_session *session)
+{
+ ctx->session = session;
+ ctx->id = apr_psprintf(session->pool, "%d-%lu", session->child_num, (unsigned long)session->id);
+}
+
+apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c2,
+ struct h2_mplx *mplx, struct h2_stream *stream,
+ struct h2_c2_transit *transit)
+{
+ h2_conn_ctx_t *conn_ctx;
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_assert(c2->master);
+ conn_ctx = h2_conn_ctx_get(c2);
+ if (!conn_ctx) {
+ h2_conn_ctx_t *c1_ctx;
+
+ c1_ctx = h2_conn_ctx_get(c2->master);
+ ap_assert(c1_ctx);
+ ap_assert(c1_ctx->session);
+
+ conn_ctx = ctx_create(c2, c1_ctx->id);
+ conn_ctx->server = c2->master->base_server;
+ }
+
+ conn_ctx->mplx = mplx;
+ conn_ctx->transit = transit;
+ conn_ctx->stream_id = stream->id;
+ apr_pool_create(&conn_ctx->req_pool, c2->pool);
+ apr_pool_tag(conn_ctx->req_pool, "H2_C2_REQ");
+ conn_ctx->request = stream->request;
+ apr_atomic_set32(&conn_ctx->started, 1);
+ conn_ctx->started_at = apr_time_now();
+ conn_ctx->done = 0;
+ conn_ctx->done_at = 0;
+
+ *pctx = conn_ctx;
+ return rv;
+}
+
+void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout)
+{
+ if (conn_ctx->beam_out) {
+ h2_beam_timeout_set(conn_ctx->beam_out, timeout);
+ }
+ if (conn_ctx->beam_in) {
+ h2_beam_timeout_set(conn_ctx->beam_in, timeout);
+ }
+ if (conn_ctx->pipe_in[H2_PIPE_OUT]) {
+ apr_file_pipe_timeout_set(conn_ctx->pipe_in[H2_PIPE_OUT], timeout);
+ }
+}
diff --git a/modules/http2/h2_conn_ctx.h b/modules/http2/h2_conn_ctx.h
new file mode 100644
index 0000000..3b44856
--- /dev/null
+++ b/modules/http2/h2_conn_ctx.h
@@ -0,0 +1,100 @@
+/* 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.
+ */
+
+#ifndef __mod_h2__h2_conn_ctx__
+#define __mod_h2__h2_conn_ctx__
+
+#include "h2.h"
+
+struct h2_session;
+struct h2_stream;
+struct h2_mplx;
+struct h2_bucket_beam;
+struct h2_response_parser;
+struct h2_c2_transit;
+
+#define H2_PIPE_OUT 0
+#define H2_PIPE_IN 1
+
+/**
+ * The h2 module context associated with a connection.
+ *
+ * It keeps track of the different types of connections:
+ * - those from clients that use HTTP/2 protocol
+ * - those from clients that do not use HTTP/2
+ * - those created by ourself to perform work on HTTP/2 streams
+ */
+struct h2_conn_ctx_t {
+ const char *id; /* c*: our identifier of this connection */
+ server_rec *server; /* c*: httpd server selected. */
+ const char *protocol; /* c1: the protocol negotiated */
+ struct h2_session *session; /* c1: the h2 session established */
+ struct h2_mplx *mplx; /* c2: the multiplexer */
+ struct h2_c2_transit *transit; /* c2: transit pool and bucket_alloc */
+
+#if !AP_HAS_RESPONSE_BUCKETS
+ int pre_conn_done; /* has pre_connection setup run? */
+#endif
+ int stream_id; /* c1: 0, c2: stream id processed */
+ apr_pool_t *req_pool; /* c2: a c2 child pool for a request */
+ const struct h2_request *request; /* c2: the request to process */
+ struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
+ struct h2_bucket_beam *beam_in; /* c2: data in or NULL, borrowed from request stream */
+ unsigned input_chunked:1; /* c2: if input needs HTTP/1.1 chunking applied */
+ unsigned is_upgrade:1; /* c2: if requst is a HTTP Upgrade */
+
+ apr_file_t *pipe_in[2]; /* c2: input produced notification pipe */
+ apr_pollfd_t pfd; /* c1: poll socket input, c2: NUL */
+
+ int has_final_response; /* final HTTP response passed on out */
+ apr_status_t last_err; /* APR_SUCCES or last error encountered in filters */
+
+ apr_off_t bytes_sent; /* c2: bytes acutaly sent via c1 */
+ /* atomic */ apr_uint32_t started; /* c2: processing was started */
+ apr_time_t started_at; /* c2: when processing started */
+ /* atomic */ apr_uint32_t done; /* c2: processing has finished */
+ apr_time_t done_at; /* c2: when processing was done */
+};
+typedef struct h2_conn_ctx_t h2_conn_ctx_t;
+
+/**
+ * Get the h2 connection context.
+ * @param c the connection to look at
+ * @return h2 context of this connection
+ */
+#define h2_conn_ctx_get(c) \
+ ((c)? (h2_conn_ctx_t*)ap_get_module_config((c)->conn_config, &http2_module) : NULL)
+
+/**
+ * Create the h2 connection context.
+ * @param c the connection to create it at
+ * @param s the server in use
+ * @param protocol the protocol selected
+ * @return created h2 context of this connection
+ */
+h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c, server_rec *s, const char *protocol);
+
+void h2_conn_ctx_assign_session(h2_conn_ctx_t *ctx, struct h2_session *session);
+
+apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c,
+ struct h2_mplx *mplx, struct h2_stream *stream,
+ struct h2_c2_transit *transit);
+
+void h2_conn_ctx_detach(conn_rec *c);
+
+void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout);
+
+#endif /* defined(__mod_h2__h2_conn_ctx__) */
diff --git a/modules/http2/h2_conn_io.c b/modules/http2/h2_conn_io.c
deleted file mode 100644
index eb6ec92..0000000
--- a/modules/http2/h2_conn_io.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/* 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
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-#include "h2_private.h"
-#include "h2_bucket_eos.h"
-#include "h2_config.h"
-#include "h2_conn_io.h"
-#include "h2_h2.h"
-#include "h2_session.h"
-#include "h2_util.h"
-
-#define TLS_DATA_MAX (16*1024)
-
-/* Calculated like this: assuming MTU 1500 bytes
- * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options)
- * - TLS overhead (60-100)
- * ~= 1300 bytes */
-#define WRITE_SIZE_INITIAL 1300
-
-/* Calculated like this: max TLS record size 16*1024
- * - 40 (IP) - 20 (TCP) - 40 (TCP options)
- * - TLS overhead (60-100)
- * which seems to create less TCP packets overall
- */
-#define WRITE_SIZE_MAX (TLS_DATA_MAX - 100)
-
-
-static void h2_conn_io_bb_log(conn_rec *c, int stream_id, int level,
- const char *tag, apr_bucket_brigade *bb)
-{
- char buffer[16 * 1024];
- const char *line = "(null)";
- apr_size_t bmax = sizeof(buffer)/sizeof(buffer[0]);
- int off = 0;
- apr_bucket *b;
-
- if (bb) {
- memset(buffer, 0, bmax--);
- for (b = APR_BRIGADE_FIRST(bb);
- bmax && (b != APR_BRIGADE_SENTINEL(bb));
- b = APR_BUCKET_NEXT(b)) {
-
- if (APR_BUCKET_IS_METADATA(b)) {
- if (APR_BUCKET_IS_EOS(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "eos ");
- }
- else if (APR_BUCKET_IS_FLUSH(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "flush ");
- }
- else if (AP_BUCKET_IS_EOR(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "eor ");
- }
- else if (H2_BUCKET_IS_H2EOS(b)) {
- off += apr_snprintf(buffer+off, bmax-off, "h2eos ");
- }
- else {
- off += apr_snprintf(buffer+off, bmax-off, "meta(unknown) ");
- }
- }
- else {
- const char *btype = "data";
- if (APR_BUCKET_IS_FILE(b)) {
- btype = "file";
- }
- else if (APR_BUCKET_IS_PIPE(b)) {
- btype = "pipe";
- }
- else if (APR_BUCKET_IS_SOCKET(b)) {
- btype = "socket";
- }
- else if (APR_BUCKET_IS_HEAP(b)) {
- btype = "heap";
- }
- else if (APR_BUCKET_IS_TRANSIENT(b)) {
- btype = "transient";
- }
- else if (APR_BUCKET_IS_IMMORTAL(b)) {
- btype = "immortal";
- }
-#if APR_HAS_MMAP
- else if (APR_BUCKET_IS_MMAP(b)) {
- btype = "mmap";
- }
-#endif
- else if (APR_BUCKET_IS_POOL(b)) {
- btype = "pool";
- }
-
- off += apr_snprintf(buffer+off, bmax-off, "%s[%ld] ",
- btype,
- (long)(b->length == ((apr_size_t)-1)?
- -1 : b->length));
- }
- }
- line = *buffer? buffer : "(empty)";
- }
- /* Intentional no APLOGNO */
- ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s",
- c->id, tag, line);
-
-}
-
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c,
- const h2_config *cfg)
-{
- io->c = c;
- io->output = apr_brigade_create(c->pool, c->bucket_alloc);
- io->is_tls = h2_h2_is_tls(c);
- io->buffer_output = io->is_tls;
- io->flush_threshold = (apr_size_t)h2_config_geti64(cfg, H2_CONF_STREAM_MAX_MEM);
-
- if (io->is_tls) {
- /* This is what we start with,
- * see https://issues.apache.org/jira/browse/TS-2503
- */
- io->warmup_size = h2_config_geti64(cfg, H2_CONF_TLS_WARMUP_SIZE);
- io->cooldown_usecs = (h2_config_geti(cfg, H2_CONF_TLS_COOLDOWN_SECS)
- * APR_USEC_PER_SEC);
- io->write_size = (io->cooldown_usecs > 0?
- WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
- }
- else {
- io->warmup_size = 0;
- io->cooldown_usecs = 0;
- io->write_size = 0;
- }
-
- if (APLOGctrace1(c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
- "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, "
- "cd_secs=%f", io->c->id, io->buffer_output,
- (long)io->warmup_size,
- ((float)io->cooldown_usecs/APR_USEC_PER_SEC));
- }
-
- return APR_SUCCESS;
-}
-
-static void append_scratch(h2_conn_io *io)
-{
- if (io->scratch && io->slen > 0) {
- apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
- apr_bucket_free,
- io->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
- io->scratch = NULL;
- io->slen = io->ssize = 0;
- }
-}
-
-static apr_size_t assure_scratch_space(h2_conn_io *io) {
- apr_size_t remain = io->ssize - io->slen;
- if (io->scratch && remain == 0) {
- append_scratch(io);
- }
- if (!io->scratch) {
- /* we control the size and it is larger than what buckets usually
- * allocate. */
- io->scratch = apr_bucket_alloc(io->write_size, io->c->bucket_alloc);
- io->ssize = io->write_size;
- io->slen = 0;
- remain = io->ssize;
- }
- return remain;
-}
-
-static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
-{
- apr_status_t status;
- const char *data;
- apr_size_t len;
-
- if (!b->length) {
- return APR_SUCCESS;
- }
-
- ap_assert(b->length <= (io->ssize - io->slen));
- if (APR_BUCKET_IS_FILE(b)) {
- apr_bucket_file *f = (apr_bucket_file *)b->data;
- apr_file_t *fd = f->fd;
- apr_off_t offset = b->start;
- apr_size_t len = b->length;
-
- /* file buckets will either mmap (which we do not want) or
- * read 8000 byte chunks and split themself. However, we do
- * know *exactly* how many bytes we need where.
- */
- status = apr_file_seek(fd, APR_SET, &offset);
- if (status != APR_SUCCESS) {
- return status;
- }
- status = apr_file_read(fd, io->scratch + io->slen, &len);
- if (status != APR_SUCCESS && status != APR_EOF) {
- return status;
- }
- io->slen += len;
- }
- else {
- status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
- if (status == APR_SUCCESS) {
- memcpy(io->scratch+io->slen, data, len);
- io->slen += len;
- }
- }
- return status;
-}
-
-static void check_write_size(h2_conn_io *io)
-{
- if (io->write_size > WRITE_SIZE_INITIAL
- && (io->cooldown_usecs > 0)
- && (apr_time_now() - io->last_write) >= io->cooldown_usecs) {
- /* long time not written, reset write size */
- io->write_size = WRITE_SIZE_INITIAL;
- io->bytes_written = 0;
- }
- else if (io->write_size < WRITE_SIZE_MAX
- && io->bytes_written >= io->warmup_size) {
- /* connection is hot, use max size */
- io->write_size = WRITE_SIZE_MAX;
- }
-}
-
-static apr_status_t pass_output(h2_conn_io *io, int flush)
-{
- conn_rec *c = io->c;
- apr_bucket_brigade *bb = io->output;
- apr_bucket *b;
- apr_off_t bblen;
- apr_status_t status;
-
- append_scratch(io);
- if (flush && !io->is_flushed) {
- b = apr_bucket_flush_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- }
-
- if (APR_BRIGADE_EMPTY(bb)) {
- return APR_SUCCESS;
- }
-
- ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
- apr_brigade_length(bb, 0, &bblen);
- h2_conn_io_bb_log(c, 0, APLOG_TRACE2, "out", bb);
-
- status = ap_pass_brigade(c->output_filters, bb);
- if (status == APR_SUCCESS) {
- io->bytes_written += (apr_size_t)bblen;
- io->last_write = apr_time_now();
- if (flush) {
- io->is_flushed = 1;
- }
- }
- apr_brigade_cleanup(bb);
-
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03044)
- "h2_conn_io(%ld): pass_out brigade %ld bytes",
- c->id, (long)bblen);
- }
- return status;
-}
-
-int h2_conn_io_needs_flush(h2_conn_io *io)
-{
- if (!io->is_flushed) {
- apr_off_t len = h2_brigade_mem_size(io->output);
- if (len > io->flush_threshold) {
- return 1;
- }
- /* if we do not exceed flush length due to memory limits,
- * we want at least flush when we have that amount of data. */
- apr_brigade_length(io->output, 0, &len);
- return len > (4 * io->flush_threshold);
- }
- return 0;
-}
-
-apr_status_t h2_conn_io_flush(h2_conn_io *io)
-{
- apr_status_t status;
- status = pass_output(io, 1);
- check_write_size(io);
- return status;
-}
-
-apr_status_t h2_conn_io_write(h2_conn_io *io, const char *data, size_t length)
-{
- apr_status_t status = APR_SUCCESS;
- apr_size_t remain;
-
- if (length > 0) {
- io->is_flushed = 0;
- }
-
- if (io->buffer_output) {
- while (length > 0) {
- remain = assure_scratch_space(io);
- if (remain >= length) {
- memcpy(io->scratch + io->slen, data, length);
- io->slen += length;
- length = 0;
- }
- else {
- memcpy(io->scratch + io->slen, data, remain);
- io->slen += remain;
- data += remain;
- length -= remain;
- }
- }
- }
- else {
- status = apr_brigade_write(io->output, NULL, NULL, data, length);
- }
- return status;
-}
-
-apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb)
-{
- apr_bucket *b;
- apr_status_t status = APR_SUCCESS;
-
- if (!APR_BRIGADE_EMPTY(bb)) {
- io->is_flushed = 0;
- }
-
- while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
- b = APR_BRIGADE_FIRST(bb);
-
- if (APR_BUCKET_IS_METADATA(b)) {
- /* need to finish any open scratch bucket, as meta data
- * needs to be forward "in order". */
- append_scratch(io);
- APR_BUCKET_REMOVE(b);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
- }
- else if (io->buffer_output) {
- apr_size_t remain = assure_scratch_space(io);
- if (b->length > remain) {
- apr_bucket_split(b, remain);
- if (io->slen == 0) {
- /* complete write_size bucket, append unchanged */
- APR_BUCKET_REMOVE(b);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
- continue;
- }
- }
- else {
- /* bucket fits in remain, copy to scratch */
- status = read_to_scratch(io, b);
- apr_bucket_delete(b);
- continue;
- }
- }
- else {
- /* no buffering, forward buckets setaside on flush */
- if (APR_BUCKET_IS_TRANSIENT(b)) {
- apr_bucket_setaside(b, io->c->pool);
- }
- APR_BUCKET_REMOVE(b);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
- }
- }
- return status;
-}
-
diff --git a/modules/http2/h2_conn_io.h b/modules/http2/h2_conn_io.h
deleted file mode 100644
index 2c3be1c..0000000
--- a/modules/http2/h2_conn_io.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_conn_io__
-#define __mod_h2__h2_conn_io__
-
-struct h2_config;
-struct h2_session;
-
-/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
- * one for input, one for output and works with the installed connection
- * filters.
- * The read is done via a callback function, so that input can be processed
- * directly without copying.
- */
-typedef struct {
- conn_rec *c;
- apr_bucket_brigade *output;
-
- int is_tls;
- apr_time_t cooldown_usecs;
- apr_int64_t warmup_size;
-
- apr_size_t write_size;
- apr_time_t last_write;
- apr_int64_t bytes_read;
- apr_int64_t bytes_written;
-
- int buffer_output;
- apr_size_t flush_threshold;
- unsigned int is_flushed : 1;
-
- char *scratch;
- apr_size_t ssize;
- apr_size_t slen;
-} h2_conn_io;
-
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c,
- const struct h2_config *cfg);
-
-/**
- * Append data to the buffered output.
- * @param buf the data to append
- * @param length the length of the data to append
- */
-apr_status_t h2_conn_io_write(h2_conn_io *io,
- const char *buf,
- size_t length);
-
-apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb);
-
-/**
- * Pass any buffered data on to the connection output filters.
- * @param io the connection io
- * @param flush if a flush bucket should be appended to any output
- */
-apr_status_t h2_conn_io_flush(h2_conn_io *io);
-
-/**
- * Check if the buffered amount of data needs flushing.
- */
-int h2_conn_io_needs_flush(h2_conn_io *io);
-
-#endif /* defined(__mod_h2__h2_conn_io__) */
diff --git a/modules/http2/h2_ctx.c b/modules/http2/h2_ctx.c
deleted file mode 100644
index d5ccc24..0000000
--- a/modules/http2/h2_ctx.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/* 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
-
-#include
-#include
-#include
-
-#include "h2_private.h"
-#include "h2_session.h"
-#include "h2_task.h"
-#include "h2_ctx.h"
-
-static h2_ctx *h2_ctx_create(const conn_rec *c)
-{
- h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx));
- ap_assert(ctx);
- ap_set_module_config(c->conn_config, &http2_module, ctx);
- h2_ctx_server_set(ctx, c->base_server);
- return ctx;
-}
-
-void h2_ctx_clear(const conn_rec *c)
-{
- ap_assert(c);
- ap_set_module_config(c->conn_config, &http2_module, NULL);
-}
-
-h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task *task)
-{
- h2_ctx *ctx = h2_ctx_create(c);
- if (ctx) {
- ctx->task = task;
- }
- return ctx;
-}
-
-h2_ctx *h2_ctx_get(const conn_rec *c, int create)
-{
- h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module);
- if (ctx == NULL && create) {
- ctx = h2_ctx_create(c);
- }
- return ctx;
-}
-
-h2_ctx *h2_ctx_rget(const request_rec *r)
-{
- return h2_ctx_get(r->connection, 0);
-}
-
-const char *h2_ctx_protocol_get(const conn_rec *c)
-{
- h2_ctx *ctx;
- if (c->master) {
- c = c->master;
- }
- ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module);
- return ctx? ctx->protocol : NULL;
-}
-
-h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto)
-{
- ctx->protocol = proto;
- return ctx;
-}
-
-h2_session *h2_ctx_session_get(h2_ctx *ctx)
-{
- return ctx? ctx->session : NULL;
-}
-
-void h2_ctx_session_set(h2_ctx *ctx, struct h2_session *session)
-{
- ctx->session = session;
-}
-
-server_rec *h2_ctx_server_get(h2_ctx *ctx)
-{
- return ctx? ctx->server : NULL;
-}
-
-h2_ctx *h2_ctx_server_set(h2_ctx *ctx, server_rec *s)
-{
- ctx->server = s;
- return ctx;
-}
-
-int h2_ctx_is_task(h2_ctx *ctx)
-{
- return ctx && ctx->task;
-}
-
-h2_task *h2_ctx_get_task(h2_ctx *ctx)
-{
- return ctx? ctx->task : NULL;
-}
-
-h2_task *h2_ctx_cget_task(conn_rec *c)
-{
- return h2_ctx_get_task(h2_ctx_get(c, 0));
-}
-
-h2_task *h2_ctx_rget_task(request_rec *r)
-{
- return h2_ctx_get_task(h2_ctx_rget(r));
-}
diff --git a/modules/http2/h2_ctx.h b/modules/http2/h2_ctx.h
deleted file mode 100644
index cb111c9..0000000
--- a/modules/http2/h2_ctx.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_ctx__
-#define __mod_h2__h2_ctx__
-
-struct h2_session;
-struct h2_task;
-struct h2_config;
-
-/**
- * The h2 module context associated with a connection.
- *
- * It keeps track of the different types of connections:
- * - those from clients that use HTTP/2 protocol
- * - those from clients that do not use HTTP/2
- * - those created by ourself to perform work on HTTP/2 streams
- */
-typedef struct h2_ctx {
- const char *protocol; /* the protocol negotiated */
- struct h2_session *session; /* the session established */
- struct h2_task *task; /* the h2_task executing or NULL */
- const char *hostname; /* hostname negotiated via SNI, optional */
- server_rec *server; /* httpd server config selected. */
- const struct h2_config *config; /* effective config in this context */
-} h2_ctx;
-
-/**
- * Get (or create) a h2 context record for this connection.
- * @param c the connection to look at
- * @param create != 0 iff missing context shall be created
- * @return h2 context of this connection
- */
-h2_ctx *h2_ctx_get(const conn_rec *c, int create);
-void h2_ctx_clear(const conn_rec *c);
-
-h2_ctx *h2_ctx_rget(const request_rec *r);
-h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task *task);
-
-
-/* Set the h2 protocol established on this connection context or
- * NULL when other protocols are in place.
- */
-h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto);
-
-/* Set the server_rec relevant for this context.
- */
-h2_ctx *h2_ctx_server_set(h2_ctx *ctx, server_rec *s);
-server_rec *h2_ctx_server_get(h2_ctx *ctx);
-
-struct h2_session *h2_ctx_session_get(h2_ctx *ctx);
-void h2_ctx_session_set(h2_ctx *ctx, struct h2_session *session);
-
-/**
- * Get the h2 protocol negotiated for this connection, or NULL.
- */
-const char *h2_ctx_protocol_get(const conn_rec *c);
-
-int h2_ctx_is_task(h2_ctx *ctx);
-
-struct h2_task *h2_ctx_get_task(h2_ctx *ctx);
-struct h2_task *h2_ctx_cget_task(conn_rec *c);
-struct h2_task *h2_ctx_rget_task(request_rec *r);
-
-#endif /* defined(__mod_h2__h2_ctx__) */
diff --git a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c
deleted file mode 100644
index 8b254b1..0000000
--- a/modules/http2/h2_filter.c
+++ /dev/null
@@ -1,568 +0,0 @@
-/* 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
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_config.h"
-#include "h2_conn_io.h"
-#include "h2_ctx.h"
-#include "h2_mplx.h"
-#include "h2_push.h"
-#include "h2_task.h"
-#include "h2_stream.h"
-#include "h2_request.h"
-#include "h2_headers.h"
-#include "h2_stream.h"
-#include "h2_session.h"
-#include "h2_util.h"
-#include "h2_version.h"
-
-#include "h2_filter.h"
-
-#define UNSET -1
-#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
-
-static apr_status_t recv_RAW_DATA(conn_rec *c, h2_filter_cin *cin,
- apr_bucket *b, apr_read_type_e block)
-{
- h2_session *session = cin->session;
- apr_status_t status = APR_SUCCESS;
- apr_size_t len;
- const char *data;
- ssize_t n;
-
- status = apr_bucket_read(b, &data, &len, block);
-
- while (status == APR_SUCCESS && len > 0) {
- n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
- (long)len, (long)n);
- if (n < 0) {
- if (nghttp2_is_fatal((int)n)) {
- h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
- (int)n, nghttp2_strerror((int)n));
- status = APR_EGENERAL;
- }
- }
- else {
- session->io.bytes_read += n;
- if (len <= n) {
- break;
- }
- len -= n;
- data += n;
- }
- }
-
- return status;
-}
-
-static apr_status_t recv_RAW_brigade(conn_rec *c, h2_filter_cin *cin,
- apr_bucket_brigade *bb,
- apr_read_type_e block)
-{
- apr_status_t status = APR_SUCCESS;
- apr_bucket* b;
- int consumed = 0;
-
- h2_util_bb_log(c, c->id, APLOG_TRACE2, "RAW_in", bb);
- while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
- b = APR_BRIGADE_FIRST(bb);
-
- if (APR_BUCKET_IS_METADATA(b)) {
- /* nop */
- }
- else {
- status = recv_RAW_DATA(c, cin, b, block);
- }
- consumed = 1;
- apr_bucket_delete(b);
- }
-
- if (!consumed && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
- return APR_EAGAIN;
- }
- return status;
-}
-
-h2_filter_cin *h2_filter_cin_create(h2_session *session)
-{
- h2_filter_cin *cin;
-
- cin = apr_pcalloc(session->pool, sizeof(*cin));
- if (!cin) {
- return NULL;
- }
- cin->session = session;
- return cin;
-}
-
-void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout)
-{
- cin->timeout = timeout;
-}
-
-apr_status_t h2_filter_core_input(ap_filter_t* f,
- apr_bucket_brigade* brigade,
- ap_input_mode_t mode,
- apr_read_type_e block,
- apr_off_t readbytes)
-{
- h2_filter_cin *cin = f->ctx;
- apr_status_t status = APR_SUCCESS;
- apr_interval_time_t saved_timeout = UNSET;
- const int trace1 = APLOGctrace1(f->c);
-
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_session(%ld): read, %s, mode=%d, readbytes=%ld",
- (long)f->c->id, (block == APR_BLOCK_READ)?
- "BLOCK_READ" : "NONBLOCK_READ", mode, (long)readbytes);
- }
-
- if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
- return ap_get_brigade(f->next, brigade, mode, block, readbytes);
- }
-
- if (mode != AP_MODE_READBYTES) {
- return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN;
- }
-
- if (!cin->bb) {
- cin->bb = apr_brigade_create(cin->session->pool, f->c->bucket_alloc);
- }
-
- if (!cin->socket) {
- cin->socket = ap_get_conn_socket(f->c);
- }
-
- if (APR_BRIGADE_EMPTY(cin->bb)) {
- /* We only do a blocking read when we have no streams to process. So,
- * in httpd scoreboard lingo, we are in a KEEPALIVE connection state.
- */
- if (block == APR_BLOCK_READ) {
- if (cin->timeout > 0) {
- apr_socket_timeout_get(cin->socket, &saved_timeout);
- apr_socket_timeout_set(cin->socket, cin->timeout);
- }
- }
- status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
- block, readbytes);
- if (saved_timeout != UNSET) {
- apr_socket_timeout_set(cin->socket, saved_timeout);
- }
- }
-
- switch (status) {
- case APR_SUCCESS:
- status = recv_RAW_brigade(f->c, cin, cin->bb, block);
- break;
- case APR_EOF:
- case APR_EAGAIN:
- case APR_TIMEUP:
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
- "h2_session(%ld): read", f->c->id);
- }
- break;
- default:
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
- "h2_session(%ld): error reading", f->c->id);
- break;
- }
- return status;
-}
-
-/*******************************************************************************
- * http2 connection status handler + stream out source
- ******************************************************************************/
-
-typedef struct {
- apr_bucket_refcount refcount;
- h2_bucket_event_cb *cb;
- void *ctx;
-} h2_bucket_observer;
-
-static apr_status_t bucket_read(apr_bucket *b, const char **str,
- apr_size_t *len, apr_read_type_e block)
-{
- (void)b;
- (void)block;
- *str = NULL;
- *len = 0;
- return APR_SUCCESS;
-}
-
-static void bucket_destroy(void *data)
-{
- h2_bucket_observer *h = data;
- if (apr_bucket_shared_destroy(h)) {
- if (h->cb) {
- h->cb(h->ctx, H2_BUCKET_EV_BEFORE_DESTROY, NULL);
- }
- apr_bucket_free(h);
- }
-}
-
-apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
- void *ctx)
-{
- h2_bucket_observer *br;
-
- br = apr_bucket_alloc(sizeof(*br), b->list);
- br->cb = cb;
- br->ctx = ctx;
-
- b = apr_bucket_shared_make(b, br, 0, 0);
- b->type = &h2_bucket_type_observer;
- return b;
-}
-
-apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list,
- h2_bucket_event_cb *cb, void *ctx)
-{
- apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
-
- APR_BUCKET_INIT(b);
- b->free = apr_bucket_free;
- b->list = list;
- b = h2_bucket_observer_make(b, cb, ctx);
- return b;
-}
-
-apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event)
-{
- if (H2_BUCKET_IS_OBSERVER(b)) {
- h2_bucket_observer *l = (h2_bucket_observer *)b->data;
- return l->cb(l->ctx, event, b);
- }
- return APR_EINVAL;
-}
-
-const apr_bucket_type_t h2_bucket_type_observer = {
- "H2OBS", 5, APR_BUCKET_METADATA,
- bucket_destroy,
- bucket_read,
- apr_bucket_setaside_noop,
- apr_bucket_split_notimpl,
- apr_bucket_shared_copy
-};
-
-apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
- apr_bucket_brigade *dest,
- const apr_bucket *src)
-{
- if (H2_BUCKET_IS_OBSERVER(src)) {
- h2_bucket_observer *l = (h2_bucket_observer *)src->data;
- apr_bucket *b = h2_bucket_observer_create(dest->bucket_alloc,
- l->cb, l->ctx);
- APR_BRIGADE_INSERT_TAIL(dest, b);
- l->cb = NULL;
- l->ctx = NULL;
- h2_bucket_observer_fire(b, H2_BUCKET_EV_BEFORE_MASTER_SEND);
- return b;
- }
- return NULL;
-}
-
-static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
- __attribute__((format(printf,2,3)));
-static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
-{
- va_list args;
- apr_status_t rv;
-
- va_start(args, fmt);
- rv = apr_brigade_vprintf(bb, NULL, NULL, fmt, args);
- va_end(args);
-
- return rv;
-}
-
-static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last)
-{
- h2_mplx *m = s->mplx;
-
- bbout(bb, " \"settings\": {\n");
- bbout(bb, " \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", m->max_streams);
- bbout(bb, " \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 16*1024);
- bbout(bb, " \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
- h2_config_geti(s->config, H2_CONF_WIN_SIZE));
- bbout(bb, " \"SETTINGS_ENABLE_PUSH\": %d\n", h2_session_push_enabled(s));
- bbout(bb, " }%s\n", last? "" : ",");
-}
-
-static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last)
-{
- bbout(bb, " \"peerSettings\": {\n");
- bbout(bb, " \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n",
- nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS));
- bbout(bb, " \"SETTINGS_MAX_FRAME_SIZE\": %d,\n",
- nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_FRAME_SIZE));
- bbout(bb, " \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
- nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE));
- bbout(bb, " \"SETTINGS_ENABLE_PUSH\": %d,\n",
- nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH));
- bbout(bb, " \"SETTINGS_HEADER_TABLE_SIZE\": %d,\n",
- nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE));
- bbout(bb, " \"SETTINGS_MAX_HEADER_LIST_SIZE\": %d\n",
- nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE));
- bbout(bb, " }%s\n", last? "" : ",");
-}
-
-typedef struct {
- apr_bucket_brigade *bb;
- h2_session *s;
- int idx;
-} stream_ctx_t;
-
-static int add_stream(h2_stream *stream, void *ctx)
-{
- stream_ctx_t *x = ctx;
- int32_t flowIn, flowOut;
-
- flowIn = nghttp2_session_get_stream_effective_local_window_size(x->s->ngh2, stream->id);
- flowOut = nghttp2_session_get_stream_remote_window_size(x->s->ngh2, stream->id);
- bbout(x->bb, "%s\n \"%d\": {\n", (x->idx? "," : ""), stream->id);
- bbout(x->bb, " \"state\": \"%s\",\n", h2_stream_state_str(stream));
- bbout(x->bb, " \"created\": %f,\n", ((double)stream->created)/APR_USEC_PER_SEC);
- bbout(x->bb, " \"flowIn\": %d,\n", flowIn);
- bbout(x->bb, " \"flowOut\": %d,\n", flowOut);
- bbout(x->bb, " \"dataIn\": %"APR_OFF_T_FMT",\n", stream->in_data_octets);
- bbout(x->bb, " \"dataOut\": %"APR_OFF_T_FMT"\n", stream->out_data_octets);
- bbout(x->bb, " }");
-
- ++x->idx;
- return 1;
-}
-
-static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last)
-{
- stream_ctx_t x;
-
- x.bb = bb;
- x.s = s;
- x.idx = 0;
- bbout(bb, " \"streams\": {");
- h2_mplx_stream_do(s->mplx, add_stream, &x);
- bbout(bb, "\n }%s\n", last? "" : ",");
-}
-
-static void add_push(apr_bucket_brigade *bb, h2_session *s,
- h2_stream *stream, int last)
-{
- h2_push_diary *diary;
- apr_status_t status;
-
- bbout(bb, " \"push\": {\n");
- diary = s->push_diary;
- if (diary) {
- const char *data;
- const char *base64_digest;
- apr_size_t len;
-
- status = h2_push_diary_digest_get(diary, bb->p, 256,
- stream->request->authority,
- &data, &len);
- if (status == APR_SUCCESS) {
- base64_digest = h2_util_base64url_encode(data, len, bb->p);
- bbout(bb, " \"cacheDigest\": \"%s\",\n", base64_digest);
- }
- }
- bbout(bb, " \"promises\": %d,\n", s->pushes_promised);
- bbout(bb, " \"submits\": %d,\n", s->pushes_submitted);
- bbout(bb, " \"resets\": %d\n", s->pushes_reset);
- bbout(bb, " }%s\n", last? "" : ",");
-}
-
-static void add_in(apr_bucket_brigade *bb, h2_session *s, int last)
-{
- bbout(bb, " \"in\": {\n");
- bbout(bb, " \"requests\": %d,\n", s->remote.emitted_count);
- bbout(bb, " \"resets\": %d, \n", s->streams_reset);
- bbout(bb, " \"frames\": %ld,\n", (long)s->frames_received);
- bbout(bb, " \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_read);
- bbout(bb, " }%s\n", last? "" : ",");
-}
-
-static void add_out(apr_bucket_brigade *bb, h2_session *s, int last)
-{
- bbout(bb, " \"out\": {\n");
- bbout(bb, " \"responses\": %d,\n", s->responses_submitted);
- bbout(bb, " \"frames\": %ld,\n", (long)s->frames_sent);
- bbout(bb, " \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_written);
- bbout(bb, " }%s\n", last? "" : ",");
-}
-
-static void add_stats(apr_bucket_brigade *bb, h2_session *s,
- h2_stream *stream, int last)
-{
- bbout(bb, " \"stats\": {\n");
- add_in(bb, s, 0);
- add_out(bb, s, 0);
- add_push(bb, s, stream, 1);
- bbout(bb, " }%s\n", last? "" : ",");
-}
-
-static apr_status_t h2_status_insert(h2_task *task, apr_bucket *b)
-{
- conn_rec *c = task->c->master;
- h2_ctx *h2ctx = h2_ctx_get(c, 0);
- h2_session *session;
- h2_stream *stream;
- apr_bucket_brigade *bb;
- apr_bucket *e;
- int32_t connFlowIn, connFlowOut;
-
-
- if (!h2ctx || (session = h2_ctx_session_get(h2ctx)) == NULL) {
- return APR_SUCCESS;
- }
-
- stream = h2_session_stream_get(session, task->stream_id);
- if (!stream) {
- /* stream already done */
- return APR_SUCCESS;
- }
-
- bb = apr_brigade_create(stream->pool, c->bucket_alloc);
-
- connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2);
- connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
-
- bbout(bb, "{\n");
- bbout(bb, " \"version\": \"draft-01\",\n");
- add_settings(bb, session, 0);
- add_peer_settings(bb, session, 0);
- bbout(bb, " \"connFlowIn\": %d,\n", connFlowIn);
- bbout(bb, " \"connFlowOut\": %d,\n", connFlowOut);
- bbout(bb, " \"sentGoAway\": %d,\n", session->local.shutdown);
-
- add_streams(bb, session, 0);
-
- add_stats(bb, session, stream, 1);
- bbout(bb, "}\n");
-
- while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
- APR_BUCKET_REMOVE(e);
- APR_BUCKET_INSERT_AFTER(b, e);
- b = e;
- }
- apr_brigade_destroy(bb);
-
- return APR_SUCCESS;
-}
-
-static apr_status_t status_event(void *ctx, h2_bucket_event event,
- apr_bucket *b)
-{
- h2_task *task = ctx;
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, task->c->master,
- "status_event(%s): %d", task->id, event);
- switch (event) {
- case H2_BUCKET_EV_BEFORE_MASTER_SEND:
- h2_status_insert(task, b);
- break;
- default:
- break;
- }
- return APR_SUCCESS;
-}
-
-int h2_filter_h2_status_handler(request_rec *r)
-{
- h2_ctx *ctx = h2_ctx_rget(r);
- conn_rec *c = r->connection;
- h2_task *task;
- apr_bucket_brigade *bb;
- apr_bucket *b;
- apr_status_t status;
-
- if (strcmp(r->handler, "http2-status")) {
- return DECLINED;
- }
- if (r->method_number != M_GET && r->method_number != M_POST) {
- return DECLINED;
- }
-
- task = ctx? h2_ctx_get_task(ctx) : NULL;
- if (task) {
-
- if ((status = ap_discard_request_body(r)) != OK) {
- return status;
- }
-
- /* We need to handle the actual output on the main thread, as
- * we need to access h2_session information. */
- r->status = 200;
- r->clength = -1;
- r->chunked = 1;
- apr_table_unset(r->headers_out, "Content-Length");
- /* Discourage content-encodings */
- apr_table_unset(r->headers_out, "Content-Encoding");
- apr_table_setn(r->subprocess_env, "no-brotli", "1");
- apr_table_setn(r->subprocess_env, "no-gzip", "1");
-
- ap_set_content_type(r, "application/json");
- apr_table_setn(r->notes, H2_FILTER_DEBUG_NOTE, "on");
-
- bb = apr_brigade_create(r->pool, c->bucket_alloc);
- b = h2_bucket_observer_create(c->bucket_alloc, status_event, task);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
-
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "status_handler(%s): checking for incoming trailers",
- task->id);
- if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "status_handler(%s): seeing incoming trailers",
- task->id);
- apr_table_setn(r->trailers_out, "h2-trailers-in",
- apr_itoa(r->pool, 1));
- }
-
- status = ap_pass_brigade(r->output_filters, bb);
- if (status == APR_SUCCESS
- || r->status != HTTP_OK
- || c->aborted) {
- return OK;
- }
- else {
- /* no way to know what type of error occurred */
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r,
- "status_handler(%s): ap_pass_brigade failed",
- task->id);
- return AP_FILTER_ERROR;
- }
- }
- return DECLINED;
-}
-
diff --git a/modules/http2/h2_filter.h b/modules/http2/h2_filter.h
deleted file mode 100644
index 12810d8..0000000
--- a/modules/http2/h2_filter.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_filter__
-#define __mod_h2__h2_filter__
-
-struct h2_bucket_beam;
-struct h2_headers;
-struct h2_stream;
-struct h2_session;
-
-typedef struct h2_filter_cin {
- apr_pool_t *pool;
- apr_socket_t *socket;
- apr_interval_time_t timeout;
- apr_bucket_brigade *bb;
- struct h2_session *session;
- apr_bucket *cur;
-} h2_filter_cin;
-
-h2_filter_cin *h2_filter_cin_create(struct h2_session *session);
-
-void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout);
-
-apr_status_t h2_filter_core_input(ap_filter_t* filter,
- apr_bucket_brigade* brigade,
- ap_input_mode_t mode,
- apr_read_type_e block,
- apr_off_t readbytes);
-
-/******* observer bucket ******************************************************/
-
-typedef enum {
- H2_BUCKET_EV_BEFORE_DESTROY,
- H2_BUCKET_EV_BEFORE_MASTER_SEND
-} h2_bucket_event;
-
-extern const apr_bucket_type_t h2_bucket_type_observer;
-
-typedef apr_status_t h2_bucket_event_cb(void *ctx, h2_bucket_event event, apr_bucket *b);
-
-#define H2_BUCKET_IS_OBSERVER(e) (e->type == &h2_bucket_type_observer)
-
-apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
- void *ctx);
-
-apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list,
- h2_bucket_event_cb *cb, void *ctx);
-
-apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event);
-
-apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
- apr_bucket_brigade *dest,
- const apr_bucket *src);
-
-/******* /.well-known/h2/state handler ****************************************/
-
-int h2_filter_h2_status_handler(request_rec *r);
-
-#endif /* __mod_h2__h2_filter__ */
diff --git a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c
deleted file mode 100644
index d69c53c..0000000
--- a/modules/http2/h2_from_h1.c
+++ /dev/null
@@ -1,875 +0,0 @@
-/* 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
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "h2_private.h"
-#include "h2_headers.h"
-#include "h2_from_h1.h"
-#include "h2_task.h"
-#include "h2_util.h"
-
-
-/* This routine is called by apr_table_do and merges all instances of
- * the passed field values into a single array that will be further
- * processed by some later routine. Originally intended to help split
- * and recombine multiple Vary fields, though it is generic to any field
- * consisting of comma/space-separated tokens.
- */
-static int uniq_field_values(void *d, const char *key, const char *val)
-{
- apr_array_header_t *values;
- char *start;
- char *e;
- char **strpp;
- int i;
-
- (void)key;
- values = (apr_array_header_t *)d;
-
- e = apr_pstrdup(values->pool, val);
-
- do {
- /* Find a non-empty fieldname */
-
- 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';
- }
-
- /* Now add it to values if it isn't already represented.
- * Could be replaced by a ap_array_strcasecmp() if we had one.
- */
- for (i = 0, strpp = (char **) values->elts; i < values->nelts;
- ++i, ++strpp) {
- if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
- break;
- }
- }
- if (i == values->nelts) { /* if not found */
- *(char **)apr_array_push(values) = start;
- }
- } while (*e != '\0');
-
- return 1;
-}
-
-/*
- * Since some clients choke violently on multiple Vary fields, or
- * Vary fields with duplicate tokens, combine any multiples and remove
- * any duplicates.
- */
-static void fix_vary(request_rec *r)
-{
- apr_array_header_t *varies;
-
- varies = apr_array_make(r->pool, 5, sizeof(char *));
-
- /* Extract all Vary fields from the headers_out, separate each into
- * its comma-separated fieldname values, and then add them to varies
- * if not already present in the array.
- */
- apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
-
- /* If we found any, replace old Vary fields with unique-ified value */
-
- if (varies->nelts > 0) {
- apr_table_setn(r->headers_out, "Vary",
- apr_array_pstrcat(r->pool, varies, ','));
- }
-}
-
-static void set_basic_http_header(apr_table_t *headers, request_rec *r,
- apr_pool_t *pool)
-{
- char *date = NULL;
- const char *proxy_date = NULL;
- const char *server = NULL;
- const char *us = ap_get_server_banner();
-
- /*
- * keep the set-by-proxy server and date headers, otherwise
- * generate a new server header / date header
- */
- if (r && r->proxyreq != PROXYREQ_NONE) {
- proxy_date = apr_table_get(r->headers_out, "Date");
- if (!proxy_date) {
- /*
- * proxy_date needs to be const. So use date for the creation of
- * our own Date header and pass it over to proxy_date later to
- * avoid a compiler warning.
- */
- date = apr_palloc(pool, APR_RFC822_DATE_LEN);
- ap_recent_rfc822_date(date, r->request_time);
- }
- server = apr_table_get(r->headers_out, "Server");
- }
- else {
- date = apr_palloc(pool, APR_RFC822_DATE_LEN);
- ap_recent_rfc822_date(date, r? r->request_time : apr_time_now());
- }
-
- apr_table_setn(headers, "Date", proxy_date ? proxy_date : date );
- if (r) {
- apr_table_unset(r->headers_out, "Date");
- }
-
- if (!server && *us) {
- server = us;
- }
- if (server) {
- apr_table_setn(headers, "Server", server);
- if (r) {
- apr_table_unset(r->headers_out, "Server");
- }
- }
-}
-
-static int copy_header(void *ctx, const char *name, const char *value)
-{
- apr_table_t *headers = ctx;
-
- apr_table_add(headers, name, value);
- return 1;
-}
-
-static h2_headers *create_response(h2_task *task, request_rec *r)
-{
- const char *clheader;
- const char *ctype;
- apr_table_t *headers;
- /*
- * Now that we are ready to send a response, we need to combine the two
- * header field tables into a single table. If we don't do this, our
- * later attempts to set or unset a given fieldname might be bypassed.
- */
- 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);
- }
-
- /*
- * Remove the 'Vary' header field if the client can't handle it.
- * Since this will have nasty effects on HTTP/1.1 caches, force
- * the response into HTTP/1.0 mode.
- */
- if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
- apr_table_unset(r->headers_out, "Vary");
- r->proto_num = HTTP_VERSION(1,0);
- apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
- }
- else {
- fix_vary(r);
- }
-
- /*
- * Now remove any ETag response header field if earlier processing
- * says so (such as a 'FileETag None' directive).
- */
- if (apr_table_get(r->notes, "no-etag") != NULL) {
- apr_table_unset(r->headers_out, "ETag");
- }
-
- /* determine the protocol and whether we should use keepalives. */
- ap_set_keepalive(r);
-
- 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;
- }
- else if (r->chunked) {
- apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
- apr_table_unset(r->headers_out, "Content-Length");
- }
-
- 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)) {
- unsigned int i;
- char *token;
- char **languages = (char **)(r->content_languages->elts);
- const char *field = apr_table_get(r->headers_out, "Content-Language");
-
- while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
- for (i = 0; i < r->content_languages->nelts; ++i) {
- if (!apr_strnatcasecmp(token, languages[i]))
- break;
- }
- if (i == r->content_languages->nelts) {
- *((char **) apr_array_push(r->content_languages)) = token;
- }
- }
-
- field = apr_array_pstrcat(r->pool, r->content_languages, ',');
- apr_table_setn(r->headers_out, "Content-Language", field);
- }
-
- /*
- * Control cachability for non-cachable responses if not already set by
- * some other part of the server configuration.
- */
- 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);
- }
-
- /* This is a hack, but I can't find anyway around it. The idea is that
- * we don't want to send out 0 Content-Lengths if it is a head request.
- * This happens when modules try to outsmart the server, and return
- * if they see a HEAD request. Apache 1.3 handlers were supposed to
- * just return in that situation, and the core handled the HEAD. In
- * 2.0, if a handler returns, then the core sends an EOS bucket down
- * the filter stack, and the content-length filter computes a C-L of
- * zero and that gets put in the headers, and we end up sending a
- * zero C-L to the client. We can't just remove the C-L filter,
- * because well behaved 2.0 handlers will send their data down the stack,
- * and we will compute a real C-L for the head request. RBB
- */
- if (r->header_only
- && (clheader = apr_table_get(r->headers_out, "Content-Length"))
- && !strcmp(clheader, "0")) {
- apr_table_unset(r->headers_out, "Content-Length");
- }
-
- headers = apr_table_make(r->pool, 10);
-
- set_basic_http_header(headers, r, r->pool);
- if (r->status == HTTP_NOT_MODIFIED) {
- apr_table_do(copy_header, headers, r->headers_out,
- "ETag",
- "Content-Location",
- "Expires",
- "Cache-Control",
- "Vary",
- "Warning",
- "WWW-Authenticate",
- "Proxy-Authenticate",
- "Set-Cookie",
- "Set-Cookie2",
- NULL);
- }
- else {
- apr_table_do(copy_header, headers, r->headers_out, NULL);
- }
-
- return h2_headers_rcreate(r, r->status, headers, r->pool);
-}
-
-typedef enum {
- H2_RP_STATUS_LINE,
- H2_RP_HEADER_LINE,
- H2_RP_DONE
-} h2_rp_state_t;
-
-typedef struct h2_response_parser {
- h2_rp_state_t state;
- h2_task *task;
- int http_status;
- apr_array_header_t *hlines;
- apr_bucket_brigade *tmp;
-} h2_response_parser;
-
-static apr_status_t parse_header(h2_response_parser *parser, char *line) {
- const char *hline;
- if (line[0] == ' ' || line[0] == '\t') {
- char **plast;
- /* continuation line from the header before this */
- while (line[0] == ' ' || line[0] == '\t') {
- ++line;
- }
-
- plast = apr_array_pop(parser->hlines);
- if (plast == NULL) {
- /* not well formed */
- return APR_EINVAL;
- }
- hline = apr_psprintf(parser->task->pool, "%s %s", *plast, line);
- }
- else {
- /* new header line */
- hline = apr_pstrdup(parser->task->pool, line);
- }
- APR_ARRAY_PUSH(parser->hlines, const char*) = hline;
- return APR_SUCCESS;
-}
-
-static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
- char *line, apr_size_t len)
-{
- h2_task *task = parser->task;
- apr_status_t status;
-
- if (!parser->tmp) {
- parser->tmp = apr_brigade_create(task->pool, task->c->bucket_alloc);
- }
- status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ,
- HUGE_STRING_LEN);
- if (status == APR_SUCCESS) {
- --len;
- status = apr_brigade_flatten(parser->tmp, line, &len);
- if (status == APR_SUCCESS) {
- /* we assume a non-0 containing line and remove trailing crlf. */
- line[len] = '\0';
- if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
- len -= 2;
- line[len] = '\0';
- apr_brigade_cleanup(parser->tmp);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
- "h2_task(%s): read response line: %s",
- task->id, line);
- }
- else {
- /* this does not look like a complete line yet */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
- "h2_task(%s): read response, incomplete line: %s",
- task->id, line);
- return APR_EAGAIN;
- }
- }
- }
- apr_brigade_cleanup(parser->tmp);
- return status;
-}
-
-static apr_table_t *make_table(h2_response_parser *parser)
-{
- h2_task *task = parser->task;
- apr_array_header_t *hlines = parser->hlines;
- if (hlines) {
- apr_table_t *headers = apr_table_make(task->pool, hlines->nelts);
- int i;
-
- for (i = 0; i < hlines->nelts; ++i) {
- char *hline = ((char **)hlines->elts)[i];
- char *sep = ap_strchr(hline, ':');
- if (!sep) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, task->c,
- APLOGNO(02955) "h2_task(%s): invalid header[%d] '%s'",
- task->id, i, (char*)hline);
- /* not valid format, abort */
- return NULL;
- }
- (*sep++) = '\0';
- while (*sep == ' ' || *sep == '\t') {
- ++sep;
- }
-
- if (!h2_util_ignore_header(hline)) {
- apr_table_merge(headers, hline, sep);
- }
- }
- return headers;
- }
- else {
- return apr_table_make(task->pool, 0);
- }
-}
-
-static apr_status_t pass_response(h2_task *task, ap_filter_t *f,
- h2_response_parser *parser)
-{
- apr_bucket *b;
- apr_status_t status;
-
- h2_headers *response = h2_headers_create(parser->http_status,
- make_table(parser),
- NULL, 0, task->pool);
- apr_brigade_cleanup(parser->tmp);
- b = h2_bucket_headers_create(task->c->bucket_alloc, response);
- APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
- b = apr_bucket_flush_create(task->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
- status = ap_pass_brigade(f->next, parser->tmp);
- apr_brigade_cleanup(parser->tmp);
-
- /* reset parser for possible next response */
- parser->state = H2_RP_STATUS_LINE;
- apr_array_clear(parser->hlines);
-
- if (response->status >= 200) {
- task->output.sent_response = 1;
- }
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
- APLOGNO(03197) "h2_task(%s): passed response %d",
- task->id, response->status);
- return status;
-}
-
-static apr_status_t parse_status(h2_task *task, char *line)
-{
- h2_response_parser *parser = task->output.rparser;
- int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
- (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
- if (sindex > 0) {
- int k = sindex + 3;
- char keepchar = line[k];
- line[k] = '\0';
- parser->http_status = atoi(&line[sindex]);
- line[k] = keepchar;
- parser->state = H2_RP_HEADER_LINE;
-
- return APR_SUCCESS;
- }
- /* Seems like there is garbage on the connection. May be a leftover
- * from a previous proxy request.
- * This should only happen if the H2_RESPONSE filter is not yet in
- * place (post_read_request has not been reached and the handler wants
- * to write something. Probably just the interim response we are
- * waiting for. But if there is other data hanging around before
- * that, this needs to fail. */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03467)
- "h2_task(%s): unable to parse status line: %s",
- task->id, line);
- return APR_EINVAL;
-}
-
-apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f,
- apr_bucket_brigade *bb)
-{
- h2_response_parser *parser = task->output.rparser;
- char line[HUGE_STRING_LEN];
- apr_status_t status = APR_SUCCESS;
-
- if (!parser) {
- parser = apr_pcalloc(task->pool, sizeof(*parser));
- parser->task = task;
- parser->state = H2_RP_STATUS_LINE;
- parser->hlines = apr_array_make(task->pool, 10, sizeof(char *));
- task->output.rparser = parser;
- }
-
- while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
- switch (parser->state) {
- case H2_RP_STATUS_LINE:
- case H2_RP_HEADER_LINE:
- status = get_line(parser, bb, line, sizeof(line));
- if (status == APR_EAGAIN) {
- /* need more data */
- return APR_SUCCESS;
- }
- else if (status != APR_SUCCESS) {
- return status;
- }
- if (parser->state == H2_RP_STATUS_LINE) {
- /* instead of parsing, just take it directly */
- status = parse_status(task, line);
- }
- else if (line[0] == '\0') {
- /* end of headers, pass response onward */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_task(%s): end of response", task->id);
- return pass_response(task, f, parser);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_task(%s): response header %s", task->id, line);
- status = parse_header(parser, line);
- }
- break;
-
- default:
- return status;
- }
- }
- return status;
-}
-
-apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
-{
- h2_task *task = f->ctx;
- request_rec *r = f->r;
- apr_bucket *b, *bresp, *body_bucket = NULL, *next;
- ap_bucket_error *eb = NULL;
- h2_headers *response = NULL;
- int headers_passing = 0;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_task(%s): output_filter called", task->id);
-
- if (!task->output.sent_response && !f->c->aborted) {
- /* check, if we need to send the response now. Until we actually
- * see a DATA bucket or some EOS/EOR, we do not do so. */
- for (b = APR_BRIGADE_FIRST(bb);
- b != APR_BRIGADE_SENTINEL(bb);
- b = APR_BUCKET_NEXT(b))
- {
- if (AP_BUCKET_IS_ERROR(b) && !eb) {
- eb = b->data;
- }
- else if (AP_BUCKET_IS_EOC(b)) {
- /* If we see an EOC bucket it is a signal that we should get out
- * of the way doing nothing.
- */
- ap_remove_output_filter(f);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
- "h2_task(%s): eoc bucket passed", task->id);
- return ap_pass_brigade(f->next, bb);
- }
- else if (H2_BUCKET_IS_HEADERS(b)) {
- headers_passing = 1;
- }
- else if (!APR_BUCKET_IS_FLUSH(b)) {
- body_bucket = b;
- break;
- }
- }
-
- if (eb) {
- int st = eb->status;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
- "h2_task(%s): err bucket status=%d", task->id, st);
- /* throw everything away and replace it with the error response
- * generated by ap_die() */
- apr_brigade_cleanup(bb);
- ap_die(st, r);
- return AP_FILTER_ERROR;
- }
-
- if (body_bucket || !headers_passing) {
- /* time to insert the response bucket before the body or if
- * no h2_headers is passed, e.g. the response is empty */
- response = create_response(task, r);
- if (response == NULL) {
- ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
- "h2_task(%s): unable to create response", task->id);
- return APR_ENOMEM;
- }
-
- bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
- if (body_bucket) {
- APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
- }
- else {
- APR_BRIGADE_INSERT_HEAD(bb, bresp);
- }
- task->output.sent_response = 1;
- r->sent_bodyct = 1;
- }
- }
-
- if (r->header_only) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_task(%s): header_only, cleanup output brigade",
- task->id);
- b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
- while (b != APR_BRIGADE_SENTINEL(bb)) {
- next = APR_BUCKET_NEXT(b);
- if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
- break;
- }
- APR_BUCKET_REMOVE(b);
- apr_bucket_destroy(b);
- b = next;
- }
- }
- else if (task->output.sent_response) {
- /* lets get out of the way, our task is done */
- ap_remove_output_filter(f);
- }
- return ap_pass_brigade(f->next, bb);
-}
-
-static void make_chunk(h2_task *task, 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 *c;
- int len;
-
- len = apr_snprintf(buffer, H2_ALEN(buffer),
- "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
- c = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
- APR_BUCKET_INSERT_BEFORE(first, c);
- c = apr_bucket_heap_create("\r\n", 2, NULL, bb->bucket_alloc);
- if (tail) {
- APR_BUCKET_INSERT_BEFORE(tail, c);
- }
- else {
- APR_BRIGADE_INSERT_TAIL(bb, c);
- }
- task->input.chunked_total += chunk_len;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
- "h2_task(%s): added chunk %ld, total %ld",
- task->id, (long)chunk_len, (long)task->input.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_task *task,
- apr_read_type_e block) {
- request_rec *r = f->r;
- apr_status_t status = APR_SUCCESS;
- apr_bucket_brigade *bb = task->input.bbchunk;
-
- if (!bb) {
- bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
- task->input.bbchunk = bb;
- }
-
- if (APR_BRIGADE_EMPTY(bb)) {
- 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, bb,
- AP_MODE_READBYTES, block, 32*1024);
- if (status == APR_EOF) {
- if (!task->input.eos) {
- status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
- task->input.eos = 1;
- return APR_SUCCESS;
- }
- ap_remove_input_filter(f);
- return status;
-
- }
- else if (status != APR_SUCCESS) {
- return status;
- }
-
- for (b = APR_BRIGADE_FIRST(bb);
- b != APR_BRIGADE_SENTINEL(bb) && !task->input.eos;
- b = next) {
- next = APR_BUCKET_NEXT(b);
- if (APR_BUCKET_IS_METADATA(b)) {
- if (first_data) {
- make_chunk(task, bb, 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_task(%s): receiving trailers", task->id);
- tmp = apr_brigade_split_ex(bb, b, NULL);
- if (!apr_is_empty_table(headers->headers)) {
- status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
- apr_table_do(ser_header, bb, headers->headers, NULL);
- status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
- }
- else {
- status = apr_brigade_puts(bb, 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(bb, tmp);
- apr_brigade_destroy(tmp);
- task->input.eos = 1;
- }
- else if (APR_BUCKET_IS_EOS(b)) {
- tmp = apr_brigade_split_ex(bb, b, NULL);
- status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
- APR_BRIGADE_CONCAT(bb, tmp);
- apr_brigade_destroy(tmp);
- task->input.eos = 1;
- }
- }
- 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(task, bb, first_data, bblen, NULL);
- }
- }
- return status;
-}
-
-apr_status_t h2_filter_request_in(ap_filter_t* f,
- apr_bucket_brigade* bb,
- ap_input_mode_t mode,
- apr_read_type_e block,
- apr_off_t readbytes)
-{
- h2_task *task = f->ctx;
- request_rec *r = f->r;
- apr_status_t status = APR_SUCCESS;
- apr_bucket *b, *next;
- core_server_config *conf =
- (core_server_config *) ap_get_module_config(r->server->module_config,
- &core_module);
-
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
- "h2_task(%s): request filter, exp=%d", task->id, r->expecting_100);
- if (!task->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_task(%s): receiving trailers", task->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_task_logio_add_bytes_in) {
- h2_task_logio_add_bytes_in(task->c, headers->raw_bytes);
- }
- break;
- }
- }
- return status;
- }
-
- /* 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, task, block)) != APR_SUCCESS) {
- return status;
- }
-
- if (mode == AP_MODE_EXHAUSTIVE) {
- /* return all we have */
- APR_BRIGADE_CONCAT(bb, task->input.bbchunk);
- }
- else if (mode == AP_MODE_READBYTES) {
- status = h2_brigade_concat_length(bb, task->input.bbchunk, readbytes);
- }
- else if (mode == AP_MODE_SPECULATIVE) {
- status = h2_brigade_copy_length(bb, task->input.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, task->input.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_task(%s): getline: %s",
- task->id, buffer);
- }
- }
- 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_task, unsupported READ mode %d", mode);
- status = APR_ENOTIMPL;
- }
-
- h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, "forwarding input", bb);
- return status;
-}
-
-apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
-{
- h2_task *task = f->ctx;
- request_rec *r = f->r;
- apr_bucket *b, *e;
-
- if (task && r) {
- /* Detect the EOS/EOR bucket and forward any trailers that may have
- * been set to our h2_headers.
- */
- for (b = APR_BRIGADE_FIRST(bb);
- b != APR_BRIGADE_SENTINEL(bb);
- b = APR_BUCKET_NEXT(b))
- {
- if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
- && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
- h2_headers *headers;
- apr_table_t *trailers;
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
- "h2_task(%s): sending trailers", task->id);
- trailers = apr_table_clone(r->pool, r->trailers_out);
- headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
- e = h2_bucket_headers_create(bb->bucket_alloc, headers);
- APR_BUCKET_INSERT_BEFORE(b, e);
- apr_table_clear(r->trailers_out);
- ap_remove_output_filter(f);
- break;
- }
- }
- }
-
- return ap_pass_brigade(f->next, bb);
-}
-
diff --git a/modules/http2/h2_from_h1.h b/modules/http2/h2_from_h1.h
deleted file mode 100644
index 68a24fd..0000000
--- a/modules/http2/h2_from_h1.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_from_h1__
-#define __mod_h2__h2_from_h1__
-
-/**
- * h2_from_h1 parses a HTTP/1.1 response into
- * - response status
- * - a list of header values
- * - a series of bytes that represent the response body alone, without
- * any meta data, such as inserted by chunked transfer encoding.
- *
- * All data is allocated from the stream memory pool.
- *
- * Again, see comments in h2_request: ideally we would take the headers
- * and status from the httpd structures instead of parsing them here, but
- * we need to have all handlers and filters involved in request/response
- * processing, so this seems to be the way for now.
- */
-struct h2_headers;
-struct h2_task;
-
-apr_status_t h2_from_h1_parse_response(struct h2_task *task, ap_filter_t *f,
- apr_bucket_brigade *bb);
-
-apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb);
-
-apr_status_t h2_filter_request_in(ap_filter_t* f,
- apr_bucket_brigade* brigade,
- ap_input_mode_t mode,
- apr_read_type_e block,
- apr_off_t readbytes);
-
-apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
-
-#endif /* defined(__mod_h2__h2_from_h1__) */
diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c
deleted file mode 100644
index 5580cef..0000000
--- a/modules/http2/h2_h2.c
+++ /dev/null
@@ -1,765 +0,0 @@
-/* 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
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "mod_ssl.h"
-
-#include "mod_http2.h"
-#include "h2_private.h"
-
-#include "h2_bucket_beam.h"
-#include "h2_stream.h"
-#include "h2_task.h"
-#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_conn.h"
-#include "h2_filter.h"
-#include "h2_request.h"
-#include "h2_headers.h"
-#include "h2_session.h"
-#include "h2_util.h"
-#include "h2_h2.h"
-#include "mod_http2.h"
-
-const char *h2_tls_protos[] = {
- "h2", NULL
-};
-
-const char *h2_clear_protos[] = {
- "h2c", NULL
-};
-
-const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
-
-/*******************************************************************************
- * The optional mod_ssl functions we need.
- */
-static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https;
-static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *opt_ssl_var_lookup;
-
-
-/*******************************************************************************
- * HTTP/2 error stuff
- */
-static const char *h2_err_descr[] = {
- "no error", /* 0x0 */
- "protocol error",
- "internal error",
- "flow control error",
- "settings timeout",
- "stream closed", /* 0x5 */
- "frame size error",
- "refused stream",
- "cancel",
- "compression error",
- "connect error", /* 0xa */
- "enhance your calm",
- "inadequate security",
- "http/1.1 required",
-};
-
-const char *h2_h2_err_description(unsigned int h2_error)
-{
- if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
- return h2_err_descr[h2_error];
- }
- return "unknown http/2 error code";
-}
-
-/*******************************************************************************
- * Check connection security requirements of RFC 7540
- */
-
-/*
- * Black Listed Ciphers from RFC 7549 Appendix A
- *
- */
-static const char *RFC7540_names[] = {
- /* ciphers with NULL encrpytion */
- "NULL-MD5", /* TLS_NULL_WITH_NULL_NULL */
- /* same */ /* TLS_RSA_WITH_NULL_MD5 */
- "NULL-SHA", /* TLS_RSA_WITH_NULL_SHA */
- "NULL-SHA256", /* TLS_RSA_WITH_NULL_SHA256 */
- "PSK-NULL-SHA", /* TLS_PSK_WITH_NULL_SHA */
- "DHE-PSK-NULL-SHA", /* TLS_DHE_PSK_WITH_NULL_SHA */
- "RSA-PSK-NULL-SHA", /* TLS_RSA_PSK_WITH_NULL_SHA */
- "PSK-NULL-SHA256", /* TLS_PSK_WITH_NULL_SHA256 */
- "PSK-NULL-SHA384", /* TLS_PSK_WITH_NULL_SHA384 */
- "DHE-PSK-NULL-SHA256", /* TLS_DHE_PSK_WITH_NULL_SHA256 */
- "DHE-PSK-NULL-SHA384", /* TLS_DHE_PSK_WITH_NULL_SHA384 */
- "RSA-PSK-NULL-SHA256", /* TLS_RSA_PSK_WITH_NULL_SHA256 */
- "RSA-PSK-NULL-SHA384", /* TLS_RSA_PSK_WITH_NULL_SHA384 */
- "ECDH-ECDSA-NULL-SHA", /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
- "ECDHE-ECDSA-NULL-SHA", /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
- "ECDH-RSA-NULL-SHA", /* TLS_ECDH_RSA_WITH_NULL_SHA */
- "ECDHE-RSA-NULL-SHA", /* TLS_ECDHE_RSA_WITH_NULL_SHA */
- "AECDH-NULL-SHA", /* TLS_ECDH_anon_WITH_NULL_SHA */
- "ECDHE-PSK-NULL-SHA", /* TLS_ECDHE_PSK_WITH_NULL_SHA */
- "ECDHE-PSK-NULL-SHA256", /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
- "ECDHE-PSK-NULL-SHA384", /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
-
- /* DES/3DES ciphers */
- "PSK-3DES-EDE-CBC-SHA", /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
- "DHE-PSK-3DES-EDE-CBC-SHA", /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
- "RSA-PSK-3DES-EDE-CBC-SHA", /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
- "ECDH-ECDSA-DES-CBC3-SHA", /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
- "ECDHE-ECDSA-DES-CBC3-SHA", /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
- "ECDH-RSA-DES-CBC3-SHA", /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
- "ECDHE-RSA-DES-CBC3-SHA", /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
- "AECDH-DES-CBC3-SHA", /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
- "SRP-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
- "SRP-RSA-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
- "SRP-DSS-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
- "ECDHE-PSK-3DES-EDE-CBC-SHA", /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
- "DES-CBC-SHA", /* TLS_RSA_WITH_DES_CBC_SHA */
- "DES-CBC3-SHA", /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
- "DHE-DSS-DES-CBC3-SHA", /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
- "DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
- "DHE-RSA-DES-CBC3-SHA", /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
- "ADH-DES-CBC-SHA", /* TLS_DH_anon_WITH_DES_CBC_SHA */
- "ADH-DES-CBC3-SHA", /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
- "EXP-DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
- "DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_WITH_DES_CBC_SHA */
- "DH-DSS-DES-CBC3-SHA", /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
- "EXP-DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
- "DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_WITH_DES_CBC_SHA */
- "DH-RSA-DES-CBC3-SHA", /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
-
- /* blacklisted EXPORT ciphers */
- "EXP-RC4-MD5", /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
- "EXP-RC2-CBC-MD5", /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
- "EXP-DES-CBC-SHA", /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
- "EXP-DHE-DSS-DES-CBC-SHA", /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
- "EXP-DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
- "EXP-ADH-DES-CBC-SHA", /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
- "EXP-ADH-RC4-MD5", /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
-
- /* blacklisted RC4 encryption */
- "RC4-MD5", /* TLS_RSA_WITH_RC4_128_MD5 */
- "RC4-SHA", /* TLS_RSA_WITH_RC4_128_SHA */
- "ADH-RC4-MD5", /* TLS_DH_anon_WITH_RC4_128_MD5 */
- "KRB5-RC4-SHA", /* TLS_KRB5_WITH_RC4_128_SHA */
- "KRB5-RC4-MD5", /* TLS_KRB5_WITH_RC4_128_MD5 */
- "EXP-KRB5-RC4-SHA", /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
- "EXP-KRB5-RC4-MD5", /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
- "PSK-RC4-SHA", /* TLS_PSK_WITH_RC4_128_SHA */
- "DHE-PSK-RC4-SHA", /* TLS_DHE_PSK_WITH_RC4_128_SHA */
- "RSA-PSK-RC4-SHA", /* TLS_RSA_PSK_WITH_RC4_128_SHA */
- "ECDH-ECDSA-RC4-SHA", /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
- "ECDHE-ECDSA-RC4-SHA", /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
- "ECDH-RSA-RC4-SHA", /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
- "ECDHE-RSA-RC4-SHA", /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
- "AECDH-RC4-SHA", /* TLS_ECDH_anon_WITH_RC4_128_SHA */
- "ECDHE-PSK-RC4-SHA", /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
-
- /* blacklisted AES128 encrpytion ciphers */
- "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA */
- "DH-DSS-AES128-SHA", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
- "DH-RSA-AES128-SHA", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
- "DHE-DSS-AES128-SHA", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
- "DHE-RSA-AES128-SHA", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
- "ADH-AES128-SHA", /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
- "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
- "DH-DSS-AES128-SHA256", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
- "DH-RSA-AES128-SHA256", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
- "DHE-DSS-AES128-SHA256", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
- "DHE-RSA-AES128-SHA256", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
- "ECDH-ECDSA-AES128-SHA", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
- "ECDHE-ECDSA-AES128-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
- "ECDH-RSA-AES128-SHA", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
- "ECDHE-RSA-AES128-SHA", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
- "AECDH-AES128-SHA", /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
- "ECDHE-ECDSA-AES128-SHA256", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
- "ECDH-ECDSA-AES128-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
- "ECDHE-RSA-AES128-SHA256", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
- "ECDH-RSA-AES128-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
- "ADH-AES128-SHA256", /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
- "PSK-AES128-CBC-SHA", /* TLS_PSK_WITH_AES_128_CBC_SHA */
- "DHE-PSK-AES128-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
- "RSA-PSK-AES128-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
- "PSK-AES128-CBC-SHA256", /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
- "DHE-PSK-AES128-CBC-SHA256", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
- "RSA-PSK-AES128-CBC-SHA256", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
- "ECDHE-PSK-AES128-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
- "ECDHE-PSK-AES128-CBC-SHA256", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
- "AES128-CCM", /* TLS_RSA_WITH_AES_128_CCM */
- "AES128-CCM8", /* TLS_RSA_WITH_AES_128_CCM_8 */
- "PSK-AES128-CCM", /* TLS_PSK_WITH_AES_128_CCM */
- "PSK-AES128-CCM8", /* TLS_PSK_WITH_AES_128_CCM_8 */
- "AES128-GCM-SHA256", /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
- "DH-RSA-AES128-GCM-SHA256", /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
- "DH-DSS-AES128-GCM-SHA256", /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
- "ADH-AES128-GCM-SHA256", /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
- "PSK-AES128-GCM-SHA256", /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
- "RSA-PSK-AES128-GCM-SHA256", /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
- "ECDH-ECDSA-AES128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
- "ECDH-RSA-AES128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
- "SRP-AES-128-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
- "SRP-RSA-AES-128-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
- "SRP-DSS-AES-128-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
-
- /* blacklisted AES256 encrpytion ciphers */
- "AES256-SHA", /* TLS_RSA_WITH_AES_256_CBC_SHA */
- "DH-DSS-AES256-SHA", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
- "DH-RSA-AES256-SHA", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
- "DHE-DSS-AES256-SHA", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
- "DHE-RSA-AES256-SHA", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
- "ADH-AES256-SHA", /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
- "AES256-SHA256", /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
- "DH-DSS-AES256-SHA256", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
- "DH-RSA-AES256-SHA256", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
- "DHE-DSS-AES256-SHA256", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
- "DHE-RSA-AES256-SHA256", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
- "ADH-AES256-SHA256", /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
- "ECDH-ECDSA-AES256-SHA", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
- "ECDHE-ECDSA-AES256-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
- "ECDH-RSA-AES256-SHA", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
- "ECDHE-RSA-AES256-SHA", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
- "AECDH-AES256-SHA", /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
- "ECDHE-ECDSA-AES256-SHA384", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
- "ECDH-ECDSA-AES256-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
- "ECDHE-RSA-AES256-SHA384", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
- "ECDH-RSA-AES256-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
- "PSK-AES256-CBC-SHA", /* TLS_PSK_WITH_AES_256_CBC_SHA */
- "DHE-PSK-AES256-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
- "RSA-PSK-AES256-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
- "PSK-AES256-CBC-SHA384", /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
- "DHE-PSK-AES256-CBC-SHA384", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
- "RSA-PSK-AES256-CBC-SHA384", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
- "ECDHE-PSK-AES256-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
- "ECDHE-PSK-AES256-CBC-SHA384", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
- "SRP-AES-256-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
- "SRP-RSA-AES-256-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
- "SRP-DSS-AES-256-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
- "AES256-CCM", /* TLS_RSA_WITH_AES_256_CCM */
- "AES256-CCM8", /* TLS_RSA_WITH_AES_256_CCM_8 */
- "PSK-AES256-CCM", /* TLS_PSK_WITH_AES_256_CCM */
- "PSK-AES256-CCM8", /* TLS_PSK_WITH_AES_256_CCM_8 */
- "AES256-GCM-SHA384", /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
- "DH-RSA-AES256-GCM-SHA384", /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
- "DH-DSS-AES256-GCM-SHA384", /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
- "ADH-AES256-GCM-SHA384", /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
- "PSK-AES256-GCM-SHA384", /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
- "RSA-PSK-AES256-GCM-SHA384", /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
- "ECDH-ECDSA-AES256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
- "ECDH-RSA-AES256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
-
- /* blacklisted CAMELLIA128 encrpytion ciphers */
- "CAMELLIA128-SHA", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
- "DH-DSS-CAMELLIA128-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
- "DH-RSA-CAMELLIA128-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
- "DHE-DSS-CAMELLIA128-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
- "DHE-RSA-CAMELLIA128-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
- "ADH-CAMELLIA128-SHA", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
- "ECDHE-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "ECDH-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "ECDHE-RSA-CAMELLIA128-SHA256", /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "ECDH-RSA-CAMELLIA128-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "PSK-CAMELLIA128-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
- "DHE-PSK-CAMELLIA128-SHA256", /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
- "RSA-PSK-CAMELLIA128-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
- "ECDHE-PSK-CAMELLIA128-SHA256", /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
- "CAMELLIA128-GCM-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
- "DH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
- "DH-DSS-CAMELLIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
- "ADH-CAMELLIA128-GCM-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
- "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
- "ECDH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
- "PSK-CAMELLIA128-GCM-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
- "RSA-PSK-CAMELLIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
- "CAMELLIA128-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "DH-DSS-CAMELLIA128-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
- "DH-RSA-CAMELLIA128-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "DHE-DSS-CAMELLIA128-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
- "DHE-RSA-CAMELLIA128-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
- "ADH-CAMELLIA128-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
-
- /* blacklisted CAMELLIA256 encrpytion ciphers */
- "CAMELLIA256-SHA", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
- "DH-RSA-CAMELLIA256-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
- "DH-DSS-CAMELLIA256-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
- "DHE-DSS-CAMELLIA256-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
- "DHE-RSA-CAMELLIA256-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
- "ADH-CAMELLIA256-SHA", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
- "ECDHE-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
- "ECDH-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
- "ECDHE-RSA-CAMELLIA256-SHA384", /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
- "ECDH-RSA-CAMELLIA256-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
- "PSK-CAMELLIA256-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
- "DHE-PSK-CAMELLIA256-SHA384", /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
- "RSA-PSK-CAMELLIA256-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
- "ECDHE-PSK-CAMELLIA256-SHA384", /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
- "CAMELLIA256-SHA256", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
- "DH-DSS-CAMELLIA256-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
- "DH-RSA-CAMELLIA256-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
- "DHE-DSS-CAMELLIA256-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
- "DHE-RSA-CAMELLIA256-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
- "ADH-CAMELLIA256-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
- "CAMELLIA256-GCM-SHA384", /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
- "DH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
- "DH-DSS-CAMELLIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
- "ADH-CAMELLIA256-GCM-SHA384", /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
- "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
- "ECDH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
- "PSK-CAMELLIA256-GCM-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
- "RSA-PSK-CAMELLIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
-
- /* The blacklisted ARIA encrpytion ciphers */
- "ARIA128-SHA256", /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
- "ARIA256-SHA384", /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
- "DH-DSS-ARIA128-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
- "DH-DSS-ARIA256-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
- "DH-RSA-ARIA128-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
- "DH-RSA-ARIA256-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
- "DHE-DSS-ARIA128-SHA256", /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
- "DHE-DSS-ARIA256-SHA384", /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
- "DHE-RSA-ARIA128-SHA256", /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
- "DHE-RSA-ARIA256-SHA384", /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
- "ADH-ARIA128-SHA256", /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
- "ADH-ARIA256-SHA384", /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
- "ECDHE-ECDSA-ARIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
- "ECDHE-ECDSA-ARIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
- "ECDH-ECDSA-ARIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
- "ECDH-ECDSA-ARIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
- "ECDHE-RSA-ARIA128-SHA256", /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
- "ECDHE-RSA-ARIA256-SHA384", /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
- "ECDH-RSA-ARIA128-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
- "ECDH-RSA-ARIA256-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
- "ARIA128-GCM-SHA256", /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
- "ARIA256-GCM-SHA384", /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
- "DH-DSS-ARIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
- "DH-DSS-ARIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
- "DH-RSA-ARIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
- "DH-RSA-ARIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
- "ADH-ARIA128-GCM-SHA256", /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
- "ADH-ARIA256-GCM-SHA384", /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
- "ECDH-ECDSA-ARIA128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
- "ECDH-ECDSA-ARIA256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
- "ECDH-RSA-ARIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
- "ECDH-RSA-ARIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
- "PSK-ARIA128-SHA256", /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
- "PSK-ARIA256-SHA384", /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
- "DHE-PSK-ARIA128-SHA256", /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
- "DHE-PSK-ARIA256-SHA384", /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
- "RSA-PSK-ARIA128-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
- "RSA-PSK-ARIA256-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
- "ARIA128-GCM-SHA256", /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
- "ARIA256-GCM-SHA384", /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
- "RSA-PSK-ARIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
- "RSA-PSK-ARIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
- "ECDHE-PSK-ARIA128-SHA256", /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
- "ECDHE-PSK-ARIA256-SHA384", /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
-
- /* blacklisted SEED encryptions */
- "SEED-SHA", /*TLS_RSA_WITH_SEED_CBC_SHA */
- "DH-DSS-SEED-SHA", /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
- "DH-RSA-SEED-SHA", /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
- "DHE-DSS-SEED-SHA", /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
- "DHE-RSA-SEED-SHA", /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */
- "ADH-SEED-SHA", /* TLS_DH_anon_WITH_SEED_CBC_SHA */
-
- /* blacklisted KRB5 ciphers */
- "KRB5-DES-CBC-SHA", /* TLS_KRB5_WITH_DES_CBC_SHA */
- "KRB5-DES-CBC3-SHA", /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
- "KRB5-IDEA-CBC-SHA", /* TLS_KRB5_WITH_IDEA_CBC_SHA */
- "KRB5-DES-CBC-MD5", /* TLS_KRB5_WITH_DES_CBC_MD5 */
- "KRB5-DES-CBC3-MD5", /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
- "KRB5-IDEA-CBC-MD5", /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
- "EXP-KRB5-DES-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
- "EXP-KRB5-DES-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
- "EXP-KRB5-RC2-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
- "EXP-KRB5-RC2-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
-
- /* blacklisted exoticas */
- "DHE-DSS-CBC-SHA", /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
- "IDEA-CBC-SHA", /* TLS_RSA_WITH_IDEA_CBC_SHA */
-
- /* not really sure if the following names are correct */
- "SSL3_CK_SCSV", /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
- "SSL3_CK_FALLBACK_SCSV"
-};
-static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
-
-
-static apr_hash_t *BLCNames;
-
-static void cipher_init(apr_pool_t *pool)
-{
- apr_hash_t *hash = apr_hash_make(pool);
- const char *source;
- unsigned int i;
-
- source = "rfc7540";
- for (i = 0; i < RFC7540_names_LEN; ++i) {
- apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
- }
-
- BLCNames = hash;
-}
-
-static int cipher_is_blacklisted(const char *cipher, const char **psource)
-{
- *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
- return !!*psource;
-}
-
-/*******************************************************************************
- * Hooks for processing incoming connections:
- * - process_conn take over connection in case of h2
- */
-static int h2_h2_process_conn(conn_rec* c);
-static int h2_h2_pre_close_conn(conn_rec* c);
-static int h2_h2_post_read_req(request_rec *r);
-static int h2_h2_late_fixups(request_rec *r);
-
-/*******************************************************************************
- * Once per lifetime init, retrieve optional functions
- */
-apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s)
-{
- (void)pool;
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init");
- opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
- opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
-
- if (!opt_ssl_is_https || !opt_ssl_var_lookup) {
- ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
- APLOGNO(02951) "mod_ssl does not seem to be enabled");
- }
-
- cipher_init(pool);
-
- return APR_SUCCESS;
-}
-
-int h2_h2_is_tls(conn_rec *c)
-{
- return opt_ssl_is_https && opt_ssl_is_https(c);
-}
-
-int h2_is_acceptable_connection(conn_rec *c, int require_all)
-{
- int is_tls = h2_h2_is_tls(c);
- const h2_config *cfg = h2_config_get(c);
-
- if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) {
- /* Check TLS connection for modern TLS parameters, as defined in
- * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
- */
- apr_pool_t *pool = c->pool;
- server_rec *s = c->base_server;
- char *val;
-
- if (!opt_ssl_var_lookup) {
- /* unable to check */
- return 0;
- }
-
- /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
- */
- val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_PROTOCOL");
- if (val && *val) {
- if (strncmp("TLS", val, 3)
- || !strcmp("TLSv1", val)
- || !strcmp("TLSv1.1", val)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03050)
- "h2_h2(%ld): tls protocol not suitable: %s",
- (long)c->id, val);
- return 0;
- }
- }
- else if (require_all) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03051)
- "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
- return 0;
- }
-
- /* Check TLS cipher blacklist
- */
- val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_CIPHER");
- if (val && *val) {
- const char *source;
- if (cipher_is_blacklisted(val, &source)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
- "h2_h2(%ld): tls cipher %s blacklisted by %s",
- (long)c->id, val, source);
- return 0;
- }
- }
- else if (require_all) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
- "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
- return 0;
- }
- }
- return 1;
-}
-
-int h2_allows_h2_direct(conn_rec *c)
-{
- const h2_config *cfg = h2_config_get(c);
- int is_tls = h2_h2_is_tls(c);
- const char *needed_protocol = is_tls? "h2" : "h2c";
- int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT);
-
- if (h2_direct < 0) {
- h2_direct = is_tls? 0 : 1;
- }
- return (h2_direct
- && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
-}
-
-int h2_allows_h2_upgrade(conn_rec *c)
-{
- const h2_config *cfg = h2_config_get(c);
- int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE);
-
- return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c));
-}
-
-/*******************************************************************************
- * Register various hooks
- */
-static const char* const mod_ssl[] = { "mod_ssl.c", NULL};
-static const char* const mod_reqtimeout[] = { "mod_reqtimeout.c", NULL};
-
-void h2_h2_register_hooks(void)
-{
- /* Our main processing needs to run quite late. Definitely after mod_ssl,
- * as we need its connection filters, but also before reqtimeout as its
- * method of timeouts is specific to HTTP/1.1 (as of now).
- * The core HTTP/1 processing run as REALLY_LAST, so we will have
- * a chance to take over before it.
- */
- ap_hook_process_connection(h2_h2_process_conn,
- mod_ssl, mod_reqtimeout, APR_HOOK_LAST);
-
- /* One last chance to properly say goodbye if we have not done so
- * already. */
- ap_hook_pre_close_connection(h2_h2_pre_close_conn, NULL, mod_ssl, APR_HOOK_LAST);
-
- /* With "H2SerializeHeaders On", we install the filter in this hook
- * that parses the response. This needs to happen before any other post
- * read function terminates the request with an error. Otherwise we will
- * never see the response.
- */
- ap_hook_post_read_request(h2_h2_post_read_req, NULL, NULL, APR_HOOK_REALLY_FIRST);
- ap_hook_fixups(h2_h2_late_fixups, NULL, NULL, APR_HOOK_LAST);
-
- /* special bucket type transfer through a h2_bucket_beam */
- h2_register_bucket_beamer(h2_bucket_headers_beam);
- h2_register_bucket_beamer(h2_bucket_observer_beam);
-}
-
-int h2_h2_process_conn(conn_rec* c)
-{
- apr_status_t status;
- h2_ctx *ctx;
-
- if (c->master) {
- return DECLINED;
- }
-
- ctx = h2_ctx_get(c, 0);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
- if (h2_ctx_is_task(ctx)) {
- /* our stream pseudo connection */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, task, declined");
- return DECLINED;
- }
-
- if (!ctx && c->keepalives == 0) {
- const char *proto = ap_get_protocol(c);
-
- if (APLOGctrace1(c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
- "new connection using protocol '%s', direct=%d, "
- "tls acceptable=%d", proto, h2_allows_h2_direct(c),
- h2_is_acceptable_connection(c, 1));
- }
-
- if (!strcmp(AP_PROTOCOL_HTTP1, proto)
- && h2_allows_h2_direct(c)
- && h2_is_acceptable_connection(c, 1)) {
- /* Fresh connection still is on http/1.1 and H2Direct is enabled.
- * Otherwise connection is in a fully acceptable state.
- * -> peek at the first 24 incoming bytes
- */
- apr_bucket_brigade *temp;
- char *s = NULL;
- apr_size_t slen;
-
- temp = apr_brigade_create(c->pool, c->bucket_alloc);
- status = ap_get_brigade(c->input_filters, temp,
- AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
-
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
- "h2_h2, error reading 24 bytes speculative");
- apr_brigade_destroy(temp);
- return DECLINED;
- }
-
- apr_brigade_pflatten(temp, &s, &slen, c->pool);
- if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_h2, direct mode detected");
- if (!ctx) {
- ctx = h2_ctx_get(c, 1);
- }
- h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c");
- }
- else if (APLOGctrace2(c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
- "h2_h2, not detected in %d bytes(base64): %s",
- (int)slen, h2_util_base64url_encode(s, slen, c->pool));
- }
-
- apr_brigade_destroy(temp);
- }
- }
-
- if (ctx) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
- if (!h2_ctx_session_get(ctx)) {
- status = h2_conn_setup(ctx, c, NULL);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
- if (status != APR_SUCCESS) {
- h2_ctx_clear(c);
- return !OK;
- }
- }
- h2_conn_run(ctx, c);
- return OK;
- }
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
- return DECLINED;
-}
-
-static int h2_h2_pre_close_conn(conn_rec *c)
-{
- h2_ctx *ctx;
-
- /* slave connection? */
- if (c->master) {
- return DECLINED;
- }
-
- ctx = h2_ctx_get(c, 0);
- if (ctx) {
- /* If the session has been closed correctly already, we will not
- * find a h2_ctx here. The presence indicates that the session
- * is still ongoing. */
- return h2_conn_pre_close(ctx, c);
- }
- return DECLINED;
-}
-
-static void check_push(request_rec *r, const char *tag)
-{
- const h2_config *conf = h2_config_rget(r);
- if (!r->expecting_100
- && conf && conf->push_list && conf->push_list->nelts > 0) {
- int i, old_status;
- const char *old_line;
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "%s, early announcing %d resources for push",
- tag, conf->push_list->nelts);
- for (i = 0; i < conf->push_list->nelts; ++i) {
- h2_push_res *push = &APR_ARRAY_IDX(conf->push_list, i, h2_push_res);
- apr_table_add(r->headers_out, "Link",
- apr_psprintf(r->pool, "<%s>; rel=preload%s",
- push->uri_ref, push->critical? "; critical" : ""));
- }
- old_status = r->status;
- old_line = r->status_line;
- r->status = 103;
- r->status_line = "103 Early Hints";
- ap_send_interim_response(r, 1);
- r->status = old_status;
- r->status_line = old_line;
- }
-}
-
-static int h2_h2_post_read_req(request_rec *r)
-{
- /* slave connection? */
- if (r->connection->master) {
- h2_ctx *ctx = h2_ctx_rget(r);
- struct h2_task *task = h2_ctx_get_task(ctx);
- /* This hook will get called twice on internal redirects. Take care
- * that we manipulate filters only once. */
- if (task && !task->filters_set) {
- ap_filter_t *f;
- ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
- "h2_task(%s): adding request filters", task->id);
-
- /* setup the correct filters to process the request for h2 */
- ap_add_input_filter("H2_REQUEST", task, r, r->connection);
-
- /* replace the core http filter that formats response headers
- * in HTTP/1 with our own that collects status and headers */
- ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
- ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
-
- for (f = r->input_filters; f; f = f->next) {
- if (!strcmp("H2_SLAVE_IN", f->frec->name)) {
- f->r = r;
- break;
- }
- }
- ap_add_output_filter("H2_TRAILERS_OUT", task, r, r->connection);
- task->filters_set = 1;
- }
- }
- return DECLINED;
-}
-
-static int h2_h2_late_fixups(request_rec *r)
-{
- /* slave connection? */
- if (r->connection->master) {
- h2_ctx *ctx = h2_ctx_rget(r);
- struct h2_task *task = h2_ctx_get_task(ctx);
- if (task) {
- /* check if we copy vs. setaside files in this location */
- task->output.copy_files = h2_config_geti(h2_config_rget(r),
- H2_CONF_COPY_FILES);
- if (task->output.copy_files) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
- "h2_slave_out(%s): copy_files on", task->id);
- h2_beam_on_file_beam(task->output.beam, h2_beam_no_files, NULL);
- }
- check_push(r, "late_fixup");
- }
- }
- return DECLINED;
-}
-
diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h
deleted file mode 100644
index 367823d..0000000
--- a/modules/http2/h2_h2.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_h2__
-#define __mod_h2__h2_h2__
-
-/**
- * List of ALPN protocol identifiers that we support in cleartext
- * negotiations. NULL terminated.
- */
-extern const char *h2_clear_protos[];
-
-/**
- * List of ALPN protocol identifiers that we support in TLS encrypted
- * negotiations. NULL terminated.
- */
-extern const char *h2_tls_protos[];
-
-/**
- * Provide a user readable description of the HTTP/2 error code-
- * @param h2_error http/2 error code, as in rfc 7540, ch. 7
- * @return textual description of code or that it is unknown.
- */
-const char *h2_h2_err_description(unsigned int h2_error);
-
-/*
- * One time, post config initialization.
- */
-apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s);
-
-/* Is the connection a TLS connection?
- */
-int h2_h2_is_tls(conn_rec *c);
-
-/* Register apache hooks for h2 protocol
- */
-void h2_h2_register_hooks(void);
-
-/**
- * Check if the given connection fulfills the requirements as configured.
- * @param c the connection
- * @param require_all != 0 iff any missing connection properties make
- * the test fail. For example, a cipher might not have been selected while
- * the handshake is still ongoing.
- * @return != 0 iff connection requirements are met
- */
-int h2_is_acceptable_connection(conn_rec *c, int require_all);
-
-/**
- * Check if the "direct" HTTP/2 mode of protocol handling is enabled
- * for the given connection.
- * @param c the connection to check
- * @return != 0 iff direct mode is enabled
- */
-int h2_allows_h2_direct(conn_rec *c);
-
-/**
- * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
- * for the given connection.
- * @param c the connection to check
- * @return != 0 iff Upgrade switching is enabled
- */
-int h2_allows_h2_upgrade(conn_rec *c);
-
-
-#endif /* defined(__mod_h2__h2_h2__) */
diff --git a/modules/http2/h2_headers.c b/modules/http2/h2_headers.c
index 8b7add6..d9b3fd0 100644
--- a/modules/http2/h2_headers.c
+++ b/modules/http2/h2_headers.c
@@ -27,11 +27,13 @@
#include
#include "h2_private.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
+#include "h2_config.h"
#include "h2_util.h"
#include "h2_request.h"
#include "h2_headers.h"
+#if !AP_HAS_RESPONSE_BUCKETS
static int is_unsafe(server_rec *s)
{
@@ -63,6 +65,7 @@ apr_bucket * h2_bucket_headers_make(apr_bucket *b, h2_headers *r)
b = apr_bucket_shared_make(b, br, 0, 0);
b->type = &h2_bucket_type_headers;
+ b->length = 0;
return b;
}
@@ -87,32 +90,35 @@ h2_headers *h2_bucket_headers_get(apr_bucket *b)
return NULL;
}
+static void bucket_destroy(void *data)
+{
+ h2_bucket_headers *h = data;
+
+ if (apr_bucket_shared_destroy(h)) {
+ apr_bucket_free(h);
+ }
+}
+
const apr_bucket_type_t h2_bucket_type_headers = {
"H2HEADERS", 5, APR_BUCKET_METADATA,
- apr_bucket_destroy_noop,
+ bucket_destroy,
bucket_read,
apr_bucket_setaside_noop,
apr_bucket_split_notimpl,
apr_bucket_shared_copy
};
-apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
- apr_bucket_brigade *dest,
- const apr_bucket *src)
+apr_bucket *h2_bucket_headers_clone(apr_bucket *b, apr_pool_t *pool,
+ apr_bucket_alloc_t *list)
{
- if (H2_BUCKET_IS_HEADERS(src)) {
- h2_headers *r = ((h2_bucket_headers *)src->data)->headers;
- apr_bucket *b = h2_bucket_headers_create(dest->bucket_alloc, r);
- APR_BRIGADE_INSERT_TAIL(dest, b);
- return b;
- }
- return NULL;
+ h2_headers *hdrs = ((h2_bucket_headers *)b->data)->headers;
+ return h2_bucket_headers_create(list, h2_headers_clone(pool, hdrs));
}
-h2_headers *h2_headers_create(int status, apr_table_t *headers_in,
- apr_table_t *notes, apr_off_t raw_bytes,
- apr_pool_t *pool)
+h2_headers *h2_headers_create(int status, const apr_table_t *headers_in,
+ const apr_table_t *notes, apr_off_t raw_bytes,
+ apr_pool_t *pool)
{
h2_headers *headers = apr_pcalloc(pool, sizeof(h2_headers));
headers->status = status;
@@ -123,26 +129,55 @@ h2_headers *h2_headers_create(int status, apr_table_t *headers_in,
return headers;
}
+static int add_header_lengths(void *ctx, const char *name, const char *value)
+{
+ apr_size_t *plen = ctx;
+ *plen += strlen(name) + strlen(value);
+ return 1;
+}
+
+apr_size_t h2_headers_length(h2_headers *headers)
+{
+ apr_size_t len = 0;
+ apr_table_do(add_header_lengths, &len, headers->headers, NULL);
+ return len;
+}
+
+apr_size_t h2_bucket_headers_headers_length(apr_bucket *b)
+{
+ h2_headers *h = h2_bucket_headers_get(b);
+ return h? h2_headers_length(h) : 0;
+}
+
h2_headers *h2_headers_rcreate(request_rec *r, int status,
- apr_table_t *header, apr_pool_t *pool)
+ const apr_table_t *header, apr_pool_t *pool)
{
h2_headers *headers = h2_headers_create(status, header, r->notes, 0, pool);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, headers->status, r,
+ "h2_headers_rcreate(%ld): status=%d",
+ (long)r->connection->id, status);
if (headers->status == HTTP_FORBIDDEN) {
- const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
- if (cause) {
- /* This request triggered a TLS renegotiation that is now allowed
- * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
- */
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, headers->status, r,
- APLOGNO(03061)
- "h2_headers(%ld): renegotiate forbidden, cause: %s",
- (long)r->connection->id, cause);
- headers->status = H2_ERR_HTTP_1_1_REQUIRED;
+ request_rec *r_prev;
+ for (r_prev = r; r_prev != NULL; r_prev = r_prev->prev) {
+ const char *cause = apr_table_get(r_prev->notes, "ssl-renegotiate-forbidden");
+ if (cause) {
+ /* This request triggered a TLS renegotiation that is not allowed
+ * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, headers->status, r,
+ APLOGNO(10399)
+ "h2_headers(%ld): renegotiate forbidden, cause: %s",
+ (long)r->connection->id, cause);
+ headers->status = H2_ERR_HTTP_1_1_REQUIRED;
+ break;
+ }
}
}
if (is_unsafe(r->server)) {
- apr_table_setn(headers->notes, H2_HDR_CONFORMANCE,
- H2_HDR_CONFORMANCE_UNSAFE);
+ apr_table_setn(headers->notes, H2_HDR_CONFORMANCE, H2_HDR_CONFORMANCE_UNSAFE);
+ }
+ if (h2_config_rgeti(r, H2_CONF_PUSH) == 0 && h2_config_sgeti(r->server, H2_CONF_PUSH) != 0) {
+ apr_table_setn(headers->notes, H2_PUSH_MODE_NOTE, "0");
}
return headers;
}
@@ -152,6 +187,11 @@ h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h)
return h2_headers_create(h->status, h->headers, h->notes, h->raw_bytes, pool);
}
+h2_headers *h2_headers_clone(apr_pool_t *pool, h2_headers *h)
+{
+ return h2_headers_create(h->status, h->headers, h->notes, h->raw_bytes, pool);
+}
+
h2_headers *h2_headers_die(apr_status_t type,
const h2_request *req, apr_pool_t *pool)
{
@@ -171,8 +211,9 @@ h2_headers *h2_headers_die(apr_status_t type,
return headers;
}
-int h2_headers_are_response(h2_headers *headers)
+int h2_headers_are_final_response(h2_headers *headers)
{
return headers->status >= 200;
}
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_headers.h b/modules/http2/h2_headers.h
index 840e8c4..3d78dc3 100644
--- a/modules/http2/h2_headers.h
+++ b/modules/http2/h2_headers.h
@@ -19,8 +19,19 @@
#include "h2.h"
+#if !AP_HAS_RESPONSE_BUCKETS
+
struct h2_bucket_beam;
+typedef struct h2_headers h2_headers;
+struct h2_headers {
+ int status;
+ apr_table_t *headers;
+ apr_table_t *notes;
+ apr_off_t raw_bytes; /* RAW network bytes that generated this request - if known. */
+};
+
+
extern const apr_bucket_type_t h2_bucket_type_headers;
#define H2_BUCKET_IS_HEADERS(e) (e->type == &h2_bucket_type_headers)
@@ -32,10 +43,6 @@ apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list,
h2_headers *h2_bucket_headers_get(apr_bucket *b);
-apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
- apr_bucket_brigade *dest,
- const apr_bucket *src);
-
/**
* Create the headers from the given status and headers
* @param status the headers status
@@ -44,8 +51,8 @@ apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
* @param raw_bytes the raw network bytes (if known) used to transmit these
* @param pool the memory pool to use
*/
-h2_headers *h2_headers_create(int status, apr_table_t *header,
- apr_table_t *notes, apr_off_t raw_bytes,
+h2_headers *h2_headers_create(int status, const apr_table_t *header,
+ const apr_table_t *notes, apr_off_t raw_bytes,
apr_pool_t *pool);
/**
@@ -56,24 +63,45 @@ h2_headers *h2_headers_create(int status, apr_table_t *header,
* @param pool the memory pool to use
*/
h2_headers *h2_headers_rcreate(request_rec *r, int status,
- apr_table_t *header, apr_pool_t *pool);
+ const apr_table_t *header, apr_pool_t *pool);
/**
- * Clone the headers into another pool. This will not copy any
+ * Copy the headers into another pool. This will not copy any
* header strings.
*/
h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h);
+/**
+ * Clone the headers into another pool. This will also clone any
+ * header strings.
+ */
+h2_headers *h2_headers_clone(apr_pool_t *pool, h2_headers *h);
+
/**
* Create the headers for the given error.
- * @param stream_id id of the stream to create the headers for
* @param type the error code
* @param req the original h2_request
* @param pool the memory pool to use
*/
h2_headers *h2_headers_die(apr_status_t type,
- const struct h2_request *req, apr_pool_t *pool);
+ const struct h2_request *req, apr_pool_t *pool);
+
+int h2_headers_are_final_response(h2_headers *headers);
+
+/**
+ * Give the number of bytes of all contained header strings.
+ */
+apr_size_t h2_headers_length(h2_headers *headers);
+
+/**
+ * For H2HEADER buckets, return the length of all contained header strings.
+ * For all other buckets, return 0.
+ */
+apr_size_t h2_bucket_headers_headers_length(apr_bucket *b);
+
+apr_bucket *h2_bucket_headers_clone(apr_bucket *b, apr_pool_t *pool,
+ apr_bucket_alloc_t *list);
-int h2_headers_are_response(h2_headers *headers);
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
#endif /* defined(__mod_h2__h2_headers__) */
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
index 0fae117..2aeea42 100644
--- a/modules/http2/h2_mplx.c
+++ b/modules/http2/h2_mplx.c
@@ -26,7 +26,9 @@
#include
#include
+#include
#include
+#include
#include
@@ -36,15 +38,14 @@
#include "h2_private.h"
#include "h2_bucket_beam.h"
#include "h2_config.h"
-#include "h2_conn.h"
-#include "h2_ctx.h"
-#include "h2_h2.h"
+#include "h2_c1.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
#include "h2_mplx.h"
-#include "h2_ngn_shed.h"
#include "h2_request.h"
#include "h2_stream.h"
#include "h2_session.h"
-#include "h2_task.h"
+#include "h2_c2.h"
#include "h2_workers.h"
#include "h2_util.h"
@@ -54,16 +55,40 @@ typedef struct {
h2_mplx *m;
h2_stream *stream;
apr_time_t now;
+ apr_size_t count;
} stream_iter_ctx;
-apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s)
+static conn_rec *c2_prod_next(void *baton, int *phas_more);
+static void c2_prod_done(void *baton, conn_rec *c2);
+static void workers_shutdown(void *baton, int graceful);
+
+static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx);
+static void m_be_annoyed(h2_mplx *m);
+
+static apr_status_t mplx_pollset_create(h2_mplx *m);
+static apr_status_t mplx_pollset_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx);
+
+static apr_pool_t *pchild;
+
+/* APR callback invoked if allocation fails. */
+static int abort_on_oom(int retcode)
{
+ ap_abort_on_oom();
+ return retcode; /* unreachable, hopefully. */
+}
+
+apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s)
+{
+ pchild = pool;
return APR_SUCCESS;
}
#define H2_MPLX_ENTER(m) \
- do { apr_status_t rv; if ((rv = apr_thread_mutex_lock(m->lock)) != APR_SUCCESS) {\
- return rv;\
+ do { apr_status_t rv_lock; if ((rv_lock = apr_thread_mutex_lock(m->lock)) != APR_SUCCESS) {\
+ return rv_lock;\
} } while(0)
#define H2_MPLX_LEAVE(m) \
@@ -72,71 +97,150 @@ apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s)
#define H2_MPLX_ENTER_ALWAYS(m) \
apr_thread_mutex_lock(m->lock)
-#define H2_MPLX_ENTER_MAYBE(m, lock) \
- if (lock) apr_thread_mutex_lock(m->lock)
-
-#define H2_MPLX_LEAVE_MAYBE(m, lock) \
- if (lock) apr_thread_mutex_unlock(m->lock)
+#define H2_MPLX_ENTER_MAYBE(m, dolock) \
+ if (dolock) apr_thread_mutex_lock(m->lock)
-static void check_data_for(h2_mplx *m, h2_stream *stream, int lock);
+#define H2_MPLX_LEAVE_MAYBE(m, dolock) \
+ if (dolock) apr_thread_mutex_unlock(m->lock)
-static void stream_output_consumed(void *ctx,
- h2_bucket_beam *beam, apr_off_t length)
+static void c1_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
{
- h2_stream *stream = ctx;
- h2_task *task = stream->task;
-
- if (length > 0 && task && task->assigned) {
- h2_req_engine_out_consumed(task->assigned, task->c, length);
- }
+ h2_stream_in_consumed(ctx, length);
}
-static void stream_input_ev(void *ctx, h2_bucket_beam *beam)
+static int stream_is_running(h2_stream *stream)
{
- h2_stream *stream = ctx;
- h2_mplx *m = stream->session->mplx;
- apr_atomic_set32(&m->event_pending, 1);
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+ return conn_ctx && apr_atomic_read32(&conn_ctx->started) != 0
+ && apr_atomic_read32(&conn_ctx->done) == 0;
}
-static void stream_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
+int h2_mplx_c1_stream_is_running(h2_mplx *m, h2_stream *stream)
{
- h2_stream_in_consumed(ctx, length);
+ int rv;
+
+ H2_MPLX_ENTER(m);
+ rv = stream_is_running(stream);
+ H2_MPLX_LEAVE(m);
+ return rv;
}
-static void stream_joined(h2_mplx *m, h2_stream *stream)
+static void c1c2_stream_joined(h2_mplx *m, h2_stream *stream)
{
- ap_assert(!stream->task || stream->task->worker_done);
+ ap_assert(!stream_is_running(stream));
h2_ihash_remove(m->shold, stream->id);
- h2_ihash_add(m->spurge, stream);
+ APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
}
-static void stream_cleanup(h2_mplx *m, h2_stream *stream)
+static void m_stream_cleanup(h2_mplx *m, h2_stream *stream)
{
- ap_assert(stream->state == H2_SS_CLEANUP);
+ h2_conn_ctx_t *c2_ctx = h2_conn_ctx_get(stream->c2);
- if (stream->input) {
- h2_beam_on_consumed(stream->input, NULL, NULL, NULL);
- h2_beam_abort(stream->input);
- }
- if (stream->output) {
- h2_beam_on_produced(stream->output, NULL, NULL);
- h2_beam_leave(stream->output);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, unsubscribing from beam events"));
+ if (c2_ctx) {
+ if (c2_ctx->beam_out) {
+ h2_beam_on_was_empty(c2_ctx->beam_out, NULL, NULL);
+ }
+ if (c2_ctx->beam_in) {
+ h2_beam_on_send(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_received(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_eagain(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_consumed(c2_ctx->beam_in, NULL, NULL);
+ }
}
-
- h2_stream_cleanup(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, removing from registries"));
+ ap_assert(stream->state == H2_SS_CLEANUP);
+ h2_stream_cleanup(stream);
h2_ihash_remove(m->streams, stream->id);
h2_iq_remove(m->q, stream->id);
- h2_ififo_remove(m->readyq, stream->id);
- h2_ihash_add(m->shold, stream);
-
- if (!stream->task || stream->task->worker_done) {
- stream_joined(m, stream);
+
+ if (c2_ctx) {
+ if (!stream_is_running(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, c2 is done, move to spurge"));
+ /* processing has finished */
+ APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, c2 is running, abort"));
+ /* c2 is still running */
+ h2_c2_abort(stream->c2, m->c1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, c2 is done, move to shold"));
+ h2_ihash_add(m->shold, stream);
+ }
}
- else if (stream->task) {
- stream->task->c->aborted = 1;
- apr_thread_cond_broadcast(m->task_thawed);
+ else {
+ /* never started */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, never started, move to spurge"));
+ APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+ }
+}
+
+static h2_c2_transit *c2_transit_create(h2_mplx *m)
+{
+ apr_allocator_t *allocator;
+ apr_pool_t *ptrans;
+ h2_c2_transit *transit;
+ apr_status_t rv;
+
+ /* We create a pool with its own allocator to be used for
+ * processing a request. This is the only way to have the processing
+ * independent of its parent pool in the sense that it can work in
+ * another thread.
+ */
+
+ rv = apr_allocator_create(&allocator);
+ if (rv == APR_SUCCESS) {
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ rv = apr_pool_create_ex(&ptrans, m->pool, NULL, allocator);
+ }
+ if (rv != APR_SUCCESS) {
+ /* maybe the log goes through, maybe not. */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1,
+ APLOGNO(10004) "h2_mplx: create transit pool");
+ ap_abort_on_oom();
+ return NULL; /* should never be reached. */
+ }
+
+ apr_allocator_owner_set(allocator, ptrans);
+ apr_pool_abort_set(abort_on_oom, ptrans);
+ apr_pool_tag(ptrans, "h2_c2_transit");
+
+ transit = apr_pcalloc(ptrans, sizeof(*transit));
+ transit->pool = ptrans;
+ transit->bucket_alloc = apr_bucket_alloc_create(ptrans);
+ return transit;
+}
+
+static void c2_transit_destroy(h2_c2_transit *transit)
+{
+ apr_pool_destroy(transit->pool);
+}
+
+static h2_c2_transit *c2_transit_get(h2_mplx *m)
+{
+ h2_c2_transit **ptransit = apr_array_pop(m->c2_transits);
+ if (ptransit) {
+ return *ptransit;
+ }
+ return c2_transit_create(m);
+}
+
+static void c2_transit_recycle(h2_mplx *m, h2_c2_transit *transit)
+{
+ if (m->c2_transits->nelts >= APR_INT32_MAX ||
+ (apr_uint32_t)m->c2_transits->nelts >= m->max_spare_transits) {
+ c2_transit_destroy(transit);
+ }
+ else {
+ APR_ARRAY_PUSH(m->c2_transits, h2_c2_transit*) = transit;
}
}
@@ -151,208 +255,118 @@ static void stream_cleanup(h2_mplx *m, h2_stream *stream)
* their HTTP/1 cousins, the separate allocator seems to work better
* than protecting a shared h2_session one with an own lock.
*/
-h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent,
- const h2_config *conf,
- h2_workers *workers)
+h2_mplx *h2_mplx_c1_create(int child_num, apr_uint32_t id, h2_stream *stream0,
+ server_rec *s, apr_pool_t *parent,
+ h2_workers *workers)
{
+ h2_conn_ctx_t *conn_ctx;
apr_status_t status = APR_SUCCESS;
apr_allocator_t *allocator;
- apr_thread_mutex_t *mutex;
- h2_mplx *m;
- h2_ctx *ctx = h2_ctx_get(c, 0);
- ap_assert(conf);
+ apr_thread_mutex_t *mutex = NULL;
+ h2_mplx *m = NULL;
m = apr_pcalloc(parent, sizeof(h2_mplx));
- if (m) {
- m->id = c->id;
- m->c = c;
- m->s = (ctx? h2_ctx_server_get(ctx) : NULL);
- if (!m->s) {
- m->s = c->base_server;
- }
-
- /* We create a pool with its own allocator to be used for
- * processing slave connections. This is the only way to have the
- * processing independant of its parent pool in the sense that it
- * can work in another thread. Also, the new allocator needs its own
- * mutex to synchronize sub-pools.
- */
- status = apr_allocator_create(&allocator);
- if (status != APR_SUCCESS) {
- return NULL;
- }
- apr_allocator_max_free_set(allocator, ap_max_mem_free);
- apr_pool_create_ex(&m->pool, parent, NULL, allocator);
- if (!m->pool) {
- apr_allocator_destroy(allocator);
- return NULL;
- }
- apr_pool_tag(m->pool, "h2_mplx");
- apr_allocator_owner_set(allocator, m->pool);
- status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT,
- m->pool);
- if (status != APR_SUCCESS) {
- apr_pool_destroy(m->pool);
- return NULL;
- }
- apr_allocator_mutex_set(allocator, mutex);
-
- status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
- m->pool);
- if (status != APR_SUCCESS) {
- apr_pool_destroy(m->pool);
- return NULL;
- }
-
- status = apr_thread_cond_create(&m->task_thawed, m->pool);
- if (status != APR_SUCCESS) {
- apr_pool_destroy(m->pool);
- return NULL;
- }
-
- m->max_streams = h2_config_geti(conf, H2_CONF_MAX_STREAMS);
- m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
-
- m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
- m->sredo = h2_ihash_create(m->pool, offsetof(h2_stream,id));
- m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
- m->spurge = h2_ihash_create(m->pool, offsetof(h2_stream,id));
- m->q = h2_iq_create(m->pool, m->max_streams);
-
- status = h2_ififo_set_create(&m->readyq, m->pool, m->max_streams);
- if (status != APR_SUCCESS) {
- apr_pool_destroy(m->pool);
- return NULL;
- }
+ m->stream0 = stream0;
+ m->c1 = stream0->c2;
+ m->s = s;
+ m->child_num = child_num;
+ m->id = id;
+
+ /* We create a pool with its own allocator to be used for
+ * processing secondary connections. This is the only way to have the
+ * processing independent of its parent pool in the sense that it
+ * can work in another thread. Also, the new allocator needs its own
+ * mutex to synchronize sub-pools.
+ */
+ status = apr_allocator_create(&allocator);
+ if (status != APR_SUCCESS) {
+ allocator = NULL;
+ goto failure;
+ }
+
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ apr_pool_create_ex(&m->pool, parent, NULL, allocator);
+ if (!m->pool) goto failure;
+
+ apr_pool_tag(m->pool, "h2_mplx");
+ apr_allocator_owner_set(allocator, m->pool);
+
+ status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (APR_SUCCESS != status) goto failure;
+ apr_allocator_mutex_set(allocator, mutex);
+
+ status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (APR_SUCCESS != status) goto failure;
+
+ m->max_streams = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
+ m->stream_max_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
+
+ m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+ m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+ m->spurge = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+ m->q = h2_iq_create(m->pool, m->max_streams);
+
+ m->workers = workers;
+ m->processing_max = H2MIN(h2_workers_get_max_workers(workers), m->max_streams);
+ m->processing_limit = 6; /* the original h1 max parallel connections */
+ m->last_mood_change = apr_time_now();
+ m->mood_update_interval = apr_time_from_msec(100);
+
+ status = mplx_pollset_create(m);
+ if (APR_SUCCESS != status) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, m->c1, APLOGNO(10308)
+ "nghttp2: could not create pollset");
+ goto failure;
+ }
+ m->streams_ev_in = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+ m->streams_ev_out = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+
+ m->streams_input_read = h2_iq_create(m->pool, 10);
+ m->streams_output_written = h2_iq_create(m->pool, 10);
+ status = apr_thread_mutex_create(&m->poll_lock, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (APR_SUCCESS != status) goto failure;
+
+ conn_ctx = h2_conn_ctx_get(m->c1);
+ if (conn_ctx->pfd.reqevents) {
+ apr_pollset_add(m->pollset, &conn_ctx->pfd);
+ }
+
+ m->max_spare_transits = 3;
+ m->c2_transits = apr_array_make(m->pool, (int)m->max_spare_transits,
+ sizeof(h2_c2_transit*));
+
+ m->producer = h2_workers_register(workers, m->pool,
+ apr_psprintf(m->pool, "h2-%u",
+ (unsigned int)m->id),
+ c2_prod_next, c2_prod_done,
+ workers_shutdown, m);
+ return m;
- m->workers = workers;
- m->max_active = workers->max_workers;
- m->limit_active = 6; /* the original h1 max parallel connections */
- m->last_limit_change = m->last_idle_block = apr_time_now();
- m->limit_change_interval = apr_time_from_msec(100);
-
- m->spare_slaves = apr_array_make(m->pool, 10, sizeof(conn_rec*));
-
- m->ngn_shed = h2_ngn_shed_create(m->pool, m->c, m->max_streams,
- m->stream_max_mem);
- h2_ngn_shed_set_ctx(m->ngn_shed , m);
+failure:
+ if (m->pool) {
+ apr_pool_destroy(m->pool);
}
- return m;
+ else if (allocator) {
+ apr_allocator_destroy(allocator);
+ }
+ return NULL;
}
-int h2_mplx_shutdown(h2_mplx *m)
+int h2_mplx_c1_shutdown(h2_mplx *m)
{
- int max_stream_started = 0;
+ int max_stream_id_started = 0;
H2_MPLX_ENTER(m);
- max_stream_started = m->max_stream_started;
+ max_stream_id_started = m->max_stream_id_started;
/* Clear schedule queue, disabling existing streams from starting */
h2_iq_clear(m->q);
H2_MPLX_LEAVE(m);
- return max_stream_started;
-}
-
-static int input_consumed_signal(h2_mplx *m, h2_stream *stream)
-{
- if (stream->input) {
- return h2_beam_report_consumption(stream->input);
- }
- return 0;
-}
-
-static int report_consumption_iter(void *ctx, void *val)
-{
- h2_stream *stream = val;
- h2_mplx *m = ctx;
-
- input_consumed_signal(m, stream);
- if (stream->state == H2_SS_CLOSED_L
- && (!stream->task || stream->task->worker_done)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
- H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing"));
- nghttp2_submit_rst_stream(stream->session->ngh2, NGHTTP2_FLAG_NONE,
- stream->id, NGHTTP2_NO_ERROR);
- }
- return 1;
-}
-
-static int output_consumed_signal(h2_mplx *m, h2_task *task)
-{
- if (task->output.beam) {
- return h2_beam_report_consumption(task->output.beam);
- }
- return 0;
-}
-
-static int stream_destroy_iter(void *ctx, void *val)
-{
- h2_mplx *m = ctx;
- h2_stream *stream = val;
-
- h2_ihash_remove(m->spurge, stream->id);
- ap_assert(stream->state == H2_SS_CLEANUP);
-
- if (stream->input) {
- /* Process outstanding events before destruction */
- input_consumed_signal(m, stream);
- h2_beam_log(stream->input, m->c, APLOG_TRACE2, "stream_destroy");
- h2_beam_destroy(stream->input);
- stream->input = NULL;
- }
-
- if (stream->task) {
- h2_task *task = stream->task;
- conn_rec *slave;
- int reuse_slave = 0;
-
- stream->task = NULL;
- slave = task->c;
- if (slave) {
- /* On non-serialized requests, the IO logging has not accounted for any
- * meta data send over the network: response headers and h2 frame headers. we
- * counted this on the stream and need to add this now.
- * This is supposed to happen before the EOR bucket triggers the
- * logging of the transaction. *fingers crossed* */
- if (task->request && !task->request->serialize && h2_task_logio_add_bytes_out) {
- apr_off_t unaccounted = stream->out_frame_octets - stream->out_data_octets;
- if (unaccounted > 0) {
- h2_task_logio_add_bytes_out(slave, unaccounted);
- }
- }
-
- if (m->s->keep_alive_max == 0 || slave->keepalives < m->s->keep_alive_max) {
- reuse_slave = ((m->spare_slaves->nelts < (m->limit_active * 3 / 2))
- && !task->rst_error);
- }
-
- if (reuse_slave && slave->keepalive == AP_CONN_KEEPALIVE) {
- h2_beam_log(task->output.beam, m->c, APLOG_DEBUG,
- APLOGNO(03385) "h2_task_destroy, reuse slave");
- h2_task_destroy(task);
- APR_ARRAY_PUSH(m->spare_slaves, conn_rec*) = slave;
- }
- else {
- h2_beam_log(task->output.beam, m->c, APLOG_TRACE1,
- "h2_task_destroy, destroy slave");
- h2_slave_destroy(slave);
- }
- }
- }
- h2_stream_destroy(stream);
- return 0;
-}
-
-static void purge_streams(h2_mplx *m, int lock)
-{
- if (!h2_ihash_empty(m->spurge)) {
- H2_MPLX_ENTER_MAYBE(m, lock);
- while (!h2_ihash_iter(m->spurge, stream_destroy_iter, m)) {
- /* repeat until empty */
- }
- H2_MPLX_LEAVE_MAYBE(m, lock);
- }
+ return max_stream_id_started;
}
typedef struct {
@@ -360,13 +374,13 @@ typedef struct {
void *ctx;
} stream_iter_ctx_t;
-static int stream_iter_wrap(void *ctx, void *stream)
+static int m_stream_iter_wrap(void *ctx, void *stream)
{
stream_iter_ctx_t *x = ctx;
return x->cb(stream, x->ctx);
}
-apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
+apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
{
stream_iter_ctx_t x;
@@ -374,276 +388,260 @@ apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
x.cb = cb;
x.ctx = ctx;
- h2_ihash_iter(m->streams, stream_iter_wrap, &x);
+ h2_ihash_iter(m->streams, m_stream_iter_wrap, &x);
H2_MPLX_LEAVE(m);
return APR_SUCCESS;
}
-static int report_stream_iter(void *ctx, void *val) {
+typedef struct {
+ int stream_count;
+ int stream_want_send;
+} stream_iter_aws_t;
+
+static int m_stream_want_send_data(void *ctx, void *stream)
+{
+ stream_iter_aws_t *x = ctx;
+ ++x->stream_count;
+ if (h2_stream_wants_send_data(stream))
+ ++x->stream_want_send;
+ return 1;
+}
+
+int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m)
+{
+ stream_iter_aws_t x;
+ x.stream_count = 0;
+ x.stream_want_send = 0;
+ H2_MPLX_ENTER(m);
+ h2_ihash_iter(m->streams, m_stream_want_send_data, &x);
+ H2_MPLX_LEAVE(m);
+ return x.stream_count && (x.stream_count == x.stream_want_send);
+}
+
+static int m_report_stream_iter(void *ctx, void *val) {
h2_mplx *m = ctx;
h2_stream *stream = val;
- h2_task *task = stream->task;
- if (APLOGctrace1(m->c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- H2_STRM_MSG(stream, "started=%d, scheduled=%d, ready=%d, out_buffer=%ld"),
- !!stream->task, stream->scheduled, h2_stream_is_ready(stream),
- (long)h2_beam_get_buffered(stream->output));
- }
- if (task) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1,
+ H2_STRM_MSG(stream, "started=%d, scheduled=%d, ready=%d, out_buffer=%ld"),
+ !!stream->c2, stream->scheduled, h2_stream_is_ready(stream),
+ (long)(stream->output? h2_beam_get_buffered(stream->output) : -1));
+ if (conn_ctx) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, /* NO APLOGNO */
H2_STRM_MSG(stream, "->03198: %s %s %s"
- "[started=%d/done=%d/frozen=%d]"),
- task->request->method, task->request->authority,
- task->request->path, task->worker_started,
- task->worker_done, task->frozen);
+ "[started=%u/done=%u]"),
+ conn_ctx->request->method, conn_ctx->request->authority,
+ conn_ctx->request->path,
+ apr_atomic_read32(&conn_ctx->started),
+ apr_atomic_read32(&conn_ctx->done));
}
else {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */
- H2_STRM_MSG(stream, "->03198: no task"));
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, /* NO APLOGNO */
+ H2_STRM_MSG(stream, "->03198: not started"));
}
return 1;
}
-static int unexpected_stream_iter(void *ctx, void *val) {
+static int m_unexpected_stream_iter(void *ctx, void *val) {
h2_mplx *m = ctx;
h2_stream *stream = val;
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1, /* NO APLOGNO */
H2_STRM_MSG(stream, "unexpected, started=%d, scheduled=%d, ready=%d"),
- !!stream->task, stream->scheduled, h2_stream_is_ready(stream));
+ !!stream->c2, stream->scheduled, h2_stream_is_ready(stream));
return 1;
}
-static int stream_cancel_iter(void *ctx, void *val) {
+static int m_stream_cancel_iter(void *ctx, void *val) {
h2_mplx *m = ctx;
h2_stream *stream = val;
- /* disabled input consumed reporting */
- if (stream->input) {
- h2_beam_on_consumed(stream->input, NULL, NULL, NULL);
- }
/* take over event monitoring */
h2_stream_set_monitor(stream, NULL);
/* Reset, should transit to CLOSED state */
h2_stream_rst(stream, H2_ERR_NO_ERROR);
/* All connection data has been sent, simulate cleanup */
h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
- stream_cleanup(m, stream);
+ m_stream_cleanup(m, stream);
return 0;
}
-void h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
+static void c1_purge_streams(h2_mplx *m);
+
+void h2_mplx_c1_destroy(h2_mplx *m)
{
apr_status_t status;
- int i, wait_secs = 60;
+ unsigned int i, wait_secs = 60;
+ int old_aborted;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "start release"));
/* How to shut down a h2 connection:
- * 0. abort and tell the workers that no more tasks will come from us */
- m->aborted = 1;
- h2_workers_unregister(m->workers, m);
-
+ * 0. abort and tell the workers that no more work will come from us */
+ m->shutdown = m->aborted = 1;
+
H2_MPLX_ENTER_ALWAYS(m);
+ /* While really terminating any c2 connections, treat the master
+ * connection as aborted. It's not as if we could send any more data
+ * at this point. */
+ old_aborted = m->c1->aborted;
+ m->c1->aborted = 1;
+
/* How to shut down a h2 connection:
* 1. cancel all streams still active */
- while (!h2_ihash_iter(m->streams, stream_cancel_iter, m)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "release, %u/%u/%d streams (total/hold/purge), %d streams"),
+ h2_ihash_count(m->streams),
+ h2_ihash_count(m->shold),
+ m->spurge->nelts, m->processing_count);
+ while (!h2_ihash_iter(m->streams, m_stream_cancel_iter, m)) {
/* until empty */
}
- /* 2. terminate ngn_shed, no more streams
- * should be scheduled or in the active set */
- h2_ngn_shed_abort(m->ngn_shed);
+ /* 2. no more streams should be scheduled or in the active set */
ap_assert(h2_ihash_empty(m->streams));
ap_assert(h2_iq_empty(m->q));
/* 3. while workers are busy on this connection, meaning they
- * are processing tasks from this connection, wait on them finishing
+ * are processing streams from this connection, wait on them finishing
* in order to wake us and let us check again.
- * Eventually, this has to succeed. */
- m->join_wait = wait;
- for (i = 0; h2_ihash_count(m->shold) > 0; ++i) {
- status = apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(wait_secs));
+ * Eventually, this has to succeed. */
+ if (!m->join_wait) {
+ apr_thread_cond_create(&m->join_wait, m->pool);
+ }
+
+ for (i = 0; h2_ihash_count(m->shold) > 0; ++i) {
+ status = apr_thread_cond_timedwait(m->join_wait, m->lock, apr_time_from_sec(wait_secs));
if (APR_STATUS_IS_TIMEUP(status)) {
/* This can happen if we have very long running requests
* that do not time out on IO. */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(03198)
- "h2_mplx(%ld): waited %d sec for %d tasks",
- m->id, i*wait_secs, (int)h2_ihash_count(m->shold));
- h2_ihash_iter(m->shold, report_stream_iter, m);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, APLOGNO(03198)
+ H2_MPLX_MSG(m, "waited %u sec for %u streams"),
+ i*wait_secs, h2_ihash_count(m->shold));
+ h2_ihash_iter(m->shold, m_report_stream_iter, m);
}
}
- ap_assert(m->tasks_active == 0);
- m->join_wait = NULL;
-
- /* 4. close the h2_req_enginge shed */
- h2_ngn_shed_destroy(m->ngn_shed);
- m->ngn_shed = NULL;
-
+
+ H2_MPLX_LEAVE(m);
+ h2_workers_join(m->workers, m->producer);
+ H2_MPLX_ENTER_ALWAYS(m);
+
/* 4. With all workers done, all streams should be in spurge */
+ ap_assert(m->processing_count == 0);
if (!h2_ihash_empty(m->shold)) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, APLOGNO(03516)
- "h2_mplx(%ld): unexpected %d streams in hold",
- m->id, (int)h2_ihash_count(m->shold));
- h2_ihash_iter(m->shold, unexpected_stream_iter, m);
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1, APLOGNO(03516)
+ H2_MPLX_MSG(m, "unexpected %u streams in hold"),
+ h2_ihash_count(m->shold));
+ h2_ihash_iter(m->shold, m_unexpected_stream_iter, m);
}
-
+
+ c1_purge_streams(m);
+
+ m->c1->aborted = old_aborted;
H2_MPLX_LEAVE(m);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): released", m->id);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "released"));
}
-apr_status_t h2_mplx_stream_cleanup(h2_mplx *m, h2_stream *stream)
+apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, h2_stream *stream,
+ unsigned int *pstream_count)
{
H2_MPLX_ENTER(m);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
H2_STRM_MSG(stream, "cleanup"));
- stream_cleanup(m, stream);
-
+ m_stream_cleanup(m, stream);
+ *pstream_count = h2_ihash_count(m->streams);
H2_MPLX_LEAVE(m);
return APR_SUCCESS;
}
-h2_stream *h2_mplx_stream_get(h2_mplx *m, int id)
+const h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id)
{
h2_stream *s = NULL;
H2_MPLX_ENTER_ALWAYS(m);
-
- s = h2_ihash_get(m->streams, id);
-
+ s = h2_ihash_get(m->streams, stream_id);
H2_MPLX_LEAVE(m);
+
return s;
}
-static void output_produced(void *ctx, h2_bucket_beam *beam, apr_off_t bytes)
-{
- h2_stream *stream = ctx;
- h2_mplx *m = stream->session->mplx;
-
- check_data_for(m, stream, 1);
-}
-static apr_status_t out_open(h2_mplx *m, int stream_id, h2_bucket_beam *beam)
+static void c1_purge_streams(h2_mplx *m)
{
- apr_status_t status = APR_SUCCESS;
- h2_stream *stream = h2_ihash_get(m->streams, stream_id);
-
- if (!stream || !stream->task || m->aborted) {
- return APR_ECONNABORTED;
- }
-
- ap_assert(stream->output == NULL);
- stream->output = beam;
-
- if (APLOGctrace2(m->c)) {
- h2_beam_log(beam, m->c, APLOG_TRACE2, "out_open");
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
- "h2_mplx(%s): out open", stream->task->id);
- }
-
- h2_beam_on_consumed(stream->output, NULL, stream_output_consumed, stream);
- h2_beam_on_produced(stream->output, output_produced, stream);
- if (stream->task->output.copy_files) {
- h2_beam_on_file_beam(stream->output, h2_beam_no_files, NULL);
- }
-
- /* we might see some file buckets in the output, see
- * if we have enough handles reserved. */
- check_data_for(m, stream, 0);
- return status;
-}
+ h2_stream *stream;
+ int i;
-apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_bucket_beam *beam)
-{
- apr_status_t status;
-
- H2_MPLX_ENTER(m);
+ for (i = 0; i < m->spurge->nelts; ++i) {
+ stream = APR_ARRAY_IDX(m->spurge, i, h2_stream*);
+ ap_assert(stream->state == H2_SS_CLEANUP);
- if (m->aborted) {
- status = APR_ECONNABORTED;
- }
- else {
- status = out_open(m, stream_id, beam);
+ if (stream->input) {
+ h2_beam_destroy(stream->input, m->c1);
+ stream->input = NULL;
+ }
+ if (stream->c2) {
+ conn_rec *c2 = stream->c2;
+ h2_conn_ctx_t *c2_ctx = h2_conn_ctx_get(c2);
+ h2_c2_transit *transit;
+
+ stream->c2 = NULL;
+ ap_assert(c2_ctx);
+ transit = c2_ctx->transit;
+ h2_c2_destroy(c2); /* c2_ctx is gone as well */
+ if (transit) {
+ c2_transit_recycle(m, transit);
+ }
+ }
+ h2_stream_destroy(stream);
}
-
- H2_MPLX_LEAVE(m);
- return status;
+ apr_array_clear(m->spurge);
}
-static apr_status_t out_close(h2_mplx *m, h2_task *task)
+void h2_mplx_c1_going_keepalive(h2_mplx *m)
{
- apr_status_t status = APR_SUCCESS;
- h2_stream *stream;
-
- if (!task) {
- return APR_ECONNABORTED;
- }
- if (task->c) {
- ++task->c->keepalives;
- }
-
- stream = h2_ihash_get(m->streams, task->stream_id);
- if (!stream) {
- return APR_ECONNABORTED;
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (m->spurge->nelts) {
+ c1_purge_streams(m);
}
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, m->c,
- "h2_mplx(%s): close", task->id);
- status = h2_beam_close(task->output.beam);
- h2_beam_log(task->output.beam, m->c, APLOG_TRACE2, "out_close");
- output_consumed_signal(m, task);
- check_data_for(m, stream, 0);
- return status;
+ H2_MPLX_LEAVE(m);
}
-apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
- apr_thread_cond_t *iowait)
+apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx)
{
- apr_status_t status;
-
+ apr_status_t rv;
+
H2_MPLX_ENTER(m);
if (m->aborted) {
- status = APR_ECONNABORTED;
- }
- else if (h2_mplx_has_master_events(m)) {
- status = APR_SUCCESS;
- }
- else {
- purge_streams(m, 0);
- h2_ihash_iter(m->streams, report_consumption_iter, m);
- m->added_output = iowait;
- status = apr_thread_cond_timedwait(m->added_output, m->lock, timeout);
- if (APLOGctrace2(m->c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
- "h2_mplx(%ld): trywait on data for %f ms)",
- m->id, timeout/1000.0);
- }
- m->added_output = NULL;
+ rv = APR_ECONNABORTED;
+ goto cleanup;
+ }
+ /* Purge (destroy) streams outside of pollset processing.
+ * Streams that are registered in the pollset, will be removed
+ * when they are destroyed, but the pollset works on copies
+ * of these registrations. So, if we destroy streams while
+ * processing pollset events, we might access freed memory.
+ */
+ if (m->spurge->nelts) {
+ c1_purge_streams(m);
}
+ rv = mplx_pollset_poll(m, timeout, on_stream_input, on_stream_output, on_ctx);
+cleanup:
H2_MPLX_LEAVE(m);
- return status;
-}
-
-static void check_data_for(h2_mplx *m, h2_stream *stream, int lock)
-{
- if (h2_ififo_push(m->readyq, stream->id) == APR_SUCCESS) {
- apr_atomic_set32(&m->event_pending, 1);
- H2_MPLX_ENTER_MAYBE(m, lock);
- if (m->added_output) {
- apr_thread_cond_signal(m->added_output);
- }
- H2_MPLX_LEAVE_MAYBE(m, lock);
- }
+ return rv;
}
-apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
+apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
+ h2_session *session)
{
apr_status_t status;
@@ -653,9 +651,9 @@ apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
status = APR_ECONNABORTED;
}
else {
- h2_iq_sort(m->q, cmp, ctx);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): reprioritize tasks", m->id);
+ h2_iq_sort(m->q, cmp, session);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "reprioritize streams"));
status = APR_SUCCESS;
}
@@ -663,560 +661,435 @@ apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
return status;
}
-static void register_if_needed(h2_mplx *m)
-{
- if (!m->aborted && !m->is_registered && !h2_iq_empty(m->q)) {
- apr_status_t status = h2_workers_register(m->workers, m);
- if (status == APR_SUCCESS) {
- m->is_registered = 1;
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, m->c, APLOGNO(10021)
- "h2_mplx(%ld): register at workers", m->id);
- }
- }
-}
-
-apr_status_t h2_mplx_process(h2_mplx *m, struct h2_stream *stream,
- h2_stream_pri_cmp *cmp, void *ctx)
+static apr_status_t c1_process_stream(h2_mplx *m,
+ h2_stream *stream,
+ h2_stream_pri_cmp_fn *cmp,
+ h2_session *session)
{
- apr_status_t status;
-
- H2_MPLX_ENTER(m);
+ apr_status_t rv = APR_SUCCESS;
if (m->aborted) {
- status = APR_ECONNABORTED;
+ rv = APR_ECONNABORTED;
+ goto cleanup;
+ }
+ if (!stream->request) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ 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%s%s%s"),
+ r->protocol? r->protocol : "",
+ r->protocol? " " : "",
+ r->method, r->scheme? r->scheme : "",
+ r->scheme? "://" : "",
+ r->authority, r->path? r->path: "");
+ }
+
+ stream->scheduled = 1;
+ h2_ihash_add(m->streams, stream);
+ if (h2_stream_is_ready(stream)) {
+ /* already have a response */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_STRM_MSG(stream, "process, ready already"));
}
else {
- status = APR_SUCCESS;
- h2_ihash_add(m->streams, stream);
- if (h2_stream_is_ready(stream)) {
- /* already have a response */
- check_data_for(m, stream, 0);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- H2_STRM_MSG(stream, "process, add to readyq"));
- }
- else {
- h2_iq_add(m->q, stream->id, cmp, ctx);
- register_if_needed(m);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- H2_STRM_MSG(stream, "process, added to q"));
- }
+ /* last chance to set anything up before stream is processed
+ * by worker threads. */
+ rv = h2_stream_prepare_processing(stream);
+ if (APR_SUCCESS != rv) goto cleanup;
+ h2_iq_add(m->q, stream->id, cmp, session);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_STRM_MSG(stream, "process, added to q"));
}
- H2_MPLX_LEAVE(m);
- return status;
+cleanup:
+ return rv;
}
-static h2_task *next_stream_task(h2_mplx *m)
+void h2_mplx_c1_process(h2_mplx *m,
+ h2_iqueue *ready_to_process,
+ h2_stream_get_fn *get_stream,
+ h2_stream_pri_cmp_fn *stream_pri_cmp,
+ h2_session *session,
+ unsigned int *pstream_count)
{
- h2_stream *stream;
+ apr_status_t rv;
int sid;
- while (!m->aborted && (m->tasks_active < m->limit_active)
- && (sid = h2_iq_shift(m->q)) > 0) {
-
- stream = h2_ihash_get(m->streams, sid);
- if (stream) {
- conn_rec *slave, **pslave;
- pslave = (conn_rec **)apr_array_pop(m->spare_slaves);
- if (pslave) {
- slave = *pslave;
- slave->aborted = 0;
- }
- else {
- slave = h2_slave_create(m->c, stream->id, m->pool);
- }
-
- if (!stream->task) {
+ H2_MPLX_ENTER_ALWAYS(m);
- if (sid > m->max_stream_started) {
- m->max_stream_started = sid;
- }
- if (stream->input) {
- h2_beam_on_consumed(stream->input, stream_input_ev,
- stream_input_consumed, stream);
- }
-
- stream->task = h2_task_create(slave, stream->id,
- stream->request, m, stream->input,
- stream->session->s->timeout,
- m->stream_max_mem);
- if (!stream->task) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, slave,
- H2_STRM_LOG(APLOGNO(02941), stream,
- "create task"));
- return NULL;
- }
-
+ while ((sid = h2_iq_shift(ready_to_process)) > 0) {
+ h2_stream *stream = get_stream(session, sid);
+ if (stream) {
+ ap_assert(!stream->scheduled);
+ rv = c1_process_stream(session->mplx, stream, stream_pri_cmp, session);
+ if (APR_SUCCESS != rv) {
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
}
-
- ++m->tasks_active;
- return stream->task;
- }
- }
- return NULL;
-}
-
-apr_status_t h2_mplx_pop_task(h2_mplx *m, h2_task **ptask)
-{
- apr_status_t rv = APR_EOF;
-
- *ptask = NULL;
- ap_assert(m);
- ap_assert(m->lock);
-
- if (APR_SUCCESS != (rv = apr_thread_mutex_lock(m->lock))) {
- return rv;
- }
-
- if (m->aborted) {
- rv = APR_EOF;
- }
- else {
- *ptask = next_stream_task(m);
- rv = (*ptask != NULL && !h2_iq_empty(m->q))? APR_EAGAIN : APR_SUCCESS;
- }
- if (APR_EAGAIN != rv) {
- m->is_registered = 0; /* h2_workers will discard this mplx */
- }
- H2_MPLX_LEAVE(m);
- return rv;
-}
-
-static void task_done(h2_mplx *m, h2_task *task, h2_req_engine *ngn)
-{
- h2_stream *stream;
-
- if (task->frozen) {
- /* this task was handed over to an engine for processing
- * and the original worker has finished. That means the
- * engine may start processing now. */
- h2_task_thaw(task);
- apr_thread_cond_broadcast(m->task_thawed);
- return;
- }
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): task(%s) done", m->id, task->id);
- out_close(m, task);
-
- if (ngn) {
- apr_off_t bytes = 0;
- h2_beam_send(task->output.beam, NULL, APR_NONBLOCK_READ);
- bytes += h2_beam_get_buffered(task->output.beam);
- if (bytes > 0) {
- /* we need to report consumed and current buffered output
- * to the engine. The request will be streamed out or cancelled,
- * no more data is coming from it and the engine should update
- * its calculations before we destroy this information. */
- h2_req_engine_out_consumed(ngn, task->c, bytes);
- }
- }
-
- if (task->engine) {
- if (!m->aborted && !task->c->aborted
- && !h2_req_engine_is_shutdown(task->engine)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(10022)
- "h2_mplx(%ld): task(%s) has not-shutdown "
- "engine(%s)", m->id, task->id,
- h2_req_engine_get_id(task->engine));
- }
- h2_ngn_shed_done_ngn(m->ngn_shed, task->engine);
- }
-
- task->worker_done = 1;
- task->done_at = apr_time_now();
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
- "h2_mplx(%s): request done, %f ms elapsed", task->id,
- (task->done_at - task->started_at) / 1000.0);
-
- if (task->started_at > m->last_idle_block) {
- /* this task finished without causing an 'idle block', e.g.
- * a block by flow control.
- */
- if (task->done_at- m->last_limit_change >= m->limit_change_interval
- && m->limit_active < m->max_active) {
- /* Well behaving stream, allow it more workers */
- m->limit_active = H2MIN(m->limit_active * 2,
- m->max_active);
- m->last_limit_change = task->done_at;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): increase worker limit to %d",
- m->id, m->limit_active);
- }
- }
-
- stream = h2_ihash_get(m->streams, task->stream_id);
- if (stream) {
- /* stream not done yet. */
- if (!m->aborted && h2_ihash_get(m->sredo, stream->id)) {
- /* reset and schedule again */
- h2_task_redo(task);
- h2_ihash_remove(m->sredo, stream->id);
- h2_iq_add(m->q, stream->id, NULL, NULL);
}
else {
- /* stream not cleaned up, stay around */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
- H2_STRM_MSG(stream, "task_done, stream open"));
- if (stream->input) {
- h2_beam_leave(stream->input);
- }
-
- /* more data will not arrive, resume the stream */
- check_data_for(m, stream, 0);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "stream %d not found to process"), sid);
}
}
- else if ((stream = h2_ihash_get(m->shold, task->stream_id)) != NULL) {
- /* stream is done, was just waiting for this. */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
- H2_STRM_MSG(stream, "task_done, in hold"));
- if (stream->input) {
- h2_beam_leave(stream->input);
+ if ((m->processing_count < m->processing_limit) && !h2_iq_empty(m->q)) {
+ H2_MPLX_LEAVE(m);
+ rv = h2_workers_activate(m->workers, m->producer);
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1, APLOGNO(10021)
+ H2_MPLX_MSG(m, "activate at workers"));
}
- stream_joined(m, stream);
- }
- else if ((stream = h2_ihash_get(m->spurge, task->stream_id)) != NULL) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,
- H2_STRM_LOG(APLOGNO(03517), stream, "already in spurge"));
- ap_assert("stream should not be in spurge" == NULL);
}
- else {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, APLOGNO(03518)
- "h2_mplx(%s): task_done, stream not found",
- task->id);
- ap_assert("stream should still be available" == NULL);
- }
-}
+ *pstream_count = h2_ihash_count(m->streams);
-void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
-{
- H2_MPLX_ENTER_ALWAYS(m);
+#if APR_POOL_DEBUG
+ do {
+ apr_size_t mem_g, mem_m, mem_s, mem_c1;
- task_done(m, task, NULL);
- --m->tasks_active;
-
- if (m->join_wait) {
- apr_thread_cond_signal(m->join_wait);
- }
- if (ptask) {
- /* caller wants another task */
- *ptask = next_stream_task(m);
- }
- register_if_needed(m);
+ mem_g = pchild? apr_pool_num_bytes(pchild, 1) : 0;
+ mem_m = apr_pool_num_bytes(m->pool, 1);
+ mem_s = apr_pool_num_bytes(session->pool, 1);
+ mem_c1 = apr_pool_num_bytes(m->c1->pool, 1);
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c1,
+ H2_MPLX_MSG(m, "child mem=%ld, mplx mem=%ld, session mem=%ld, c1=%ld"),
+ (long)mem_g, (long)mem_m, (long)mem_s, (long)mem_c1);
+
+ } while (0);
+#endif
H2_MPLX_LEAVE(m);
}
-/*******************************************************************************
- * h2_mplx DoS protection
- ******************************************************************************/
+static void c2_beam_input_write_notify(void *ctx, h2_bucket_beam *beam)
+{
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
-static int latest_repeatable_unsubmitted_iter(void *data, void *val)
+ (void)beam;
+ if (conn_ctx && conn_ctx->stream_id && conn_ctx->pipe_in[H2_PIPE_IN]) {
+ apr_file_putc(1, conn_ctx->pipe_in[H2_PIPE_IN]);
+ }
+}
+
+static void add_stream_poll_event(h2_mplx *m, int stream_id, h2_iqueue *q)
{
- stream_iter_ctx *ctx = data;
- h2_stream *stream = val;
-
- if (stream->task && !stream->task->worker_done
- && h2_task_can_redo(stream->task)
- && !h2_ihash_get(ctx->m->sredo, stream->id)) {
- if (!h2_stream_is_ready(stream)) {
- /* this task occupies a worker, the response has not been submitted
- * yet, not been cancelled and it is a repeatable request
- * -> it can be re-scheduled later */
- if (!ctx->stream
- || (ctx->stream->task->started_at < stream->task->started_at)) {
- /* we did not have one or this one was started later */
- ctx->stream = stream;
- }
- }
+ apr_thread_mutex_lock(m->poll_lock);
+ if (h2_iq_append(q, stream_id) && h2_iq_count(q) == 1) {
+ /* newly added first */
+ apr_pollset_wakeup(m->pollset);
}
- return 1;
+ apr_thread_mutex_unlock(m->poll_lock);
}
-static h2_stream *get_latest_repeatable_unsubmitted_stream(h2_mplx *m)
+static void c2_beam_input_read_notify(void *ctx, h2_bucket_beam *beam)
{
- stream_iter_ctx ctx;
- ctx.m = m;
- ctx.stream = NULL;
- h2_ihash_iter(m->streams, latest_repeatable_unsubmitted_iter, &ctx);
- return ctx.stream;
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->stream_id) {
+ add_stream_poll_event(conn_ctx->mplx, conn_ctx->stream_id,
+ conn_ctx->mplx->streams_input_read);
+ }
}
-static int timed_out_busy_iter(void *data, void *val)
+static void c2_beam_input_read_eagain(void *ctx, h2_bucket_beam *beam)
{
- stream_iter_ctx *ctx = data;
- h2_stream *stream = val;
- if (stream->task && !stream->task->worker_done
- && (ctx->now - stream->task->started_at) > stream->task->timeout) {
- /* timed out stream occupying a worker, found */
- ctx->stream = stream;
- return 0;
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ /* installed in the input bucket beams when we use pipes.
+ * Drain the pipe just before the beam returns APR_EAGAIN.
+ * A clean state for allowing polling on the pipe to rest
+ * when the beam is empty */
+ if (conn_ctx && conn_ctx->pipe_in[H2_PIPE_OUT]) {
+ h2_util_drain_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
}
- return 1;
}
-static h2_stream *get_timed_out_busy_stream(h2_mplx *m)
+static void c2_beam_output_write_notify(void *ctx, h2_bucket_beam *beam)
{
- stream_iter_ctx ctx;
- ctx.m = m;
- ctx.stream = NULL;
- ctx.now = apr_time_now();
- h2_ihash_iter(m->streams, timed_out_busy_iter, &ctx);
- return ctx.stream;
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->stream_id) {
+ add_stream_poll_event(conn_ctx->mplx, conn_ctx->stream_id,
+ conn_ctx->mplx->streams_output_written);
+ }
}
-static apr_status_t unschedule_slow_tasks(h2_mplx *m)
+static apr_status_t c2_setup_io(h2_mplx *m, conn_rec *c2, h2_stream *stream, h2_c2_transit *transit)
{
- h2_stream *stream;
- int n;
-
- /* Try to get rid of streams that occupy workers. Look for safe requests
- * that are repeatable. If none found, fail the connection.
- */
- n = (m->tasks_active - m->limit_active - (int)h2_ihash_count(m->sredo));
- while (n > 0 && (stream = get_latest_repeatable_unsubmitted_stream(m))) {
- h2_task_rst(stream->task, H2_ERR_CANCEL);
- h2_ihash_add(m->sredo, stream);
- --n;
+ h2_conn_ctx_t *conn_ctx;
+ apr_status_t rv = APR_SUCCESS;
+ const char *action = "init";
+
+ rv = h2_conn_ctx_init_for_c2(&conn_ctx, c2, m, stream, transit);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (!conn_ctx->beam_out) {
+ action = "create output beam";
+ rv = h2_beam_create(&conn_ctx->beam_out, c2, conn_ctx->req_pool,
+ stream->id, "output", 0, c2->base_server->timeout);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ h2_beam_buffer_size_set(conn_ctx->beam_out, m->stream_max_mem);
+ h2_beam_on_was_empty(conn_ctx->beam_out, c2_beam_output_write_notify, c2);
}
-
- if ((m->tasks_active - h2_ihash_count(m->sredo)) > m->limit_active) {
- h2_stream *stream = get_timed_out_busy_stream(m);
- if (stream) {
- /* Too many busy workers, unable to cancel enough streams
- * and with a busy, timed out stream, we tell the client
- * to go away... */
- return APR_TIMEUP;
- }
+
+ memset(&conn_ctx->pipe_in, 0, sizeof(conn_ctx->pipe_in));
+ if (stream->input) {
+ conn_ctx->beam_in = stream->input;
+ h2_beam_on_send(stream->input, c2_beam_input_write_notify, c2);
+ h2_beam_on_received(stream->input, c2_beam_input_read_notify, c2);
+ h2_beam_on_consumed(stream->input, c1_input_consumed, stream);
+#if H2_USE_PIPES
+ action = "create input write pipe";
+ rv = apr_file_pipe_create_pools(&conn_ctx->pipe_in[H2_PIPE_OUT],
+ &conn_ctx->pipe_in[H2_PIPE_IN],
+ APR_READ_BLOCK,
+ c2->pool, c2->pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+#endif
+ h2_beam_on_eagain(stream->input, c2_beam_input_read_eagain, c2);
+ if (!h2_beam_empty(stream->input))
+ c2_beam_input_write_notify(c2, stream->input);
+ }
+
+cleanup:
+ stream->output = (APR_SUCCESS == rv)? conn_ctx->beam_out : NULL;
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c2,
+ H2_STRM_LOG(APLOGNO(10309), stream,
+ "error %s"), action);
}
- return APR_SUCCESS;
+ return rv;
}
-apr_status_t h2_mplx_idle(h2_mplx *m)
+static conn_rec *s_next_c2(h2_mplx *m)
{
- apr_status_t status = APR_SUCCESS;
- apr_time_t now;
- apr_size_t scount;
-
- H2_MPLX_ENTER(m);
+ h2_stream *stream = NULL;
+ apr_status_t rv = APR_SUCCESS;
+ apr_uint32_t sid;
+ conn_rec *c2 = NULL;
+ h2_c2_transit *transit = NULL;
- scount = h2_ihash_count(m->streams);
- if (scount > 0) {
- if (m->tasks_active) {
- /* If we have streams in connection state 'IDLE', meaning
- * all streams are ready to sent data out, but lack
- * WINDOW_UPDATEs.
- *
- * This is ok, unless we have streams that still occupy
- * h2 workers. As worker threads are a scarce resource,
- * we need to take measures that we do not get DoSed.
- *
- * This is what we call an 'idle block'. Limit the amount
- * of busy workers we allow for this connection until it
- * well behaves.
- */
- now = apr_time_now();
- m->last_idle_block = now;
- if (m->limit_active > 2
- && now - m->last_limit_change >= m->limit_change_interval) {
- if (m->limit_active > 16) {
- m->limit_active = 16;
- }
- else if (m->limit_active > 8) {
- m->limit_active = 8;
- }
- else if (m->limit_active > 4) {
- m->limit_active = 4;
- }
- else if (m->limit_active > 2) {
- m->limit_active = 2;
- }
- m->last_limit_change = now;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): decrease worker limit to %d",
- m->id, m->limit_active);
- }
-
- if (m->tasks_active > m->limit_active) {
- status = unschedule_slow_tasks(m);
- }
- }
- else if (!h2_iq_empty(m->q)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): idle, but %d streams to process",
- m->id, (int)h2_iq_count(m->q));
- status = APR_EAGAIN;
- }
- else {
- /* idle, have streams, but no tasks active. what are we waiting for?
- * WINDOW_UPDATEs from client? */
- h2_stream *stream = NULL;
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): idle, no tasks ongoing, %d streams",
- m->id, (int)h2_ihash_count(m->streams));
- h2_ihash_shift(m->streams, (void**)&stream, 1);
- if (stream) {
- h2_ihash_add(m->streams, stream);
- if (stream->output && !stream->out_checked) {
- /* FIXME: this looks like a race between the session thinking
- * it is idle and the EOF on a stream not being sent.
- * Signal to caller to leave IDLE state.
- */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
- H2_STRM_MSG(stream, "output closed=%d, mplx idle"
- ", out has %ld bytes buffered"),
- h2_beam_is_closed(stream->output),
- (long)h2_beam_get_buffered(stream->output));
- h2_ihash_add(m->streams, stream);
- check_data_for(m, stream, 0);
- stream->out_checked = 1;
- status = APR_EAGAIN;
- }
- }
+ while (!m->aborted && !stream && (m->processing_count < m->processing_limit)
+ && (sid = h2_iq_shift(m->q)) > 0) {
+ stream = h2_ihash_get(m->streams, sid);
+ }
+
+ if (!stream) {
+ if (m->processing_count >= m->processing_limit && !h2_iq_empty(m->q)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+ H2_MPLX_MSG(m, "delaying request processing. "
+ "Current limit is %d and %d workers are in use."),
+ m->processing_limit, m->processing_count);
}
+ goto cleanup;
}
- register_if_needed(m);
- H2_MPLX_LEAVE(m);
- return status;
-}
+ if (sid > m->max_stream_id_started) {
+ m->max_stream_id_started = sid;
+ }
-/*******************************************************************************
- * HTTP/2 request engines
- ******************************************************************************/
+ transit = c2_transit_get(m);
+#if AP_HAS_RESPONSE_BUCKETS
+ c2 = ap_create_secondary_connection(transit->pool, m->c1, transit->bucket_alloc);
+#else
+ c2 = h2_c2_create(m->c1, transit->pool, transit->bucket_alloc);
+#endif
+ if (!c2) goto cleanup;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c1,
+ H2_STRM_MSG(stream, "created new c2"));
-typedef struct {
- h2_mplx * m;
- h2_req_engine *ngn;
- int streams_updated;
-} ngn_update_ctx;
+ rv = c2_setup_io(m, c2, stream, transit);
+ if (APR_SUCCESS != rv) goto cleanup;
-static int ngn_update_window(void *ctx, void *val)
-{
- ngn_update_ctx *uctx = ctx;
- h2_stream *stream = val;
- if (stream->task && stream->task->assigned == uctx->ngn
- && output_consumed_signal(uctx->m, stream->task)) {
- ++uctx->streams_updated;
+ stream->c2 = c2;
+ ++m->processing_count;
+
+cleanup:
+ if (APR_SUCCESS != rv && c2) {
+ h2_c2_destroy(c2);
+ c2 = NULL;
}
- return 1;
+ if (transit && !c2) {
+ c2_transit_recycle(m, transit);
+ }
+ return c2;
}
-static apr_status_t ngn_out_update_windows(h2_mplx *m, h2_req_engine *ngn)
+static conn_rec *c2_prod_next(void *baton, int *phas_more)
{
- ngn_update_ctx ctx;
-
- ctx.m = m;
- ctx.ngn = ngn;
- ctx.streams_updated = 0;
- h2_ihash_iter(m->streams, ngn_update_window, &ctx);
-
- return ctx.streams_updated? APR_SUCCESS : APR_EAGAIN;
+ h2_mplx *m = baton;
+ conn_rec *c = NULL;
+
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (!m->aborted) {
+ c = s_next_c2(m);
+ *phas_more = (c != NULL && !h2_iq_empty(m->q));
+ }
+ H2_MPLX_LEAVE(m);
+ return c;
}
-apr_status_t h2_mplx_req_engine_push(const char *ngn_type,
- request_rec *r,
- http2_req_engine_init *einit)
+static void s_c2_done(h2_mplx *m, conn_rec *c2, h2_conn_ctx_t *conn_ctx)
{
- apr_status_t status;
- h2_mplx *m;
- h2_task *task;
h2_stream *stream;
+
+ ap_assert(conn_ctx);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_mplx(%s-%d): c2 done", conn_ctx->id, conn_ctx->stream_id);
+
+ AP_DEBUG_ASSERT(apr_atomic_read32(&conn_ctx->done) == 0);
+ apr_atomic_set32(&conn_ctx->done, 1);
+ conn_ctx->done_at = apr_time_now();
+ ++c2->keepalives;
+ /* From here on, the final handling of c2 is done by c1 processing.
+ * Which means we can give it c1's scoreboard handle for updates. */
+ c2->sbh = m->c1->sbh;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_mplx(%s-%d): request done, %f ms elapsed",
+ conn_ctx->id, conn_ctx->stream_id,
+ (conn_ctx->done_at - conn_ctx->started_at) / 1000.0);
- task = h2_ctx_rget_task(r);
- if (!task) {
- return APR_ECONNABORTED;
+ if (!conn_ctx->has_final_response) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+ "h2_c2(%s-%d): processing finished without final response",
+ conn_ctx->id, conn_ctx->stream_id);
+ c2->aborted = 1;
+ if (conn_ctx->beam_out)
+ h2_beam_abort(conn_ctx->beam_out, c2);
+ }
+ else if (!conn_ctx->beam_out || !h2_beam_is_complete(conn_ctx->beam_out)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+ "h2_c2(%s-%d): processing finished with incomplete output",
+ conn_ctx->id, conn_ctx->stream_id);
+ c2->aborted = 1;
+ h2_beam_abort(conn_ctx->beam_out, c2);
+ }
+ else if (!c2->aborted) {
+ s_mplx_be_happy(m, c2, conn_ctx);
}
- m = task->mplx;
- H2_MPLX_ENTER(m);
-
- stream = h2_ihash_get(m->streams, task->stream_id);
+ stream = h2_ihash_get(m->streams, conn_ctx->stream_id);
if (stream) {
- status = h2_ngn_shed_push_request(m->ngn_shed, ngn_type, r, einit);
+ /* stream not done yet. trigger a potential polling on the output
+ * since nothing more will happening here. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ H2_STRM_MSG(stream, "c2_done, stream open"));
+ c2_beam_output_write_notify(c2, NULL);
}
- else {
- status = APR_ECONNABORTED;
+ else if ((stream = h2_ihash_get(m->shold, conn_ctx->stream_id)) != NULL) {
+ /* stream is done, was just waiting for this. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ H2_STRM_MSG(stream, "c2_done, in hold"));
+ c1c2_stream_joined(m, stream);
}
+ else {
+ int i;
+
+ for (i = 0; i < m->spurge->nelts; ++i) {
+ if (stream == APR_ARRAY_IDX(m->spurge, i, h2_stream*)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2,
+ H2_STRM_LOG(APLOGNO(03517), stream, "already in spurge"));
+ ap_assert("stream should not be in spurge" == NULL);
+ return;
+ }
+ }
- H2_MPLX_LEAVE(m);
- return status;
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2, APLOGNO(03518)
+ "h2_mplx(%s-%d): c2_done, stream not found",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_assert("stream should still be available" == NULL);
+ }
}
-apr_status_t h2_mplx_req_engine_pull(h2_req_engine *ngn,
- apr_read_type_e block,
- int capacity,
- request_rec **pr)
-{
- h2_ngn_shed *shed = h2_ngn_shed_get_shed(ngn);
- h2_mplx *m = h2_ngn_shed_get_ctx(shed);
- apr_status_t status;
- int want_shutdown;
-
- H2_MPLX_ENTER(m);
+static void c2_prod_done(void *baton, conn_rec *c2)
+{
+ h2_mplx *m = baton;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
- want_shutdown = (block == APR_BLOCK_READ);
+ AP_DEBUG_ASSERT(conn_ctx);
+ H2_MPLX_ENTER_ALWAYS(m);
- /* Take this opportunity to update output consummation
- * for this engine */
- ngn_out_update_windows(m, ngn);
-
- if (want_shutdown && !h2_iq_empty(m->q)) {
- /* For a blocking read, check first if requests are to be
- * had and, if not, wait a short while before doing the
- * blocking, and if unsuccessful, terminating read.
- */
- status = h2_ngn_shed_pull_request(shed, ngn, capacity, 1, pr);
- if (APR_STATUS_IS_EAGAIN(status)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
- "h2_mplx(%ld): start block engine pull", m->id);
- apr_thread_cond_timedwait(m->task_thawed, m->lock,
- apr_time_from_msec(20));
- status = h2_ngn_shed_pull_request(shed, ngn, capacity, 1, pr);
- }
- }
- else {
- status = h2_ngn_shed_pull_request(shed, ngn, capacity,
- want_shutdown, pr);
- }
+ --m->processing_count;
+ s_c2_done(m, c2, conn_ctx);
+ if (m->join_wait) apr_thread_cond_signal(m->join_wait);
H2_MPLX_LEAVE(m);
- return status;
}
-
-void h2_mplx_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn,
- apr_status_t status)
+
+static void workers_shutdown(void *baton, int graceful)
{
- h2_task *task = h2_ctx_cget_task(r_conn);
-
- if (task) {
- h2_mplx *m = task->mplx;
- h2_stream *stream;
+ h2_mplx *m = baton;
+
+ apr_thread_mutex_lock(m->poll_lock);
+ /* time to wakeup and assess what to do */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "workers shutdown, waking pollset"));
+ m->shutdown = 1;
+ if (!graceful) {
+ m->aborted = 1;
+ }
+ apr_pollset_wakeup(m->pollset);
+ apr_thread_mutex_unlock(m->poll_lock);
+}
- H2_MPLX_ENTER_ALWAYS(m);
+/*******************************************************************************
+ * h2_mplx DoS protection
+ ******************************************************************************/
- stream = h2_ihash_get(m->streams, task->stream_id);
-
- ngn_out_update_windows(m, ngn);
- h2_ngn_shed_done_task(m->ngn_shed, ngn, task);
-
- if (status != APR_SUCCESS && stream
- && h2_task_can_redo(task)
- && !h2_ihash_get(m->sredo, stream->id)) {
- h2_ihash_add(m->sredo, stream);
- }
+static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx)
+{
+ apr_time_t now;
- if (task->engine) {
- /* cannot report that as done until engine returns */
- }
- else {
- task_done(m, task, ngn);
+ if (m->processing_limit < m->processing_max
+ && conn_ctx->started_at > m->last_mood_change) {
+ --m->irritations_since;
+ if (m->processing_limit < m->processing_max
+ && ((now = apr_time_now()) - m->last_mood_change >= m->mood_update_interval
+ || m->irritations_since < -m->processing_limit)) {
+ m->processing_limit = H2MIN(m->processing_limit * 2, m->processing_max);
+ m->last_mood_change = now;
+ m->irritations_since = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ H2_MPLX_MSG(m, "mood update, increasing worker limit to %d"),
+ m->processing_limit);
}
+ }
+}
- H2_MPLX_LEAVE(m);
+static void m_be_annoyed(h2_mplx *m)
+{
+ apr_time_t now;
+
+ if (m->processing_limit > 2) {
+ ++m->irritations_since;
+ if (((now = apr_time_now()) - m->last_mood_change >= m->mood_update_interval)
+ || (m->irritations_since >= m->processing_limit)) {
+
+ if (m->processing_limit > 16) {
+ m->processing_limit = 16;
+ }
+ else if (m->processing_limit > 8) {
+ m->processing_limit = 8;
+ }
+ else if (m->processing_limit > 4) {
+ m->processing_limit = 4;
+ }
+ else if (m->processing_limit > 2) {
+ m->processing_limit = 2;
+ }
+ m->last_mood_change = now;
+ m->irritations_since = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "mood update, decreasing worker limit to %d"),
+ m->processing_limit);
+ }
}
}
@@ -1224,59 +1097,168 @@ void h2_mplx_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn,
* mplx master events dispatching
******************************************************************************/
-int h2_mplx_has_master_events(h2_mplx *m)
+static int reset_is_acceptable(h2_stream *stream)
{
- return apr_atomic_read32(&m->event_pending) > 0;
+ /* client may terminate a stream via H2 RST_STREAM message at any time.
+ * This is annyoing when we have committed resources (e.g. worker threads)
+ * to it, so our mood (e.g. willingness to commit resources on this
+ * connection in the future) goes down.
+ *
+ * This is a DoS protection. We do not want to make it too easy for
+ * a client to eat up server resources.
+ *
+ * However: there are cases where a RST_STREAM is the only way to end
+ * a request. This includes websockets and server-side-event streams (SSEs).
+ * The responses to such requests continue forever otherwise.
+ *
+ */
+ if (!stream_is_running(stream)) return 1;
+ if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
+ if (!stream->response) return 0; /* no response headers produced yet. bad. */
+ if (!stream->out_data_frames) return 0; /* no response body data sent yet. bad. */
+ return 1; /* otherwise, be forgiving */
}
-apr_status_t h2_mplx_dispatch_master_events(h2_mplx *m,
- stream_ev_callback *on_resume,
- void *on_ctx)
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id, h2_stream *stream)
{
- h2_stream *stream;
- int n, id;
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
- "h2_mplx(%ld): dispatch events", m->id);
- apr_atomic_set32(&m->event_pending, 0);
+ apr_status_t status = APR_SUCCESS;
+ int registered;
- /* update input windows for streams */
- h2_ihash_iter(m->streams, report_consumption_iter, m);
- purge_streams(m, 1);
-
- n = h2_ififo_count(m->readyq);
- while (n > 0
- && (h2_ififo_try_pull(m->readyq, &id) == APR_SUCCESS)) {
- --n;
- stream = h2_ihash_get(m->streams, id);
- if (stream) {
- on_resume(on_ctx, stream);
- }
+ H2_MPLX_ENTER_ALWAYS(m);
+ registered = (h2_ihash_get(m->streams, stream_id) != NULL);
+ if (!stream) {
+ /* a RST might arrive so late, we have already forgotten
+ * about it. Seems ok. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+ H2_MPLX_MSG(m, "RST on unknown stream %d"), stream_id);
+ AP_DEBUG_ASSERT(!registered);
+ }
+ else if (!registered) {
+ /* a RST on a stream that mplx has not been told about, but
+ * which the session knows. Very early and annoying. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+ H2_STRM_MSG(stream, "very early RST, drop"));
+ h2_stream_set_monitor(stream, NULL);
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+ m_stream_cleanup(m, stream);
+ m_be_annoyed(m);
+ }
+ else if (!reset_is_acceptable(stream)) {
+ m_be_annoyed(m);
}
-
- return APR_SUCCESS;
+ H2_MPLX_LEAVE(m);
+ return status;
}
-apr_status_t h2_mplx_keep_active(h2_mplx *m, h2_stream *stream)
+static apr_status_t mplx_pollset_create(h2_mplx *m)
{
- check_data_for(m, stream, 1);
- return APR_SUCCESS;
+ /* stream0 output only */
+ return apr_pollset_create(&m->pollset, 1, m->pool,
+ APR_POLLSET_WAKEABLE);
}
-int h2_mplx_awaits_data(h2_mplx *m)
+static apr_status_t mplx_pollset_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx)
{
- int waiting = 1;
-
- H2_MPLX_ENTER_ALWAYS(m);
+ apr_status_t rv;
+ const apr_pollfd_t *results, *pfd;
+ apr_int32_t nresults, i;
+ h2_conn_ctx_t *conn_ctx;
+ h2_stream *stream;
- if (h2_ihash_empty(m->streams)) {
- waiting = 0;
- }
- else if (!m->tasks_active && !h2_ififo_count(m->readyq)
- && h2_iq_empty(m->q)) {
- waiting = 0;
- }
+ /* Make sure we are not called recursively. */
+ ap_assert(!m->polling);
+ m->polling = 1;
+ do {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "enter polling timeout=%d"),
+ (int)apr_time_sec(timeout));
+
+ apr_array_clear(m->streams_ev_in);
+ apr_array_clear(m->streams_ev_out);
+
+ do {
+ /* add streams we started processing in the meantime */
+ apr_thread_mutex_lock(m->poll_lock);
+ if (!h2_iq_empty(m->streams_input_read)
+ || !h2_iq_empty(m->streams_output_written)) {
+ while ((i = h2_iq_shift(m->streams_input_read))) {
+ stream = h2_ihash_get(m->streams, i);
+ if (stream) {
+ APR_ARRAY_PUSH(m->streams_ev_in, h2_stream*) = stream;
+ }
+ }
+ while ((i = h2_iq_shift(m->streams_output_written))) {
+ stream = h2_ihash_get(m->streams, i);
+ if (stream) {
+ APR_ARRAY_PUSH(m->streams_ev_out, h2_stream*) = stream;
+ }
+ }
+ nresults = 0;
+ rv = APR_SUCCESS;
+ apr_thread_mutex_unlock(m->poll_lock);
+ break;
+ }
+ apr_thread_mutex_unlock(m->poll_lock);
+
+ H2_MPLX_LEAVE(m);
+ rv = apr_pollset_poll(m->pollset, timeout >= 0? timeout : -1, &nresults, &results);
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (APR_STATUS_IS_EINTR(rv) && m->shutdown) {
+ if (!m->aborted) {
+ rv = APR_SUCCESS;
+ }
+ goto cleanup;
+ }
+ } while (APR_STATUS_IS_EINTR(rv));
- H2_MPLX_LEAVE(m);
- return waiting;
+ if (APR_SUCCESS != rv) {
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "polling timed out "));
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1, APLOGNO(10310) \
+ H2_MPLX_MSG(m, "polling failed"));
+ }
+ goto cleanup;
+ }
+
+ for (i = 0; i < nresults; i++) {
+ pfd = &results[i];
+ conn_ctx = pfd->client_data;
+
+ AP_DEBUG_ASSERT(conn_ctx);
+ if (conn_ctx->stream_id == 0) {
+ if (on_stream_input) {
+ APR_ARRAY_PUSH(m->streams_ev_in, h2_stream*) = m->stream0;
+ }
+ continue;
+ }
+ }
+
+ if (on_stream_input && m->streams_ev_in->nelts) {
+ H2_MPLX_LEAVE(m);
+ for (i = 0; i < m->streams_ev_in->nelts; ++i) {
+ on_stream_input(on_ctx, APR_ARRAY_IDX(m->streams_ev_in, i, h2_stream*));
+ }
+ H2_MPLX_ENTER_ALWAYS(m);
+ }
+ if (on_stream_output && m->streams_ev_out->nelts) {
+ H2_MPLX_LEAVE(m);
+ for (i = 0; i < m->streams_ev_out->nelts; ++i) {
+ on_stream_output(on_ctx, APR_ARRAY_IDX(m->streams_ev_out, i, h2_stream*));
+ }
+ H2_MPLX_ENTER_ALWAYS(m);
+ }
+ break;
+ } while(1);
+
+cleanup:
+ m->polling = 0;
+ return rv;
}
+
diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
index 2890b98..860f916 100644
--- a/modules/http2/h2_mplx.h
+++ b/modules/http2/h2_mplx.h
@@ -18,21 +18,16 @@
#define __mod_h2__h2_mplx__
/**
- * The stream multiplexer. It pushes buckets from the connection
- * thread to the stream threads and vice versa. It's thread-safe
- * to use.
+ * The stream multiplexer. It performs communication between the
+ * primary HTTP/2 connection (c1) to the secondary connections (c2)
+ * that process the requests, aka. HTTP/2 streams.
*
- * There is one h2_mplx instance for each h2_session, which sits on top
- * of a particular httpd conn_rec. Input goes from the connection to
- * the stream tasks. Output goes from the stream tasks to the connection,
- * e.g. the client.
+ * There is one h2_mplx instance for each h2_session.
*
- * For each stream, there can be at most "H2StreamMaxMemSize" output bytes
- * queued in the multiplexer. If a task thread tries to write more
- * data, it is blocked until space becomes available.
- *
- * Writing input is never blocked. In order to use flow control on the input,
- * the mplx can be polled for input data consumption.
+ * Naming Convention:
+ * "h2_mplx_c1_" are methods only to be called by the primary connection
+ * "h2_mplx_c2_" are methods only to be called by a secondary connection
+ * "h2_mplx_worker_" are methods only to be called by a h2 worker thread
*/
struct apr_pool_t;
@@ -41,108 +36,97 @@ struct apr_thread_cond_t;
struct h2_bucket_beam;
struct h2_config;
struct h2_ihash_t;
-struct h2_task;
struct h2_stream;
struct h2_request;
struct apr_thread_cond_t;
struct h2_workers;
struct h2_iqueue;
-struct h2_ngn_shed;
-struct h2_req_engine;
#include
+#include "h2_workers.h"
+
+typedef struct h2_c2_transit h2_c2_transit;
+
+struct h2_c2_transit {
+ apr_pool_t *pool;
+ apr_bucket_alloc_t *bucket_alloc;
+};
+
typedef struct h2_mplx h2_mplx;
struct h2_mplx {
- long id;
- conn_rec *c;
+ int child_num; /* child this runs in */
+ apr_uint32_t id; /* id unique per child */
+ conn_rec *c1; /* the main connection */
apr_pool_t *pool;
+ struct h2_stream *stream0; /* HTTP/2's stream 0 */
server_rec *s; /* server for master conn */
- unsigned int event_pending;
- unsigned int aborted;
- unsigned int is_registered; /* is registered at h2_workers */
+ int shutdown; /* we are shutting down */
+ int aborted; /* we need to get out of here asap */
+ int polling; /* is waiting/processing pollset events */
+ ap_conn_producer_t *producer; /* registered producer at h2_workers */
- struct h2_ihash_t *streams; /* all streams currently processing */
- struct h2_ihash_t *sredo; /* all streams that need to be re-started */
- struct h2_ihash_t *shold; /* all streams done with task ongoing */
- struct h2_ihash_t *spurge; /* all streams done, ready for destroy */
+ struct h2_ihash_t *streams; /* all streams active */
+ struct h2_ihash_t *shold; /* all streams done with c2 processing ongoing */
+ apr_array_header_t *spurge; /* all streams done, ready for destroy */
struct h2_iqueue *q; /* all stream ids that need to be started */
- struct h2_ififo *readyq; /* all stream ids ready for output */
-
- struct h2_ihash_t *redo_tasks; /* all tasks that need to be redone */
+
+ apr_size_t stream_max_mem; /* max memory to buffer for a stream */
+ apr_uint32_t max_streams; /* max # of concurrent streams */
+ apr_uint32_t max_stream_id_started; /* highest stream id that started processing */
+
+ apr_uint32_t processing_count; /* # of c2 working for this mplx */
+ apr_uint32_t processing_limit; /* current limit on processing c2s, dynamic */
+ apr_uint32_t processing_max; /* max, hard limit of processing c2s */
- int max_streams; /* max # of concurrent streams */
- int max_stream_started; /* highest stream id that started processing */
- int tasks_active; /* # of tasks being processed from this mplx */
- int limit_active; /* current limit on active tasks, dynamic */
- int max_active; /* max, hard limit # of active tasks in a process */
- apr_time_t last_idle_block; /* last time, this mplx entered IDLE while
- * streams were ready */
- apr_time_t last_limit_change; /* last time, worker limit changed */
- apr_interval_time_t limit_change_interval;
+ apr_time_t last_mood_change; /* last time, processing limit changed */
+ apr_interval_time_t mood_update_interval; /* how frequent we update at most */
+ apr_uint32_t irritations_since; /* irritations (>0) or happy events (<0) since last mood change */
apr_thread_mutex_t *lock;
- struct apr_thread_cond_t *added_output;
- struct apr_thread_cond_t *task_thawed;
struct apr_thread_cond_t *join_wait;
- apr_size_t stream_max_mem;
-
- apr_pool_t *spare_io_pool;
- apr_array_header_t *spare_slaves; /* spare slave connections */
-
- struct h2_workers *workers;
-
- struct h2_ngn_shed *ngn_shed;
-};
+ apr_pollset_t *pollset; /* pollset for c1/c2 IO events */
+ apr_array_header_t *streams_ev_in;
+ apr_array_header_t *streams_ev_out;
+ apr_thread_mutex_t *poll_lock; /* protect modifications of queues below */
+ struct h2_iqueue *streams_input_read; /* streams whose input has been read from */
+ struct h2_iqueue *streams_output_written; /* streams whose output has been written to */
+ struct h2_workers *workers; /* h2 workers process wide instance */
-/*******************************************************************************
- * Object lifecycle and information.
- ******************************************************************************/
+ apr_uint32_t max_spare_transits; /* max number of transit pools idling */
+ apr_array_header_t *c2_transits; /* base pools for running c2 connections */
+};
-apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s);
+apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s);
/**
* Create the multiplexer for the given HTTP2 session.
* Implicitly has reference count 1.
*/
-h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *master,
- const struct h2_config *conf,
- struct h2_workers *workers);
+h2_mplx *h2_mplx_c1_create(int child_id, apr_uint32_t id,
+ struct h2_stream *stream0,
+ server_rec *s, apr_pool_t *master,
+ struct h2_workers *workers);
/**
- * Decreases the reference counter of this mplx and waits for it
- * to reached 0, destroy the mplx afterwards.
- * This is to be called from the thread that created the mplx in
- * the first place.
- * @param m the mplx to be released and destroyed
+ * Destroy the mplx, shutting down all ongoing processing.
+ * @param m the mplx destroyed
* @param wait condition var to wait on for ref counter == 0
*/
-void h2_mplx_release_and_join(h2_mplx *m, struct apr_thread_cond_t *wait);
-
-apr_status_t h2_mplx_pop_task(h2_mplx *m, struct h2_task **ptask);
-
-void h2_mplx_task_done(h2_mplx *m, struct h2_task *task, struct h2_task **ptask);
+void h2_mplx_c1_destroy(h2_mplx *m);
/**
* Shut down the multiplexer gracefully. Will no longer schedule new streams
* but let the ongoing ones finish normally.
* @return the highest stream id being/been processed
*/
-int h2_mplx_shutdown(h2_mplx *m);
-
-int h2_mplx_is_busy(h2_mplx *m);
-
-/*******************************************************************************
- * IO lifetime of streams.
- ******************************************************************************/
-
-struct h2_stream *h2_mplx_stream_get(h2_mplx *m, int id);
+int h2_mplx_c1_shutdown(h2_mplx *m);
/**
* Notifies mplx that a stream has been completely handled on the main
@@ -150,33 +134,28 @@ struct h2_stream *h2_mplx_stream_get(h2_mplx *m, int id);
*
* @param m the mplx itself
* @param stream the stream ready for cleanup
+ * @param pstream_count return the number of streams active
*/
-apr_status_t h2_mplx_stream_cleanup(h2_mplx *m, struct h2_stream *stream);
-
-/**
- * Waits on output data from any stream in this session to become available.
- * Returns APR_TIMEUP if no data arrived in the given time.
- */
-apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
- struct apr_thread_cond_t *iowait);
+apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, struct h2_stream *stream,
+ unsigned int *pstream_count);
-apr_status_t h2_mplx_keep_active(h2_mplx *m, struct h2_stream *stream);
-
-/*******************************************************************************
- * Stream processing.
- ******************************************************************************/
+int h2_mplx_c1_stream_is_running(h2_mplx *m, struct h2_stream *stream);
/**
* Process a stream request.
*
* @param m the multiplexer
- * @param stream the identifier of the stream
- * @param r the request to be processed
+ * @param read_to_process
+ * @param input_pending
* @param cmp the stream priority compare function
- * @param ctx context data for the compare function
+ * @param pstream_count on return the number of streams active in mplx
*/
-apr_status_t h2_mplx_process(h2_mplx *m, struct h2_stream *stream,
- h2_stream_pri_cmp *cmp, void *ctx);
+void h2_mplx_c1_process(h2_mplx *m,
+ struct h2_iqueue *read_to_process,
+ h2_stream_get_fn *get_stream,
+ h2_stream_pri_cmp_fn *cmp,
+ struct h2_session *session,
+ unsigned int *pstream_count);
/**
* Stream priorities have changed, reschedule pending requests.
@@ -185,146 +164,67 @@ apr_status_t h2_mplx_process(h2_mplx *m, struct h2_stream *stream,
* @param cmp the stream priority compare function
* @param ctx context data for the compare function
*/
-apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
-
-typedef apr_status_t stream_ev_callback(void *ctx, struct h2_stream *stream);
+apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
+ struct h2_session *session);
-/**
- * Check if the multiplexer has events for the master connection pending.
- * @return != 0 iff there are events pending
- */
-int h2_mplx_has_master_events(h2_mplx *m);
+typedef void stream_ev_callback(void *ctx, struct h2_stream *stream);
/**
- * Dispatch events for the master connection, such as
- ± @param m the multiplexer
- * @param on_resume new output data has arrived for a suspended stream
- * @param ctx user supplied argument to invocation.
+ * Poll the primary connection for input and the active streams for output.
+ * Invoke the callback for any stream where an event happened.
*/
-apr_status_t h2_mplx_dispatch_master_events(h2_mplx *m,
- stream_ev_callback *on_resume,
- void *ctx);
-
-int h2_mplx_awaits_data(h2_mplx *m);
+apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx);
-typedef int h2_mplx_stream_cb(struct h2_stream *s, void *ctx);
+void h2_mplx_c2_input_read(h2_mplx *m, conn_rec *c2);
+void h2_mplx_c2_output_written(h2_mplx *m, conn_rec *c2);
-apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
-
-/*******************************************************************************
- * Output handling of streams.
- ******************************************************************************/
+typedef int h2_mplx_stream_cb(struct h2_stream *s, void *userdata);
/**
- * Opens the output for the given stream with the specified response.
+ * Iterate over all streams known to mplx from the primary connection.
+ * @param m the mplx
+ * @param cb the callback to invoke on each stream
+ * @param ctx userdata passed to the callback
*/
-apr_status_t h2_mplx_out_open(h2_mplx *mplx, int stream_id,
- struct h2_bucket_beam *beam);
-
-/*******************************************************************************
- * h2_mplx list Manipulation.
- ******************************************************************************/
+apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
/**
- * The magic pointer value that indicates the head of a h2_mplx list
- * @param b The mplx list
- * @return The magic pointer value
+ * Return != 0 iff all open streams want to send data
*/
-#define H2_MPLX_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_mplx, link)
+int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m);
/**
- * Determine if the mplx list is empty
- * @param b The list to check
- * @return true or false
+ * A stream has been RST_STREAM by the client. Abort
+ * any processing going on and remove from processing
+ * queue.
*/
-#define H2_MPLX_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_mplx, link)
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id,
+ struct h2_stream *stream);
/**
- * Return the first mplx in a list
- * @param b The list to query
- * @return The first mplx in the list
+ * Get readonly access to a stream for a secondary connection.
*/
-#define H2_MPLX_LIST_FIRST(b) APR_RING_FIRST(b)
+const struct h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id);
/**
- * Return the last mplx in a list
- * @param b The list to query
- * @return The last mplx int he list
+ * A h2 worker asks for a secondary connection to process.
+ * @param out_c2 non-NULL, a pointer where to reveive the next
+ * secondary connection to process.
*/
-#define H2_MPLX_LIST_LAST(b) APR_RING_LAST(b)
+apr_status_t h2_mplx_worker_pop_c2(h2_mplx *m, conn_rec **out_c2);
-/**
- * Insert a single mplx at the front of a list
- * @param b The list to add to
- * @param e The mplx to insert
- */
-#define H2_MPLX_LIST_INSERT_HEAD(b, e) do { \
-h2_mplx *ap__b = (e); \
-APR_RING_INSERT_HEAD((b), ap__b, h2_mplx, link); \
-} while (0)
/**
- * Insert a single mplx at the end of a list
- * @param b The list to add to
- * @param e The mplx to insert
+ * Session processing is entering KEEPALIVE, e.g. giving control
+ * to the MPM for monitoring incoming socket events only.
+ * Last chance for maintenance work before losing control.
*/
-#define H2_MPLX_LIST_INSERT_TAIL(b, e) do { \
-h2_mplx *ap__b = (e); \
-APR_RING_INSERT_TAIL((b), ap__b, h2_mplx, link); \
-} while (0)
+void h2_mplx_c1_going_keepalive(h2_mplx *m);
-/**
- * Get the next mplx in the list
- * @param e The current mplx
- * @return The next mplx
- */
-#define H2_MPLX_NEXT(e) APR_RING_NEXT((e), link)
-/**
- * Get the previous mplx in the list
- * @param e The current mplx
- * @return The previous mplx
- */
-#define H2_MPLX_PREV(e) APR_RING_PREV((e), link)
-
-/**
- * Remove a mplx from its list
- * @param e The mplx to remove
- */
-#define H2_MPLX_REMOVE(e) APR_RING_REMOVE((e), link)
-
-/*******************************************************************************
- * h2_mplx DoS protection
- ******************************************************************************/
-
-/**
- * Master connection has entered idle mode.
- * @param m the mplx instance of the master connection
- * @return != SUCCESS iff connection should be terminated
- */
-apr_status_t h2_mplx_idle(h2_mplx *m);
-
-/*******************************************************************************
- * h2_req_engine handling
- ******************************************************************************/
-
-typedef void h2_output_consumed(void *ctx, conn_rec *c, apr_off_t consumed);
-typedef apr_status_t h2_mplx_req_engine_init(struct h2_req_engine *engine,
- const char *id,
- const char *type,
- apr_pool_t *pool,
- apr_size_t req_buffer_size,
- request_rec *r,
- h2_output_consumed **pconsumed,
- void **pbaton);
-
-apr_status_t h2_mplx_req_engine_push(const char *ngn_type,
- request_rec *r,
- h2_mplx_req_engine_init *einit);
-apr_status_t h2_mplx_req_engine_pull(struct h2_req_engine *ngn,
- apr_read_type_e block,
- int capacity,
- request_rec **pr);
-void h2_mplx_req_engine_done(struct h2_req_engine *ngn, conn_rec *r_conn,
- apr_status_t status);
+#define H2_MPLX_MSG(m, msg) \
+ "h2_mplx(%d-%lu): "msg, m->child_num, (unsigned long)m->id
#endif /* defined(__mod_h2__h2_mplx__) */
diff --git a/modules/http2/h2_ngn_shed.c b/modules/http2/h2_ngn_shed.c
deleted file mode 100644
index fb85776..0000000
--- a/modules/http2/h2_ngn_shed.c
+++ /dev/null
@@ -1,392 +0,0 @@
-/* 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
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include "mod_http2.h"
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_config.h"
-#include "h2_conn.h"
-#include "h2_ctx.h"
-#include "h2_h2.h"
-#include "h2_mplx.h"
-#include "h2_request.h"
-#include "h2_task.h"
-#include "h2_util.h"
-#include "h2_ngn_shed.h"
-
-
-typedef struct h2_ngn_entry h2_ngn_entry;
-struct h2_ngn_entry {
- APR_RING_ENTRY(h2_ngn_entry) link;
- h2_task *task;
- request_rec *r;
-};
-
-#define H2_NGN_ENTRY_NEXT(e) APR_RING_NEXT((e), link)
-#define H2_NGN_ENTRY_PREV(e) APR_RING_PREV((e), link)
-#define H2_NGN_ENTRY_REMOVE(e) APR_RING_REMOVE((e), link)
-
-#define H2_REQ_ENTRIES_SENTINEL(b) APR_RING_SENTINEL((b), h2_ngn_entry, link)
-#define H2_REQ_ENTRIES_EMPTY(b) APR_RING_EMPTY((b), h2_ngn_entry, link)
-#define H2_REQ_ENTRIES_FIRST(b) APR_RING_FIRST(b)
-#define H2_REQ_ENTRIES_LAST(b) APR_RING_LAST(b)
-
-#define H2_REQ_ENTRIES_INSERT_HEAD(b, e) do { \
-h2_ngn_entry *ap__b = (e); \
-APR_RING_INSERT_HEAD((b), ap__b, h2_ngn_entry, link); \
-} while (0)
-
-#define H2_REQ_ENTRIES_INSERT_TAIL(b, e) do { \
-h2_ngn_entry *ap__b = (e); \
-APR_RING_INSERT_TAIL((b), ap__b, h2_ngn_entry, link); \
-} while (0)
-
-struct h2_req_engine {
- const char *id; /* identifier */
- const char *type; /* name of the engine type */
- apr_pool_t *pool; /* pool for engine specific allocations */
- conn_rec *c; /* connection this engine is assigned to */
- h2_task *task; /* the task this engine is based on, running in */
- h2_ngn_shed *shed;
-
- unsigned int shutdown : 1; /* engine is being shut down */
- unsigned int done : 1; /* engine has finished */
-
- APR_RING_HEAD(h2_req_entries, h2_ngn_entry) entries;
- int capacity; /* maximum concurrent requests */
- int no_assigned; /* # of assigned requests */
- int no_live; /* # of live */
- int no_finished; /* # of finished */
-
- h2_output_consumed *out_consumed;
- void *out_consumed_ctx;
-};
-
-const char *h2_req_engine_get_id(h2_req_engine *engine)
-{
- return engine->id;
-}
-
-int h2_req_engine_is_shutdown(h2_req_engine *engine)
-{
- return engine->shutdown;
-}
-
-void h2_req_engine_out_consumed(h2_req_engine *engine, conn_rec *c,
- apr_off_t bytes)
-{
- if (engine->out_consumed) {
- engine->out_consumed(engine->out_consumed_ctx, c, bytes);
- }
-}
-
-h2_ngn_shed *h2_ngn_shed_create(apr_pool_t *pool, conn_rec *c,
- int default_capacity,
- apr_size_t req_buffer_size)
-{
- h2_ngn_shed *shed;
-
- shed = apr_pcalloc(pool, sizeof(*shed));
- shed->c = c;
- shed->pool = pool;
- shed->default_capacity = default_capacity;
- shed->req_buffer_size = req_buffer_size;
- shed->ngns = apr_hash_make(pool);
-
- return shed;
-}
-
-void h2_ngn_shed_set_ctx(h2_ngn_shed *shed, void *user_ctx)
-{
- shed->user_ctx = user_ctx;
-}
-
-void *h2_ngn_shed_get_ctx(h2_ngn_shed *shed)
-{
- return shed->user_ctx;
-}
-
-h2_ngn_shed *h2_ngn_shed_get_shed(h2_req_engine *ngn)
-{
- return ngn->shed;
-}
-
-void h2_ngn_shed_abort(h2_ngn_shed *shed)
-{
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c, APLOGNO(03394)
- "h2_ngn_shed(%ld): abort", shed->c->id);
- shed->aborted = 1;
-}
-
-static void ngn_add_task(h2_req_engine *ngn, h2_task *task, request_rec *r)
-{
- h2_ngn_entry *entry = apr_pcalloc(task->pool, sizeof(*entry));
- APR_RING_ELEM_INIT(entry, link);
- entry->task = task;
- entry->r = r;
- H2_REQ_ENTRIES_INSERT_TAIL(&ngn->entries, entry);
- ngn->no_assigned++;
-}
-
-
-apr_status_t h2_ngn_shed_push_request(h2_ngn_shed *shed, const char *ngn_type,
- request_rec *r,
- http2_req_engine_init *einit)
-{
- h2_req_engine *ngn;
- h2_task *task = h2_ctx_rget_task(r);
-
- ap_assert(task);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
- "h2_ngn_shed(%ld): PUSHing request (task=%s)", shed->c->id,
- task->id);
- if (task->request->serialize) {
- /* Max compatibility, deny processing of this */
- return APR_EOF;
- }
-
- if (task->assigned) {
- --task->assigned->no_assigned;
- --task->assigned->no_live;
- task->assigned = NULL;
- }
-
- if (task->engine) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
- "h2_ngn_shed(%ld): push task(%s) hosting engine %s "
- "already with %d tasks",
- shed->c->id, task->id, task->engine->id,
- task->engine->no_assigned);
- task->assigned = task->engine;
- ngn_add_task(task->engine, task, r);
- return APR_SUCCESS;
- }
-
- ngn = apr_hash_get(shed->ngns, ngn_type, APR_HASH_KEY_STRING);
- if (ngn && !ngn->shutdown) {
- /* this task will be processed in another thread,
- * freeze any I/O for the time being. */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
- "h2_ngn_shed(%ld): pushing request %s to %s",
- shed->c->id, task->id, ngn->id);
- if (!h2_task_has_thawed(task)) {
- h2_task_freeze(task);
- }
- ngn_add_task(ngn, task, r);
- return APR_SUCCESS;
- }
-
- /* no existing engine or being shut down, start a new one */
- if (einit) {
- apr_status_t status;
- apr_pool_t *pool = task->pool;
- h2_req_engine *newngn;
-
- newngn = apr_pcalloc(pool, sizeof(*ngn));
- newngn->pool = pool;
- newngn->id = apr_psprintf(pool, "ngn-%s", task->id);
- newngn->type = apr_pstrdup(pool, ngn_type);
- newngn->c = task->c;
- newngn->shed = shed;
- newngn->capacity = shed->default_capacity;
- newngn->no_assigned = 1;
- newngn->no_live = 1;
- APR_RING_INIT(&newngn->entries, h2_ngn_entry, link);
-
- status = einit(newngn, newngn->id, newngn->type, newngn->pool,
- shed->req_buffer_size, r,
- &newngn->out_consumed, &newngn->out_consumed_ctx);
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c, APLOGNO(03395)
- "h2_ngn_shed(%ld): create engine %s (%s)",
- shed->c->id, newngn->id, newngn->type);
- if (status == APR_SUCCESS) {
- newngn->task = task;
- task->engine = newngn;
- task->assigned = newngn;
- apr_hash_set(shed->ngns, newngn->type, APR_HASH_KEY_STRING, newngn);
- }
- return status;
- }
- return APR_EOF;
-}
-
-static h2_ngn_entry *pop_detached(h2_req_engine *ngn)
-{
- h2_ngn_entry *entry;
- for (entry = H2_REQ_ENTRIES_FIRST(&ngn->entries);
- entry != H2_REQ_ENTRIES_SENTINEL(&ngn->entries);
- entry = H2_NGN_ENTRY_NEXT(entry)) {
- if (h2_task_has_thawed(entry->task)
- || (entry->task->engine == ngn)) {
- /* The task hosting this engine can always be pulled by it.
- * For other task, they need to become detached, e.g. no longer
- * assigned to another worker. */
- H2_NGN_ENTRY_REMOVE(entry);
- return entry;
- }
- }
- return NULL;
-}
-
-apr_status_t h2_ngn_shed_pull_request(h2_ngn_shed *shed,
- h2_req_engine *ngn,
- int capacity,
- int want_shutdown,
- request_rec **pr)
-{
- h2_ngn_entry *entry;
-
- ap_assert(ngn);
- *pr = NULL;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, shed->c, APLOGNO(03396)
- "h2_ngn_shed(%ld): pull task for engine %s, shutdown=%d",
- shed->c->id, ngn->id, want_shutdown);
- if (shed->aborted) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c, APLOGNO(03397)
- "h2_ngn_shed(%ld): abort while pulling requests %s",
- shed->c->id, ngn->id);
- ngn->shutdown = 1;
- return APR_ECONNABORTED;
- }
-
- ngn->capacity = capacity;
- if (H2_REQ_ENTRIES_EMPTY(&ngn->entries)) {
- if (want_shutdown) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
- "h2_ngn_shed(%ld): emtpy queue, shutdown engine %s",
- shed->c->id, ngn->id);
- ngn->shutdown = 1;
- }
- return ngn->shutdown? APR_EOF : APR_EAGAIN;
- }
-
- if ((entry = pop_detached(ngn))) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, entry->task->c, APLOGNO(03398)
- "h2_ngn_shed(%ld): pulled request %s for engine %s",
- shed->c->id, entry->task->id, ngn->id);
- ngn->no_live++;
- *pr = entry->r;
- entry->task->assigned = ngn;
- /* task will now run in ngn's own thread. Modules like lua
- * seem to require the correct thread set in the conn_rec.
- * See PR 59542. */
- if (entry->task->c && ngn->c) {
- entry->task->c->current_thread = ngn->c->current_thread;
- }
- if (entry->task->engine == ngn) {
- /* If an engine pushes its own base task, and then pulls
- * it back to itself again, it needs to be thawed.
- */
- h2_task_thaw(entry->task);
- }
- return APR_SUCCESS;
- }
-
- if (1) {
- h2_ngn_entry *entry = H2_REQ_ENTRIES_FIRST(&ngn->entries);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, shed->c, APLOGNO(03399)
- "h2_ngn_shed(%ld): pull task, nothing, first task %s",
- shed->c->id, entry->task->id);
- }
- return APR_EAGAIN;
-}
-
-static apr_status_t ngn_done_task(h2_ngn_shed *shed, h2_req_engine *ngn,
- h2_task *task, int waslive, int aborted)
-{
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, shed->c, APLOGNO(03400)
- "h2_ngn_shed(%ld): task %s %s by %s",
- shed->c->id, task->id, aborted? "aborted":"done", ngn->id);
- ngn->no_finished++;
- if (waslive) ngn->no_live--;
- ngn->no_assigned--;
- task->assigned = NULL;
-
- return APR_SUCCESS;
-}
-
-apr_status_t h2_ngn_shed_done_task(h2_ngn_shed *shed,
- struct h2_req_engine *ngn, h2_task *task)
-{
- return ngn_done_task(shed, ngn, task, 1, 0);
-}
-
-void h2_ngn_shed_done_ngn(h2_ngn_shed *shed, struct h2_req_engine *ngn)
-{
- if (ngn->done) {
- return;
- }
-
- if (!shed->aborted && !H2_REQ_ENTRIES_EMPTY(&ngn->entries)) {
- h2_ngn_entry *entry;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
- "h2_ngn_shed(%ld): exit engine %s (%s), "
- "has still requests queued, shutdown=%d,"
- "assigned=%ld, live=%ld, finished=%ld",
- shed->c->id, ngn->id, ngn->type,
- ngn->shutdown,
- (long)ngn->no_assigned, (long)ngn->no_live,
- (long)ngn->no_finished);
- for (entry = H2_REQ_ENTRIES_FIRST(&ngn->entries);
- entry != H2_REQ_ENTRIES_SENTINEL(&ngn->entries);
- entry = H2_NGN_ENTRY_NEXT(entry)) {
- h2_task *task = entry->task;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
- "h2_ngn_shed(%ld): engine %s has queued task %s, "
- "frozen=%d, aborting",
- shed->c->id, ngn->id, task->id, task->frozen);
- ngn_done_task(shed, ngn, task, 0, 1);
- task->engine = task->assigned = NULL;
- }
- }
- if (!shed->aborted && (ngn->no_assigned > 1 || ngn->no_live > 1)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
- "h2_ngn_shed(%ld): exit engine %s (%s), "
- "assigned=%ld, live=%ld, finished=%ld",
- shed->c->id, ngn->id, ngn->type,
- (long)ngn->no_assigned, (long)ngn->no_live,
- (long)ngn->no_finished);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
- "h2_ngn_shed(%ld): exit engine %s",
- shed->c->id, ngn->id);
- }
-
- apr_hash_set(shed->ngns, ngn->type, APR_HASH_KEY_STRING, NULL);
- ngn->done = 1;
-}
-
-void h2_ngn_shed_destroy(h2_ngn_shed *shed)
-{
- ap_assert(apr_hash_count(shed->ngns) == 0);
-}
-
diff --git a/modules/http2/h2_ngn_shed.h b/modules/http2/h2_ngn_shed.h
deleted file mode 100644
index 7764c18..0000000
--- a/modules/http2/h2_ngn_shed.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* 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.
- */
-
-#ifndef h2_req_shed_h
-#define h2_req_shed_h
-
-struct h2_req_engine;
-struct h2_task;
-
-typedef struct h2_ngn_shed h2_ngn_shed;
-struct h2_ngn_shed {
- conn_rec *c;
- apr_pool_t *pool;
- apr_hash_t *ngns;
- void *user_ctx;
-
- unsigned int aborted : 1;
-
- int default_capacity;
- apr_size_t req_buffer_size; /* preferred buffer size for responses */
-};
-
-const char *h2_req_engine_get_id(h2_req_engine *engine);
-int h2_req_engine_is_shutdown(h2_req_engine *engine);
-
-void h2_req_engine_out_consumed(h2_req_engine *engine, conn_rec *c,
- apr_off_t bytes);
-
-typedef apr_status_t h2_shed_ngn_init(h2_req_engine *engine,
- const char *id,
- const char *type,
- apr_pool_t *pool,
- apr_size_t req_buffer_size,
- request_rec *r,
- h2_output_consumed **pconsumed,
- void **pbaton);
-
-h2_ngn_shed *h2_ngn_shed_create(apr_pool_t *pool, conn_rec *c,
- int default_capactiy,
- apr_size_t req_buffer_size);
-
-void h2_ngn_shed_destroy(h2_ngn_shed *shed);
-
-void h2_ngn_shed_set_ctx(h2_ngn_shed *shed, void *user_ctx);
-void *h2_ngn_shed_get_ctx(h2_ngn_shed *shed);
-
-h2_ngn_shed *h2_ngn_shed_get_shed(struct h2_req_engine *ngn);
-
-void h2_ngn_shed_abort(h2_ngn_shed *shed);
-
-apr_status_t h2_ngn_shed_push_request(h2_ngn_shed *shed, const char *ngn_type,
- request_rec *r,
- h2_shed_ngn_init *init_cb);
-
-apr_status_t h2_ngn_shed_pull_request(h2_ngn_shed *shed, h2_req_engine *pub_ngn,
- int capacity,
- int want_shutdown, request_rec **pr);
-
-apr_status_t h2_ngn_shed_done_task(h2_ngn_shed *shed,
- struct h2_req_engine *ngn,
- struct h2_task *task);
-
-void h2_ngn_shed_done_ngn(h2_ngn_shed *shed, struct h2_req_engine *ngn);
-
-
-#endif /* h2_req_shed_h */
diff --git a/modules/http2/h2_protocol.c b/modules/http2/h2_protocol.c
new file mode 100644
index 0000000..874753e
--- /dev/null
+++ b/modules/http2/h2_protocol.c
@@ -0,0 +1,485 @@
+/* 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
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mod_http2.h"
+#include "h2_private.h"
+
+#include "h2_bucket_beam.h"
+#include "h2_stream.h"
+#include "h2_c2.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_util.h"
+#include "h2_protocol.h"
+#include "mod_http2.h"
+
+const char *h2_protocol_ids_tls[] = {
+ "h2", NULL
+};
+
+const char *h2_protocol_ids_clear[] = {
+ "h2c", NULL
+};
+
+const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+/*******************************************************************************
+ * HTTP/2 error stuff
+ */
+static const char *h2_err_descr[] = {
+ "no error", /* 0x0 */
+ "protocol error",
+ "internal error",
+ "flow control error",
+ "settings timeout",
+ "stream closed", /* 0x5 */
+ "frame size error",
+ "refused stream",
+ "cancel",
+ "compression error",
+ "connect error", /* 0xa */
+ "enhance your calm",
+ "inadequate security",
+ "http/1.1 required",
+};
+
+const char *h2_protocol_err_description(unsigned int h2_error)
+{
+ if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
+ return h2_err_descr[h2_error];
+ }
+ return "unknown http/2 error code";
+}
+
+/*******************************************************************************
+ * Check connection security requirements of RFC 7540
+ */
+
+/*
+ * Black Listed Ciphers from RFC 7549 Appendix A
+ *
+ */
+static const char *RFC7540_names[] = {
+ /* ciphers with NULL encrpytion */
+ "NULL-MD5", /* TLS_NULL_WITH_NULL_NULL */
+ /* same */ /* TLS_RSA_WITH_NULL_MD5 */
+ "NULL-SHA", /* TLS_RSA_WITH_NULL_SHA */
+ "NULL-SHA256", /* TLS_RSA_WITH_NULL_SHA256 */
+ "PSK-NULL-SHA", /* TLS_PSK_WITH_NULL_SHA */
+ "DHE-PSK-NULL-SHA", /* TLS_DHE_PSK_WITH_NULL_SHA */
+ "RSA-PSK-NULL-SHA", /* TLS_RSA_PSK_WITH_NULL_SHA */
+ "PSK-NULL-SHA256", /* TLS_PSK_WITH_NULL_SHA256 */
+ "PSK-NULL-SHA384", /* TLS_PSK_WITH_NULL_SHA384 */
+ "DHE-PSK-NULL-SHA256", /* TLS_DHE_PSK_WITH_NULL_SHA256 */
+ "DHE-PSK-NULL-SHA384", /* TLS_DHE_PSK_WITH_NULL_SHA384 */
+ "RSA-PSK-NULL-SHA256", /* TLS_RSA_PSK_WITH_NULL_SHA256 */
+ "RSA-PSK-NULL-SHA384", /* TLS_RSA_PSK_WITH_NULL_SHA384 */
+ "ECDH-ECDSA-NULL-SHA", /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
+ "ECDHE-ECDSA-NULL-SHA", /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
+ "ECDH-RSA-NULL-SHA", /* TLS_ECDH_RSA_WITH_NULL_SHA */
+ "ECDHE-RSA-NULL-SHA", /* TLS_ECDHE_RSA_WITH_NULL_SHA */
+ "AECDH-NULL-SHA", /* TLS_ECDH_anon_WITH_NULL_SHA */
+ "ECDHE-PSK-NULL-SHA", /* TLS_ECDHE_PSK_WITH_NULL_SHA */
+ "ECDHE-PSK-NULL-SHA256", /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
+ "ECDHE-PSK-NULL-SHA384", /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
+
+ /* DES/3DES ciphers */
+ "PSK-3DES-EDE-CBC-SHA", /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
+ "DHE-PSK-3DES-EDE-CBC-SHA", /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
+ "RSA-PSK-3DES-EDE-CBC-SHA", /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
+ "ECDH-ECDSA-DES-CBC3-SHA", /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
+ "ECDHE-ECDSA-DES-CBC3-SHA", /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
+ "ECDH-RSA-DES-CBC3-SHA", /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
+ "ECDHE-RSA-DES-CBC3-SHA", /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
+ "AECDH-DES-CBC3-SHA", /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
+ "SRP-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
+ "SRP-RSA-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
+ "SRP-DSS-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
+ "ECDHE-PSK-3DES-EDE-CBC-SHA", /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
+ "DES-CBC-SHA", /* TLS_RSA_WITH_DES_CBC_SHA */
+ "DES-CBC3-SHA", /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
+ "DHE-DSS-DES-CBC3-SHA", /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
+ "DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
+ "DHE-RSA-DES-CBC3-SHA", /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
+ "ADH-DES-CBC-SHA", /* TLS_DH_anon_WITH_DES_CBC_SHA */
+ "ADH-DES-CBC3-SHA", /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
+ "EXP-DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
+ "DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_WITH_DES_CBC_SHA */
+ "DH-DSS-DES-CBC3-SHA", /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
+ "EXP-DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
+ "DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_WITH_DES_CBC_SHA */
+ "DH-RSA-DES-CBC3-SHA", /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
+
+ /* blacklisted EXPORT ciphers */
+ "EXP-RC4-MD5", /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
+ "EXP-RC2-CBC-MD5", /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
+ "EXP-DES-CBC-SHA", /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-DHE-DSS-DES-CBC-SHA", /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-ADH-DES-CBC-SHA", /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-ADH-RC4-MD5", /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
+
+ /* blacklisted RC4 encryption */
+ "RC4-MD5", /* TLS_RSA_WITH_RC4_128_MD5 */
+ "RC4-SHA", /* TLS_RSA_WITH_RC4_128_SHA */
+ "ADH-RC4-MD5", /* TLS_DH_anon_WITH_RC4_128_MD5 */
+ "KRB5-RC4-SHA", /* TLS_KRB5_WITH_RC4_128_SHA */
+ "KRB5-RC4-MD5", /* TLS_KRB5_WITH_RC4_128_MD5 */
+ "EXP-KRB5-RC4-SHA", /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
+ "EXP-KRB5-RC4-MD5", /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
+ "PSK-RC4-SHA", /* TLS_PSK_WITH_RC4_128_SHA */
+ "DHE-PSK-RC4-SHA", /* TLS_DHE_PSK_WITH_RC4_128_SHA */
+ "RSA-PSK-RC4-SHA", /* TLS_RSA_PSK_WITH_RC4_128_SHA */
+ "ECDH-ECDSA-RC4-SHA", /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
+ "ECDHE-ECDSA-RC4-SHA", /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
+ "ECDH-RSA-RC4-SHA", /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
+ "ECDHE-RSA-RC4-SHA", /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
+ "AECDH-RC4-SHA", /* TLS_ECDH_anon_WITH_RC4_128_SHA */
+ "ECDHE-PSK-RC4-SHA", /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
+
+ /* blacklisted AES128 encrpytion ciphers */
+ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA */
+ "DH-DSS-AES128-SHA", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
+ "DH-RSA-AES128-SHA", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
+ "DHE-DSS-AES128-SHA", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
+ "DHE-RSA-AES128-SHA", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
+ "ADH-AES128-SHA", /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
+ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
+ "DH-DSS-AES128-SHA256", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
+ "DH-RSA-AES128-SHA256", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
+ "DHE-DSS-AES128-SHA256", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
+ "DHE-RSA-AES128-SHA256", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
+ "ECDH-ECDSA-AES128-SHA", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
+ "ECDHE-ECDSA-AES128-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
+ "ECDH-RSA-AES128-SHA", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
+ "ECDHE-RSA-AES128-SHA", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
+ "AECDH-AES128-SHA", /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
+ "ECDHE-ECDSA-AES128-SHA256", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
+ "ECDH-ECDSA-AES128-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
+ "ECDHE-RSA-AES128-SHA256", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
+ "ECDH-RSA-AES128-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
+ "ADH-AES128-SHA256", /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
+ "PSK-AES128-CBC-SHA", /* TLS_PSK_WITH_AES_128_CBC_SHA */
+ "DHE-PSK-AES128-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
+ "RSA-PSK-AES128-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
+ "PSK-AES128-CBC-SHA256", /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
+ "DHE-PSK-AES128-CBC-SHA256", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
+ "RSA-PSK-AES128-CBC-SHA256", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
+ "ECDHE-PSK-AES128-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
+ "ECDHE-PSK-AES128-CBC-SHA256", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
+ "AES128-CCM", /* TLS_RSA_WITH_AES_128_CCM */
+ "AES128-CCM8", /* TLS_RSA_WITH_AES_128_CCM_8 */
+ "PSK-AES128-CCM", /* TLS_PSK_WITH_AES_128_CCM */
+ "PSK-AES128-CCM8", /* TLS_PSK_WITH_AES_128_CCM_8 */
+ "AES128-GCM-SHA256", /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
+ "DH-RSA-AES128-GCM-SHA256", /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
+ "DH-DSS-AES128-GCM-SHA256", /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
+ "ADH-AES128-GCM-SHA256", /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
+ "PSK-AES128-GCM-SHA256", /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
+ "RSA-PSK-AES128-GCM-SHA256", /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
+ "ECDH-ECDSA-AES128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
+ "ECDH-RSA-AES128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
+ "SRP-AES-128-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
+ "SRP-RSA-AES-128-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
+ "SRP-DSS-AES-128-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
+
+ /* blacklisted AES256 encrpytion ciphers */
+ "AES256-SHA", /* TLS_RSA_WITH_AES_256_CBC_SHA */
+ "DH-DSS-AES256-SHA", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
+ "DH-RSA-AES256-SHA", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
+ "DHE-DSS-AES256-SHA", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
+ "DHE-RSA-AES256-SHA", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
+ "ADH-AES256-SHA", /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
+ "AES256-SHA256", /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
+ "DH-DSS-AES256-SHA256", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
+ "DH-RSA-AES256-SHA256", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
+ "DHE-DSS-AES256-SHA256", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
+ "DHE-RSA-AES256-SHA256", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
+ "ADH-AES256-SHA256", /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
+ "ECDH-ECDSA-AES256-SHA", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
+ "ECDHE-ECDSA-AES256-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
+ "ECDH-RSA-AES256-SHA", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
+ "ECDHE-RSA-AES256-SHA", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
+ "AECDH-AES256-SHA", /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
+ "ECDHE-ECDSA-AES256-SHA384", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
+ "ECDH-ECDSA-AES256-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
+ "ECDHE-RSA-AES256-SHA384", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
+ "ECDH-RSA-AES256-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
+ "PSK-AES256-CBC-SHA", /* TLS_PSK_WITH_AES_256_CBC_SHA */
+ "DHE-PSK-AES256-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
+ "RSA-PSK-AES256-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
+ "PSK-AES256-CBC-SHA384", /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
+ "DHE-PSK-AES256-CBC-SHA384", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
+ "RSA-PSK-AES256-CBC-SHA384", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
+ "ECDHE-PSK-AES256-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
+ "ECDHE-PSK-AES256-CBC-SHA384", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
+ "SRP-AES-256-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
+ "SRP-RSA-AES-256-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
+ "SRP-DSS-AES-256-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
+ "AES256-CCM", /* TLS_RSA_WITH_AES_256_CCM */
+ "AES256-CCM8", /* TLS_RSA_WITH_AES_256_CCM_8 */
+ "PSK-AES256-CCM", /* TLS_PSK_WITH_AES_256_CCM */
+ "PSK-AES256-CCM8", /* TLS_PSK_WITH_AES_256_CCM_8 */
+ "AES256-GCM-SHA384", /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
+ "DH-RSA-AES256-GCM-SHA384", /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
+ "DH-DSS-AES256-GCM-SHA384", /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
+ "ADH-AES256-GCM-SHA384", /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
+ "PSK-AES256-GCM-SHA384", /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
+ "RSA-PSK-AES256-GCM-SHA384", /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
+ "ECDH-ECDSA-AES256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
+ "ECDH-RSA-AES256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
+
+ /* blacklisted CAMELLIA128 encrpytion ciphers */
+ "CAMELLIA128-SHA", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
+ "DH-DSS-CAMELLIA128-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
+ "DH-RSA-CAMELLIA128-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
+ "DHE-DSS-CAMELLIA128-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
+ "DHE-RSA-CAMELLIA128-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
+ "ADH-CAMELLIA128-SHA", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
+ "ECDHE-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDH-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDHE-RSA-CAMELLIA128-SHA256", /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDH-RSA-CAMELLIA128-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "PSK-CAMELLIA128-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DHE-PSK-CAMELLIA128-SHA256", /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "RSA-PSK-CAMELLIA128-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDHE-PSK-CAMELLIA128-SHA256", /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "CAMELLIA128-GCM-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "DH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "DH-DSS-CAMELLIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
+ "ADH-CAMELLIA128-GCM-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
+ "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "ECDH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "PSK-CAMELLIA128-GCM-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
+ "RSA-PSK-CAMELLIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
+ "CAMELLIA128-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DH-DSS-CAMELLIA128-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DH-RSA-CAMELLIA128-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DHE-DSS-CAMELLIA128-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DHE-RSA-CAMELLIA128-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ADH-CAMELLIA128-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
+
+ /* blacklisted CAMELLIA256 encrpytion ciphers */
+ "CAMELLIA256-SHA", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
+ "DH-RSA-CAMELLIA256-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
+ "DH-DSS-CAMELLIA256-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
+ "DHE-DSS-CAMELLIA256-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
+ "DHE-RSA-CAMELLIA256-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
+ "ADH-CAMELLIA256-SHA", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
+ "ECDHE-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDH-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDHE-RSA-CAMELLIA256-SHA384", /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDH-RSA-CAMELLIA256-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "PSK-CAMELLIA256-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "DHE-PSK-CAMELLIA256-SHA384", /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "RSA-PSK-CAMELLIA256-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDHE-PSK-CAMELLIA256-SHA384", /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "CAMELLIA256-SHA256", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DH-DSS-CAMELLIA256-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DH-RSA-CAMELLIA256-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DHE-DSS-CAMELLIA256-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DHE-RSA-CAMELLIA256-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+ "ADH-CAMELLIA256-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
+ "CAMELLIA256-GCM-SHA384", /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "DH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "DH-DSS-CAMELLIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
+ "ADH-CAMELLIA256-GCM-SHA384", /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
+ "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "ECDH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "PSK-CAMELLIA256-GCM-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
+ "RSA-PSK-CAMELLIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
+
+ /* The blacklisted ARIA encrpytion ciphers */
+ "ARIA128-SHA256", /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "ARIA256-SHA384", /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "DH-DSS-ARIA128-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
+ "DH-DSS-ARIA256-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
+ "DH-RSA-ARIA128-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "DH-RSA-ARIA256-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "DHE-DSS-ARIA128-SHA256", /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
+ "DHE-DSS-ARIA256-SHA384", /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
+ "DHE-RSA-ARIA128-SHA256", /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "DHE-RSA-ARIA256-SHA384", /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "ADH-ARIA128-SHA256", /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
+ "ADH-ARIA256-SHA384", /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
+ "ECDHE-ECDSA-ARIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDHE-ECDSA-ARIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
+ "ECDH-ECDSA-ARIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDH-ECDSA-ARIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
+ "ECDHE-RSA-ARIA128-SHA256", /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDHE-RSA-ARIA256-SHA384", /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "ECDH-RSA-ARIA128-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDH-RSA-ARIA256-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "ARIA128-GCM-SHA256", /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
+ "ARIA256-GCM-SHA384", /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
+ "DH-DSS-ARIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
+ "DH-DSS-ARIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
+ "DH-RSA-ARIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
+ "DH-RSA-ARIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
+ "ADH-ARIA128-GCM-SHA256", /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
+ "ADH-ARIA256-GCM-SHA384", /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
+ "ECDH-ECDSA-ARIA128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
+ "ECDH-ECDSA-ARIA256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
+ "ECDH-RSA-ARIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
+ "ECDH-RSA-ARIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
+ "PSK-ARIA128-SHA256", /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "PSK-ARIA256-SHA384", /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
+ "DHE-PSK-ARIA128-SHA256", /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "DHE-PSK-ARIA256-SHA384", /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
+ "RSA-PSK-ARIA128-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "RSA-PSK-ARIA256-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
+ "ARIA128-GCM-SHA256", /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
+ "ARIA256-GCM-SHA384", /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
+ "RSA-PSK-ARIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
+ "RSA-PSK-ARIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
+ "ECDHE-PSK-ARIA128-SHA256", /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "ECDHE-PSK-ARIA256-SHA384", /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
+
+ /* blacklisted SEED encryptions */
+ "SEED-SHA", /*TLS_RSA_WITH_SEED_CBC_SHA */
+ "DH-DSS-SEED-SHA", /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
+ "DH-RSA-SEED-SHA", /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
+ "DHE-DSS-SEED-SHA", /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
+ "DHE-RSA-SEED-SHA", /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */
+ "ADH-SEED-SHA", /* TLS_DH_anon_WITH_SEED_CBC_SHA */
+
+ /* blacklisted KRB5 ciphers */
+ "KRB5-DES-CBC-SHA", /* TLS_KRB5_WITH_DES_CBC_SHA */
+ "KRB5-DES-CBC3-SHA", /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
+ "KRB5-IDEA-CBC-SHA", /* TLS_KRB5_WITH_IDEA_CBC_SHA */
+ "KRB5-DES-CBC-MD5", /* TLS_KRB5_WITH_DES_CBC_MD5 */
+ "KRB5-DES-CBC3-MD5", /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
+ "KRB5-IDEA-CBC-MD5", /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
+ "EXP-KRB5-DES-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
+ "EXP-KRB5-DES-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
+ "EXP-KRB5-RC2-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
+ "EXP-KRB5-RC2-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
+
+ /* blacklisted exoticas */
+ "DHE-DSS-CBC-SHA", /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
+ "IDEA-CBC-SHA", /* TLS_RSA_WITH_IDEA_CBC_SHA */
+
+ /* not really sure if the following names are correct */
+ "SSL3_CK_SCSV", /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
+ "SSL3_CK_FALLBACK_SCSV"
+};
+static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
+
+
+static apr_hash_t *BLCNames;
+
+static void cipher_init(apr_pool_t *pool)
+{
+ apr_hash_t *hash = apr_hash_make(pool);
+ const char *source;
+ unsigned int i;
+
+ source = "rfc7540";
+ for (i = 0; i < RFC7540_names_LEN; ++i) {
+ apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
+ }
+
+ BLCNames = hash;
+}
+
+static int cipher_is_blacklisted(const char *cipher, const char **psource)
+{
+ *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
+ return !!*psource;
+}
+
+apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s)
+{
+ (void)pool;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init");
+ cipher_init(pool);
+
+ return APR_SUCCESS;
+}
+
+int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all)
+{
+ int is_tls = ap_ssl_conn_is_ssl(c);
+
+ if (is_tls && h2_config_cgeti(c, H2_CONF_MODERN_TLS_ONLY) > 0) {
+ /* Check TLS connection for modern TLS parameters, as defined in
+ * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
+ */
+ apr_pool_t *pool = c->pool;
+ server_rec *s = c->base_server;
+ const char *val;
+
+ /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
+ */
+ val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
+ if (val && *val) {
+ if (strncmp("TLS", val, 3)
+ || !strcmp("TLSv1", val)
+ || !strcmp("TLSv1.1", val)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03050)
+ "h2_h2(%ld): tls protocol not suitable: %s",
+ (long)c->id, val);
+ return 0;
+ }
+ }
+ else if (require_all) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03051)
+ "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
+ return 0;
+ }
+
+ if (val && !strcmp("TLSv1.2", val)) {
+ /* Check TLS cipher blacklist, defined pre-TLSv1.3, so only
+ * checking for 1.2 */
+ val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
+ if (val && *val) {
+ const char *source;
+ if (cipher_is_blacklisted(val, &source)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
+ "h2_h2(%ld): tls cipher %s blacklisted by %s",
+ (long)c->id, val, source);
+ return 0;
+ }
+ }
+ else if (require_all) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
+ "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
diff --git a/modules/http2/h2_protocol.h b/modules/http2/h2_protocol.h
new file mode 100644
index 0000000..ed48e89
--- /dev/null
+++ b/modules/http2/h2_protocol.h
@@ -0,0 +1,56 @@
+/* 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.
+ */
+
+#ifndef __mod_h2__h2_protocol__
+#define __mod_h2__h2_protocol__
+
+/**
+ * List of protocol identifiers that we support in cleartext
+ * negotiations. NULL terminated.
+ */
+extern const char *h2_protocol_ids_clear[];
+
+/**
+ * List of protocol identifiers that we support in TLS encrypted
+ * negotiations (ALPN). NULL terminated.
+ */
+extern const char *h2_protocol_ids_tls[];
+
+/**
+ * Provide a user readable description of the HTTP/2 error code-
+ * @param h2_error http/2 error code, as in rfc 7540, ch. 7
+ * @return textual description of code or that it is unknown.
+ */
+const char *h2_protocol_err_description(unsigned int h2_error);
+
+/*
+ * One time, post config initialization.
+ */
+apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Check if the given primary connection fulfills the protocol
+ * requirements for HTTP/2.
+ * @param c the connection
+ * @param require_all != 0 iff any missing connection properties make
+ * the test fail. For example, a cipher might not have been selected while
+ * the handshake is still ongoing.
+ * @return != 0 iff protocol requirements are met
+ */
+int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all);
+
+
+#endif /* defined(__mod_h2__h2_protocol__) */
diff --git a/modules/http2/h2_proxy_session.c b/modules/http2/h2_proxy_session.c
index 8389c7c..db22301 100644
--- a/modules/http2/h2_proxy_session.c
+++ b/modules/http2/h2_proxy_session.c
@@ -20,6 +20,7 @@
#include
#include
+#include
#include
#include "mod_http2.h"
@@ -36,6 +37,7 @@ typedef struct h2_proxy_stream {
const char *url;
request_rec *r;
+ conn_rec *cfront;
h2_proxy_request *req;
const char *real_server_uri;
const char *p_server_uri;
@@ -45,6 +47,7 @@ typedef struct h2_proxy_stream {
unsigned int suspended : 1;
unsigned int waiting_on_100 : 1;
unsigned int waiting_on_ping : 1;
+ unsigned int headers_ended : 1;
uint32_t error_code;
apr_bucket_brigade *input;
@@ -61,7 +64,123 @@ static void dispatch_event(h2_proxy_session *session, h2_proxys_event_t ev,
static void ping_arrived(h2_proxy_session *session);
static apr_status_t check_suspended(h2_proxy_session *session);
static void stream_resume(h2_proxy_stream *stream);
+static apr_status_t submit_trailers(h2_proxy_stream *stream);
+
+/*
+ * The H2_PING connection sub-state: a state independant of the H2_SESSION state
+ * of the connection:
+ * - H2_PING_ST_NONE: no interference with request handling, ProxyTimeout in effect.
+ * When entered, all suspended streams are unsuspended again.
+ * - H2_PING_ST_AWAIT_ANY: new requests are suspended, a possibly configured "ping"
+ * timeout is in effect. Any frame received transits to H2_PING_ST_NONE.
+ * - H2_PING_ST_AWAIT_PING: same as above, but only a PING frame transits
+ * to H2_PING_ST_NONE.
+ *
+ * An AWAIT state is entered on a new connection or when re-using a connection and
+ * the last frame received has been some time ago. The latter sends a PING frame
+ * and insists on an answer, the former is satisfied by any frame received from the
+ * backend.
+ *
+ * This works for new connections as there is always at least one SETTINGS frame
+ * that the backend sends. When re-using connection, we send a PING and insist on
+ * receiving one back, as there might be frames in our connection buffers from
+ * some time ago. Since some servers have protections against PING flooding, we
+ * only ever have one PING unanswered.
+ *
+ * Requests are suspended while in a PING state, as we do not want to send data
+ * before we can be reasonably sure that the connection is working (at least on
+ * the h2 protocol level). This also means that the session can do blocking reads
+ * when expecting PING answers.
+ */
+static void set_ping_timeout(h2_proxy_session *session)
+{
+ if (session->ping_timeout != -1 && session->save_timeout == -1) {
+ apr_socket_t *socket = NULL;
+ socket = ap_get_conn_socket(session->c);
+ if (socket) {
+ apr_socket_timeout_get(socket, &session->save_timeout);
+ apr_socket_timeout_set(socket, session->ping_timeout);
+ }
+ }
+}
+
+static void unset_ping_timeout(h2_proxy_session *session)
+{
+ if (session->save_timeout != -1) {
+ apr_socket_t *socket = NULL;
+
+ socket = ap_get_conn_socket(session->c);
+ if (socket) {
+ apr_socket_timeout_set(socket, session->save_timeout);
+ session->save_timeout = -1;
+ }
+ }
+}
+
+static void enter_ping_state(h2_proxy_session *session, h2_ping_state_t state)
+{
+ if (session->ping_state == state) return;
+ switch (session->ping_state) {
+ case H2_PING_ST_NONE:
+ /* leaving NONE, enforce timeout, send frame maybe */
+ if (H2_PING_ST_AWAIT_PING == state) {
+ unset_ping_timeout(session);
+ nghttp2_submit_ping(session->ngh2, 0, (const uint8_t *)"nevergonnagiveyouup");
+ }
+ set_ping_timeout(session);
+ session->ping_state = state;
+ break;
+ default:
+ /* no switching between the != NONE states */
+ if (H2_PING_ST_NONE == state) {
+ session->ping_state = state;
+ unset_ping_timeout(session);
+ ping_arrived(session);
+ }
+ break;
+ }
+}
+
+static void ping_new_session(h2_proxy_session *session, proxy_conn_rec *p_conn)
+{
+ session->save_timeout = -1;
+ session->ping_timeout = (p_conn->worker->s->ping_timeout_set?
+ p_conn->worker->s->ping_timeout : -1);
+ session->ping_state = H2_PING_ST_NONE;
+ enter_ping_state(session, H2_PING_ST_AWAIT_ANY);
+}
+
+static void ping_reuse_session(h2_proxy_session *session)
+{
+ if (H2_PING_ST_NONE == session->ping_state) {
+ apr_interval_time_t age = apr_time_now() - session->last_frame_received;
+ if (age > apr_time_from_sec(1)) {
+ enter_ping_state(session, H2_PING_ST_AWAIT_PING);
+ }
+ }
+}
+
+static void ping_ev_frame_received(h2_proxy_session *session, const nghttp2_frame *frame)
+{
+ session->last_frame_received = apr_time_now();
+ switch (session->ping_state) {
+ case H2_PING_ST_NONE:
+ /* nop */
+ break;
+ case H2_PING_ST_AWAIT_ANY:
+ enter_ping_state(session, H2_PING_ST_NONE);
+ break;
+ case H2_PING_ST_AWAIT_PING:
+ if (NGHTTP2_PING == frame->hd.type) {
+ enter_ping_state(session, H2_PING_ST_NONE);
+ }
+ /* we may receive many other frames while we are waiting for the
+ * PING answer. They may come all from our connection buffers and
+ * say nothing about the current state of the backend. */
+ break;
+ }
+}
static apr_status_t proxy_session_pre_close(void *theconn)
{
@@ -152,7 +271,8 @@ static int on_frame_recv(nghttp2_session *ngh2, const nghttp2_frame *frame,
session->id, buffer);
}
- session->last_frame_received = apr_time_now();
+ ping_ev_frame_received(session, frame);
+ /* Action for frame types: */
switch (frame->hd.type) {
case NGHTTP2_HEADERS:
stream = nghttp2_session_get_stream_user_data(ngh2, frame->hd.stream_id);
@@ -193,10 +313,6 @@ static int on_frame_recv(nghttp2_session *ngh2, const nghttp2_frame *frame,
stream_resume(stream);
break;
case NGHTTP2_PING:
- if (session->check_ping) {
- session->check_ping = 0;
- ping_arrived(session);
- }
break;
case NGHTTP2_PUSH_PROMISE:
break;
@@ -241,7 +357,8 @@ static int add_header(void *table, const char *n, const char *v)
return 1;
}
-static void process_proxy_header(h2_proxy_stream *stream, const char *n, const char *v)
+static void process_proxy_header(apr_table_t *headers, h2_proxy_stream *stream,
+ const char *n, const char *v)
{
static const struct {
const char *name;
@@ -262,20 +379,18 @@ static void process_proxy_header(h2_proxy_stream *stream, const char *n, const c
if (!dconf->preserve_host) {
for (i = 0; transform_hdrs[i].name; ++i) {
if (!ap_cstr_casecmp(transform_hdrs[i].name, n)) {
- apr_table_add(r->headers_out, n,
- (*transform_hdrs[i].func)(r, dconf, v));
+ apr_table_add(headers, n, (*transform_hdrs[i].func)(r, dconf, v));
return;
}
}
if (!ap_cstr_casecmp("Link", n)) {
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
- apr_table_add(r->headers_out, n,
- h2_proxy_link_reverse_map(r, dconf,
- stream->real_server_uri, stream->p_server_uri, v));
+ apr_table_add(headers, n, h2_proxy_link_reverse_map(r, dconf,
+ stream->real_server_uri, stream->p_server_uri, v));
return;
}
}
- apr_table_add(r->headers_out, n, v);
+ apr_table_add(headers, n, v);
}
static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
@@ -287,7 +402,7 @@ static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
char *s = apr_pstrndup(stream->r->pool, v, vlen);
apr_table_setn(stream->r->notes, "proxy-status", s);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): got status %s",
stream->session->id, stream->id, s);
stream->r->status = (int)apr_atoi64(s);
@@ -299,17 +414,22 @@ static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
return APR_SUCCESS;
}
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
+ "h2_proxy_stream(%s-%d): on_header %s: %s",
+ stream->session->id, stream->id, n, v);
if (!h2_proxy_res_ignore_header(n, nlen)) {
char *hname, *hvalue;
+ apr_table_t *headers = (stream->headers_ended?
+ stream->r->trailers_out : stream->r->headers_out);
hname = apr_pstrndup(stream->pool, n, nlen);
h2_proxy_util_camel_case_header(hname, nlen);
hvalue = apr_pstrndup(stream->pool, v, vlen);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): got header %s: %s",
stream->session->id, stream->id, hname, hvalue);
- process_proxy_header(stream, hname, hvalue);
+ process_proxy_header(headers, stream, hname, hvalue);
}
return APR_SUCCESS;
}
@@ -328,6 +448,7 @@ static void h2_proxy_stream_end_headers_out(h2_proxy_stream *stream)
h2_proxy_session *session = stream->session;
request_rec *r = stream->r;
apr_pool_t *p = r->pool;
+ const char *buf;
/* Now, add in the cookies from the response to the ones already saved */
apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL);
@@ -337,6 +458,10 @@ static void h2_proxy_stream_end_headers_out(h2_proxy_stream *stream)
apr_table_unset(r->headers_out, "Set-Cookie");
r->headers_out = apr_table_overlay(p, r->headers_out, stream->saves);
}
+
+ if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
+ ap_set_content_type(r, apr_pstrdup(p, buf));
+ }
/* handle Via header in response */
if (session->conf->viaopt != via_off
@@ -374,6 +499,7 @@ static void h2_proxy_stream_end_headers_out(h2_proxy_stream *stream)
server_name, portstr)
);
}
+ if (r->status >= 200) stream->headers_ended = 1;
if (APLOGrtrace2(stream->r)) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, stream->r,
@@ -407,34 +533,27 @@ static int stream_response_data(nghttp2_session *ngh2, uint8_t flags,
h2_proxy_stream_end_headers_out(stream);
}
stream->data_received += len;
-
- b = apr_bucket_transient_create((const char*)data, len,
- stream->r->connection->bucket_alloc);
+ b = apr_bucket_transient_create((const char*)data, len,
+ stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
/* always flush after a DATA frame, as we have no other indication
* of buffer use */
- b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
+ b = apr_bucket_flush_create(stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
-
+
status = ap_pass_brigade(stream->r->output_filters, stream->output);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03359)
"h2_proxy_session(%s): stream=%d, response DATA %ld, %ld"
" total", session->id, stream_id, (long)len,
(long)stream->data_received);
if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03344)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03344)
"h2_proxy_session(%s): passing output on stream %d",
session->id, stream->id);
nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE,
stream_id, NGHTTP2_STREAM_CLOSED);
return NGHTTP2_ERR_STREAM_CLOSING;
}
- if (stream->standalone) {
- nghttp2_session_consume(ngh2, stream_id, len);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, stream->r,
- "h2_proxy_session(%s): stream %d, win_update %d bytes",
- session->id, stream_id, (int)len);
- }
return 0;
}
@@ -493,12 +612,12 @@ static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
stream = nghttp2_session_get_stream_user_data(ngh2, stream_id);
if (!stream) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(03361)
- "h2_proxy_stream(%s): data_read, stream %d not found",
- stream->session->id, stream_id);
+ "h2_proxy_stream(NULL): data_read, stream %d not found",
+ stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- if (stream->session->check_ping) {
+ if (stream->session->ping_state != H2_PING_ST_NONE) {
/* suspend until we hear from the other side */
stream->waiting_on_ping = 1;
status = APR_EAGAIN;
@@ -518,7 +637,7 @@ static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
}
if (status == APR_SUCCESS) {
- ssize_t readlen = 0;
+ size_t readlen = 0;
while (status == APR_SUCCESS
&& (readlen < length)
&& !APR_BRIGADE_EMPTY(stream->input)) {
@@ -537,7 +656,7 @@ static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
status = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
if (status == APR_SUCCESS && blen > 0) {
- ssize_t copylen = H2MIN(length - readlen, blen);
+ size_t copylen = H2MIN(length - readlen, blen);
memcpy(buf, bdata, copylen);
buf += copylen;
readlen += copylen;
@@ -553,9 +672,14 @@ static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
stream->data_sent += readlen;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03468)
"h2_proxy_stream(%d): request DATA %ld, %ld"
- " total, flags=%d",
- stream->id, (long)readlen, (long)stream->data_sent,
+ " total, flags=%d", stream->id, (long)readlen, (long)stream->data_sent,
(int)*data_flags);
+ if ((*data_flags & NGHTTP2_DATA_FLAG_EOF) && !apr_is_empty_table(stream->r->trailers_in)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(10179)
+ "h2_proxy_stream(%d): submit trailers", stream->id);
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ submit_trailers(stream);
+ }
return readlen;
}
else if (APR_STATUS_IS_EAGAIN(status)) {
@@ -575,7 +699,7 @@ static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
}
#ifdef H2_NG2_INVALID_HEADER_CB
-static int on_invalid_header_cb(nghttp2_session *ngh2,
+static int on_invalid_header_cb(nghttp2_session *ngh2,
const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
@@ -638,26 +762,22 @@ h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
#ifdef H2_NG2_INVALID_HEADER_CB
nghttp2_session_callbacks_set_on_invalid_header_callback(cbs, on_invalid_header_cb);
#endif
-
nghttp2_option_new(&option);
nghttp2_option_set_peer_max_concurrent_streams(option, 100);
- nghttp2_option_set_no_auto_window_update(option, 1);
+ nghttp2_option_set_no_auto_window_update(option, 0);
nghttp2_session_client_new2(&session->ngh2, cbs, session, option);
nghttp2_option_del(option);
nghttp2_session_callbacks_del(cbs);
+ ping_new_session(session, p_conn);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03362)
"setup session for %s", p_conn->hostname);
}
else {
h2_proxy_session *session = p_conn->data;
- apr_interval_time_t age = apr_time_now() - session->last_frame_received;
- if (age > apr_time_from_sec(1)) {
- session->check_ping = 1;
- nghttp2_submit_ping(session->ngh2, 0, (const uint8_t *)"nevergonnagiveyouup");
- }
+ ping_reuse_session(session);
}
return p_conn->data;
}
@@ -698,7 +818,7 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
{
h2_proxy_stream *stream;
apr_uri_t puri;
- const char *authority, *scheme, *path;
+ const char *authority, *scheme, *path, *orig_host;
apr_status_t status;
proxy_dir_conf *dconf;
@@ -707,24 +827,29 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
stream->pool = r->pool;
stream->url = url;
stream->r = r;
+ stream->cfront = r->connection;
stream->standalone = standalone;
stream->session = session;
stream->state = H2_STREAM_ST_IDLE;
- stream->input = apr_brigade_create(stream->pool, session->c->bucket_alloc);
- stream->output = apr_brigade_create(stream->pool, session->c->bucket_alloc);
+ stream->input = apr_brigade_create(stream->pool, stream->cfront->bucket_alloc);
+ stream->output = apr_brigade_create(stream->pool, stream->cfront->bucket_alloc);
- stream->req = h2_proxy_req_create(1, stream->pool, 0);
+ stream->req = h2_proxy_req_create(1, stream->pool);
status = apr_uri_parse(stream->pool, url, &puri);
if (status != APR_SUCCESS)
return status;
scheme = (strcmp(puri.scheme, "h2")? "http" : "https");
-
+ orig_host = apr_table_get(r->headers_in, "Host");
+ if (orig_host == NULL) {
+ orig_host = r->hostname;
+ }
+
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
if (dconf->preserve_host) {
- authority = r->hostname;
+ authority = orig_host;
}
else {
authority = puri.hostname;
@@ -733,20 +858,27 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
/* port info missing and port is not default for scheme: append */
authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port);
}
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
+ "authority=%s from uri.hostname=%s and uri.port=%d",
+ authority, puri.hostname, puri.port);
}
-
+ /* See #235, we use only :authority when available and remove Host:
+ * since differing values are not acceptable, see RFC 9113 ch. 8.3.1 */
+ if (authority && strlen(authority)) {
+ apr_table_unset(r->headers_in, "Host");
+ }
+
/* we need this for mapping relative uris in headers ("Link") back
* to local uris */
stream->real_server_uri = apr_psprintf(stream->pool, "%s://%s", scheme, authority);
stream->p_server_uri = apr_psprintf(stream->pool, "%s://%s", puri.scheme, authority);
path = apr_uri_unparse(stream->pool, &puri, APR_URI_UNP_OMITSITEPART);
+
h2_proxy_req_make(stream->req, stream->pool, r->method, scheme,
authority, path, r->headers_in);
if (dconf->add_forwarded_headers) {
if (PROXYREQ_REVERSE == r->proxyreq) {
- const char *buf;
-
/* Add X-Forwarded-For: so that the upstream has a chance to
* determine, where the original request came from.
*/
@@ -756,8 +888,9 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
/* Add X-Forwarded-Host: so that upstream knows what the
* original request hostname was.
*/
- if ((buf = apr_table_get(r->headers_in, "Host"))) {
- apr_table_mergen(stream->req->headers, "X-Forwarded-Host", buf);
+ if (orig_host) {
+ apr_table_mergen(stream->req->headers, "X-Forwarded-Host",
+ orig_host);
}
/* Add X-Forwarded-Server: so that upstream knows what the
@@ -768,7 +901,7 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
r->server->server_hostname);
}
}
-
+
/* Tuck away all already existing cookies */
stream->saves = apr_table_make(r->pool, 2);
apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL);
@@ -811,7 +944,7 @@ static apr_status_t submit_stream(h2_proxy_session *session, h2_proxy_stream *st
rv = nghttp2_submit_request(session->ngh2, NULL,
hd->nv, hd->nvlen, pp, stream);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03363)
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->cfront, APLOGNO(03363)
"h2_proxy_session(%s): submit %s%s -> %d",
session->id, stream->req->authority, stream->req->path,
rv);
@@ -826,12 +959,22 @@ static apr_status_t submit_stream(h2_proxy_session *session, h2_proxy_stream *st
return APR_EGENERAL;
}
+static apr_status_t submit_trailers(h2_proxy_stream *stream)
+{
+ h2_proxy_ngheader *hd;
+ int rv;
+
+ hd = h2_proxy_util_nghd_make(stream->pool, stream->r->trailers_in);
+ rv = nghttp2_submit_trailer(stream->session->ngh2, stream->id, hd->nv, hd->nvlen);
+ return rv == 0? APR_SUCCESS: APR_EGENERAL;
+}
+
static apr_status_t feed_brigade(h2_proxy_session *session, apr_bucket_brigade *bb)
{
apr_status_t status = APR_SUCCESS;
apr_size_t readlen = 0;
ssize_t n;
-
+
while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
apr_bucket* b = APR_BRIGADE_FIRST(bb);
@@ -854,9 +997,10 @@ static apr_status_t feed_brigade(h2_proxy_session *session, apr_bucket_brigade *
}
}
else {
- readlen += n;
- if (n < blen) {
- apr_bucket_split(b, n);
+ size_t rlen = (size_t)n;
+ readlen += rlen;
+ if (rlen < blen) {
+ apr_bucket_split(b, rlen);
}
}
}
@@ -882,7 +1026,7 @@ static apr_status_t h2_proxy_session_read(h2_proxy_session *session, int block,
apr_socket_t *socket = NULL;
apr_time_t save_timeout = -1;
- if (block) {
+ if (block && timeout > 0) {
socket = ap_get_conn_socket(session->c);
if (socket) {
apr_socket_timeout_get(socket, &save_timeout);
@@ -945,7 +1089,7 @@ apr_status_t h2_proxy_session_submit(h2_proxy_session *session,
static void stream_resume(h2_proxy_stream *stream)
{
h2_proxy_session *session = stream->session;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): resuming",
session->id, stream->id);
stream->suspended = 0;
@@ -954,6 +1098,14 @@ static void stream_resume(h2_proxy_stream *stream)
dispatch_event(session, H2_PROXYS_EV_STREAM_RESUMED, 0, NULL);
}
+static int is_waiting_for_backend(h2_proxy_session *session)
+{
+ return ((session->ping_state != H2_PING_ST_NONE)
+ || ((session->suspended->nelts <= 0)
+ && !nghttp2_session_want_write(session->ngh2)
+ && nghttp2_session_want_read(session->ngh2)));
+}
+
static apr_status_t check_suspended(h2_proxy_session *session)
{
h2_proxy_stream *stream;
@@ -978,7 +1130,7 @@ static apr_status_t check_suspended(h2_proxy_session *session)
return APR_SUCCESS;
}
else if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, stream->cfront,
APLOGNO(03382) "h2_proxy_stream(%s-%d): check input",
session->id, stream_id);
stream_resume(stream);
@@ -1006,7 +1158,7 @@ static apr_status_t session_shutdown(h2_proxy_session *session, int reason,
if (!err && reason) {
err = nghttp2_strerror(reason);
}
- nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
+ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
reason, (uint8_t*)err, err? strlen(err):0);
status = nghttp2_session_send(session->ngh2);
dispatch_event(session, H2_PROXYS_EV_LOCAL_GOAWAY, reason, err);
@@ -1208,39 +1360,56 @@ static void ev_stream_done(h2_proxy_session *session, int stream_id,
const char *msg)
{
h2_proxy_stream *stream;
-
+ apr_bucket *b;
+
stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
if (stream) {
- int touched = (stream->data_sent ||
- stream_id <= session->last_stream_id);
+ /* if the stream's connection is aborted, do not send anything
+ * more on it. */
apr_status_t status = (stream->error_code == 0)? APR_SUCCESS : APR_EINVAL;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03364)
- "h2_proxy_sesssion(%s): stream(%d) closed "
- "(touched=%d, error=%d)",
- session->id, stream_id, touched, stream->error_code);
-
- if (status != APR_SUCCESS) {
- stream->r->status = 500;
- }
- else if (!stream->data_received) {
- apr_bucket *b;
- /* if the response had no body, this is the time to flush
- * an empty brigade which will also write the resonse
- * headers */
- h2_proxy_stream_end_headers_out(stream);
- stream->data_received = 1;
- b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(stream->output, b);
- b = apr_bucket_eos_create(stream->r->connection->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(stream->output, b);
- ap_pass_brigade(stream->r->output_filters, stream->output);
+ int touched = (stream->data_sent || stream->data_received ||
+ stream_id <= session->last_stream_id);
+ if (!stream->cfront->aborted) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->cfront, APLOGNO(03364)
+ "h2_proxy_sesssion(%s): stream(%d) closed "
+ "(touched=%d, error=%d)",
+ session->id, stream_id, touched, stream->error_code);
+
+ if (status != APR_SUCCESS) {
+ /* stream failed. If we have received (and forwarded) response
+ * data already, we need to append an error buckt to inform
+ * consumers.
+ * Otherwise, we have an early fail on the connection and may
+ * retry this request on a new one. In that case, keep the
+ * output virgin so that a new attempt can be made. */
+ if (stream->data_received) {
+ int http_status = ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ b = ap_bucket_error_create(http_status, NULL, stream->r->pool,
+ stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
+ }
+ else if (!stream->data_received) {
+ /* if the response had no body, this is the time to flush
+ * an empty brigade which will also write the response headers */
+ h2_proxy_stream_end_headers_out(stream);
+ stream->data_received = 1;
+ b = apr_bucket_flush_create(stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
}
-
+
stream->state = H2_STREAM_ST_CLOSED;
h2_proxy_ihash_remove(session->streams, stream_id);
h2_proxy_iq_remove(session->suspended, stream_id);
if (session->done) {
- session->done(session, stream->r, status, touched);
+ session->done(session, stream->r, status, touched, stream->error_code);
}
}
@@ -1408,7 +1577,22 @@ run_loop:
break;
case H2_PROXYS_ST_WAIT:
- if (check_suspended(session) == APR_EAGAIN) {
+ if (is_waiting_for_backend(session)) {
+ /* we can do a blocking read with the default timeout (as
+ * configured via ProxyTimeout in our socket. There is
+ * nothing we want to send or check until we get more data
+ * from the backend. */
+ status = h2_proxy_session_read(session, 1, 0);
+ if (status == APR_SUCCESS) {
+ have_read = 1;
+ dispatch_event(session, H2_PROXYS_EV_DATA_READ, 0, NULL);
+ }
+ else {
+ dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, status, NULL);
+ return status;
+ }
+ }
+ else if (check_suspended(session) == APR_EAGAIN) {
/* no stream has become resumed. Do a blocking read with
* ever increasing timeouts... */
if (session->wait_timeout < 25) {
@@ -1423,7 +1607,7 @@ run_loop:
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
APLOGNO(03365)
"h2_proxy_session(%s): WAIT read, timeout=%fms",
- session->id, (float)session->wait_timeout/1000.0);
+ session->id, session->wait_timeout/1000.0);
if (status == APR_SUCCESS) {
have_read = 1;
dispatch_event(session, H2_PROXYS_EV_DATA_READ, 0, NULL);
@@ -1495,9 +1679,19 @@ static int done_iter(void *udata, void *val)
{
cleanup_iter_ctx *ctx = udata;
h2_proxy_stream *stream = val;
- int touched = (stream->data_sent ||
+ int touched = (stream->data_sent || stream->data_received ||
stream->id <= ctx->session->last_stream_id);
- ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched);
+ if (touched && stream->output) {
+ apr_bucket *b = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL,
+ stream->r->pool,
+ stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
+ ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched,
+ stream->error_code);
return 1;
}
@@ -1516,6 +1710,12 @@ void h2_proxy_session_cleanup(h2_proxy_session *session,
}
}
+int h2_proxy_session_is_reusable(h2_proxy_session *session)
+{
+ return (session->state != H2_PROXYS_ST_DONE) &&
+ h2_proxy_ihash_empty(session->streams);
+}
+
static int ping_arrived_iter(void *udata, void *val)
{
h2_proxy_stream *stream = val;
@@ -1543,42 +1743,3 @@ typedef struct {
int updated;
} win_update_ctx;
-static int win_update_iter(void *udata, void *val)
-{
- win_update_ctx *ctx = udata;
- h2_proxy_stream *stream = val;
-
- if (stream->r && stream->r->connection == ctx->c) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, ctx->session->c,
- "h2_proxy_session(%s-%d): win_update %ld bytes",
- ctx->session->id, (int)stream->id, (long)ctx->bytes);
- nghttp2_session_consume(ctx->session->ngh2, stream->id, ctx->bytes);
- ctx->updated = 1;
- return 0;
- }
- return 1;
-}
-
-
-void h2_proxy_session_update_window(h2_proxy_session *session,
- conn_rec *c, apr_off_t bytes)
-{
- if (!h2_proxy_ihash_empty(session->streams)) {
- win_update_ctx ctx;
- ctx.session = session;
- ctx.c = c;
- ctx.bytes = bytes;
- ctx.updated = 0;
- h2_proxy_ihash_iter(session->streams, win_update_iter, &ctx);
-
- if (!ctx.updated) {
- /* could not find the stream any more, possibly closed, update
- * the connection window at least */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- "h2_proxy_session(%s): win_update conn %ld bytes",
- session->id, (long)bytes);
- nghttp2_session_consume_connection(session->ngh2, (size_t)bytes);
- }
- }
-}
-
diff --git a/modules/http2/h2_proxy_session.h b/modules/http2/h2_proxy_session.h
index ecebb61..3bc16d7 100644
--- a/modules/http2/h2_proxy_session.h
+++ b/modules/http2/h2_proxy_session.h
@@ -60,10 +60,16 @@ typedef enum {
H2_PROXYS_EV_PRE_CLOSE, /* connection will close after this */
} h2_proxys_event_t;
+typedef enum {
+ H2_PING_ST_NONE, /* normal connection mode, ProxyTimeout rules */
+ H2_PING_ST_AWAIT_ANY, /* waiting for any frame from backend */
+ H2_PING_ST_AWAIT_PING, /* waiting for PING frame from backend */
+} h2_ping_state_t;
typedef struct h2_proxy_session h2_proxy_session;
typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
- apr_status_t status, int touched);
+ apr_status_t status, int touched,
+ int error_code);
struct h2_proxy_session {
const char *id;
@@ -74,7 +80,6 @@ struct h2_proxy_session {
nghttp2_session *ngh2; /* the nghttp2 session itself */
unsigned int aborted : 1;
- unsigned int check_ping : 1;
unsigned int h2_front : 1; /* if front-end connection is HTTP/2 */
h2_proxy_request_done *done;
@@ -94,6 +99,10 @@ struct h2_proxy_session {
apr_bucket_brigade *input;
apr_bucket_brigade *output;
+
+ h2_ping_state_t ping_state;
+ apr_time_t ping_timeout;
+ apr_time_t save_timeout;
};
h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
@@ -120,9 +129,8 @@ void h2_proxy_session_cancel_all(h2_proxy_session *s);
void h2_proxy_session_cleanup(h2_proxy_session *s, h2_proxy_request_done *done);
-void h2_proxy_session_update_window(h2_proxy_session *s,
- conn_rec *c, apr_off_t bytes);
-
#define H2_PROXY_REQ_URL_NOTE "h2-proxy-req-url"
+int h2_proxy_session_is_reusable(h2_proxy_session *s);
+
#endif /* h2_proxy_session_h */
diff --git a/modules/http2/h2_proxy_util.c b/modules/http2/h2_proxy_util.c
index bd45294..dc69ec0 100644
--- a/modules/http2/h2_proxy_util.c
+++ b/modules/http2/h2_proxy_util.c
@@ -452,6 +452,22 @@ h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p,
return ngh;
}
+h2_proxy_ngheader *h2_proxy_util_nghd_make(apr_pool_t *p, apr_table_t *headers)
+{
+
+ h2_proxy_ngheader *ngh;
+ size_t n;
+
+ n = 0;
+ apr_table_do(count_header, &n, headers, NULL);
+
+ ngh = apr_pcalloc(p, sizeof(h2_proxy_ngheader));
+ ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
+ apr_table_do(add_table_header, ngh, headers, NULL);
+
+ return ngh;
+}
+
/*******************************************************************************
* header HTTP/1 <-> HTTP/2 conversions
******************************************************************************/
@@ -480,7 +496,7 @@ static int ignore_header(const literal *lits, size_t llen,
const char *name, size_t nlen)
{
const literal *lit;
- int i;
+ size_t i;
for (i = 0; i < llen; ++i) {
lit = &lits[i];
@@ -567,8 +583,7 @@ static apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool,
static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const char *method,
const char *scheme, const char *authority,
- const char *path, apr_table_t *header,
- int serialize)
+ const char *path, apr_table_t *header)
{
h2_proxy_request *req = apr_pcalloc(pool, sizeof(h2_proxy_request));
@@ -578,14 +593,13 @@ static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const ch
req->path = path;
req->headers = header? header : apr_table_make(pool, 10);
req->request_time = apr_time_now();
- req->serialize = serialize;
-
+
return req;
}
-h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize)
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool)
{
- return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL, serialize);
+ return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL);
}
typedef struct {
@@ -609,6 +623,7 @@ apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
apr_table_t *headers)
{
h1_ctx x;
+ const char *val;
req->method = method;
req->scheme = scheme;
@@ -623,6 +638,11 @@ apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
x.pool = pool;
x.headers = req->headers;
apr_table_do(set_h1_header, &x, headers, NULL);
+ if ((val = apr_table_get(headers, "TE")) && ap_find_token(pool, val, "trailers")) {
+ /* client accepts trailers, forward this information */
+ apr_table_addn(req->headers, "TE", "trailers");
+ }
+ apr_table_setn(req->headers, "te", "trailers");
return APR_SUCCESS;
}
@@ -915,12 +935,12 @@ static size_t subst_str(link_ctx *ctx, int start, int end, const char *ns)
nlen = (int)strlen(ns);
delta = nlen - olen;
plen = ctx->slen + delta + 1;
- p = apr_pcalloc(ctx->pool, plen);
+ p = apr_palloc(ctx->pool, plen);
memcpy(p, ctx->s, start);
memcpy(p + start, ns, nlen);
strcpy(p + start + nlen, ctx->s + end);
ctx->s = p;
- ctx->slen = (int)strlen(p);
+ ctx->slen = plen - 1; /* (int)strlen(p) */
if (ctx->i >= end) {
ctx->i += delta;
}
@@ -931,7 +951,7 @@ static void map_link(link_ctx *ctx)
{
if (ctx->link_start < ctx->link_end) {
char buffer[HUGE_STRING_LEN];
- int need_len, link_len, buffer_len, prepend_p_server;
+ size_t need_len, link_len, buffer_len, prepend_p_server;
const char *mapped;
buffer[0] = '\0';
diff --git a/modules/http2/h2_proxy_util.h b/modules/http2/h2_proxy_util.h
index a88fb7e..202363d 100644
--- a/modules/http2/h2_proxy_util.h
+++ b/modules/http2/h2_proxy_util.h
@@ -168,6 +168,8 @@ typedef struct h2_proxy_ngheader {
h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p,
const struct h2_proxy_request *req);
+h2_proxy_ngheader *h2_proxy_util_nghd_make(apr_pool_t *p, apr_table_t *headers);
+
/*******************************************************************************
* h2_proxy_request helpers
******************************************************************************/
@@ -183,11 +185,10 @@ struct h2_proxy_request {
apr_time_t request_time;
- unsigned int chunked : 1; /* iff requst body needs to be forwarded as chunked */
- unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
+ int chunked; /* iff request body needs to be forwarded as chunked */
};
-h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize);
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool);
apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
const char *method, const char *scheme,
const char *authority, const char *path,
diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c
index 9a3b19b..e6a10c5 100644
--- a/modules/http2/h2_push.c
+++ b/modules/http2/h2_push.c
@@ -23,19 +23,19 @@
#include
#ifdef H2_OPENSSL
-#include
+#include
#endif
#include
#include
#include
+#include
#include "h2_private.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
#include "h2_util.h"
#include "h2_push.h"
#include "h2_request.h"
-#include "h2_headers.h"
#include "h2_session.h"
#include "h2_stream.h"
@@ -59,7 +59,7 @@ static const char *policy_str(h2_push_policy policy)
typedef struct {
const h2_request *req;
- int push_policy;
+ apr_uint32_t push_policy;
apr_pool_t *pool;
apr_array_header_t *pushes;
const char *s;
@@ -348,11 +348,10 @@ static int add_push(link_ctx *ctx)
}
headers = apr_table_make(ctx->pool, 5);
apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
- req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
- ctx->req->authority, path, headers,
- ctx->req->serialize);
+ req = h2_request_create(0, ctx->pool, method, ctx->req->scheme,
+ ctx->req->authority, path, headers);
/* atm, we do not push on pushes */
- h2_request_end_headers(req, ctx->pool, 1, 0);
+ h2_request_end_headers(req, ctx->pool, 0);
push->req = req;
if (has_param(ctx, "critical")) {
h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
@@ -427,14 +426,23 @@ static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
static int head_iter(void *ctx, const char *key, const char *value)
{
- if (!apr_strnatcasecmp("link", key)) {
+ if (!ap_cstr_casecmp("link", key)) {
inspect_link(ctx, value, strlen(value));
}
return 1;
}
-apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
- int push_policy, const h2_headers *res)
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const struct h2_headers *res)
+#endif
{
if (req && push_policy != H2_PUSH_NONE) {
/* Collect push candidates from the request/response pair.
@@ -464,33 +472,6 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
return NULL;
}
-/*******************************************************************************
- * push diary
- *
- * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
- * connection. It records a hash value from the absolute URL of the resource
- * pushed.
- * - Lacking openssl, it uses 'apr_hashfunc_default' for the value
- * - with openssl, it uses SHA256 to calculate the hash value
- * - whatever the method to generate the hash, the diary keeps a maximum of 64
- * bits per hash, limiting the memory consumption to about
- * H2PushDiarySize * 8
- * bytes. Entries are sorted by most recently used and oldest entries are
- * forgotten first.
- * - Clients can initialize/replace the push diary by sending a 'Cache-Digest'
- * header. Currently, this is the base64url encoded value of the cache digest
- * as specified in https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
- * This draft can be expected to evolve and the definition of the header
- * will be added there and refined.
- * - The cache digest header is a Golomb Coded Set of hash values, but it may
- * limit the amount of bits per hash value even further. For a good description
- * of GCS, read here:
- * http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters
- * - The means that the push diary might be initialized with hash values of much
- * less than 64 bits, leading to more false positives, but smaller digest size.
- ******************************************************************************/
-
-
#define GCSLOG_LEVEL APLOG_TRACE1
typedef struct h2_push_diary_entry {
@@ -499,27 +480,32 @@ typedef struct h2_push_diary_entry {
#ifdef H2_OPENSSL
-static void sha256_update(SHA256_CTX *ctx, const char *s)
+static void sha256_update(EVP_MD_CTX *ctx, const char *s)
{
- SHA256_Update(ctx, s, strlen(s));
+ EVP_DigestUpdate(ctx, s, strlen(s));
}
static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
{
- SHA256_CTX sha256;
+ EVP_MD_CTX *md;
apr_uint64_t val;
- unsigned char hash[SHA256_DIGEST_LENGTH];
- int i;
-
- SHA256_Init(&sha256);
- sha256_update(&sha256, push->req->scheme);
- sha256_update(&sha256, "://");
- sha256_update(&sha256, push->req->authority);
- sha256_update(&sha256, push->req->path);
- SHA256_Final(hash, &sha256);
+ unsigned char hash[EVP_MAX_MD_SIZE];
+ unsigned len, i;
+
+ md = EVP_MD_CTX_create();
+ ap_assert(md != NULL);
+
+ i = EVP_DigestInit_ex(md, EVP_sha256(), NULL);
+ ap_assert(i == 1);
+ sha256_update(md, push->req->scheme);
+ sha256_update(md, "://");
+ sha256_update(md, push->req->authority);
+ sha256_update(md, push->req->path);
+ EVP_DigestFinal(md, hash, &len);
+ EVP_MD_CTX_destroy(md);
val = 0;
- for (i = 0; i != sizeof(val); ++i)
+ for (i = 0; i != len; ++i)
val = val * 256 + hash[i];
*phash = val >> (64 - diary->mask_bits);
}
@@ -528,13 +514,14 @@ static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push
static unsigned int val_apr_hash(const char *str)
{
- apr_ssize_t len = strlen(str);
+ apr_ssize_t len = (apr_ssize_t)strlen(str);
return apr_hashfunc_default(str, &len);
}
static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
{
apr_uint64_t val;
+ (void)diary;
#if APR_UINT64_MAX > UINT_MAX
val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
@@ -617,38 +604,48 @@ static int h2_push_diary_find(h2_push_diary *diary, apr_uint64_t hash)
return -1;
}
-static h2_push_diary_entry *move_to_last(h2_push_diary *diary, apr_size_t idx)
+static void move_to_last(h2_push_diary *diary, apr_size_t idx)
{
h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
h2_push_diary_entry e;
- apr_size_t lastidx = diary->entries->nelts-1;
+ apr_size_t lastidx;
+ /* Move an existing entry to the last place */
+ if (diary->entries->nelts <= 0)
+ return;
+
/* move entry[idx] to the end */
+ lastidx = diary->entries->nelts - 1;
if (idx < lastidx) {
e = entries[idx];
- memmove(entries+idx, entries+idx+1, sizeof(e) * (lastidx - idx));
+ memmove(entries+idx, entries+idx+1, sizeof(h2_push_diary_entry) * (lastidx - idx));
entries[lastidx] = e;
}
- return &entries[lastidx];
}
-static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
+static void remove_first(h2_push_diary *diary)
{
- h2_push_diary_entry *ne;
+ h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
+ int lastidx;
- if (diary->entries->nelts < diary->N) {
- /* append a new diary entry at the end */
- APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
- ne = &APR_ARRAY_IDX(diary->entries, diary->entries->nelts-1, h2_push_diary_entry);
+ /* move remaining entries to index 0 */
+ lastidx = diary->entries->nelts - 1;
+ if (lastidx > 0) {
+ --diary->entries->nelts;
+ memmove(entries, entries+1, sizeof(h2_push_diary_entry) * diary->entries->nelts);
}
- else {
- /* replace content with new digest. keeps memory usage constant once diary is full */
- ne = move_to_last(diary, 0);
- *ne = *e;
+}
+
+static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
+{
+ while (diary->entries->nelts >= diary->N) {
+ remove_first(diary);
}
+ /* append a new diary entry at the end */
+ APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
/* Intentional no APLOGNO */
ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, diary->entries->pool,
- "push_diary_append: %"APR_UINT64_T_HEX_FMT, ne->hash);
+ "push_diary_append: %"APR_UINT64_T_HEX_FMT, e->hash);
}
apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t *pushes)
@@ -668,13 +665,13 @@ apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t
idx = h2_push_diary_find(session->push_diary, e.hash);
if (idx >= 0) {
/* Intentional no APLOGNO */
- ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
+ ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
"push_diary_update: already there PUSH %s", push->req->path);
- move_to_last(session->push_diary, idx);
+ move_to_last(session->push_diary, (apr_size_t)idx);
}
else {
/* Intentional no APLOGNO */
- ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
+ ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
"push_diary_update: adding PUSH %s", push->req->path);
if (!npushes) {
npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
@@ -687,34 +684,22 @@ apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t
return npushes;
}
-apr_array_header_t *h2_push_collect_update(h2_stream *stream,
- const struct h2_request *req,
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
const struct h2_headers *res)
+#endif
{
- h2_session *session = stream->session;
- const char *cache_digest = apr_table_get(req->headers, "Cache-Digest");
apr_array_header_t *pushes;
- apr_status_t status;
- if (cache_digest && session->push_diary) {
- status = h2_push_diary_digest64_set(session->push_diary, req->authority,
- cache_digest, stream->pool);
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
- H2_SSSN_LOG(APLOGNO(03057), session,
- "push diary set from Cache-Digest: %s"), cache_digest);
- }
- }
pushes = h2_push_collect(stream->pool, req, stream->push_policy, res);
return h2_push_diary_update(stream->session, pushes);
}
-static apr_int32_t h2_log2inv(unsigned char log2)
-{
- return log2? (1 << log2) : 1;
-}
-
-
typedef struct {
h2_push_diary *diary;
unsigned char log2p;
@@ -822,23 +807,18 @@ apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool,
int maxP, const char *authority,
const char **pdata, apr_size_t *plen)
{
- int nelts, N, i;
+ int nelts, N;
unsigned char log2n, log2pmax;
gset_encoder encoder;
apr_uint64_t *hashes;
- apr_size_t hash_count;
+ apr_size_t hash_count, i;
nelts = diary->entries->nelts;
-
- if (nelts > APR_UINT32_MAX) {
- /* should not happen */
- return APR_ENOTIMPL;
- }
N = ceil_power_of_2(nelts);
log2n = h2_log2(N);
/* Now log2p is the max number of relevant bits, so that
- * log2p + log2n == mask_bits. We can uise a lower log2p
+ * log2p + log2n == mask_bits. We can use a lower log2p
* and have a shorter set encoding...
*/
log2pmax = h2_log2(ceil_power_of_2(maxP));
@@ -895,166 +875,3 @@ apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool,
return APR_SUCCESS;
}
-typedef struct {
- h2_push_diary *diary;
- apr_pool_t *pool;
- unsigned char log2p;
- const unsigned char *data;
- apr_size_t datalen;
- apr_size_t offset;
- unsigned int bit;
- apr_uint64_t last_val;
-} gset_decoder;
-
-static int gset_decode_next_bit(gset_decoder *decoder)
-{
- if (++decoder->bit >= 8) {
- if (++decoder->offset >= decoder->datalen) {
- return -1;
- }
- decoder->bit = 0;
- }
- return (decoder->data[decoder->offset] & cbit_mask[decoder->bit])? 1 : 0;
-}
-
-static apr_status_t gset_decode_next(gset_decoder *decoder, apr_uint64_t *phash)
-{
- apr_uint64_t flex = 0, fixed = 0, delta;
- int i;
-
- /* read 1 bits until we encounter 0, then read log2n(diary-P) bits.
- * On a malformed bit-string, this will not fail, but produce results
- * which are pbly too large. Luckily, the diary will modulo the hash.
- */
- while (1) {
- int bit = gset_decode_next_bit(decoder);
- if (bit == -1) {
- return APR_EINVAL;
- }
- if (!bit) {
- break;
- }
- ++flex;
- }
-
- for (i = 0; i < decoder->log2p; ++i) {
- int bit = gset_decode_next_bit(decoder);
- if (bit == -1) {
- return APR_EINVAL;
- }
- fixed = (fixed << 1) | bit;
- }
-
- delta = (flex << decoder->log2p) | fixed;
- *phash = delta + decoder->last_val;
- decoder->last_val = *phash;
-
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, decoder->pool,
- "h2_push_diary_digest_dec: val=%"APR_UINT64_T_HEX_FMT", delta=%"
- APR_UINT64_T_HEX_FMT", flex=%d, fixed=%"APR_UINT64_T_HEX_FMT,
- *phash, delta, (int)flex, fixed);
-
- return APR_SUCCESS;
-}
-
-/**
- * Initialize the push diary by a cache digest as described in
- * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
- * .
- * @param diary the diary to set the digest into
- * @param data the binary cache digest
- * @param len the length of the cache digest
- * @return APR_EINVAL if digest was not successfully parsed
- */
-apr_status_t h2_push_diary_digest_set(h2_push_diary *diary, const char *authority,
- const char *data, apr_size_t len)
-{
- gset_decoder decoder;
- unsigned char log2n, log2p;
- int N, i;
- apr_pool_t *pool = diary->entries->pool;
- h2_push_diary_entry e;
- apr_status_t status = APR_SUCCESS;
-
- if (len < 2) {
- /* at least this should be there */
- return APR_EINVAL;
- }
- log2n = data[0];
- log2p = data[1];
- diary->mask_bits = log2n + log2p;
- if (diary->mask_bits > 64) {
- /* cannot handle */
- return APR_ENOTIMPL;
- }
-
- /* whatever is in the digest, it replaces the diary entries */
- apr_array_clear(diary->entries);
- if (!authority || !strcmp("*", authority)) {
- diary->authority = NULL;
- }
- else if (!diary->authority || strcmp(diary->authority, authority)) {
- diary->authority = apr_pstrdup(diary->entries->pool, authority);
- }
-
- N = h2_log2inv(log2n + log2p);
-
- decoder.diary = diary;
- decoder.pool = pool;
- decoder.log2p = log2p;
- decoder.data = (const unsigned char*)data;
- decoder.datalen = len;
- decoder.offset = 1;
- decoder.bit = 8;
- decoder.last_val = 0;
-
- diary->N = N;
- /* Determine effective N we use for storage */
- if (!N) {
- /* a totally empty cache digest. someone tells us that she has no
- * entries in the cache at all. Use our own preferences for N+mask
- */
- diary->N = diary->NMax;
- return APR_SUCCESS;
- }
- else if (N > diary->NMax) {
- /* Store not more than diary is configured to hold. We open us up
- * to DOS attacks otherwise. */
- diary->N = diary->NMax;
- }
-
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
- "h2_push_diary_digest_set: N=%d, log2n=%d, "
- "diary->mask_bits=%d, dec.log2p=%d",
- (int)diary->N, (int)log2n, diary->mask_bits,
- (int)decoder.log2p);
-
- for (i = 0; i < diary->N; ++i) {
- if (gset_decode_next(&decoder, &e.hash) != APR_SUCCESS) {
- /* the data may have less than N values */
- break;
- }
- h2_push_diary_append(diary, &e);
- }
-
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
- "h2_push_diary_digest_set: diary now with %d entries, mask_bits=%d",
- (int)diary->entries->nelts, diary->mask_bits);
- return status;
-}
-
-apr_status_t h2_push_diary_digest64_set(h2_push_diary *diary, const char *authority,
- const char *data64url, apr_pool_t *pool)
-{
- const char *data;
- apr_size_t len = h2_util_base64url_decode(&data, data64url, pool);
- /* Intentional no APLOGNO */
- ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
- "h2_push_diary_digest64_set: digest=%s, dlen=%d",
- data64url, (int)len);
- return h2_push_diary_digest_set(diary, authority, data, len);
-}
-
diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h
index bc24e68..947b73b 100644
--- a/modules/http2/h2_push.h
+++ b/modules/http2/h2_push.h
@@ -17,10 +17,12 @@
#ifndef __mod_h2__h2_push__
#define __mod_h2__h2_push__
+#include
+
#include "h2.h"
+#include "h2_headers.h"
struct h2_request;
-struct h2_headers;
struct h2_ngheader;
struct h2_session;
struct h2_stream;
@@ -35,6 +37,44 @@ typedef enum {
H2_PUSH_DIGEST_SHA256
} h2_push_digest_type;
+/*******************************************************************************
+ * push diary
+ *
+ * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
+ * connection. It records a hash value from the absolute URL of the resource
+ * pushed.
+ * - Lacking openssl,
+ * - with openssl, it uses SHA256 to calculate the hash value, otherwise it
+ * falls back to apr_hashfunc_default()
+ * - whatever the method to generate the hash, the diary keeps a maximum of 64
+ * bits per hash, limiting the memory consumption to about
+ * H2PushDiarySize * 8
+ * bytes. Entries are sorted by most recently used and oldest entries are
+ * forgotten first.
+ * - While useful by itself to avoid duplicated PUSHes on the same connection,
+ * the original idea was that clients provided a 'Cache-Digest' header with
+ * the values of *their own* cached resources. This was described in
+ *
+ * and some subsequent revisions that tweaked values but kept the overall idea.
+ * - The draft was abandoned by the IETF http-wg, as support from major clients,
+ * e.g. browsers, was lacking for various reasons.
+ * - For these reasons, mod_h2 abandoned its support for client supplied values
+ * but keeps the diary. It seems to provide value for applications using PUSH,
+ * is configurable in size and defaults to a very moderate amount of memory
+ * used.
+ * - The cache digest header is a Golomb Coded Set of hash values, but it may
+ * limit the amount of bits per hash value even further. For a good description
+ * of GCS, read here:
+ *
+ ******************************************************************************/
+
+
+/*
+ * The push diary is based on the abandoned draft
+ *
+ * that describes how to use golomb filters.
+ */
+
typedef struct h2_push_diary h2_push_diary;
typedef void h2_push_digest_calc(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push);
@@ -59,14 +99,21 @@ struct h2_push_diary {
* @param res the response from the server
* @return array of h2_push addresses or NULL
*/
-apr_array_header_t *h2_push_collect(apr_pool_t *p,
- const struct h2_request *req,
- int push_policy,
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const ap_bucket_response *res);
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
const struct h2_headers *res);
+#endif
/**
* Create a new push diary for the given maximum number of entries.
- *
+ *
* @param p the pool to use
* @param N the max number of entries, rounded up to 2^x
* @return the created diary, might be NULL of max_entries is 0
@@ -83,14 +130,21 @@ apr_array_header_t *h2_push_diary_update(struct h2_session *session, apr_array_h
* Collect pushes for the given request/response pair, enter them into the
* diary and return those pushes newly entered.
*/
-apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
- const struct h2_request *req,
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const ap_bucket_response *res);
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
const struct h2_headers *res);
+#endif
+
/**
* Get a cache digest as described in
* https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
* from the contents of the push diary.
- *
+ *
* @param diary the diary to calculdate the digest from
* @param p the pool to use
* @param authority the authority to get the data for, use NULL/"*" for all
@@ -101,20 +155,4 @@ apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *p,
int maxP, const char *authority,
const char **pdata, apr_size_t *plen);
-/**
- * Initialize the push diary by a cache digest as described in
- * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
- * .
- * @param diary the diary to set the digest into
- * @param authority the authority to set the data for
- * @param data the binary cache digest
- * @param len the length of the cache digest
- * @return APR_EINVAL if digest was not successfully parsed
- */
-apr_status_t h2_push_diary_digest_set(h2_push_diary *diary, const char *authority,
- const char *data, apr_size_t len);
-
-apr_status_t h2_push_diary_digest64_set(h2_push_diary *diary, const char *authority,
- const char *data64url, apr_pool_t *pool);
-
#endif /* defined(__mod_h2__h2_push__) */
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
-#include
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
+
+#include
#include
#include
@@ -24,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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;
+}
diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h
index 48aee09..7e20b69 100644
--- a/modules/http2/h2_request.h
+++ b/modules/http2/h2_request.h
@@ -19,18 +19,24 @@
#include "h2.h"
-apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
+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);
+
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
request_rec *r);
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 h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
const char *name, size_t nlen,
const char *value, size_t vlen);
-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);
h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
@@ -40,9 +46,14 @@ h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
*
* @param req the h2 request to process
* @param conn the connection to process the request on
+ * @param no_body != 0 iff the request is known to have no body
* @return the request_rec representing the request
*/
-request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn);
+request_rec *h2_create_request_rec(const h2_request *req, conn_rec *conn,
+ int no_body);
+#if AP_HAS_RESPONSE_BUCKETS
+apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r);
+#endif
#endif /* defined(__mod_h2__h2_request__) */
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
index ed96cf0..5724fda 100644
--- a/modules/http2/h2_session.c
+++ b/modules/http2/h2_session.c
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
@@ -26,33 +27,35 @@
#include
#include
#include
+#include
#include
#include
+#if APR_HAVE_UNISTD_H
+#include /* for getpid() */
+#endif
+
#include "h2_private.h"
#include "h2.h"
#include "h2_bucket_beam.h"
#include "h2_bucket_eos.h"
#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_filter.h"
-#include "h2_h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
#include "h2_mplx.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_headers.h"
#include "h2_stream.h"
-#include "h2_task.h"
+#include "h2_c2.h"
#include "h2_session.h"
#include "h2_util.h"
#include "h2_version.h"
#include "h2_workers.h"
-static apr_status_t dispatch_master(h2_session *session);
-static apr_status_t h2_session_read(h2_session *session, int block);
-static void transit(h2_session *session, const char *action,
+static void transit(h2_session *session, const char *action,
h2_session_state nstate);
static void on_stream_state_enter(void *ctx, h2_stream *stream);
@@ -73,23 +76,20 @@ static int h2_session_status_from_apr_status(apr_status_t rv)
return NGHTTP2_ERR_PROTO;
}
-h2_stream *h2_session_stream_get(h2_session *session, int stream_id)
+static h2_stream *get_stream(h2_session *session, int stream_id)
{
return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
}
-static void dispatch_event(h2_session *session, h2_session_event_t ev,
- int err, const char *msg);
-
-void h2_session_event(h2_session *session, h2_session_event_t ev,
+void h2_session_event(h2_session *session, h2_session_event_t ev,
int err, const char *msg)
{
- dispatch_event(session, ev, err, msg);
+ h2_session_dispatch_event(session, ev, err, msg);
}
static int rst_unprocessed_stream(h2_stream *stream, void *ctx)
{
- int unprocessed = (!h2_stream_was_closed(stream)
+ int unprocessed = (!h2_stream_is_at_or_past(stream, H2_SS_CLOSED)
&& (H2_STREAM_CLIENT_INITIATED(stream->id)?
(!stream->session->local.accepting
&& stream->id > stream->session->local.accepted_max)
@@ -106,7 +106,7 @@ static int rst_unprocessed_stream(h2_stream *stream, void *ctx)
static void cleanup_unprocessed_streams(h2_session *session)
{
- h2_mplx_stream_do(session->mplx, rst_unprocessed_stream, session);
+ h2_mplx_c1_streams_do(session->mplx, rst_unprocessed_stream, session);
}
static h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
@@ -127,7 +127,7 @@ static h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
}
/**
- * Determine the importance of streams when scheduling tasks.
+ * Determine the priority order of streams.
* - if both stream depend on the same one, compare weights
* - if one stream is closer to the root, prioritize that one
* - if both are on the same level, use the weight of their root
@@ -187,20 +187,26 @@ static ssize_t send_cb(nghttp2_session *ngh2,
int flags, void *userp)
{
h2_session *session = (h2_session *)userp;
- apr_status_t status;
+ apr_status_t rv;
(void)ngh2;
(void)flags;
-
- status = h2_conn_io_write(&session->io, (const char *)data, length);
- if (status == APR_SUCCESS) {
+
+ if (h2_c1_io_needs_flush(&session->io)) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ rv = h2_c1_io_add_data(&session->io, (const char *)data, length);
+ if (APR_SUCCESS == rv) {
return length;
}
- if (APR_STATUS_IS_EAGAIN(status)) {
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
return NGHTTP2_ERR_WOULDBLOCK;
}
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03062)
- "h2_session: send error");
- return h2_session_status_from_apr_status(status);
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, session->c1,
+ APLOGNO(03062) "h2_session: send error");
+ return h2_session_status_from_apr_status(rv);
+ }
}
static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
@@ -210,11 +216,11 @@ static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
h2_session *session = (h2_session *)userp;
(void)ngh2;
- if (APLOGcdebug(session->c)) {
+ if (APLOGcdebug(session->c1)) {
char buffer[256];
h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03063), session,
"recv invalid FRAME[%s], frames=%ld/%ld (r/s)"),
buffer, (long)session->frames_received,
@@ -232,15 +238,17 @@ static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
h2_stream * stream;
int rv = 0;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "write %ld bytes of DATA"),
+ (long)len);
status = h2_stream_recv_DATA(stream, flags, data, len);
- dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream data rcvd");
}
else {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03064)
- "h2_stream(%ld-%d): on_data_chunk for unknown stream",
- session->id, (int)stream_id);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03064)
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "on_data_chunk for unknown stream"));
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
}
@@ -258,13 +266,13 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
h2_stream *stream;
(void)ngh2;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (stream) {
if (error_code) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_STRM_LOG(APLOGNO(03065), stream,
"closing with err=%d %s"),
- (int)error_code, h2_h2_err_description(error_code));
+ (int)error_code, h2_protocol_err_description(error_code));
h2_stream_rst(stream, error_code);
}
}
@@ -275,16 +283,16 @@ static int on_begin_headers_cb(nghttp2_session *ngh2,
const nghttp2_frame *frame, void *userp)
{
h2_session *session = (h2_session *)userp;
- h2_stream *s;
+ h2_stream *s = NULL;
/* We may see HEADERs at the start of a stream or after all DATA
* streams to carry trailers. */
(void)ngh2;
- s = h2_session_stream_get(session, frame->hd.stream_id);
+ s = get_stream(session, frame->hd.stream_id);
if (s) {
/* nop */
}
- else {
+ else if (session->local.accepting) {
s = h2_session_open_stream(userp, frame->hd.stream_id, 0);
}
return s? 0 : NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
@@ -301,17 +309,23 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
apr_status_t status;
(void)flags;
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ stream = get_stream(session, frame->hd.stream_id);
if (!stream) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(02920)
- "h2_stream(%ld-%d): on_header unknown stream",
- session->id, (int)frame->hd.stream_id);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(02920)
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "on_header unknown stream"));
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
status = h2_stream_add_header(stream, (const char *)name, namelen,
(const char *)value, valuelen);
- if (status != APR_SUCCESS && !h2_stream_is_ready(stream)) {
+ if (status != APR_SUCCESS &&
+ (!stream->rtmp ||
+ stream->rtmp->http_status == H2_HTTP_STATUS_UNSET ||
+ /* We accept a certain amount of failures in order to reply
+ * with an informative HTTP error response like 413. But if the
+ * client is too wrong, we fail the request a RESET of the stream */
+ stream->request_headers_failed > 100)) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
return 0;
@@ -330,15 +344,25 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
h2_stream *stream;
apr_status_t rv = APR_SUCCESS;
- if (APLOGcdebug(session->c)) {
+ stream = frame->hd.stream_id? get_stream(session, frame->hd.stream_id) : NULL;
+ if (APLOGcdebug(session->c1)) {
char buffer[256];
-
+
h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- H2_SSSN_LOG(APLOGNO(03066), session,
- "recv FRAME[%s], frames=%ld/%ld (r/s)"),
- buffer, (long)session->frames_received,
- (long)session->frames_sent);
+ if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10302), stream,
+ "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03066), session,
+ "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
}
++session->frames_received;
@@ -347,16 +371,14 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
/* This can be HEADERS for a new stream, defining the request,
* or HEADER may come after DATA at the end of a stream as in
* trailers */
- stream = h2_session_stream_get(session, frame->hd.stream_id);
if (stream) {
rv = h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags,
frame->hd.length + H2_FRAME_HDR_LEN);
}
break;
case NGHTTP2_DATA:
- stream = h2_session_stream_get(session, frame->hd.stream_id);
if (stream) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_STRM_LOG(APLOGNO(02923), stream,
"DATA, len=%ld, flags=%d"),
(long)frame->hd.length, frame->hd.flags);
@@ -366,35 +388,40 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
break;
case NGHTTP2_PRIORITY:
session->reprioritize = 1;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- "h2_stream(%ld-%d): PRIORITY frame "
- " weight=%d, dependsOn=%d, exclusive=%d",
- session->id, (int)frame->hd.stream_id,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id, "PRIORITY frame "
+ " weight=%d, dependsOn=%d, exclusive=%d"),
frame->priority.pri_spec.weight,
frame->priority.pri_spec.stream_id,
frame->priority.pri_spec.exclusive);
break;
case NGHTTP2_WINDOW_UPDATE:
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- "h2_stream(%ld-%d): WINDOW_UPDATE incr=%d",
- session->id, (int)frame->hd.stream_id,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "WINDOW_UPDATE incr=%d"),
frame->window_update.window_size_increment);
- if (nghttp2_session_want_write(session->ngh2)) {
- dispatch_event(session, H2_SESSION_EV_FRAME_RCVD, 0, "window update");
- }
break;
case NGHTTP2_RST_STREAM:
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03067)
- "h2_stream(%ld-%d): RST_STREAM by client, errror=%d",
- session->id, (int)frame->hd.stream_id,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03067)
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "RST_STREAM by client, error=%d"),
(int)frame->rst_stream.error_code);
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ if (stream) {
+ rv = h2_stream_recv_frame(stream, NGHTTP2_RST_STREAM, frame->hd.flags,
+ frame->hd.length + H2_FRAME_HDR_LEN);
+ }
if (stream && stream->initiated_on) {
+ /* A stream reset on a request we sent it. Normal, when the
+ * client does not want it. */
++session->pushes_reset;
}
else {
- ++session->streams_reset;
+ /* A stream reset on a request it sent us. Could happen in a browser
+ * when the user navigates away or cancels loading - maybe. */
+ h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id,
+ stream);
}
+ ++session->streams_reset;
break;
case NGHTTP2_GOAWAY:
if (frame->goaway.error_code == 0
@@ -404,23 +431,21 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
}
else {
session->remote.accepted_max = frame->goaway.last_stream_id;
- dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY,
+ h2_session_dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY,
frame->goaway.error_code, NULL);
}
break;
case NGHTTP2_SETTINGS:
- if (APLOGctrace2(session->c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length);
- }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length);
break;
default:
- if (APLOGctrace2(session->c)) {
+ if (APLOGctrace2(session->c1)) {
char buffer[256];
h2_util_frame_print(frame, buffer,
sizeof(buffer)/sizeof(buffer[0]));
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
H2_SSSN_MSG(session, "on_frame_rcv %s"), buffer);
}
break;
@@ -436,7 +461,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
* become in serving this connection. This is expressed in increasing "idle_delays".
* Eventually, the connection will timeout and we'll close it. */
session->idle_frames = H2MIN(session->idle_frames + 1, session->frames_received);
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c1,
H2_SSSN_MSG(session, "session has %ld idle frames"),
(long)session->idle_frames);
if (session->idle_frames > 10) {
@@ -461,16 +486,6 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
return 0;
}
-static int h2_session_continue_data(h2_session *session) {
- if (h2_mplx_has_master_events(session->mplx)) {
- return 0;
- }
- if (h2_conn_io_needs_flush(&session->io)) {
- return 0;
- }
- return 1;
-}
-
static char immortal_zeros[H2_MAX_PADLEN];
static int on_send_data_cb(nghttp2_session *ngh2,
@@ -491,48 +506,42 @@ static int on_send_data_cb(nghttp2_session *ngh2,
(void)ngh2;
(void)source;
- if (!h2_session_continue_data(session)) {
- return NGHTTP2_ERR_WOULDBLOCK;
- }
-
- if (frame->data.padlen > H2_MAX_PADLEN) {
- return NGHTTP2_ERR_PROTO;
- }
+ ap_assert(frame->data.padlen <= (H2_MAX_PADLEN+1));
padlen = (unsigned char)frame->data.padlen;
- stream = h2_session_stream_get(session, stream_id);
+ stream = get_stream(session, stream_id);
if (!stream) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c1,
APLOGNO(02924)
- "h2_stream(%ld-%d): send_data, stream not found",
- session->id, (int)stream_id);
+ H2_SSSN_STRM_MSG(session, stream_id, "send_data, stream not found"));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
H2_STRM_MSG(stream, "send_data_cb for %ld bytes"),
(long)length);
- status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
+ status = h2_c1_io_add_data(&session->io, (const char *)framehd, H2_FRAME_HDR_LEN);
if (padlen && status == APR_SUCCESS) {
- status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
+ --padlen;
+ status = h2_c1_io_add_data(&session->io, (const char *)&padlen, 1);
}
if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
H2_STRM_MSG(stream, "writing frame header"));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
status = h2_stream_read_to(stream, session->bbtmp, &len, &eos);
if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
H2_STRM_MSG(stream, "send_data_cb, reading stream"));
apr_brigade_cleanup(session->bbtmp);
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- else if (len != length) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+ else if (len != (apr_off_t)length) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
H2_STRM_MSG(stream, "send_data_cb, wanted %ld bytes, "
"got %ld from stream"), (long)length, (long)len);
apr_brigade_cleanup(session->bbtmp);
@@ -541,20 +550,23 @@ static int on_send_data_cb(nghttp2_session *ngh2,
if (padlen) {
b = apr_bucket_immortal_create(immortal_zeros, padlen,
- session->c->bucket_alloc);
+ session->c1->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
}
- status = h2_conn_io_pass(&session->io, session->bbtmp);
+ status = h2_c1_io_append(&session->io, session->bbtmp);
apr_brigade_cleanup(session->bbtmp);
if (status == APR_SUCCESS) {
stream->out_data_frames++;
stream->out_data_octets += length;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "sent data length=%ld, total=%ld"),
+ (long)length, (long)stream->out_data_octets);
return 0;
}
else {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
H2_STRM_LOG(APLOGNO(02925), stream, "failed send_data_cb"));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
@@ -578,18 +590,27 @@ static int on_frame_send_cb(nghttp2_session *ngh2,
break;
}
- if (APLOGcdebug(session->c)) {
+ stream = get_stream(session, stream_id);
+ if (APLOGcdebug(session->c1)) {
char buffer[256];
h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- H2_SSSN_LOG(APLOGNO(03068), session,
- "sent FRAME[%s], frames=%ld/%ld (r/s)"),
- buffer, (long)session->frames_received,
- (long)session->frames_sent);
+ if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10303), stream,
+ "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03068), session,
+ "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
}
- stream = h2_session_stream_get(session, stream_id);
if (stream) {
h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags,
frame->hd.length + H2_FRAME_HDR_LEN);
@@ -598,7 +619,7 @@ static int on_frame_send_cb(nghttp2_session *ngh2,
}
#ifdef H2_NG2_INVALID_HEADER_CB
-static int on_invalid_header_cb(nghttp2_session *ngh2,
+static int on_invalid_header_cb(nghttp2_session *ngh2,
const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
@@ -607,14 +628,11 @@ static int on_invalid_header_cb(nghttp2_session *ngh2,
h2_session *session = user_data;
h2_stream *stream;
- if (APLOGcdebug(session->c)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03456)
- "h2_stream(%ld-%d): invalid header '%s: %s'",
- session->id, (int)frame->hd.stream_id,
- apr_pstrndup(session->pool, (const char *)name, namelen),
- apr_pstrndup(session->pool, (const char *)value, valuelen));
- }
- stream = h2_session_stream_get(session, frame->hd.stream_id);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03456)
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "invalid header '%.*s: %.*s'"),
+ (int)namelen, name, (int)valuelen, value);
+ stream = get_stream(session, frame->hd.stream_id);
if (stream) {
h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
}
@@ -622,6 +640,37 @@ static int on_invalid_header_cb(nghttp2_session *ngh2,
}
#endif
+static ssize_t select_padding_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ size_t max_payloadlen, void *user_data)
+{
+ h2_session *session = user_data;
+ size_t frame_len = frame->hd.length + H2_FRAME_HDR_LEN; /* the total length without padding */
+ size_t padded_len = frame_len;
+
+ /* Determine # of padding bytes to append to frame. Unless session->padding_always
+ * the number my be capped by the ui.write_size that currently applies.
+ */
+ if (session->padding_max) {
+ int n = ap_random_pick(0, session->padding_max);
+ padded_len = H2MIN(max_payloadlen + H2_FRAME_HDR_LEN, frame_len + n);
+ }
+
+ if (padded_len != frame_len) {
+ if (!session->padding_always && session->io.write_size
+ && (padded_len > session->io.write_size)
+ && (frame_len <= session->io.write_size)) {
+ padded_len = session->io.write_size;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ "select padding from [%d, %d]: %d (frame length: 0x%04x, write size: %d)",
+ (int)frame_len, (int)max_payloadlen+H2_FRAME_HDR_LEN,
+ (int)(padded_len - frame_len), (int)padded_len, (int)session->io.write_size);
+ return padded_len - H2_FRAME_HDR_LEN;
+ }
+ return frame->hd.length;
+}
+
#define NGH2_SET_CALLBACK(callbacks, name, fn)\
nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
@@ -647,9 +696,37 @@ static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
#ifdef H2_NG2_INVALID_HEADER_CB
NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb);
#endif
+ NGH2_SET_CALLBACK(*pcb, select_padding, select_padding_cb);
return APR_SUCCESS;
}
+static void update_child_status(h2_session *session, int status,
+ const char *msg, const h2_stream *stream)
+{
+ /* Assume that we also change code/msg when something really happened and
+ * avoid updating the scoreboard in between */
+ if (session->last_status_code != status
+ || session->last_status_msg != msg) {
+ char sbuffer[1024];
+ sbuffer[0] = '\0';
+ if (stream) {
+ apr_snprintf(sbuffer, sizeof(sbuffer),
+ ": stream %d, %s %s",
+ stream->id,
+ stream->request? stream->request->method : "",
+ stream->request? stream->request->path : "");
+ }
+ apr_snprintf(session->status, sizeof(session->status),
+ "[%d/%d] %s%s",
+ (int)(session->remote.emitted_count + session->pushes_submitted),
+ (int)session->streams_done,
+ msg? msg : "-", sbuffer);
+ ap_update_child_status_from_server(session->c1->sbh, status,
+ session->c1, session->s);
+ ap_update_child_status_descr(session->c1->sbh, status, session->status);
+ }
+}
+
static apr_status_t h2_session_shutdown_notice(h2_session *session)
{
apr_status_t status;
@@ -663,9 +740,9 @@ static apr_status_t h2_session_shutdown_notice(h2_session *session)
session->local.accepting = 0;
status = nghttp2_session_send(session->ngh2);
if (status == APR_SUCCESS) {
- status = h2_conn_io_flush(&session->io);
+ status = h2_c1_io_assure_flushed(&session->io);
}
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03457), session, "sent shutdown notice"));
return status;
}
@@ -679,10 +756,13 @@ static apr_status_t h2_session_shutdown(h2_session *session, int error,
if (session->local.shutdown) {
return APR_SUCCESS;
}
- if (!msg && error) {
- msg = nghttp2_strerror(error);
+
+ if (error && !msg) {
+ if (APR_STATUS_IS_EPIPE(error)) {
+ msg = "remote close";
+ }
}
-
+
if (error || force_close) {
/* not a graceful shutdown, we want to leave...
* Do not start further streams that are waiting to be scheduled.
@@ -691,8 +771,9 @@ static apr_status_t h2_session_shutdown(h2_session *session, int error,
* Remove all streams greater than this number without submitting
* a RST_STREAM frame, since that should be clear from the GOAWAY
* we send. */
- session->local.accepted_max = h2_mplx_shutdown(session->mplx);
+ session->local.accepted_max = h2_mplx_c1_shutdown(session->mplx);
session->local.error = error;
+ session->local.error_msg = msg;
}
else {
/* graceful shutdown. we will continue processing all streams
@@ -702,25 +783,25 @@ static apr_status_t h2_session_shutdown(h2_session *session, int error,
session->local.accepting = 0;
session->local.shutdown = 1;
- if (!session->c->aborted) {
+ if (!session->c1->aborted) {
nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
session->local.accepted_max,
error, (uint8_t*)msg, msg? strlen(msg):0);
status = nghttp2_session_send(session->ngh2);
if (status == APR_SUCCESS) {
- status = h2_conn_io_flush(&session->io);
+ status = h2_c1_io_assure_flushed(&session->io);
}
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03069), session,
"sent GOAWAY, err=%d, msg=%s"), error, msg? msg : "");
}
- dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
+ h2_session_dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
return status;
}
static apr_status_t session_cleanup(h2_session *session, const char *trigger)
{
- conn_rec *c = session->c;
+ conn_rec *c = session->c1;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
H2_SSSN_MSG(session, "pool_cleanup"));
@@ -734,40 +815,54 @@ static apr_status_t session_cleanup(h2_session *session, const char *trigger)
* connection when sending the next request, this has the effect
* that at least this one request will fail.
*/
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
H2_SSSN_LOG(APLOGNO(03199), session,
"connection disappeared without proper "
"goodbye, clients will be confused, should not happen"));
}
+ if (!h2_iq_empty(session->ready_to_process)) {
+ int sid;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ H2_SSSN_LOG(APLOGNO(10485), session,
+ "cleanup, resetting %d streams in ready-to-process"),
+ h2_iq_count(session->ready_to_process));
+ while ((sid = h2_iq_shift(session->ready_to_process)) > 0) {
+ h2_mplx_c1_client_rst(session->mplx, sid, get_stream(session, sid));
+ }
+ }
+
transit(session, trigger, H2_SESSION_ST_CLEANUP);
- h2_mplx_release_and_join(session->mplx, session->iowait);
+ h2_mplx_c1_destroy(session->mplx);
session->mplx = NULL;
ap_assert(session->ngh2);
nghttp2_session_del(session->ngh2);
session->ngh2 = NULL;
- h2_ctx_clear(c);
-
-
+ h2_conn_ctx_detach(c);
+
return APR_SUCCESS;
}
static apr_status_t session_pool_cleanup(void *data)
{
conn_rec *c = data;
- h2_session *session;
- h2_ctx *ctx = h2_ctx_get(c, 0);
-
- if (ctx && (session = h2_ctx_session_get(ctx))) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ h2_session *session = conn_ctx? conn_ctx->session : NULL;
+
+ if (session) {
+ int mpm_state = 0;
+ int level;
+
+ ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state);
+ level = (AP_MPMQ_STOPPING == mpm_state)? APLOG_DEBUG : APLOG_WARNING;
/* if the session is still there, now is the last chance
* to perform cleanup. Normally, cleanup should have happened
- * earlier in the connection pre_close. Main reason is that
- * any ongoing requests on slave connections might still access
- * data which has, at this time, already been freed. An example
- * is mod_ssl that uses request hooks. */
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
- H2_SSSN_LOG(APLOGNO(10020), session,
+ * earlier in the connection pre_close.
+ * However, when the server is stopping, it may shutdown connections
+ * without running the pre_close hooks. Do not want about that. */
+ ap_log_cerror(APLOG_MARK, level, 0, c,
+ H2_SSSN_LOG(APLOGNO(10020), session,
"session cleanup triggered by pool cleanup. "
"this should have happened earlier already."));
return session_cleanup(session, "pool cleanup");
@@ -775,101 +870,83 @@ static apr_status_t session_pool_cleanup(void *data)
return APR_SUCCESS;
}
-static apr_status_t h2_session_create_int(h2_session **psession,
- conn_rec *c,
- request_rec *r,
- h2_ctx *ctx,
- h2_workers *workers)
+static /* atomic */ apr_uint32_t next_id;
+
+apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *r,
+ server_rec *s, h2_workers *workers)
{
nghttp2_session_callbacks *callbacks = NULL;
nghttp2_option *options = NULL;
- apr_allocator_t *allocator;
- apr_thread_mutex_t *mutex;
uint32_t n;
+ int thread_num;
apr_pool_t *pool = NULL;
h2_session *session;
+ h2_stream *stream0;
apr_status_t status;
int rv;
*psession = NULL;
- status = apr_allocator_create(&allocator);
- if (status != APR_SUCCESS) {
- return status;
- }
- apr_allocator_max_free_set(allocator, ap_max_mem_free);
- apr_pool_create_ex(&pool, c->pool, NULL, allocator);
- if (!pool) {
- apr_allocator_destroy(allocator);
- return APR_ENOMEM;
- }
+ apr_pool_create(&pool, c->pool);
apr_pool_tag(pool, "h2_session");
- apr_allocator_owner_set(allocator, pool);
- status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool);
- if (status != APR_SUCCESS) {
- apr_pool_destroy(pool);
- return APR_ENOMEM;
- }
- apr_allocator_mutex_set(allocator, mutex);
-
session = apr_pcalloc(pool, sizeof(h2_session));
if (!session) {
return APR_ENOMEM;
}
*psession = session;
- session->id = c->id;
- session->c = c;
+ /* c->id does not give a unique id for the lifetime of the session.
+ * mpms like event change c->id when re-activating a keepalive
+ * connection based on the child_num+thread_num of the worker
+ * processing it.
+ * We'd like to have an id that remains constant and unique bc
+ * h2 streams can live through keepalive periods. While double id
+ * will not lead to processing failures, it will confuse log analysis.
+ */
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 8)
+ ap_sb_get_child_thread(c->sbh, &session->child_num, &thread_num);
+#else
+ (void)thread_num;
+ session->child_num = (int)getpid();
+#endif
+ session->id = apr_atomic_inc32(&next_id);
+ session->c1 = c;
session->r = r;
- session->s = h2_ctx_server_get(ctx);
+ session->s = s;
session->pool = pool;
- session->config = h2_config_sget(session->s);
session->workers = workers;
session->state = H2_SESSION_ST_INIT;
session->local.accepting = 1;
session->remote.accepting = 1;
- session->max_stream_count = h2_config_geti(session->config,
- H2_CONF_MAX_STREAMS);
- session->max_stream_mem = h2_config_geti(session->config,
- H2_CONF_STREAM_MAX_MEM);
-
- status = apr_thread_cond_create(&session->iowait, session->pool);
- if (status != APR_SUCCESS) {
- apr_pool_destroy(pool);
- return status;
- }
-
- session->in_pending = h2_iq_create(session->pool, (int)session->max_stream_count);
- if (session->in_pending == NULL) {
- apr_pool_destroy(pool);
- return APR_ENOMEM;
- }
+ session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
+ session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
+ session->max_data_frame_len = h2_config_sgeti(s, H2_CONF_MAX_DATA_FRAME_LEN);
+
+ session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count);
+ session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count);
- session->in_process = h2_iq_create(session->pool, (int)session->max_stream_count);
- if (session->in_process == NULL) {
- apr_pool_destroy(pool);
- return APR_ENOMEM;
- }
-
session->monitor = apr_pcalloc(pool, sizeof(h2_stream_monitor));
- if (session->monitor == NULL) {
- apr_pool_destroy(pool);
- return APR_ENOMEM;
- }
session->monitor->ctx = session;
session->monitor->on_state_enter = on_stream_state_enter;
session->monitor->on_state_event = on_stream_state_event;
session->monitor->on_event = on_stream_event;
-
- session->mplx = h2_mplx_create(c, session->pool, session->config,
- workers);
-
- /* connection input filter that feeds the session */
- session->cin = h2_filter_cin_create(session);
- ap_add_input_filter("H2_IN", session->cin, r, c);
-
- h2_conn_io_init(&session->io, c, session->config);
+
+ stream0 = h2_stream_create(0, session->pool, session, NULL, 0);
+ stream0->c2 = session->c1; /* stream0's connection is the main connection */
+ session->mplx = h2_mplx_c1_create(session->child_num, session->id,
+ stream0, s, session->pool, workers);
+ if (!session->mplx) {
+ apr_pool_destroy(pool);
+ return APR_ENOTIMPL;
+ }
+
+ h2_c1_io_init(&session->io, session);
+ session->padding_max = h2_config_sgeti(s, H2_CONF_PADDING_BITS);
+ if (session->padding_max) {
+ session->padding_max = (0x01 << session->padding_max) - 1;
+ }
+ session->padding_always = h2_config_sgeti(s, H2_CONF_PADDING_ALWAYS);
session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
status = init_callbacks(c, &callbacks);
@@ -888,12 +965,23 @@ static apr_status_t h2_session_create_int(h2_session **psession,
apr_pool_destroy(pool);
return status;
}
- nghttp2_option_set_peer_max_concurrent_streams(
- options, (uint32_t)session->max_stream_count);
+ nghttp2_option_set_peer_max_concurrent_streams(options, (uint32_t)session->max_stream_count);
/* We need to handle window updates ourself, otherwise we
* get flooded by nghttp2. */
nghttp2_option_set_no_auto_window_update(options, 1);
-
+#ifdef H2_NG2_NO_CLOSED_STREAMS
+ /* We do not want nghttp2 to keep information about closed streams as
+ * that accumulates memory on long connections. This makes PRIORITY
+ * setting in relation to older streams non-working. */
+ nghttp2_option_set_no_closed_streams(options, 1);
+#endif
+#ifdef H2_NG2_RFC9113_STRICTNESS
+ /* nghttp2 v1.50.0 introduces the strictness checks on leading/trailing
+ * whitespace of RFC 9113 for fields. But, by default, it RST streams
+ * carrying such. We do not want that. We want to strip the ws and
+ * handle them, just like the HTTP/1.1 parser does. */
+ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(options, 1);
+#endif
rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
session, options);
nghttp2_session_callbacks_del(callbacks);
@@ -907,7 +995,7 @@ static apr_status_t h2_session_create_int(h2_session **psession,
return APR_ENOMEM;
}
- n = h2_config_geti(session->config, H2_CONF_PUSH_DIARY_SIZE);
+ n = h2_config_sgeti(s, H2_CONF_PUSH_DIARY_SIZE);
session->push_diary = h2_push_diary_create(session->pool, n);
if (APLOGcdebug(c)) {
@@ -915,35 +1003,26 @@ static apr_status_t h2_session_create_int(h2_session **psession,
H2_SSSN_LOG(APLOGNO(03200), session,
"created, max_streams=%d, stream_mem=%d, "
"workers_limit=%d, workers_max=%d, "
- "push_diary(type=%d,N=%d)"),
+ "push_diary(type=%d,N=%d), "
+ "max_data_frame_len=%d"),
(int)session->max_stream_count,
(int)session->max_stream_mem,
- session->mplx->limit_active,
- session->mplx->max_active,
+ session->mplx->processing_limit,
+ session->mplx->processing_max,
session->push_diary->dtype,
- (int)session->push_diary->N);
+ (int)session->push_diary->N,
+ (int)session->max_data_frame_len);
}
- apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
+ apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
+
return APR_SUCCESS;
}
-apr_status_t h2_session_create(h2_session **psession,
- conn_rec *c, h2_ctx *ctx, h2_workers *workers)
-{
- return h2_session_create_int(psession, c, NULL, ctx, workers);
-}
-
-apr_status_t h2_session_rcreate(h2_session **psession,
- request_rec *r, h2_ctx *ctx, h2_workers *workers)
-{
- return h2_session_create_int(psession, r->connection, r, ctx, workers);
-}
-
static apr_status_t h2_session_start(h2_session *session, int *rv)
{
apr_status_t status = APR_SUCCESS;
- nghttp2_settings_entry settings[3];
+ nghttp2_settings_entry settings[4];
size_t slen;
int win_size;
@@ -1004,14 +1083,21 @@ static apr_status_t h2_session_start(h2_session *session, int *rv)
settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
settings[slen].value = (uint32_t)session->max_stream_count;
++slen;
- win_size = h2_config_geti(session->config, H2_CONF_WIN_SIZE);
+ win_size = h2_config_sgeti(session->s, H2_CONF_WIN_SIZE);
if (win_size != H2_INITIAL_WINDOW_SIZE) {
settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
settings[slen].value = win_size;
++slen;
}
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+#if H2_USE_WEBSOCKETS
+ if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) {
+ settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+ settings[slen].value = 1;
+ ++slen;
+ }
+#endif
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
H2_SSSN_LOG(APLOGNO(03201), session,
"start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"),
(long)win_size, (int)session->max_stream_count);
@@ -1019,7 +1105,7 @@ static apr_status_t h2_session_start(h2_session *session, int *rv)
settings, slen);
if (*rv != 0) {
status = APR_EGENERAL;
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1,
H2_SSSN_LOG(APLOGNO(02935), session,
"nghttp2_submit_settings: %s"), nghttp2_strerror(*rv));
}
@@ -1037,7 +1123,7 @@ static apr_status_t h2_session_start(h2_session *session, int *rv)
0, NGHTTP2_MAX_WINDOW_SIZE - win_size);
if (*rv != 0) {
status = APR_EGENERAL;
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1,
H2_SSSN_LOG(APLOGNO(02970), session,
"nghttp2_submit_window_update: %s"),
nghttp2_strerror(*rv));
@@ -1047,87 +1133,6 @@ static apr_status_t h2_session_start(h2_session *session, int *rv)
return status;
}
-static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,
- h2_headers *headers, apr_off_t len,
- int eos);
-
-static ssize_t stream_data_cb(nghttp2_session *ng2s,
- int32_t stream_id,
- uint8_t *buf,
- size_t length,
- uint32_t *data_flags,
- nghttp2_data_source *source,
- void *puser)
-{
- h2_session *session = (h2_session *)puser;
- apr_off_t nread = length;
- int eos = 0;
- apr_status_t status;
- h2_stream *stream;
- ap_assert(session);
-
- /* The session wants to send more DATA for the stream. We need
- * to find out how much of the requested length we can send without
- * blocking.
- * Indicate EOS when we encounter it or DEFERRED if the stream
- * should be suspended. Beware of trailers.
- */
-
- (void)ng2s;
- (void)buf;
- (void)source;
- stream = h2_session_stream_get(session, stream_id);
- if (!stream) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
- APLOGNO(02937)
- "h2_stream(%ld-%d): data_cb, stream not found",
- session->id, (int)stream_id);
- return NGHTTP2_ERR_CALLBACK_FAILURE;
- }
-
- status = h2_stream_out_prepare(stream, &nread, &eos, NULL);
- if (nread) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- H2_STRM_MSG(stream, "prepared no_copy, len=%ld, eos=%d"),
- (long)nread, eos);
- *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
- }
-
- switch (status) {
- case APR_SUCCESS:
- break;
-
- case APR_EOF:
- eos = 1;
- break;
-
- case APR_ECONNRESET:
- case APR_ECONNABORTED:
- return NGHTTP2_ERR_CALLBACK_FAILURE;
-
- case APR_EAGAIN:
- /* If there is no data available, our session will automatically
- * suspend this stream and not ask for more data until we resume
- * it. Remember at our h2_stream that we need to do this.
- */
- nread = 0;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- H2_STRM_LOG(APLOGNO(03071), stream, "suspending"));
- return NGHTTP2_ERR_DEFERRED;
-
- default:
- nread = 0;
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
- H2_STRM_LOG(APLOGNO(02938), stream, "reading data"));
- return NGHTTP2_ERR_CALLBACK_FAILURE;
- }
-
- if (eos) {
- *data_flags |= NGHTTP2_DATA_FLAG_EOF;
- }
- return (ssize_t)nread;
-}
-
struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
h2_push *push)
{
@@ -1142,21 +1147,21 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
ngh->nv, ngh->nvlen, NULL);
}
if (status != APR_SUCCESS || nid <= 0) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
H2_STRM_LOG(APLOGNO(03075), is,
"submitting push promise fail: %s"), nghttp2_strerror(nid));
return NULL;
}
++session->pushes_promised;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_STRM_LOG(APLOGNO(03076), is, "SERVER_PUSH %d for %s %s on %d"),
nid, push->req->method, push->req->path, is->id);
stream = h2_session_open_stream(session, nid, is->id);
if (!stream) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- H2_STRM_LOG(APLOGNO(03077), stream,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03077), is,
"failed to create stream obj %d"), nid);
/* kill the push_promise */
nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid,
@@ -1166,7 +1171,6 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
h2_session_set_prio(session, stream, push->priority);
h2_stream_set_request(stream, push->req);
- ++session->unsent_promises;
return stream;
}
@@ -1181,7 +1185,6 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
const h2_priority *prio)
{
apr_status_t status = APR_SUCCESS;
-#ifdef H2_NG2_CHANGE_PRIO
nghttp2_stream *s_grandpa, *s_parent, *s;
if (prio == NULL) {
@@ -1190,7 +1193,7 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
}
s = nghttp2_session_find_stream(session->ngh2, stream->id);
if (!s) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
H2_STRM_MSG(stream, "lookup of nghttp2_stream failed"));
return APR_EINVAL;
}
@@ -1239,10 +1242,10 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps);
if (rv < 0) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03202)
- "h2_stream(%ld-%d): PUSH BEFORE, weight=%d, "
- "depends=%d, returned=%d",
- session->id, id_parent, ps.weight, ps.stream_id, rv);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03202)
+ H2_SSSN_STRM_MSG(session, id_parent,
+ "PUSH BEFORE, weight=%d, depends=%d, returned=%d"),
+ ps.weight, ps.stream_id, rv);
return APR_EGENERAL;
}
nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
@@ -1261,18 +1264,13 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- ""H2_STRM_LOG(APLOGNO(03203), stream,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03203), stream,
"PUSH %s, weight=%d, depends=%d, returned=%d"),
ptype, ps.weight, ps.stream_id, rv);
status = (rv < 0)? APR_EGENERAL : APR_SUCCESS;
}
-#else
- (void)session;
- (void)stream;
- (void)prio;
- (void)valid_weight;
-#endif
+
return status;
}
@@ -1280,338 +1278,89 @@ int h2_session_push_enabled(h2_session *session)
{
/* iff we can and they can and want */
return (session->remote.accepting /* remote GOAWAY received */
- && h2_config_geti(session->config, H2_CONF_PUSH)
+ && h2_config_sgeti(session->s, H2_CONF_PUSH)
&& nghttp2_session_get_remote_settings(session->ngh2,
NGHTTP2_SETTINGS_ENABLE_PUSH));
}
-static apr_status_t h2_session_send(h2_session *session)
+static int h2_session_want_send(h2_session *session)
{
- apr_interval_time_t saved_timeout;
- int rv;
- apr_socket_t *socket;
-
- socket = ap_get_conn_socket(session->c);
- if (socket) {
- apr_socket_timeout_get(socket, &saved_timeout);
- apr_socket_timeout_set(socket, session->s->timeout);
- }
-
- rv = nghttp2_session_send(session->ngh2);
-
- if (socket) {
- apr_socket_timeout_set(socket, saved_timeout);
- }
- session->have_written = 1;
- if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) {
- if (nghttp2_is_fatal(rv)) {
- dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
- return APR_EGENERAL;
- }
- }
-
- session->unsent_promises = 0;
- session->unsent_submits = 0;
-
- return APR_SUCCESS;
+ return nghttp2_session_want_write(session->ngh2)
+ || h2_c1_io_pending(&session->io);
}
-/**
- * headers for the stream are ready.
- */
-static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,
- h2_headers *headers, apr_off_t len,
- int eos)
+static apr_status_t h2_session_send(h2_session *session)
{
- apr_status_t status = APR_SUCCESS;
- int rv = 0;
+ int ngrv, pending = 0;
+ apr_status_t rv = APR_SUCCESS;
- ap_assert(session);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- H2_STRM_MSG(stream, "on_headers"));
- if (headers->status < 100) {
- h2_stream_rst(stream, headers->status);
- goto leave;
- }
- else if (stream->has_response) {
- h2_ngheader *nh;
-
- status = h2_res_create_ngtrailer(&nh, stream->pool, headers);
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
- H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"),
- (int)nh->nvlen);
- if (status == APR_SUCCESS) {
- rv = nghttp2_submit_trailer(session->ngh2, stream->id,
- nh->nv, nh->nvlen);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
- H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers"));
- h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
- }
- goto leave;
- }
- else {
- nghttp2_data_provider provider, *pprovider = NULL;
- h2_ngheader *ngh;
- const char *note;
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- H2_STRM_LOG(APLOGNO(03073), stream, "submit response %d, REMOTE_WINDOW_SIZE=%u"),
- headers->status,
- (unsigned int)nghttp2_session_get_stream_remote_window_size(session->ngh2, stream->id));
-
- if (!eos || len > 0) {
- memset(&provider, 0, sizeof(provider));
- provider.source.fd = stream->id;
- provider.read_callback = stream_data_cb;
- pprovider = &provider;
- }
-
- /* If this stream is not a pushed one itself,
- * and HTTP/2 server push is enabled here,
- * and the response HTTP status is not sth >= 400,
- * and the remote side has pushing enabled,
- * -> find and perform any pushes on this stream
- * *before* we submit the stream response itself.
- * This helps clients avoid opening new streams on Link
- * headers that get pushed right afterwards.
- *
- * *) the response code is relevant, as we do not want to
- * make pushes on 401 or 403 codes and friends.
- * And if we see a 304, we do not push either
- * as the client, having this resource in its cache, might
- * also have the pushed ones as well.
- */
- if (!stream->initiated_on
- && !stream->has_response
- && stream->request && stream->request->method
- && !strcmp("GET", stream->request->method)
- && (headers->status < 400)
- && (headers->status != 304)
- && h2_session_push_enabled(session)) {
-
- h2_stream_submit_pushes(stream, headers);
- }
-
- if (!stream->pref_priority) {
- stream->pref_priority = h2_stream_get_priority(stream, headers);
- }
- h2_session_set_prio(session, stream, stream->pref_priority);
-
- note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE);
- if (note && !strcmp("on", note)) {
- int32_t connFlowIn, connFlowOut;
-
- connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2);
- connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
- headers = h2_headers_copy(stream->pool, headers);
- apr_table_setn(headers->headers, "conn-flow-in",
- apr_itoa(stream->pool, connFlowIn));
- apr_table_setn(headers->headers, "conn-flow-out",
- apr_itoa(stream->pool, connFlowOut));
- }
-
- if (headers->status == 103
- && !h2_config_geti(session->config, H2_CONF_EARLY_HINTS)) {
- /* suppress sending this to the client, it might have triggered
- * pushes and served its purpose nevertheless */
- rv = 0;
- goto leave;
- }
-
- status = h2_res_create_ngheader(&ngh, stream->pool, headers);
- if (status == APR_SUCCESS) {
- rv = nghttp2_submit_response(session->ngh2, stream->id,
- ngh->nv, ngh->nvlen, pprovider);
- stream->has_response = h2_headers_are_response(headers);
- session->have_written = 1;
-
- if (stream->initiated_on) {
- ++session->pushes_submitted;
- }
- else {
- ++session->responses_submitted;
+ while (nghttp2_session_want_write(session->ngh2)) {
+ ngrv = nghttp2_session_send(session->ngh2);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ "nghttp2_session_send: %d", (int)ngrv);
+ pending = 1;
+ if (ngrv != 0 && ngrv != NGHTTP2_ERR_WOULDBLOCK) {
+ if (nghttp2_is_fatal(ngrv)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR,
+ ngrv, nghttp2_strerror(ngrv));
+ rv = APR_EGENERAL;
+ goto cleanup;
}
}
- else {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
- H2_STRM_LOG(APLOGNO(10025), stream, "invalid response"));
- h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ if (h2_c1_io_needs_flush(&session->io) ||
+ ngrv == NGHTTP2_ERR_WOULDBLOCK) {
+ rv = h2_c1_io_assure_flushed(&session->io);
+ if (rv != APR_SUCCESS)
+ goto cleanup;
+ pending = 0;
}
}
-
-leave:
- if (nghttp2_is_fatal(rv)) {
- status = APR_EGENERAL;
- dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
- APLOGNO(02940) "submit_response: %s",
- nghttp2_strerror(rv));
+ if (pending) {
+ rv = h2_c1_io_pass(&session->io);
}
-
- ++session->unsent_submits;
-
- /* Unsent push promises are written immediately, as nghttp2
- * 1.5.0 realizes internal stream data structures only on
- * send and we might need them for other submits.
- * Also, to conserve memory, we send at least every 10 submits
- * so that nghttp2 does not buffer all outbound items too
- * long.
- */
- if (status == APR_SUCCESS
- && (session->unsent_promises || session->unsent_submits > 10)) {
- status = h2_session_send(session);
+cleanup:
+ if (rv != APR_SUCCESS) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
}
- return status;
+ return rv;
}
/**
- * A stream was resumed as new response/output data arrived.
+ * A streams input state has changed.
*/
-static apr_status_t on_stream_resume(void *ctx, h2_stream *stream)
+static void on_stream_input(void *ctx, h2_stream *stream)
{
h2_session *session = ctx;
- apr_status_t status = APR_EAGAIN;
- int rv;
- apr_off_t len = 0;
- int eos = 0;
- h2_headers *headers;
-
- ap_assert(stream);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- H2_STRM_MSG(stream, "on_resume"));
-
-send_headers:
- headers = NULL;
- status = h2_stream_out_prepare(stream, &len, &eos, &headers);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
- H2_STRM_MSG(stream, "prepared len=%ld, eos=%d"),
- (long)len, eos);
- if (headers) {
- status = on_stream_headers(session, stream, headers, len, eos);
- if (status != APR_SUCCESS || stream->rst_error) {
- return status;
- }
- goto send_headers;
- }
- else if (status != APR_EAGAIN) {
- /* we have DATA to send */
- if (!stream->has_response) {
- /* but no response */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- H2_STRM_LOG(APLOGNO(03466), stream,
- "no response, RST_STREAM"));
- h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
- return APR_SUCCESS;
- }
- rv = nghttp2_session_resume_data(session->ngh2, stream->id);
- session->have_written = 1;
- ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
- APLOG_ERR : APLOG_DEBUG, 0, session->c,
- H2_STRM_LOG(APLOGNO(02936), stream, "resumed"));
- }
- return status;
-}
-static void h2_session_in_flush(h2_session *session)
-{
- int id;
-
- while ((id = h2_iq_shift(session->in_process)) > 0) {
- h2_stream *stream = h2_session_stream_get(session, id);
- if (stream) {
- ap_assert(!stream->scheduled);
- if (h2_stream_prep_processing(stream) == APR_SUCCESS) {
- h2_mplx_process(session->mplx, stream, stream_pri_cmp, session);
- }
- else {
- h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
- }
- }
+ ap_assert(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "on_input change"));
+ update_child_status(session, SERVER_BUSY_READ, "read", stream);
+ if (stream->id == 0) {
+ /* input on primary connection available? read */
+ h2_c1_read(session);
}
-
- while ((id = h2_iq_shift(session->in_pending)) > 0) {
- h2_stream *stream = h2_session_stream_get(session, id);
- if (stream) {
- h2_stream_flush_input(stream);
- }
+ else {
+ h2_stream_on_input_change(stream);
}
}
-static apr_status_t session_read(h2_session *session, apr_size_t readlen, int block)
+/**
+ * A streams output state has changed.
+ */
+static void on_stream_output(void *ctx, h2_stream *stream)
{
- apr_status_t status, rstatus = APR_EAGAIN;
- conn_rec *c = session->c;
- apr_off_t read_start = session->io.bytes_read;
-
- while (1) {
- /* H2_IN filter handles all incoming data against the session.
- * We just pull at the filter chain to make it happen */
- status = ap_get_brigade(c->input_filters,
- session->bbtmp, AP_MODE_READBYTES,
- block? APR_BLOCK_READ : APR_NONBLOCK_READ,
- H2MAX(APR_BUCKET_BUFF_SIZE, readlen));
- /* get rid of any possible data we do not expect to get */
- apr_brigade_cleanup(session->bbtmp);
-
- switch (status) {
- case APR_SUCCESS:
- /* successful read, reset our idle timers */
- rstatus = APR_SUCCESS;
- if (block) {
- /* successful blocked read, try unblocked to
- * get more. */
- block = 0;
- }
- break;
- case APR_EAGAIN:
- return rstatus;
- case APR_TIMEUP:
- return status;
- default:
- if (session->io.bytes_read == read_start) {
- /* first attempt failed */
- if (APR_STATUS_IS_ETIMEDOUT(status)
- || APR_STATUS_IS_ECONNABORTED(status)
- || APR_STATUS_IS_ECONNRESET(status)
- || APR_STATUS_IS_EOF(status)
- || APR_STATUS_IS_EBADF(status)) {
- /* common status for a client that has left */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
- H2_SSSN_MSG(session, "input gone"));
- }
- else {
- /* uncommon status, log on INFO so that we see this */
- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c,
- H2_SSSN_LOG(APLOGNO(02950), session,
- "error reading, terminating"));
- }
- return status;
- }
- /* subsequent failure after success(es), return initial
- * status. */
- return rstatus;
- }
- if ((session->io.bytes_read - read_start) > readlen) {
- /* read enough in one go, give write a chance */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
- H2_SSSN_MSG(session, "read enough, returning"));
- break;
- }
+ h2_session *session = ctx;
+
+ ap_assert(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "on_output change"));
+ if (stream->id != 0) {
+ update_child_status(session, SERVER_BUSY_WRITE, "write", stream);
+ h2_stream_on_output_change(stream);
}
- return rstatus;
}
-static apr_status_t h2_session_read(h2_session *session, int block)
-{
- apr_status_t status = session_read(session, session->max_stream_mem
- * H2MAX(2, session->open_streams),
- block);
- h2_session_in_flush(session);
- return status;
-}
static const char *StateNames[] = {
"INIT", /* H2_SESSION_ST_INIT */
@@ -1630,40 +1379,14 @@ const char *h2_session_state_str(h2_session_state state)
return StateNames[state];
}
-static void update_child_status(h2_session *session, int status, const char *msg)
-{
- /* Assume that we also change code/msg when something really happened and
- * avoid updating the scoreboard in between */
- if (session->last_status_code != status
- || session->last_status_msg != msg) {
- apr_snprintf(session->status, sizeof(session->status),
- "%s, streams: %d/%d/%d/%d/%d (open/recv/resp/push/rst)",
- msg? msg : "-",
- (int)session->open_streams,
- (int)session->remote.emitted_count,
- (int)session->responses_submitted,
- (int)session->pushes_submitted,
- (int)session->pushes_reset + session->streams_reset);
- ap_update_child_status_descr(session->c->sbh, status, session->status);
- }
-}
-
static void transit(h2_session *session, const char *action, h2_session_state nstate)
{
- apr_time_t timeout;
- int ostate, loglvl;
- const char *s;
-
+ int ostate;
+
if (session->state != nstate) {
ostate = session->state;
- session->state = nstate;
-
- loglvl = APLOG_DEBUG;
- if ((ostate == H2_SESSION_ST_BUSY && nstate == H2_SESSION_ST_WAIT)
- || (ostate == H2_SESSION_ST_WAIT && nstate == H2_SESSION_ST_BUSY)){
- loglvl = APLOG_TRACE1;
- }
- ap_log_cerror(APLOG_MARK, loglvl, 0, session->c,
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03078), session,
"transit [%s] -- %s --> [%s]"),
h2_session_state_str(ostate), action,
@@ -1678,35 +1401,21 @@ static void transit(h2_session *session, const char *action, h2_session_state ns
* If we return to mpm right away, this connection has the
* same chance of being cleaned up by the mpm as connections
* that already served requests - not fair. */
- session->idle_sync_until = apr_time_now() + apr_time_from_sec(1);
- s = "timeout";
- timeout = H2MAX(session->s->timeout, session->s->keep_alive_timeout);
- update_child_status(session, SERVER_BUSY_READ, "idle");
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_SSSN_LOG("", session, "enter idle, timeout = %d sec"),
- (int)apr_time_sec(H2MAX(session->s->timeout, session->s->keep_alive_timeout)));
- }
- else if (session->open_streams) {
- s = "timeout";
- timeout = session->s->keep_alive_timeout;
- update_child_status(session, SERVER_BUSY_KEEPALIVE, "idle");
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_SSSN_LOG("", session, "enter idle"));
}
else {
/* normal keepalive setup */
- s = "keepalive";
- timeout = session->s->keep_alive_timeout;
- update_child_status(session, SERVER_BUSY_KEEPALIVE, "idle");
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_SSSN_LOG("", session, "enter keepalive"));
}
- session->idle_until = apr_time_now() + timeout;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_SSSN_LOG("", session, "enter idle, %s = %d sec"),
- s, (int)apr_time_sec(timeout));
+ session->state = nstate;
break;
case H2_SESSION_ST_DONE:
- update_child_status(session, SERVER_CLOSING, "done");
break;
default:
/* nop */
+ session->state = nstate;
break;
}
}
@@ -1724,12 +1433,45 @@ static void h2_session_ev_init(h2_session *session, int arg, const char *msg)
}
}
+static void h2_session_ev_input_pending(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_INIT:
+ case H2_SESSION_ST_IDLE:
+ case H2_SESSION_ST_WAIT:
+ transit(session, "input read", H2_SESSION_ST_BUSY);
+ break;
+ default:
+ break;
+ }
+}
+
+static void h2_session_ev_input_exhausted(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_BUSY:
+ if (!h2_session_want_send(session)) {
+ if (session->open_streams == 0) {
+ transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE);
+ }
+ else {
+ transit(session, "input exhausted", H2_SESSION_ST_WAIT);
+ }
+ }
+ break;
+ case H2_SESSION_ST_WAIT:
+ if (session->open_streams == 0) {
+ transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
{
cleanup_unprocessed_streams(session);
- if (!session->remote.shutdown) {
- update_child_status(session, SERVER_CLOSING, "local goaway");
- }
transit(session, "local goaway", H2_SESSION_ST_DONE);
}
@@ -1740,7 +1482,6 @@ static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char
session->remote.accepting = 0;
session->remote.shutdown = 1;
cleanup_unprocessed_streams(session);
- update_child_status(session, SERVER_CLOSING, "remote goaway");
transit(session, "remote goaway", H2_SESSION_ST_DONE);
}
}
@@ -1755,7 +1496,7 @@ static void h2_session_ev_conn_error(h2_session *session, int arg, const char *m
break;
default:
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03401), session,
"conn error -> shutdown"));
h2_session_shutdown(session, arg, msg, 0);
@@ -1766,7 +1507,7 @@ static void h2_session_ev_conn_error(h2_session *session, int arg, const char *m
static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg)
{
if (!session->local.shutdown) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03402), session,
"proto error -> shutdown"));
h2_session_shutdown(session, arg, msg, 0);
@@ -1781,115 +1522,91 @@ static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char
}
}
-static void h2_session_ev_no_io(h2_session *session, int arg, const char *msg)
+static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg)
{
switch (session->state) {
- case H2_SESSION_ST_BUSY:
- /* Nothing to READ, nothing to WRITE on the master connection.
- * Possible causes:
- * - we wait for the client to send us sth
- * - we wait for started tasks to produce output
- * - we have finished all streams and the client has sent GO_AWAY
- */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- H2_SSSN_MSG(session, "NO_IO event, %d streams open"),
- session->open_streams);
- h2_conn_io_flush(&session->io);
- if (session->open_streams > 0) {
- if (h2_mplx_awaits_data(session->mplx)) {
- /* waiting for at least one stream to produce data */
- transit(session, "no io", H2_SESSION_ST_WAIT);
- }
- else {
- /* we have streams open, and all are submitted and none
- * is suspended. The only thing keeping us from WRITEing
- * more must be the flow control.
- * This means we only wait for WINDOW_UPDATE from the
- * client and can block on READ. */
- transit(session, "no io (flow wait)", H2_SESSION_ST_IDLE);
- /* Make sure we have flushed all previously written output
- * so that the client will react. */
- if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
- return;
- }
- }
- }
- else if (session->local.accepting) {
- /* When we have no streams, but accept new, switch to idle */
- transit(session, "no io (keepalive)", H2_SESSION_ST_IDLE);
- }
- else {
- /* We are no longer accepting new streams and there are
- * none left. Time to leave. */
- h2_session_shutdown(session, arg, msg, 0);
- transit(session, "no io", H2_SESSION_ST_DONE);
- }
- break;
- default:
+ case H2_SESSION_ST_DONE:
/* nop */
break;
- }
-}
-
-static void h2_session_ev_frame_rcvd(h2_session *session, int arg, const char *msg)
-{
- switch (session->state) {
- case H2_SESSION_ST_IDLE:
- case H2_SESSION_ST_WAIT:
- transit(session, "frame received", H2_SESSION_ST_BUSY);
- break;
default:
- /* nop */
+ transit(session, "nghttp2 done", H2_SESSION_ST_DONE);
break;
}
}
-static void h2_session_ev_stream_change(h2_session *session, int arg, const char *msg)
+static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char *msg)
{
switch (session->state) {
- case H2_SESSION_ST_IDLE:
- case H2_SESSION_ST_WAIT:
- transit(session, "stream change", H2_SESSION_ST_BUSY);
+ case H2_SESSION_ST_DONE:
+ /* nop */
break;
default:
- /* nop */
+ h2_session_shutdown_notice(session);
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
+ h2_workers_graceful_shutdown(session->workers);
+#endif
break;
}
}
-static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg)
+static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
{
- switch (session->state) {
- case H2_SESSION_ST_DONE:
- /* nop */
- break;
- default:
- transit(session, "nghttp2 done", H2_SESSION_ST_DONE);
- break;
- }
+ h2_session_shutdown(session, arg, msg, 1);
}
-static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char *msg)
+static void h2_session_ev_no_more_streams(h2_session *session)
{
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(10304), session, "no more streams"));
switch (session->state) {
- case H2_SESSION_ST_DONE:
- /* nop */
+ case H2_SESSION_ST_BUSY:
+ case H2_SESSION_ST_WAIT:
+ if (!h2_session_want_send(session)) {
+ if (session->local.accepting) {
+ /* We wait for new frames on c1 only. */
+ transit(session, "all streams done", H2_SESSION_ST_IDLE);
+ }
+ else {
+ /* We are no longer accepting new streams.
+ * Time to leave. */
+ h2_session_shutdown(session, 0, "done", 0);
+ transit(session, "c1 done after goaway", H2_SESSION_ST_DONE);
+ }
+ }
+ else {
+ transit(session, "no more streams", H2_SESSION_ST_WAIT);
+ }
break;
default:
- h2_session_shutdown_notice(session);
+ /* nop */
break;
}
}
-static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
+static void ev_stream_created(h2_session *session, h2_stream *stream)
{
- h2_session_shutdown(session, arg, msg, 1);
+ /* nop */
}
static void ev_stream_open(h2_session *session, h2_stream *stream)
{
- h2_iq_append(session->in_process, stream->id);
+ if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
+ ++session->remote.emitted_count;
+ if (stream->id > session->remote.emitted_max) {
+ session->remote.emitted_max = stream->id;
+ session->local.accepted_max = stream->id;
+ }
+ }
+ else {
+ if (stream->id > session->local.emitted_max) {
+ ++session->local.emitted_count;
+ session->remote.emitted_max = stream->id;
+ }
+ }
+ /* Stream state OPEN means we have received all request headers
+ * and can start processing the stream. */
+ h2_iq_append(session->ready_to_process, stream->id);
+ update_child_status(session, SERVER_BUSY_READ, "schedule", stream);
}
static void ev_stream_closed(h2_session *session, h2_stream *stream)
@@ -1900,75 +1617,72 @@ static void ev_stream_closed(h2_session *session, h2_stream *stream)
&& (stream->id > session->local.completed_max)) {
session->local.completed_max = stream->id;
}
- switch (session->state) {
- case H2_SESSION_ST_IDLE:
- break;
- default:
- break;
- }
-
/* The stream might have data in the buffers of the main connection.
* We can only free the allocated resources once all had been written.
* Send a special buckets on the connection that gets destroyed when
* all preceding data has been handled. On its destruction, it is safe
* to purge all resources of the stream. */
- b = h2_bucket_eos_create(session->c->bucket_alloc, stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "adding h2_eos to c1 out"));
+ b = h2_bucket_eos_create(session->c1->bucket_alloc, stream);
APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
- h2_conn_io_pass(&session->io, session->bbtmp);
+ h2_c1_io_append(&session->io, session->bbtmp);
apr_brigade_cleanup(session->bbtmp);
}
static void on_stream_state_enter(void *ctx, h2_stream *stream)
{
h2_session *session = ctx;
- /* stream entered a new state */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
H2_STRM_MSG(stream, "entered state"));
switch (stream->state) {
case H2_SS_IDLE: /* stream was created */
- ++session->open_streams;
- if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
- ++session->remote.emitted_count;
- if (stream->id > session->remote.emitted_max) {
- session->remote.emitted_max = stream->id;
- session->local.accepted_max = stream->id;
- }
- }
- else {
- if (stream->id > session->local.emitted_max) {
- ++session->local.emitted_count;
- session->remote.emitted_max = stream->id;
- }
- }
+ ev_stream_created(session, stream);
break;
case H2_SS_OPEN: /* stream has request headers */
- case H2_SS_RSVD_L: /* stream has request headers */
+ case H2_SS_RSVD_L:
ev_stream_open(session, stream);
break;
- case H2_SS_CLOSED_L: /* stream output was closed */
+ case H2_SS_CLOSED_L: /* stream output was closed, but remote end is not */
+ /* If the stream is still being processed, it could still be reading
+ * its input (theoretically, http request hangling does not normally).
+ * But when processing is done, we need to cancel the stream as no
+ * one is consuming the input any longer.
+ * This happens, for example, on a large POST when the response
+ * is ready early due to the POST being denied. */
+ if (!h2_mplx_c1_stream_is_running(session->mplx, stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10305), stream, "remote close missing"));
+ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+ stream->id, H2_ERR_NO_ERROR);
+ }
break;
case H2_SS_CLOSED_R: /* stream input was closed */
break;
case H2_SS_CLOSED: /* stream in+out were closed */
- --session->open_streams;
ev_stream_closed(session, stream);
break;
case H2_SS_CLEANUP:
- h2_mplx_stream_cleanup(session->mplx, stream);
+ nghttp2_session_set_stream_user_data(session->ngh2, stream->id, NULL);
+ h2_mplx_c1_stream_cleanup(session->mplx, stream, &session->open_streams);
+ ++session->streams_done;
+ update_child_status(session, SERVER_BUSY_WRITE, "done", stream);
break;
default:
break;
}
- dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream state change");
}
-static void on_stream_event(void *ctx, h2_stream *stream,
- h2_stream_event_t ev)
+static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev)
{
h2_session *session = ctx;
switch (ev) {
case H2_SEV_IN_DATA_PENDING:
- h2_iq_append(session->in_pending, stream->id);
+ session->input_flushed = 1;
+ break;
+ case H2_SEV_OUT_C1_BLOCK:
+ h2_iq_append(session->out_c1_blocked, stream->id);
break;
default:
/* NOP */
@@ -1993,13 +1707,19 @@ static void on_stream_state_event(void *ctx, h2_stream *stream,
}
}
-static void dispatch_event(h2_session *session, h2_session_event_t ev,
- int arg, const char *msg)
+void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
+ apr_status_t arg, const char *msg)
{
switch (ev) {
case H2_SESSION_EV_INIT:
h2_session_ev_init(session, arg, msg);
break;
+ case H2_SESSION_EV_INPUT_PENDING:
+ h2_session_ev_input_pending(session, arg, msg);
+ break;
+ case H2_SESSION_EV_INPUT_EXHAUSTED:
+ h2_session_ev_input_exhausted(session, arg, msg);
+ break;
case H2_SESSION_EV_LOCAL_GOAWAY:
h2_session_ev_local_goaway(session, arg, msg);
break;
@@ -2015,12 +1735,6 @@ static void dispatch_event(h2_session *session, h2_session_event_t ev,
case H2_SESSION_EV_CONN_TIMEOUT:
h2_session_ev_conn_timeout(session, arg, msg);
break;
- case H2_SESSION_EV_NO_IO:
- h2_session_ev_no_io(session, arg, msg);
- break;
- case H2_SESSION_EV_FRAME_RCVD:
- h2_session_ev_frame_rcvd(session, arg, msg);
- break;
case H2_SESSION_EV_NGH2_DONE:
h2_session_ev_ngh2_done(session, arg, msg);
break;
@@ -2030,311 +1744,265 @@ static void dispatch_event(h2_session *session, h2_session_event_t ev,
case H2_SESSION_EV_PRE_CLOSE:
h2_session_ev_pre_close(session, arg, msg);
break;
- case H2_SESSION_EV_STREAM_CHANGE:
- h2_session_ev_stream_change(session, arg, msg);
+ case H2_SESSION_EV_NO_MORE_STREAMS:
+ h2_session_ev_no_more_streams(session);
break;
default:
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
H2_SSSN_MSG(session, "unknown event %d"), ev);
break;
}
}
-/* trigger window updates, stream resumes and submits */
-static apr_status_t dispatch_master(h2_session *session) {
- apr_status_t status;
-
- status = h2_mplx_dispatch_master_events(session->mplx,
- on_stream_resume, session);
- if (status == APR_EAGAIN) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
- H2_SSSN_MSG(session, "no master event available"));
- }
- else if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
- H2_SSSN_MSG(session, "dispatch error"));
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
- H2_ERR_INTERNAL_ERROR, "dispatch error");
+static void unblock_c1_out(h2_session *session) {
+ int sid;
+
+ while ((sid = h2_iq_shift(session->out_c1_blocked)) > 0) {
+ nghttp2_session_resume_data(session->ngh2, sid);
}
- return status;
}
-static const int MAX_WAIT_MICROS = 200 * 1000;
-
apr_status_t h2_session_process(h2_session *session, int async)
{
apr_status_t status = APR_SUCCESS;
- conn_rec *c = session->c;
+ conn_rec *c = session->c1;
int rv, mpm_state, trace = APLOGctrace3(c);
- apr_time_t now;
-
+
if (trace) {
ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
H2_SSSN_MSG(session, "process start, async=%d"), async);
}
-
+
+ if (H2_SESSION_ST_INIT == session->state) {
+ if (!h2_protocol_is_acceptable_c1(c, session->r, 1)) {
+ const char *msg = nghttp2_strerror(NGHTTP2_INADEQUATE_SECURITY);
+ update_child_status(session, SERVER_BUSY_READ, msg, NULL);
+ h2_session_shutdown(session, APR_EINVAL, msg, 1);
+ }
+ else {
+ update_child_status(session, SERVER_BUSY_READ, "init", NULL);
+ status = h2_session_start(session, &rv);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(03079), session,
+ "started on %s:%d"),
+ session->s->server_hostname,
+ c->local_addr->port);
+ if (status != APR_SUCCESS) {
+ h2_session_dispatch_event(session,
+ H2_SESSION_EV_CONN_ERROR, status, NULL);
+ }
+ else {
+ h2_session_dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
+ }
+ }
+ }
+
while (session->state != H2_SESSION_ST_DONE) {
- now = apr_time_now();
- session->have_read = session->have_written = 0;
- if (session->local.accepting
+ /* PR65731: we may get a new connection to process while the
+ * MPM already is stopping. For example due to having reached
+ * MaxRequestsPerChild limit.
+ * Since this is supposed to handle things gracefully, we need to:
+ * a) fully initialize the session before GOAWAYing
+ * b) give the client the chance to submit at least one request
+ */
+ if (session->state != H2_SESSION_ST_INIT /* no longer intializing */
+ && session->local.accepted_max > 0 /* have gotten at least one stream */
+ && session->local.accepting /* have not already locally shut down */
&& !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
if (mpm_state == AP_MPMQ_STOPPING) {
- dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
+ h2_session_dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
}
}
-
+
session->status[0] = '\0';
+ if (h2_session_want_send(session)) {
+ h2_session_send(session);
+ }
+ else if (!nghttp2_session_want_read(session->ngh2)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL);
+ }
+
+ if (!h2_iq_empty(session->ready_to_process)) {
+ h2_mplx_c1_process(session->mplx, session->ready_to_process,
+ get_stream, stream_pri_cmp, session,
+ &session->open_streams);
+ transit(session, "scheduled stream", H2_SESSION_ST_BUSY);
+ }
+
+ if (session->input_flushed) {
+ transit(session, "forwarded input", H2_SESSION_ST_BUSY);
+ session->input_flushed = 0;
+ }
+
+ if (!h2_iq_empty(session->out_c1_blocked)) {
+ unblock_c1_out(session);
+ transit(session, "unblocked output", H2_SESSION_ST_BUSY);
+ }
+
+ if (session->reprioritize) {
+ h2_mplx_c1_reprioritize(session->mplx, stream_pri_cmp, session);
+ session->reprioritize = 0;
+ }
+
+ if (h2_session_want_send(session)) {
+ h2_session_send(session);
+ }
+
+ status = h2_c1_io_assure_flushed(&session->io);
+ if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ }
+
switch (session->state) {
- case H2_SESSION_ST_INIT:
- ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
- if (!h2_is_acceptable_connection(c, 1)) {
- update_child_status(session, SERVER_BUSY_READ,
- "inadequate security");
- h2_session_shutdown(session,
- NGHTTP2_INADEQUATE_SECURITY, NULL, 1);
- }
- else {
- update_child_status(session, SERVER_BUSY_READ, "init");
- status = h2_session_start(session, &rv);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
- H2_SSSN_LOG(APLOGNO(03079), session,
- "started on %s:%d"),
- session->s->server_hostname,
- c->local_addr->port);
- if (status != APR_SUCCESS) {
- dispatch_event(session,
- H2_SESSION_EV_CONN_ERROR, 0, NULL);
- }
- dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
- }
- break;
-
- case H2_SESSION_ST_IDLE:
- if (session->idle_until && (apr_time_now() + session->idle_delay) > session->idle_until) {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
- H2_SSSN_MSG(session, "idle, timeout reached, closing"));
- if (session->idle_delay) {
- apr_table_setn(session->c->notes, "short-lingering-close", "1");
- }
- dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
- goto out;
- }
-
- if (session->idle_delay) {
- /* we are less interested in spending time on this connection */
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, c,
- H2_SSSN_MSG(session, "session is idle (%ld ms), idle wait %ld sec left"),
- (long)apr_time_as_msec(session->idle_delay),
- (long)apr_time_sec(session->idle_until - now));
- apr_sleep(session->idle_delay);
- session->idle_delay = 0;
- }
+ case H2_SESSION_ST_INIT:
+ ap_assert(0);
+ h2_c1_read(session);
+ break;
- h2_conn_io_flush(&session->io);
- if (async && !session->r && (now > session->idle_sync_until)) {
- if (trace) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
- H2_SSSN_MSG(session,
- "nonblock read, %d streams open"),
- session->open_streams);
- }
- status = h2_session_read(session, 0);
-
- if (status == APR_SUCCESS) {
- session->have_read = 1;
- }
- else if (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) {
- status = APR_EAGAIN;
- goto out;
- }
- else {
+ case H2_SESSION_ST_IDLE:
+ ap_assert(session->open_streams == 0);
+ ap_assert(nghttp2_session_want_read(session->ngh2));
+ if (!h2_session_want_send(session)) {
+ /* Give any new incoming request a short grace period to
+ * arrive while we are still hot and return to the mpm
+ * connection handling when nothing really happened. */
+ h2_c1_read(session);
+ if (H2_SESSION_ST_IDLE == session->state) {
+ if (async) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
- H2_SSSN_LOG(APLOGNO(03403), session,
- "no data, error"));
- dispatch_event(session,
- H2_SESSION_EV_CONN_ERROR, 0, "timeout");
- }
- }
- else {
- /* make certain, we send everything before we idle */
- if (trace) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
- H2_SSSN_MSG(session,
- "sync, stutter 1-sec, %d streams open"),
- session->open_streams);
- }
- /* We wait in smaller increments, using a 1 second timeout.
- * That gives us the chance to check for MPMQ_STOPPING often.
- */
- status = h2_mplx_idle(session->mplx);
- if (status == APR_EAGAIN) {
- break;
- }
- else if (status != APR_SUCCESS) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
- H2_ERR_ENHANCE_YOUR_CALM, "less is more");
- }
- h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1));
- status = h2_session_read(session, 1);
- if (status == APR_SUCCESS) {
- session->have_read = 1;
- }
- else if (status == APR_EAGAIN) {
- /* nothing to read */
- }
- else if (APR_STATUS_IS_TIMEUP(status)) {
- /* continue reading handling */
- }
- else if (APR_STATUS_IS_ECONNABORTED(status)
- || APR_STATUS_IS_ECONNRESET(status)
- || APR_STATUS_IS_EOF(status)
- || APR_STATUS_IS_EBADF(status)) {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
- H2_SSSN_MSG(session, "input gone"));
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ H2_SSSN_LOG(APLOGNO(10306), session,
+ "returning to mpm c1 monitoring"));
+ goto leaving;
}
else {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
- H2_SSSN_MSG(session,
- "(1 sec timeout) read failed"));
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error");
- }
- }
- if (nghttp2_session_want_write(session->ngh2)) {
- ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL);
- status = h2_session_send(session);
- if (status == APR_SUCCESS) {
- status = h2_conn_io_flush(&session->io);
- }
- if (status != APR_SUCCESS) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
- H2_ERR_INTERNAL_ERROR, "writing");
- break;
+ /* Not an async mpm, we must continue waiting
+ * for client data to arrive until the configured
+ * server Timeout/KeepAliveTimeout happens */
+ apr_time_t timeout = (session->open_streams == 0)?
+ session->s->keep_alive_timeout :
+ session->s->timeout;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
+ H2_SSSN_MSG(session, "polling timeout=%d"),
+ (int)apr_time_sec(timeout));
+ status = h2_mplx_c1_poll(session->mplx, timeout,
+ on_stream_input,
+ on_stream_output, session);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ if (session->open_streams == 0) {
+ h2_session_dispatch_event(session,
+ H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
+ break;
+ }
+ }
+ else if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session,
+ H2_SESSION_EV_CONN_ERROR, status, NULL);
+ break;
+ }
}
}
+ }
+ else {
+ transit(session, "c1 io pending", H2_SESSION_ST_BUSY);
+ }
+ break;
+
+ case H2_SESSION_ST_BUSY:
+ /* IO happening in and out. Make sure we react to c2 events
+ * inbetween send and receive. */
+ status = h2_mplx_c1_poll(session->mplx, 0,
+ on_stream_input, on_stream_output, session);
+ if (APR_SUCCESS != status && !APR_STATUS_IS_TIMEUP(status)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
break;
-
- case H2_SESSION_ST_BUSY:
- if (nghttp2_session_want_read(session->ngh2)) {
- ap_update_child_status(session->c->sbh, SERVER_BUSY_READ, NULL);
- h2_filter_cin_timeout_set(session->cin, session->s->timeout);
- status = h2_session_read(session, 0);
- if (status == APR_SUCCESS) {
- session->have_read = 1;
- }
- else if (status == APR_EAGAIN) {
- /* nothing to read */
- }
- else if (APR_STATUS_IS_TIMEUP(status)) {
- dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, NULL);
- break;
- }
- else {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
- }
- }
+ }
+ h2_c1_read(session);
+ break;
- status = dispatch_master(session);
- if (status != APR_SUCCESS && status != APR_EAGAIN) {
+ case H2_SESSION_ST_WAIT:
+ status = h2_c1_io_assure_flushed(&session->io);
+ if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ break;
+ }
+ if (session->open_streams == 0) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS,
+ 0, "streams really done");
+ if (session->state != H2_SESSION_ST_WAIT) {
break;
}
-
- if (nghttp2_session_want_write(session->ngh2)) {
- ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL);
- status = h2_session_send(session);
- if (status == APR_SUCCESS) {
- status = h2_conn_io_flush(&session->io);
- }
- if (status != APR_SUCCESS) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
- H2_ERR_INTERNAL_ERROR, "writing");
- break;
- }
- }
-
- if (session->have_read || session->have_written) {
- if (session->wait_us) {
- session->wait_us = 0;
- }
- }
- else if (!nghttp2_session_want_write(session->ngh2)) {
- dispatch_event(session, H2_SESSION_EV_NO_IO, 0, NULL);
+ }
+ /* No IO happening and input is exhausted. Make sure we have
+ * flushed any possibly pending output and then wait with
+ * the c1 connection timeout for sth to happen in our c1/c2 sockets/pipes */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
+ H2_SSSN_MSG(session, "polling timeout=%d, open_streams=%d"),
+ (int)apr_time_sec(session->s->timeout), session->open_streams);
+ status = h2_mplx_c1_poll(session->mplx, session->s->timeout,
+ on_stream_input, on_stream_output, session);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ /* If we timeout without streams open, no new request from client
+ * arrived.
+ * If we timeout without nghttp2 wanting to write something, but
+ * all open streams have something to send, it means we are
+ * blocked on HTTP/2 flow control and the client did not send
+ * WINDOW_UPDATEs to us. */
+ if (session->open_streams == 0 ||
+ (!h2_session_want_send(session) &&
+ h2_mplx_c1_all_streams_want_send_data(session->mplx))) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
+ break;
}
+ }
+ else if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
break;
-
- case H2_SESSION_ST_WAIT:
- if (session->wait_us <= 0) {
- session->wait_us = 10;
- if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
- break;
- }
- }
- else {
- /* repeating, increase timer for graceful backoff */
- session->wait_us = H2MIN(session->wait_us*2, MAX_WAIT_MICROS);
- }
+ }
+ break;
- if (trace) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
- "h2_session: wait for data, %ld micros",
- (long)session->wait_us);
- }
- status = h2_mplx_out_trywait(session->mplx, session->wait_us,
- session->iowait);
- if (status == APR_SUCCESS) {
- session->wait_us = 0;
- dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, NULL);
- }
- else if (APR_STATUS_IS_TIMEUP(status)) {
- /* go back to checking all inputs again */
- transit(session, "wait cycle", session->local.shutdown?
- H2_SESSION_ST_DONE : H2_SESSION_ST_BUSY);
- }
- else if (APR_STATUS_IS_ECONNRESET(status)
- || APR_STATUS_IS_ECONNABORTED(status)) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, c,
- H2_SSSN_LOG(APLOGNO(03404), session,
- "waiting on conditional"));
- h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR,
- "cond wait error", 0);
- }
- break;
-
- default:
- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
- H2_SSSN_LOG(APLOGNO(03080), session,
- "unknown state"));
- dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, 0, NULL);
- break;
- }
+ case H2_SESSION_ST_DONE:
+ h2_c1_read(session);
+ break;
- if (!nghttp2_session_want_read(session->ngh2)
- && !nghttp2_session_want_write(session->ngh2)) {
- dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL);
- }
- if (session->reprioritize) {
- h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
- session->reprioritize = 0;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ H2_SSSN_LOG(APLOGNO(03080), session,
+ "unknown state"));
+ h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, APR_EGENERAL, NULL);
+ break;
}
}
-
-out:
+
+leaving:
if (trace) {
ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
H2_SSSN_MSG(session, "process returns"));
}
-
- if ((session->state != H2_SESSION_ST_DONE)
- && (APR_STATUS_IS_EOF(status)
+ h2_mplx_c1_going_keepalive(session->mplx);
+
+ if (session->state == H2_SESSION_ST_DONE) {
+ if (session->local.error) {
+ char buffer[128];
+ const char *msg;
+ if (session->local.error_msg) {
+ msg = session->local.error_msg;
+ }
+ else {
+ msg = apr_strerror(session->local.error, buffer, sizeof(buffer));
+ }
+ update_child_status(session, SERVER_CLOSING, msg, NULL);
+ }
+ else {
+ update_child_status(session, SERVER_CLOSING, "done", NULL);
+ }
+ }
+ else if (APR_STATUS_IS_EOF(status)
|| APR_STATUS_IS_ECONNRESET(status)
- || APR_STATUS_IS_ECONNABORTED(status))) {
- dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ || APR_STATUS_IS_ECONNABORTED(status)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ update_child_status(session, SERVER_CLOSING, "error", NULL);
}
return (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
@@ -2344,14 +2012,14 @@ apr_status_t h2_session_pre_close(h2_session *session, int async)
{
apr_status_t status;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
H2_SSSN_MSG(session, "pre_close"));
- dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0,
+ h2_session_dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0,
(session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL);
status = session_cleanup(session, "pre_close");
if (status == APR_SUCCESS) {
/* no one should hold a reference to this session any longer and
- * the h2_ctx was removed from the connection.
+ * the h2_conn_ctx_twas removed from the connection.
* Take the pool (and thus all subpools etc. down now, instead of
* during cleanup of main connection pool. */
apr_pool_destroy(session->pool);
diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h
index df2a862..3328509 100644
--- a/modules/http2/h2_session.h
+++ b/modules/http2/h2_session.h
@@ -17,25 +17,15 @@
#ifndef __mod_h2__h2_session__
#define __mod_h2__h2_session__
-#include "h2_conn_io.h"
+#include "h2_c1_io.h"
/**
* A HTTP/2 connection, a session with a specific client.
*
* h2_session sits on top of a httpd conn_rec* instance and takes complete
* control of the connection data. It receives protocol frames from the
- * client. For new HTTP/2 streams it creates h2_task(s) that are sent
- * via callback to a dispatcher (see h2_conn.c).
- * h2_session keeps h2_io's for each ongoing stream which buffer the
- * payload for that stream.
- *
- * New incoming HEADER frames are converted into a h2_stream+h2_task instance
- * that both represent a HTTP/2 stream, but may have separate lifetimes. This
- * allows h2_task to be scheduled in other threads without semaphores
- * all over the place. It allows task memory to be freed independent of
- * session lifetime and sessions may close down while tasks are still running.
- *
- *
+ * client. For new HTTP/2 streams it creates secondary connections
+ * to execute the requests in h2 workers.
*/
#include "h2.h"
@@ -44,7 +34,6 @@ struct apr_thread_mutext_t;
struct apr_thread_cond_t;
struct h2_ctx;
struct h2_config;
-struct h2_filter_cin;
struct h2_ihash_t;
struct h2_mplx;
struct h2_priority;
@@ -53,39 +42,39 @@ struct h2_push_diary;
struct h2_session;
struct h2_stream;
struct h2_stream_monitor;
-struct h2_task;
struct h2_workers;
struct nghttp2_session;
typedef enum {
H2_SESSION_EV_INIT, /* session was initialized */
+ H2_SESSION_EV_INPUT_PENDING, /* c1 input may have data pending */
+ H2_SESSION_EV_INPUT_EXHAUSTED, /* c1 input exhausted */
H2_SESSION_EV_LOCAL_GOAWAY, /* we send a GOAWAY */
H2_SESSION_EV_REMOTE_GOAWAY, /* remote send us a GOAWAY */
H2_SESSION_EV_CONN_ERROR, /* connection error */
H2_SESSION_EV_PROTO_ERROR, /* protocol error */
H2_SESSION_EV_CONN_TIMEOUT, /* connection timeout */
- H2_SESSION_EV_NO_IO, /* nothing has been read or written */
- H2_SESSION_EV_FRAME_RCVD, /* a frame has been received */
H2_SESSION_EV_NGH2_DONE, /* nghttp2 wants neither read nor write anything */
H2_SESSION_EV_MPM_STOPPING, /* the process is stopping */
H2_SESSION_EV_PRE_CLOSE, /* connection will close after this */
- H2_SESSION_EV_STREAM_CHANGE, /* a stream (state/input/output) changed */
+ H2_SESSION_EV_NO_MORE_STREAMS, /* no more streams to process */
} h2_session_event_t;
typedef struct h2_session {
- long id; /* identifier of this session, unique
- * inside a httpd process */
- conn_rec *c; /* the connection this session serves */
+ int child_num; /* child number this session runs in */
+ apr_uint32_t id; /* identifier of this session, unique per child */
+ conn_rec *c1; /* the main connection this session serves */
request_rec *r; /* the request that started this in case
* of 'h2c', NULL otherwise */
server_rec *s; /* server/vhost we're starting on */
- const struct h2_config *config; /* Relevant config for this session */
apr_pool_t *pool; /* pool to use in session */
struct h2_mplx *mplx; /* multiplexer for stream data */
- struct h2_workers *workers; /* for executing stream tasks */
- struct h2_filter_cin *cin; /* connection input filter context */
- h2_conn_io io; /* io on httpd conn filters */
+ struct h2_workers *workers; /* for executing streams */
+ struct h2_c1_io_in_ctx_t *cin; /* connection input filter context */
+ h2_c1_io io; /* io on httpd conn filters */
+ unsigned int padding_max; /* max number of padding bytes */
+ int padding_always; /* padding has precedence over I/O optimizations */
struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */
h2_session_state state; /* state session is in */
@@ -95,43 +84,39 @@ typedef struct h2_session {
unsigned int reprioritize : 1; /* scheduled streams priority changed */
unsigned int flush : 1; /* flushing output necessary */
- unsigned int have_read : 1; /* session has read client data */
- unsigned int have_written : 1; /* session did write data to client */
apr_interval_time_t wait_us; /* timeout during BUSY_WAIT state, micro secs */
struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */
struct h2_stream_monitor *monitor;/* monitor callbacks for streams */
- int open_streams; /* number of client streams open */
- int unsent_submits; /* number of submitted, but not yet written responses. */
- int unsent_promises; /* number of submitted, but not yet written push promises */
-
- int responses_submitted; /* number of http/2 responses submitted */
- int streams_reset; /* number of http/2 streams reset by client */
- int pushes_promised; /* number of http/2 push promises submitted */
- int pushes_submitted; /* number of http/2 pushed responses submitted */
- int pushes_reset; /* number of http/2 pushed reset by client */
+ unsigned int open_streams; /* number of streams processing */
+
+ unsigned int streams_done; /* number of http/2 streams handled */
+ unsigned int responses_submitted; /* number of http/2 responses submitted */
+ unsigned int streams_reset; /* number of http/2 streams reset by client */
+ unsigned int pushes_promised; /* number of http/2 push promises submitted */
+ unsigned int pushes_submitted; /* number of http/2 pushed responses submitted */
+ unsigned int pushes_reset; /* number of http/2 pushed reset by client */
apr_size_t frames_received; /* number of http/2 frames received */
apr_size_t frames_sent; /* number of http/2 frames sent */
apr_size_t max_stream_count; /* max number of open streams */
apr_size_t max_stream_mem; /* max buffer memory for a single stream */
-
- apr_time_t idle_until; /* Time we shut down due to sheer boredom */
- apr_time_t idle_sync_until; /* Time we sync wait until keepalive handling kicks in */
+ apr_size_t max_data_frame_len; /* max amount of bytes for a single DATA frame */
+
apr_size_t idle_frames; /* number of rcvd frames that kept session in idle state */
apr_interval_time_t idle_delay; /* Time we delay processing rcvd frames in idle state */
apr_bucket_brigade *bbtmp; /* brigade for keeping temporary data */
- struct apr_thread_cond_t *iowait; /* our cond when trywaiting for data */
-
+
char status[64]; /* status message for scoreboard */
int last_status_code; /* the one already reported */
const char *last_status_msg; /* the one already reported */
-
- struct h2_iqueue *in_pending; /* all streams with input pending */
- struct h2_iqueue *in_process; /* all streams ready for processing on slave */
+
+ int input_flushed; /* stream input was flushed */
+ struct h2_iqueue *out_c1_blocked; /* all streams with output blocked on c1 buffer full */
+ struct h2_iqueue *ready_to_process; /* all streams ready for processing */
} h2_session;
@@ -142,29 +127,17 @@ const char *h2_session_state_str(h2_session_state state);
* The session will apply the configured parameter.
* @param psession pointer receiving the created session on success or NULL
* @param c the connection to work on
+ * @param r optional request when protocol was upgraded
* @param cfg the module config to apply
* @param workers the worker pool to use
* @return the created session
*/
apr_status_t h2_session_create(h2_session **psession,
- conn_rec *c, struct h2_ctx *ctx,
+ conn_rec *c, request_rec *r, server_rec *,
struct h2_workers *workers);
-/**
- * Create a new h2_session for the given request.
- * The session will apply the configured parameter.
- * @param psession pointer receiving the created session on success or NULL
- * @param r the request that was upgraded
- * @param cfg the module config to apply
- * @param workers the worker pool to use
- * @return the created session
- */
-apr_status_t h2_session_rcreate(h2_session **psession,
- request_rec *r, struct h2_ctx *ctx,
- struct h2_workers *workers);
-
void h2_session_event(h2_session *session, h2_session_event_t ev,
- int err, const char *msg);
+ int err, const char *msg);
/**
* Process the given HTTP/2 session until it is ended or a fatal
@@ -187,22 +160,12 @@ apr_status_t h2_session_pre_close(h2_session *session, int async);
*/
void h2_session_abort(h2_session *session, apr_status_t reason);
-/**
- * Close and deallocate the given session.
- */
-void h2_session_close(h2_session *session);
-
/**
* Returns if client settings have push enabled.
* @param != 0 iff push is enabled in client settings
*/
int h2_session_push_enabled(h2_session *session);
-/**
- * Look up the stream in this session with the given id.
- */
-struct h2_stream *h2_session_stream_get(h2_session *session, int stream_id);
-
/**
* Submit a push promise on the stream and schedule the new steam for
* processing..
@@ -219,10 +182,25 @@ apr_status_t h2_session_set_prio(h2_session *session,
struct h2_stream *stream,
const struct h2_priority *prio);
+/**
+ * Dispatch a event happending during session processing.
+ * @param session the sessiont
+ * @param ev the event that happened
+ * @param arg integer argument (event type dependant)
+ * @param msg destriptive message
+ */
+void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
+ int arg, const char *msg);
+
+
#define H2_SSSN_MSG(s, msg) \
- "h2_session(%ld,%s,%d): "msg, s->id, h2_session_state_str(s->state), \
+ "h2_session(%d-%lu,%s,%d): "msg, s->child_num, (unsigned long)s->id, \
+ h2_session_state_str(s->state), \
s->open_streams
#define H2_SSSN_LOG(aplogno, s, msg) aplogno H2_SSSN_MSG(s, msg)
+#define H2_SSSN_STRM_MSG(s, stream_id, msg) \
+ "h2_stream(%d-%lu-%d): "msg, s->child_num, (unsigned long)s->id, stream_id
+
#endif /* defined(__mod_h2__h2_session__) */
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
index 24ebc56..ee87555 100644
--- a/modules/http2/h2_stream.c
+++ b/modules/http2/h2_stream.c
@@ -17,34 +17,39 @@
#include
#include
-#include
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
#include
#include
#include
#include
+#include
+#include
#include
#include "h2_private.h"
#include "h2.h"
#include "h2_bucket_beam.h"
-#include "h2_conn.h"
+#include "h2_c1.h"
#include "h2_config.h"
-#include "h2_h2.h"
+#include "h2_protocol.h"
#include "h2_mplx.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_headers.h"
#include "h2_session.h"
#include "h2_stream.h"
-#include "h2_task.h"
-#include "h2_ctx.h"
-#include "h2_task.h"
+#include "h2_c2.h"
+#include "h2_conn_ctx.h"
+#include "h2_c2.h"
#include "h2_util.h"
-static const char *h2_ss_str(h2_stream_state_t state)
+static const char *h2_ss_str(const h2_stream_state_t state)
{
switch (state) {
case H2_SS_IDLE:
@@ -68,7 +73,7 @@ static const char *h2_ss_str(h2_stream_state_t state)
}
}
-const char *h2_stream_state_str(h2_stream *stream)
+const char *h2_stream_state_str(const h2_stream *stream)
{
return h2_ss_str(stream->state);
}
@@ -120,7 +125,8 @@ static int trans_on_event[][H2_SS_MAX] = {
{ S_XXX, S_ERR, S_ERR, S_CL_L, S_CLS, S_XXX, S_XXX, S_XXX, },/* EV_CLOSED_L*/
{ S_ERR, S_ERR, S_ERR, S_CL_R, S_ERR, S_CLS, S_NOP, S_NOP, },/* EV_CLOSED_R*/
{ S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_NOP, S_NOP, },/* EV_CANCELLED*/
-{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_XXX, },/* EV_EOS_SENT*/
+{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_NOP, },/* EV_EOS_SENT*/
+{ S_NOP, S_XXX, S_CLS, S_XXX, S_XXX, S_CLS, S_XXX, S_XXX, },/* EV_IN_ERROR*/
};
static int on_map(h2_stream_state_t state, int map[H2_SS_MAX])
@@ -142,7 +148,7 @@ static int on_frame(h2_stream_state_t state, int frame_type,
{
ap_assert(frame_type >= 0);
ap_assert(state >= 0);
- if (frame_type >= maxlen) {
+ if ((apr_size_t)frame_type >= maxlen) {
return state; /* NOP, ignore unknown frame types */
}
return on_map(state, frame_map[frame_type]);
@@ -160,6 +166,7 @@ static int on_frame_recv(h2_stream_state_t state, int frame_type)
static int on_event(h2_stream* stream, h2_stream_event_t ev)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->monitor && stream->monitor->on_event) {
stream->monitor->on_event(stream->monitor->ctx, stream, ev);
}
@@ -169,10 +176,18 @@ static int on_event(h2_stream* stream, h2_stream_event_t ev)
return stream->state;
}
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+ int32_t stream_id,
+ uint8_t *buf,
+ size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *puser);
+
static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
{
- if (APLOG_C_IS_LEVEL(s->session->c, lvl)) {
- conn_rec *c = s->session->c;
+ if (APLOG_C_IS_LEVEL(s->session->c1, lvl)) {
+ conn_rec *c = s->session->c1;
char buffer[4 * 1024];
apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
@@ -182,76 +197,116 @@ static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
}
}
-static apr_status_t setup_input(h2_stream *stream) {
- if (stream->input == NULL) {
- int empty = (stream->input_eof
- && (!stream->in_buffer
- || APR_BRIGADE_EMPTY(stream->in_buffer)));
- if (!empty) {
- h2_beam_create(&stream->input, stream->pool, stream->id,
- "input", H2_BEAM_OWNER_SEND, 0,
- stream->session->s->timeout);
- h2_beam_send_from(stream->input, stream->pool);
- }
+static void stream_setup_input(h2_stream *stream)
+{
+ if (stream->input != NULL) return;
+ ap_assert(!stream->input_closed);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "setup input beam"));
+ h2_beam_create(&stream->input, stream->session->c1,
+ stream->pool, stream->id,
+ "input", 0, stream->session->s->timeout);
+}
+
+apr_status_t h2_stream_prepare_processing(h2_stream *stream)
+{
+ /* Right before processing starts, last chance to decide if
+ * there is need to an input beam. */
+ if (!stream->input_closed) {
+ stream_setup_input(stream);
}
return APR_SUCCESS;
}
-static apr_status_t close_input(h2_stream *stream)
+static int input_buffer_is_empty(h2_stream *stream)
+{
+ return !stream->in_buffer || APR_BRIGADE_EMPTY(stream->in_buffer);
+}
+
+static apr_status_t input_flush(h2_stream *stream)
{
- conn_rec *c = stream->session->c;
apr_status_t status = APR_SUCCESS;
+ apr_off_t written;
- stream->input_eof = 1;
- if (stream->input && h2_beam_is_closed(stream->input)) {
- return APR_SUCCESS;
- }
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
- H2_STRM_MSG(stream, "closing input"));
- if (stream->rst_error) {
- return APR_ECONNRESET;
+ if (input_buffer_is_empty(stream)) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "flush input"));
+ status = h2_beam_send(stream->input, stream->session->c1,
+ stream->in_buffer, APR_BLOCK_READ, &written);
+ stream->in_last_write = apr_time_now();
+ if (APR_SUCCESS != status && h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c1,
+ H2_STRM_MSG(stream, "send input error"));
+ h2_stream_dispatch(stream, H2_SEV_IN_ERROR);
}
-
- if (stream->trailers && !apr_is_empty_table(stream->trailers)) {
- apr_bucket *b;
- h2_headers *r;
-
- if (!stream->in_buffer) {
- stream->in_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
- }
-
- r = h2_headers_create(HTTP_OK, stream->trailers, NULL,
- stream->in_trailer_octets, stream->pool);
- stream->trailers = NULL;
- b = h2_bucket_headers_create(c->bucket_alloc, r);
- APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
-
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
- H2_STRM_MSG(stream, "added trailers"));
- h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+cleanup:
+ return status;
+}
+
+static void input_append_bucket(h2_stream *stream, apr_bucket *b)
+{
+ if (!stream->in_buffer) {
+ stream_setup_input(stream);
+ stream->in_buffer = apr_brigade_create(
+ stream->pool, stream->session->c1->bucket_alloc);
}
- if (stream->input) {
- h2_stream_flush_input(stream);
- return h2_beam_close(stream->input);
+ APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
+}
+
+static void input_append_data(h2_stream *stream, const char *data, apr_size_t len)
+{
+ if (!stream->in_buffer) {
+ stream_setup_input(stream);
+ stream->in_buffer = apr_brigade_create(
+ stream->pool, stream->session->c1->bucket_alloc);
}
- return status;
+ apr_brigade_write(stream->in_buffer, NULL, NULL, data, len);
}
-static apr_status_t close_output(h2_stream *stream)
+
+static apr_status_t close_input(h2_stream *stream)
{
- if (!stream->output || h2_beam_is_closed(stream->output)) {
- return APR_SUCCESS;
+ conn_rec *c = stream->session->c1;
+ apr_status_t rv = APR_SUCCESS;
+ apr_bucket *b;
+
+ if (stream->input_closed) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "closing input"));
+ if (!stream->rst_error
+ && stream->trailers_in
+ && !apr_is_empty_table(stream->trailers_in)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "adding trailers"));
+#if AP_HAS_RESPONSE_BUCKETS
+ b = ap_bucket_headers_create(stream->trailers_in,
+ stream->pool, c->bucket_alloc);
+#else
+ b = h2_bucket_headers_create(c->bucket_alloc,
+ h2_headers_create(HTTP_OK, stream->trailers_in, NULL,
+ stream->in_trailer_octets, stream->pool));
+#endif
+ input_append_bucket(stream, b);
+ stream->trailers_in = NULL;
+ }
+
+ stream->input_closed = 1;
+ if (stream->input) {
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ input_append_bucket(stream, b);
+ input_flush(stream);
+ h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "input flush + EOS"));
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
- H2_STRM_MSG(stream, "closing output"));
- return h2_beam_leave(stream->output);
+
+cleanup:
+ return rv;
}
-static void on_state_enter(h2_stream *stream)
+static void on_state_enter(h2_stream *stream)
{
if (stream->monitor && stream->monitor->on_state_enter) {
stream->monitor->on_state_enter(stream->monitor->ctx, stream);
@@ -271,7 +326,7 @@ static void on_state_invalid(h2_stream *stream)
stream->monitor->on_state_invalid(stream->monitor->ctx, stream);
}
/* stream got an event/frame invalid in its state */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "invalid state event"));
switch (stream->state) {
case H2_SS_OPEN:
@@ -288,17 +343,17 @@ static void on_state_invalid(h2_stream *stream)
static apr_status_t transit(h2_stream *stream, int new_state)
{
- if (new_state == stream->state) {
+ if ((h2_stream_state_t)new_state == stream->state) {
return APR_SUCCESS;
}
else if (new_state < 0) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(03081), stream, "invalid transition"));
on_state_invalid(stream);
return APR_EINVAL;
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "transit to [%s]"), h2_ss_str(new_state));
stream->state = new_state;
switch (new_state) {
@@ -312,14 +367,12 @@ static apr_status_t transit(h2_stream *stream, int new_state)
case H2_SS_OPEN:
break;
case H2_SS_CLOSED_L:
- close_output(stream);
break;
case H2_SS_CLOSED_R:
close_input(stream);
break;
case H2_SS_CLOSED:
close_input(stream);
- close_output(stream);
if (stream->out_buffer) {
apr_brigade_cleanup(stream->out_buffer);
}
@@ -340,19 +393,20 @@ void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev)
{
int new_state;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
H2_STRM_MSG(stream, "dispatch event %d"), ev);
new_state = on_event(stream, ev);
if (new_state < 0) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10002), stream, "invalid event %d"), ev);
on_state_invalid(stream);
AP_DEBUG_ASSERT(new_state > S_XXX);
return;
}
- else if (new_state == stream->state) {
+ else if ((h2_stream_state_t)new_state == stream->state) {
/* nop */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
H2_STRM_MSG(stream, "non-state event %d"), ev);
return;
}
@@ -365,9 +419,7 @@ void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev)
static void set_policy_for(h2_stream *stream, h2_request *r)
{
int enabled = h2_session_push_enabled(stream->session);
- stream->push_policy = h2_push_policy_determine(r->headers, stream->pool,
- enabled);
- r->serialize = h2_config_geti(stream->session->config, H2_CONF_SER_HEADERS);
+ stream->push_policy = h2_push_policy_determine(r->headers, stream->pool, enabled);
}
apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
@@ -375,9 +427,10 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
apr_status_t status = APR_SUCCESS;
int new_state, eos = 0;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
new_state = on_frame_send(stream->state, ftype);
if (new_state < 0) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "invalid frame %d send"), ftype);
AP_DEBUG_ASSERT(new_state > S_XXX);
return transit(stream, new_state);
@@ -385,6 +438,12 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
++stream->out_frames;
stream->out_frame_octets += frame_len;
+ if(stream->c2) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+ if(conn_ctx)
+ conn_ctx->bytes_sent = stream->out_frame_octets;
+ }
+
switch (ftype) {
case NGHTTP2_DATA:
eos = (flags & NGHTTP2_FLAG_END_STREAM);
@@ -398,24 +457,18 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
/* start pushed stream */
ap_assert(stream->request == NULL);
ap_assert(stream->rtmp != NULL);
- status = h2_request_end_headers(stream->rtmp, stream->pool, 1, 0);
- if (status != APR_SUCCESS) {
- return status;
- }
- set_policy_for(stream, stream->rtmp);
- stream->request = stream->rtmp;
- stream->rtmp = NULL;
+ status = h2_stream_end_headers(stream, 1, 0);
+ if (status != APR_SUCCESS) goto leave;
break;
default:
break;
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
- H2_STRM_MSG(stream, "send frame %d, eos=%d"), ftype, eos);
status = transit(stream, new_state);
if (status == APR_SUCCESS && eos) {
status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
}
+leave:
return status;
}
@@ -424,9 +477,10 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
apr_status_t status = APR_SUCCESS;
int new_state, eos = 0;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
new_state = on_frame_recv(stream->state, ftype);
if (new_state < 0) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "invalid frame %d recv"), ftype);
AP_DEBUG_ASSERT(new_state > S_XXX);
return transit(stream, new_state);
@@ -439,7 +493,7 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
case NGHTTP2_HEADERS:
eos = (flags & NGHTTP2_FLAG_END_STREAM);
- if (stream->state == H2_SS_OPEN) {
+ if (h2_stream_is_at_or_past(stream, H2_SS_OPEN)) {
/* trailer HEADER */
if (!eos) {
h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
@@ -451,18 +505,13 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
ap_assert(stream->request == NULL);
if (stream->rtmp == NULL) {
/* This can only happen, if the stream has received no header
- * name/value pairs at all. The lastest nghttp2 version have become
+ * name/value pairs at all. The latest nghttp2 version have become
* pretty good at detecting this early. In any case, we have
* to abort the connection here, since this is clearly a protocol error */
return APR_EINVAL;
}
- status = h2_request_end_headers(stream->rtmp, stream->pool, eos, frame_len);
- if (status != APR_SUCCESS) {
- return status;
- }
- set_policy_for(stream, stream->rtmp);
- stream->request = stream->rtmp;
- stream->rtmp = NULL;
+ status = h2_stream_end_headers(stream, eos, frame_len);
+ if (status != APR_SUCCESS) goto leave;
}
break;
@@ -473,22 +522,7 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
if (status == APR_SUCCESS && eos) {
status = transit(stream, on_event(stream, H2_SEV_CLOSED_R));
}
- return status;
-}
-
-apr_status_t h2_stream_flush_input(h2_stream *stream)
-{
- apr_status_t status = APR_SUCCESS;
-
- if (stream->in_buffer && !APR_BRIGADE_EMPTY(stream->in_buffer)) {
- setup_input(stream);
- status = h2_beam_send(stream->input, stream->in_buffer, APR_BLOCK_READ);
- stream->in_last_write = apr_time_now();
- }
- if (stream->input_eof
- && stream->input && !h2_beam_is_closed(stream->input)) {
- status = h2_beam_close(stream->input);
- }
+leave:
return status;
}
@@ -498,41 +532,59 @@ apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
h2_session *session = stream->session;
apr_status_t status = APR_SUCCESS;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
stream->in_data_frames++;
if (len > 0) {
- if (APLOGctrace3(session->c)) {
+ if (APLOGctrace3(session->c1)) {
const char *load = apr_pstrndup(stream->pool, (const char *)data, len);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c1,
H2_STRM_MSG(stream, "recv DATA, len=%d: -->%s<--"),
(int)len, load);
}
else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
H2_STRM_MSG(stream, "recv DATA, len=%d"), (int)len);
}
stream->in_data_octets += len;
- if (!stream->in_buffer) {
- stream->in_buffer = apr_brigade_create(stream->pool,
- session->c->bucket_alloc);
- }
- apr_brigade_write(stream->in_buffer, NULL, NULL, (const char *)data, len);
+ input_append_data(stream, (const char*)data, len);
+ input_flush(stream);
h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
}
return status;
}
-static void prep_output(h2_stream *stream) {
- conn_rec *c = stream->session->c;
- if (!stream->out_buffer) {
- stream->out_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
+#ifdef AP_DEBUG
+static apr_status_t stream_pool_destroy(void *data)
+{
+ h2_stream *stream = data;
+ switch (stream->magic) {
+ case H2_STRM_MAGIC_OK:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "was not destroyed explicitly"));
+ AP_DEBUG_ASSERT(0);
+ break;
+ case H2_STRM_MAGIC_SDEL:
+ /* stream has been explicitly destroyed, as it should */
+ H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_PDEL);
+ break;
+ case H2_STRM_MAGIC_PDEL:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "already pool destroyed"));
+ AP_DEBUG_ASSERT(0);
+ break;
+ default:
+ AP_DEBUG_ASSERT(0);
}
+ return APR_SUCCESS;
}
+#endif
h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
h2_stream_monitor *monitor, int initiated_on)
{
h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
-
+
+ H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_OK);
stream->id = id;
stream->initiated_on = initiated_on;
stream->created = apr_time_now();
@@ -540,15 +592,21 @@ h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
stream->pool = pool;
stream->session = session;
stream->monitor = monitor;
- stream->max_mem = session->max_stream_mem;
-
-#ifdef H2_NG2_LOCAL_WIN_SIZE
- stream->in_window_size =
- nghttp2_session_get_stream_local_window_size(
- stream->session->ngh2, stream->id);
+#ifdef AP_DEBUG
+ if (id) { /* stream 0 has special lifetime */
+ apr_pool_cleanup_register(pool, stream, stream_pool_destroy,
+ apr_pool_cleanup_null);
+ }
#endif
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+ if (id) {
+ stream->in_window_size =
+ nghttp2_session_get_stream_local_window_size(
+ stream->session->ngh2, stream->id);
+ }
+#endif
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_STRM_LOG(APLOGNO(03082), stream, "created"));
on_state_enter(stream);
return stream;
@@ -556,59 +614,35 @@ h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
void h2_stream_cleanup(h2_stream *stream)
{
- apr_status_t status;
-
+ /* Stream is done on c1. There might still be processing on a c2
+ * going on. The input/output beams get aborted and the stream's
+ * end of the in/out notifications get closed.
+ */
ap_assert(stream);
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->out_buffer) {
- /* remove any left over output buckets that may still have
- * references into request pools */
apr_brigade_cleanup(stream->out_buffer);
}
- if (stream->input) {
- h2_beam_abort(stream->input);
- status = h2_beam_wait_empty(stream->input, APR_NONBLOCK_READ);
- if (status == APR_EAGAIN) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
- H2_STRM_MSG(stream, "wait on input drain"));
- status = h2_beam_wait_empty(stream->input, APR_BLOCK_READ);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c,
- H2_STRM_MSG(stream, "input drain returned"));
- }
- }
}
void h2_stream_destroy(h2_stream *stream)
{
ap_assert(stream);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c,
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c1,
H2_STRM_MSG(stream, "destroy"));
+ H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_SDEL);
apr_pool_destroy(stream->pool);
}
-apr_status_t h2_stream_prep_processing(h2_stream *stream)
-{
- if (stream->request) {
- const h2_request *r = stream->request;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
- H2_STRM_MSG(stream, "schedule %s %s://%s%s chunked=%d"),
- r->method, r->scheme, r->authority, r->path, r->chunked);
- setup_input(stream);
- stream->scheduled = 1;
- return APR_SUCCESS;
- }
- return APR_EINVAL;
-}
-
void h2_stream_rst(h2_stream *stream, int error_code)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
stream->rst_error = error_code;
- if (stream->input) {
- h2_beam_abort(stream->input);
- }
- if (stream->output) {
- h2_beam_leave(stream->output);
+ if (stream->c2) {
+ h2_c2_abort(stream->c2, stream->session->c1);
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "reset, error=%d"), error_code);
h2_stream_dispatch(stream, H2_SEV_CANCELLED);
}
@@ -619,6 +653,7 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream,
h2_request *req;
apr_status_t status;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_assert(stream->request == NULL);
ap_assert(stream->rtmp == NULL);
if (stream->rst_error) {
@@ -640,6 +675,7 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream,
void h2_stream_set_request(h2_stream *stream, const h2_request *r)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_assert(stream->request == NULL);
ap_assert(stream->rtmp == NULL);
stream->rtmp = h2_request_clone(stream->pool, r);
@@ -647,43 +683,46 @@ void h2_stream_set_request(h2_stream *stream, const h2_request *r)
static void set_error_response(h2_stream *stream, int http_status)
{
- if (!h2_stream_is_ready(stream)) {
- conn_rec *c = stream->session->c;
- apr_bucket *b;
- h2_headers *response;
-
- response = h2_headers_die(http_status, stream->request, stream->pool);
- prep_output(stream);
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_HEAD(stream->out_buffer, b);
- b = h2_bucket_headers_create(c->bucket_alloc, response);
- APR_BRIGADE_INSERT_HEAD(stream->out_buffer, b);
+ if (!h2_stream_is_ready(stream) && stream->rtmp) {
+ stream->rtmp->http_status = http_status;
}
}
static apr_status_t add_trailer(h2_stream *stream,
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)
{
- conn_rec *c = stream->session->c;
+ conn_rec *c = stream->session->c1;
char *hname, *hvalue;
+ const char *existing;
+ *pwas_added = 0;
if (nlen == 0 || name[0] == ':') {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c,
H2_STRM_LOG(APLOGNO(03060), stream,
"pseudo header in trailer"));
return APR_EINVAL;
}
- if (h2_req_ignore_trailer(name, nlen)) {
+ if (h2_ignore_req_trailer(name, nlen)) {
return APR_SUCCESS;
}
- if (!stream->trailers) {
- stream->trailers = apr_table_make(stream->pool, 5);
+ if (!stream->trailers_in) {
+ stream->trailers_in = apr_table_make(stream->pool, 5);
}
hname = apr_pstrndup(stream->pool, name, nlen);
- hvalue = apr_pstrndup(stream->pool, value, vlen);
h2_util_camel_case_header(hname, nlen);
- apr_table_mergen(stream->trailers, hname, hvalue);
+ existing = apr_table_get(stream->trailers_in, hname);
+ if (max_field_len
+ && ((existing? strlen(existing)+2 : 0) + vlen + nlen + 2 > max_field_len)) {
+ /* "key: (oldval, )?nval" is too long */
+ return APR_EINVAL;
+ }
+ if (!existing) *pwas_added = 1;
+ hvalue = apr_pstrndup(stream->pool, value, vlen);
+ apr_table_mergen(stream->trailers_in, hname, hvalue);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue);
return APR_SUCCESS;
}
@@ -693,274 +732,487 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
const char *value, size_t vlen)
{
h2_session *session = stream->session;
- int error = 0;
- apr_status_t status;
+ int error = 0, was_added = 0;
+ apr_status_t status = APR_SUCCESS;
- if (stream->has_response) {
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ if (stream->response) {
return APR_EINVAL;
}
- ++stream->request_headers_added;
+
if (name[0] == ':') {
- if ((vlen) > session->s->limit_req_line) {
+ if (vlen > APR_INT32_MAX || (int)vlen > session->s->limit_req_line) {
/* pseudo header: approximation of request line size check */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_STRM_MSG(stream, "pseudo %s too long"), name);
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10178), stream,
+ "Request pseudo header exceeds "
+ "LimitRequestFieldSize: %s"), name);
+ }
error = HTTP_REQUEST_URI_TOO_LARGE;
+ goto cleanup;
}
}
- else if ((nlen + 2 + vlen) > session->s->limit_req_fieldsize) {
+
+ if (session->s->limit_req_fields > 0
+ && stream->request_headers_added > session->s->limit_req_fields) {
+ /* already over limit, count this attempt, but do not take it in */
+ ++stream->request_headers_added;
+ }
+ else if (H2_SS_IDLE == stream->state) {
+ if (!stream->rtmp) {
+ stream->rtmp = h2_request_create(stream->id, stream->pool,
+ NULL, NULL, NULL, NULL, NULL);
+ }
+ status = h2_request_add_header(stream->rtmp, stream->pool,
+ name, nlen, value, vlen,
+ session->s->limit_req_fieldsize, &was_added);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
+ H2_STRM_MSG(stream, "add_header: '%.*s: %.*s"),
+ (int)nlen, name, (int)vlen, value);
+ if (was_added) ++stream->request_headers_added;
+ }
+ else if (H2_SS_OPEN == stream->state) {
+ status = add_trailer(stream, name, nlen, value, vlen,
+ session->s->limit_req_fieldsize, &was_added);
+ if (was_added) ++stream->request_headers_added;
+ }
+ else {
+ status = APR_EINVAL;
+ goto cleanup;
+ }
+
+ if (APR_EINVAL == status) {
/* header too long */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_STRM_MSG(stream, "header %s too long"), name);
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
+ "LimitRequestFieldSize: %.*s"),
+ (int)H2MIN(nlen, 80), name);
+ }
error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ goto cleanup;
}
- if (stream->request_headers_added > session->s->limit_req_fields + 4) {
- /* too many header lines, include 4 pseudo headers */
- if (stream->request_headers_added
- > session->s->limit_req_fields + 4 + 100) {
- /* yeah, right */
+ if (session->s->limit_req_fields > 0
+ && stream->request_headers_added > session->s->limit_req_fields) {
+ /* too many header lines */
+ if (stream->request_headers_added > session->s->limit_req_fields + 100) {
+ /* yeah, right, this request is way over the limit, say goodbye */
h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
return APR_ECONNRESET;
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_STRM_MSG(stream, "too many header lines"));
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
+ "exceeds LimitRequestFields"));
+ }
error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ goto cleanup;
}
+cleanup:
if (error) {
+ ++stream->request_headers_failed;
set_error_response(stream, error);
return APR_EINVAL;
}
- else if (H2_SS_IDLE == stream->state) {
- if (!stream->rtmp) {
- stream->rtmp = h2_req_create(stream->id, stream->pool,
- NULL, NULL, NULL, NULL, NULL, 0);
- }
- status = h2_request_add_header(stream->rtmp, stream->pool,
- name, nlen, value, vlen);
+ else if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_STRM_MSG(stream, "header %s not accepted"), name);
+ h2_stream_dispatch(stream, H2_SEV_CANCELLED);
}
- else if (H2_SS_OPEN == stream->state) {
- status = add_trailer(stream, name, nlen, value, vlen);
+ return status;
+}
+
+typedef struct {
+ apr_size_t maxlen;
+ const char *failed_key;
+} val_len_check_ctx;
+
+static int table_check_val_len(void *baton, const char *key, const char *value)
+{
+ val_len_check_ctx *ctx = baton;
+
+ if (strlen(value) <= ctx->maxlen) return 1;
+ ctx->failed_key = key;
+ return 0;
+}
+
+apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes)
+{
+ apr_status_t status;
+ val_len_check_ctx ctx;
+ int is_http_or_https;
+ h2_request *req = stream->rtmp;
+
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ status = h2_request_end_headers(req, stream->pool, raw_bytes);
+ if (APR_SUCCESS != status || req->http_status != H2_HTTP_STATUS_UNSET) {
+ goto cleanup;
+ }
+
+ /* keep on returning APR_SUCCESS for error responses, so that we
+ * send it and do not RST the stream.
+ */
+ set_policy_for(stream, req);
+
+ ctx.maxlen = stream->session->s->limit_req_fieldsize;
+ ctx.failed_key = NULL;
+ apr_table_do(table_check_val_len, &ctx, req->headers, NULL);
+ if (ctx.failed_key) {
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10230), stream,"Request header exceeds "
+ "LimitRequestFieldSize: %.*s"),
+ (int)H2MIN(strlen(ctx.failed_key), 80), ctx.failed_key);
+ }
+ set_error_response(stream, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
+ goto cleanup;
+ }
+
+ /* http(s) scheme. rfc7540, ch. 8.1.2.3:
+ * This [:path] pseudo-header field MUST NOT be empty for "http" or "https"
+ * URIs; "http" or "https" URIs that do not contain a path component
+ * MUST include a value of '/'. The exception to this rule is an
+ * OPTIONS request for an "http" or "https" URI that does not include
+ * a path component; these MUST include a ":path" pseudo-header field
+ * with a value of '*'
+ *
+ * All HTTP/2 requests MUST include exactly one valid value for the
+ * ":method", ":scheme", and ":path" pseudo-header fields, unless it is
+ * a CONNECT request.
+ */
+ is_http_or_https = (!req->scheme
+ || !(ap_cstr_casecmpn(req->scheme, "http", 4) != 0
+ || (req->scheme[4] != '\0'
+ && (apr_tolower(req->scheme[4]) != 's'
+ || req->scheme[5] != '\0'))));
+
+ /* CONNECT. rfc7540, ch. 8.3:
+ * In HTTP/2, the CONNECT method is used to establish a tunnel over a
+ * single HTTP/2 stream to a remote host for similar purposes. The HTTP
+ * header field mapping works as defined in Section 8.1.2.3 ("Request
+ * Pseudo-Header Fields"), with a few differences. Specifically:
+ * o The ":method" pseudo-header field is set to "CONNECT".
+ * o The ":scheme" and ":path" pseudo-header fields MUST be omitted.
+ * o The ":authority" pseudo-header field contains the host and port to
+ * connect to (equivalent to the authority-form of the request-target
+ * of CONNECT requests (see [RFC7230], Section 5.3)).
+ */
+ if (!ap_cstr_casecmp(req->method, "CONNECT")) {
+ if (req->protocol) {
+ if (!strcmp("websocket", req->protocol)) {
+ if (!req->scheme || !req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
+ "without :scheme or :path, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+ }
+ else {
+ /* do not know that protocol */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1, APLOGNO(10460)
+ "':protocol: %s' header present in %s request",
+ req->protocol, req->method);
+ set_error_response(stream, HTTP_NOT_IMPLEMENTED);
+ goto cleanup;
+ }
+ }
+ else if (req->scheme || req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10384), stream, "Request to CONNECT "
+ "with :scheme or :path specified, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
}
- else {
- status = APR_EINVAL;
+ else if (is_http_or_https) {
+ if (!req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10385), stream, "Request for http(s) "
+ "resource without :path, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+ if (!req->scheme) {
+ req->scheme = ap_ssl_conn_is_ssl(stream->session->c1)? "https" : "http";
+ }
}
-
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- H2_STRM_MSG(stream, "header %s not accepted"), name);
- h2_stream_dispatch(stream, H2_SEV_CANCELLED);
+
+ if (req->scheme && (req->path && req->path[0] != '/')) {
+ /* We still have a scheme, which means we need to pass an absolute URI into
+ * our HTTP protocol handling and the missing '/' at the start will prevent
+ * us from doing so (as it then confuses path and authority). */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10379), stream, "Request :scheme '%s' and "
+ "path '%s' do not allow creating an absolute URL. Failing "
+ "request with 400."), req->scheme, req->path);
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+
+cleanup:
+ if (APR_SUCCESS == status) {
+ stream->request = req;
+ stream->rtmp = NULL;
+
+ if (APLOGctrace4(stream->session->c1)) {
+ int i;
+ const apr_array_header_t *t_h = apr_table_elts(req->headers);
+ const apr_table_entry_t *t_elt = (apr_table_entry_t *)t_h->elts;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, stream->session->c1,
+ H2_STRM_MSG(stream,"headers received from client:"));
+ for (i = 0; i < t_h->nelts; i++, t_elt++) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, stream->session->c1,
+ H2_STRM_MSG(stream, " %s: %s"),
+ ap_escape_logitem(stream->pool, t_elt->key),
+ ap_escape_logitem(stream->pool, t_elt->val));
+ }
+ }
}
return status;
}
-static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
+static apr_bucket *get_first_response_bucket(apr_bucket_brigade *bb)
{
if (bb) {
apr_bucket *b = APR_BRIGADE_FIRST(bb);
while (b != APR_BRIGADE_SENTINEL(bb)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ return b;
+ }
+#else
if (H2_BUCKET_IS_HEADERS(b)) {
return b;
}
+#endif
b = APR_BUCKET_NEXT(b);
}
}
return NULL;
}
-static apr_status_t add_buffered_data(h2_stream *stream, apr_off_t requested,
- apr_off_t *plen, int *peos, int *is_all,
- h2_headers **pheaders)
+static void stream_do_error_bucket(h2_stream *stream, apr_bucket *b)
{
- apr_bucket *b, *e;
-
- *peos = 0;
- *plen = 0;
- *is_all = 0;
- if (pheaders) {
- *pheaders = NULL;
+ int err = ((ap_bucket_error *)(b->data))->status;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "error bucket received, err=%d"), err);
+ if (err >= 500) {
+ err = NGHTTP2_INTERNAL_ERROR;
}
-
- H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "add_buffered_data");
- b = APR_BRIGADE_FIRST(stream->out_buffer);
- while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
- e = APR_BUCKET_NEXT(b);
- if (APR_BUCKET_IS_METADATA(b)) {
- if (APR_BUCKET_IS_FLUSH(b)) {
- APR_BUCKET_REMOVE(b);
- apr_bucket_destroy(b);
- }
- else if (APR_BUCKET_IS_EOS(b)) {
- *peos = 1;
- return APR_SUCCESS;
- }
- else if (H2_BUCKET_IS_HEADERS(b)) {
- if (*plen > 0) {
- /* data before the response, can only return up to here */
- return APR_SUCCESS;
- }
- else if (pheaders) {
- *pheaders = h2_bucket_headers_get(b);
- APR_BUCKET_REMOVE(b);
- apr_bucket_destroy(b);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
- H2_STRM_MSG(stream, "prep, -> response %d"),
- (*pheaders)->status);
- return APR_SUCCESS;
- }
- else {
- return APR_EAGAIN;
- }
- }
- }
- else if (b->length == 0) {
- APR_BUCKET_REMOVE(b);
- apr_bucket_destroy(b);
- }
- else {
- ap_assert(b->length != (apr_size_t)-1);
- *plen += b->length;
- if (*plen >= requested) {
- *plen = requested;
- return APR_SUCCESS;
- }
- }
- b = e;
+ else if (err >= 400) {
+ err = NGHTTP2_STREAM_CLOSED;
}
- *is_all = 1;
- return APR_SUCCESS;
+ else {
+ err = NGHTTP2_PROTOCOL_ERROR;
+ }
+ h2_stream_rst(stream, err);
}
-apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
- int *peos, h2_headers **pheaders)
+static apr_status_t buffer_output_receive(h2_stream *stream)
{
- apr_status_t status = APR_SUCCESS;
- apr_off_t requested, missing, max_chunk = H2_DATA_CHUNK_SIZE;
- conn_rec *c;
- int complete;
+ apr_status_t rv = APR_EAGAIN;
+ apr_off_t buf_len;
+ conn_rec *c1 = stream->session->c1;
+ apr_bucket *b, *e;
- ap_assert(stream);
-
+ if (!stream->output) {
+ goto cleanup;
+ }
if (stream->rst_error) {
- *plen = 0;
- *peos = 1;
- return APR_ECONNRESET;
+ rv = APR_ECONNRESET;
+ goto cleanup;
}
-
- c = stream->session->c;
- prep_output(stream);
- /* determine how much we'd like to send. We cannot send more than
- * is requested. But we can reduce the size in case the master
- * connection operates in smaller chunks. (TSL warmup) */
- if (stream->session->io.write_size > 0) {
- max_chunk = stream->session->io.write_size - 9; /* header bits */
+ if (!stream->out_buffer) {
+ stream->out_buffer = apr_brigade_create(stream->pool, c1->bucket_alloc);
+ buf_len = 0;
}
- requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
-
- /* count the buffered data until eos or a headers bucket */
- status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
-
- if (status == APR_EAGAIN) {
- /* TODO: ugly, someone needs to retrieve the response first */
- h2_mplx_keep_active(stream->session->mplx, stream);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- H2_STRM_MSG(stream, "prep, response eagain"));
- return status;
+ else {
+ /* if the brigade contains a file bucket, its normal report length
+ * might be megabytes, but the memory used is tiny. For buffering,
+ * we are only interested in the memory footprint. */
+ buf_len = h2_brigade_mem_size(stream->out_buffer);
}
- else if (status != APR_SUCCESS) {
- return status;
+
+ if (buf_len > APR_INT32_MAX
+ || (apr_size_t)buf_len >= stream->session->max_stream_mem) {
+ /* we have buffered enough. No need to read more.
+ * However, we have now output pending for which we may not
+ * receive another poll event. We need to make sure that this
+ * stream is not suspended so we keep on processing output.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_STRM_MSG(stream, "out_buffer, already has %ld length"),
+ (long)buf_len);
+ rv = APR_SUCCESS;
+ goto cleanup;
}
-
- if (pheaders && *pheaders) {
- return APR_SUCCESS;
+
+ if (stream->output_eos) {
+ rv = APR_BRIGADE_EMPTY(stream->out_buffer)? APR_EOF : APR_SUCCESS;
}
-
- /* If there we do not have enough buffered data to satisfy the requested
- * length *and* we counted the _complete_ buffer (and did not stop in the middle
- * because of meta data there), lets see if we can read more from the
- * output beam */
- missing = H2MIN(requested, stream->max_mem) - *plen;
- if (complete && !*peos && missing > 0) {
- apr_status_t rv = APR_EOF;
-
- if (stream->output) {
- H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
- rv = h2_beam_receive(stream->output, stream->out_buffer,
- APR_NONBLOCK_READ, stream->max_mem - *plen);
- H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
- }
-
- if (rv == APR_SUCCESS) {
- /* count the buffer again, now that we have read output */
- status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
- }
- else if (APR_STATUS_IS_EOF(rv)) {
- apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
- *peos = 1;
- }
- else if (APR_STATUS_IS_EAGAIN(rv)) {
- /* we set this is the status of this call only if there
- * is no buffered data, see check below */
- }
- else {
- /* real error reading. Give this back directly, even though
- * we may have something buffered. */
- status = rv;
+ else {
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+ rv = h2_beam_receive(stream->output, stream->session->c1, stream->out_buffer,
+ APR_NONBLOCK_READ, stream->session->max_stream_mem - buf_len);
+ if (APR_SUCCESS != rv) {
+ if (APR_EAGAIN != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_STRM_MSG(stream, "out_buffer, receive unsuccessful"));
+ }
}
}
-
- if (status == APR_SUCCESS) {
- if (*peos || *plen) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- H2_STRM_MSG(stream, "prepare, len=%ld eos=%d"),
- (long)*plen, *peos);
- }
- else {
- status = (stream->output && h2_beam_is_closed(stream->output))? APR_EOF : APR_EAGAIN;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- H2_STRM_MSG(stream, "prepare, no data"));
+
+ /* get rid of buckets we have no need for */
+ if (!APR_BRIGADE_EMPTY(stream->out_buffer)) {
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_FLUSH(b)) { /* we flush any c1 data already */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ stream->output_eos = 1;
+ }
+ else if (AP_BUCKET_IS_ERROR(b)) {
+ stream_do_error_bucket(stream, b);
+ break;
+ }
+ }
+ else if (b->length == 0) { /* zero length data */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ b = e;
}
}
- return status;
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "out_buffer, after receive");
+
+cleanup:
+ return rv;
}
-static int is_not_headers(apr_bucket *b)
+static int bucket_pass_to_c1(apr_bucket *b)
{
- return !H2_BUCKET_IS_HEADERS(b);
+#if AP_HAS_RESPONSE_BUCKETS
+ return !AP_BUCKET_IS_RESPONSE(b)
+ && !AP_BUCKET_IS_HEADERS(b)
+ && !APR_BUCKET_IS_EOS(b);
+#else
+ return !H2_BUCKET_IS_HEADERS(b) && !APR_BUCKET_IS_EOS(b);
+#endif
}
apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
apr_off_t *plen, int *peos)
{
- conn_rec *c = stream->session->c;
- apr_status_t status = APR_SUCCESS;
+ apr_status_t rv = APR_SUCCESS;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->rst_error) {
return APR_ECONNRESET;
}
- status = h2_append_brigade(bb, stream->out_buffer, plen, peos, is_not_headers);
- if (status == APR_SUCCESS && !*peos && !*plen) {
- status = APR_EAGAIN;
+ rv = h2_append_brigade(bb, stream->out_buffer, plen, peos, bucket_pass_to_c1);
+ if (APR_SUCCESS == rv && !*peos && !*plen) {
+ rv = APR_EAGAIN;
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
- H2_STRM_MSG(stream, "read_to, len=%ld eos=%d"),
- (long)*plen, *peos);
- return status;
+ return rv;
}
+static apr_status_t stream_do_trailers(h2_stream *stream)
+{
+ conn_rec *c1 = stream->session->c1;
+ int ngrv;
+ h2_ngheader *nh = NULL;
+ apr_bucket *b, *e;
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_headers *headers = NULL;
+#else
+ h2_headers *headers = NULL;
+#endif
+ apr_status_t rv;
+
+ ap_assert(stream->response);
+ ap_assert(stream->out_buffer);
+
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_HEADERS(b)) {
+ headers = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ headers = h2_bucket_headers_get(b);
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_STRM_MSG(stream, "process trailers"));
+ break;
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ b = e;
+ }
+ if (!headers) {
+ rv = APR_EAGAIN;
+ goto cleanup;
+ }
+
+ rv = h2_res_create_ngtrailer(&nh, stream->pool, headers);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"),
+ (int)nh->nvlen);
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers"));
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ goto cleanup;
+ }
+
+ ngrv = nghttp2_submit_trailer(stream->session->ngh2, stream->id, nh->nv, nh->nvlen);
+ if (nghttp2_is_fatal(ngrv)) {
+ rv = APR_EGENERAL;
+ h2_session_dispatch_event(stream->session,
+ H2_SESSION_EV_PROTO_ERROR, ngrv, nghttp2_strerror(rv));
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ APLOGNO(02940) "submit_response: %s",
+ nghttp2_strerror(rv));
+ }
+ stream->sent_trailers = 1;
+
+cleanup:
+ return rv;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_stream_submit_pushes(h2_stream *stream, ap_bucket_response *response)
+#else
apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
+#endif
{
apr_status_t status = APR_SUCCESS;
apr_array_header_t *pushes;
int i;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
pushes = h2_push_collect_update(stream, stream->request, response);
if (pushes && !apr_is_empty_array(pushes)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "found %d push candidates"),
pushes->nelts);
for (i = 0; i < pushes->nelts; ++i) {
@@ -977,17 +1229,24 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
apr_table_t *h2_stream_get_trailers(h2_stream *stream)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
return NULL;
}
-const h2_priority *h2_stream_get_priority(h2_stream *stream,
+#if AP_HAS_RESPONSE_BUCKETS
+const h2_priority *h2_stream_get_priority(h2_stream *stream,
+ ap_bucket_response *response)
+#else
+const h2_priority *h2_stream_get_priority(h2_stream *stream,
h2_headers *response)
+#endif
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (response && stream->initiated_on) {
const char *ctype = apr_table_get(response->headers, "content-type");
if (ctype) {
/* FIXME: Not good enough, config needs to come from request->server */
- return h2_config_get_priority(stream->session->config, ctype);
+ return h2_cconfig_get_priority(stream->session->c1, ctype);
}
}
return NULL;
@@ -995,21 +1254,47 @@ const h2_priority *h2_stream_get_priority(h2_stream *stream,
int h2_stream_is_ready(h2_stream *stream)
{
- if (stream->has_response) {
+ /* Have we sent a response or do we have the response in our buffer? */
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ if (stream->response) {
return 1;
}
- else if (stream->out_buffer && get_first_headers_bucket(stream->out_buffer)) {
+ else if (stream->out_buffer && get_first_response_bucket(stream->out_buffer)) {
return 1;
}
return 0;
}
-int h2_stream_was_closed(const h2_stream *stream)
+int h2_stream_wants_send_data(h2_stream *stream)
{
- switch (stream->state) {
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ return h2_stream_is_ready(stream) &&
+ ((stream->out_buffer && !APR_BRIGADE_EMPTY(stream->out_buffer)) ||
+ (stream->output && !h2_beam_empty(stream->output)));
+}
+
+int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state)
+{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ return stream->state == state;
+}
+
+int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state)
+{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ switch (state) {
+ case H2_SS_IDLE:
+ return 1; /* by definition */
+ case H2_SS_RSVD_R: /*fall through*/
+ case H2_SS_RSVD_L: /*fall through*/
+ case H2_SS_OPEN:
+ return stream->state == state || stream->state >= H2_SS_OPEN;
+ case H2_SS_CLOSED_R: /*fall through*/
+ case H2_SS_CLOSED_L: /*fall through*/
case H2_SS_CLOSED:
+ return stream->state == state || stream->state >= H2_SS_CLOSED;
case H2_SS_CLEANUP:
- return 1;
+ return stream->state == state;
default:
return 0;
}
@@ -1019,6 +1304,7 @@ apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
{
h2_session *session = stream->session;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (amount > 0) {
apr_off_t consumed = amount;
@@ -1066,13 +1352,477 @@ apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
nghttp2_session_set_local_window_size(session->ngh2,
NGHTTP2_FLAG_NONE, stream->id, win);
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
- "h2_stream(%ld-%d): consumed %ld bytes, window now %d/%d",
- session->id, stream->id, (long)amount,
- cur_size, stream->in_window_size);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "consumed %ld bytes, window now %d/%d"),
+ (long)amount, cur_size, stream->in_window_size);
}
-#endif
+#endif /* #ifdef H2_NG2_LOCAL_WIN_SIZE */
}
return APR_SUCCESS;
}
+static apr_off_t output_data_buffered(h2_stream *stream, int *peos, int *pheader_blocked)
+{
+ /* How much data do we have in our buffers that we can write? */
+ apr_off_t buf_len = 0;
+ apr_bucket *b;
+
+ *peos = *pheader_blocked = 0;
+ if (stream->out_buffer) {
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ *peos = 1;
+ break;
+ }
+#if AP_HAS_RESPONSE_BUCKETS
+ else if (AP_BUCKET_IS_RESPONSE(b)) {
+ break;
+ }
+ else if (AP_BUCKET_IS_HEADERS(b)) {
+ *pheader_blocked = 1;
+ break;
+ }
+#else
+ else if (H2_BUCKET_IS_HEADERS(b)) {
+ *pheader_blocked = 1;
+ break;
+ }
+#endif
+ }
+ else {
+ buf_len += b->length;
+ }
+ b = APR_BUCKET_NEXT(b);
+ }
+ }
+ return buf_len;
+}
+
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+ int32_t stream_id,
+ uint8_t *buf,
+ size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *puser)
+{
+ h2_session *session = (h2_session *)puser;
+ conn_rec *c1 = session->c1;
+ apr_off_t buf_len;
+ int eos, header_blocked;
+ apr_status_t rv;
+ h2_stream *stream;
+
+ /* nghttp2 wants to send more DATA for the stream.
+ * we should have submitted the final response at this time
+ * after receiving output via stream_do_responses() */
+ ap_assert(session);
+ (void)ng2s;
+ (void)buf;
+ (void)source;
+ stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
+
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c1,
+ APLOGNO(02937)
+ H2_SSSN_STRM_MSG(session, stream_id, "data_cb, stream not found"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ if (!stream->output || !stream->response || !stream->out_buffer) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ if (stream->rst_error) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ if (h2_c1_io_needs_flush(&session->io)) {
+ rv = h2_c1_io_pass(&session->io);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "suspending on c1 out needs flush"));
+ h2_stream_dispatch(stream, H2_SEV_OUT_C1_BLOCK);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ else if (rv) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ /* determine how much we'd like to send. We cannot send more than
+ * is requested. But we can reduce the size in case the master
+ * connection operates in smaller chunks. (TSL warmup) */
+ if (stream->session->io.write_size > 0) {
+ apr_size_t chunk_len = stream->session->io.write_size - H2_FRAME_HDR_LEN;
+ if (length > chunk_len) {
+ length = chunk_len;
+ }
+ }
+ /* We allow configurable max DATA frame length. */
+ if (stream->session->max_data_frame_len > 0
+ && length > stream->session->max_data_frame_len) {
+ length = stream->session->max_data_frame_len;
+ }
+
+ /* How much data do we have in our buffers that we can write?
+ * if not enough, receive more. */
+ buf_len = output_data_buffered(stream, &eos, &header_blocked);
+ if (buf_len < (apr_off_t)length && !eos
+ && !header_blocked && !stream->rst_error) {
+ /* read more? */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "need more (read len=%ld, %ld in buffer)"),
+ (long)length, (long)buf_len);
+ rv = buffer_output_receive(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "buffer_output_received"));
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* currently, no more is available */
+ }
+ else if (APR_SUCCESS == rv) {
+ /* got some, re-assess */
+ buf_len = output_data_buffered(stream, &eos, &header_blocked);
+ }
+ else if (APR_EOF == rv) {
+ if (!stream->output_eos) {
+ /* Seeing APR_EOF without an EOS bucket received before indicates
+ * that stream output is incomplete. Commonly, we expect to see
+ * an ERROR bucket to have been generated. But faulty handlers
+ * may not have generated one.
+ * We need to RST the stream bc otherwise the client thinks
+ * it is all fine. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "rst stream"));
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "eof on receive (read len=%ld, %ld in buffer)"),
+ (long)length, (long)buf_len);
+ eos = 1;
+ rv = APR_SUCCESS;
+ }
+ else if (APR_ECONNRESET == rv || APR_ECONNABORTED == rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10471), stream, "data_cb, reading data"));
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ H2_STRM_LOG(APLOGNO(02938), stream, "data_cb, reading data"));
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ }
+
+ if (stream->rst_error) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ if (buf_len == 0 && header_blocked) {
+ rv = stream_do_trailers(stream);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ H2_STRM_LOG(APLOGNO(10300), stream,
+ "data_cb, error processing trailers"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ length = 0;
+ eos = 0;
+ }
+ else if (buf_len > (apr_off_t)length) {
+ eos = 0; /* Any EOS we have in the buffer does not apply yet */
+ }
+ else {
+ length = (size_t)buf_len;
+ }
+
+ if (length) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_STRM_MSG(stream, "data_cb, sending len=%ld, eos=%d"),
+ (long)length, eos);
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+ }
+ else if (!eos && !stream->sent_trailers) {
+ /* We have not reached the end of DATA yet, DEFER sending */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+ H2_STRM_LOG(APLOGNO(03071), stream, "data_cb, suspending"));
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ if (eos) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ return length;
+}
+
+static apr_status_t stream_do_response(h2_stream *stream)
+{
+ conn_rec *c1 = stream->session->c1;
+ apr_status_t rv = APR_EAGAIN;
+ int ngrv, is_empty = 0;
+ h2_ngheader *nh = NULL;
+ apr_bucket *b, *e;
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_response *resp = NULL;
+#else
+ h2_headers *resp = NULL;
+#endif
+ nghttp2_data_provider provider, *pprovider = NULL;
+
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ ap_assert(!stream->response);
+ ap_assert(stream->out_buffer);
+
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ resp = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ resp = h2_bucket_headers_get(b);
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_STRM_MSG(stream, "process response %d"),
+ resp->status);
+ is_empty = (e != APR_BRIGADE_SENTINEL(stream->out_buffer)
+ && APR_BUCKET_IS_EOS(e));
+ break;
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ else if (AP_BUCKET_IS_ERROR(b)) {
+ stream_do_error_bucket(stream, b);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ }
+ else {
+ /* data buckets before response headers, an error */
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ b = e;
+ }
+
+ if (!resp) {
+ rv = APR_EAGAIN;
+ goto cleanup;
+ }
+
+ if (resp->status < 100) {
+ h2_stream_rst(stream, resp->status);
+ goto cleanup;
+ }
+
+ if (resp->status == HTTP_FORBIDDEN && resp->notes) {
+ const char *cause = apr_table_get(resp->notes, "ssl-renegotiate-forbidden");
+ if (cause) {
+ /* This request triggered a TLS renegotiation that is not allowed
+ * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, resp->status, c1,
+ H2_STRM_LOG(APLOGNO(03061), stream,
+ "renegotiate forbidden, cause: %s"), cause);
+ h2_stream_rst(stream, H2_ERR_HTTP_1_1_REQUIRED);
+ goto cleanup;
+ }
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+ H2_STRM_LOG(APLOGNO(03073), stream,
+ "submit response %d"), resp->status);
+
+ /* If this stream is not a pushed one itself,
+ * and HTTP/2 server push is enabled here,
+ * and the response HTTP status is not sth >= 400,
+ * and the remote side has pushing enabled,
+ * -> find and perform any pushes on this stream
+ * *before* we submit the stream response itself.
+ * This helps clients avoid opening new streams on Link
+ * resp that get pushed right afterwards.
+ *
+ * *) the response code is relevant, as we do not want to
+ * make pushes on 401 or 403 codes and friends.
+ * And if we see a 304, we do not push either
+ * as the client, having this resource in its cache, might
+ * also have the pushed ones as well.
+ */
+ if (!stream->initiated_on
+ && !stream->response
+ && stream->request && stream->request->method
+ && !strcmp("GET", stream->request->method)
+ && (resp->status < 400)
+ && (resp->status != 304)
+ && h2_session_push_enabled(stream->session)) {
+ /* PUSH is possible and enabled on server, unless the request
+ * denies it, submit resources to push */
+ const char *s = apr_table_get(resp->notes, H2_PUSH_MODE_NOTE);
+ if (!s || strcmp(s, "0")) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_STRM_MSG(stream, "submit pushes, note=%s"), s);
+ h2_stream_submit_pushes(stream, resp);
+ }
+ }
+
+ if (!stream->pref_priority) {
+ stream->pref_priority = h2_stream_get_priority(stream, resp);
+ }
+ h2_session_set_prio(stream->session, stream, stream->pref_priority);
+
+ if (resp->status == 103
+ && !h2_config_sgeti(stream->session->s, H2_CONF_EARLY_HINTS)) {
+ /* suppress sending this to the client, it might have triggered
+ * pushes and served its purpose nevertheless */
+ rv = APR_SUCCESS;
+ goto cleanup;
+ }
+ if (resp->status >= 200) {
+ stream->response = resp;
+ }
+
+ if (!is_empty) {
+ memset(&provider, 0, sizeof(provider));
+ provider.source.fd = stream->id;
+ provider.read_callback = stream_data_cb;
+ pprovider = &provider;
+ }
+
+ rv = h2_res_create_ngheader(&nh, stream->pool, resp);
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10025), stream, "invalid response"));
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ goto cleanup;
+ }
+
+ ngrv = nghttp2_submit_response(stream->session->ngh2, stream->id,
+ nh->nv, nh->nvlen, pprovider);
+ if (nghttp2_is_fatal(ngrv)) {
+ rv = APR_EGENERAL;
+ h2_session_dispatch_event(stream->session,
+ H2_SESSION_EV_PROTO_ERROR, ngrv, nghttp2_strerror(rv));
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ APLOGNO(10402) "submit_response: %s",
+ nghttp2_strerror(rv));
+ goto cleanup;
+ }
+
+ if (stream->initiated_on) {
+ ++stream->session->pushes_submitted;
+ }
+ else {
+ ++stream->session->responses_submitted;
+ }
+
+cleanup:
+ return rv;
+}
+
+static void stream_do_responses(h2_stream *stream)
+{
+ h2_session *session = stream->session;
+ conn_rec *c1 = session->c1;
+ apr_status_t rv;
+
+ ap_assert(!stream->response);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_STRM_MSG(stream, "do_response"));
+ rv = buffer_output_receive(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream->id,
+ "buffer_output_received2"));
+ if (APR_SUCCESS != rv && APR_EAGAIN != rv) {
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ }
+ else {
+ /* process all headers sitting at the buffer head. */
+ do {
+ rv = stream_do_response(stream);
+ } while (APR_SUCCESS == rv
+ && !stream->rst_error
+ && !stream->response);
+ }
+}
+
+void h2_stream_on_output_change(h2_stream *stream)
+{
+ conn_rec *c1 = stream->session->c1;
+ apr_status_t rv = APR_EAGAIN;
+
+ /* stream->pout_recv_write signalled a change. Check what has happend, read
+ * from it and act on seeing a response/data. */
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ if (!stream->output) {
+ /* c2 has not assigned the output beam to the stream (yet). */
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c1,
+ H2_STRM_MSG(stream, "read_output, no output beam registered"));
+ }
+ else if (h2_stream_is_at_or_past(stream, H2_SS_CLOSED)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10301), stream, "already closed"));
+ }
+ else if (h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
+ /* We have delivered a response to a stream that was not closed
+ * by the client. This could be a POST with body that we negate
+ * and we need to RST_STREAM to end if. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+ H2_STRM_LOG(APLOGNO(10313), stream, "remote close missing"));
+ h2_stream_rst(stream, H2_ERR_NO_ERROR);
+ }
+ else {
+ /* stream is not closed, a change in output happened. There are
+ * two modes of operation here:
+ * 1) the final response has been submitted. nghttp2 is invoking
+ * stream_data_cb() to progress the stream. This handles DATA,
+ * trailers, EOS and ERRORs.
+ * When stream_data_cb() runs out of things to send, it returns
+ * NGHTTP2_ERR_DEFERRED and nghttp2 *suspends* further processing
+ * until we tell it to resume.
+ * 2) We have not seen the *final* response yet. The stream can not
+ * send any response DATA. The nghttp2 stream_data_cb() is not
+ * invoked. We need to receive output, expecting not DATA but
+ * RESPONSEs (intermediate may arrive) and submit those. On
+ * the final response, nghttp2 will start calling stream_data_cb().
+ */
+ if (stream->response) {
+ nghttp2_session_resume_data(stream->session->ngh2, stream->id);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_STRM_MSG(stream, "resumed"));
+ }
+ else {
+ stream_do_responses(stream);
+ if (!stream->rst_error) {
+ nghttp2_session_resume_data(stream->session->ngh2, stream->id);
+ }
+ }
+ }
+}
+
+void h2_stream_on_input_change(h2_stream *stream)
+{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ ap_assert(stream->input);
+ h2_beam_report_consumption(stream->input);
+ if (h2_stream_is_at(stream, H2_SS_CLOSED_L)
+ && !h2_mplx_c1_stream_is_running(stream->session->mplx, stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing"));
+ h2_stream_rst(stream, H2_ERR_NO_ERROR);
+ }
+}
diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
index 7ecc0ad..405978a 100644
--- a/modules/http2/h2_stream.h
+++ b/modules/http2/h2_stream.h
@@ -17,7 +17,10 @@
#ifndef __mod_h2__h2_stream__
#define __mod_h2__h2_stream__
+#include
+
#include "h2.h"
+#include "h2_headers.h"
/**
* A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms.
@@ -26,8 +29,8 @@
* connection to the client. The h2_session writes to the h2_stream,
* adding HEADERS and DATA and finally an EOS. When headers are done,
* h2_stream is scheduled for handling, which is expected to produce
- * a response h2_headers at least.
- *
+ * h2_headers/RESPONSE buckets.
+ *
* The h2_headers may be followed by more h2_headers (interim responses) and
* by DATA frames read from the h2_stream until EOS is reached. Trailers
* are send when a last h2_headers is received. This always closes the stream
@@ -37,9 +40,7 @@
struct h2_mplx;
struct h2_priority;
struct h2_request;
-struct h2_headers;
struct h2_session;
-struct h2_task;
struct h2_bucket_beam;
typedef struct h2_stream h2_stream;
@@ -62,7 +63,22 @@ typedef struct h2_stream_monitor {
trigger a state change */
} h2_stream_monitor;
+#ifdef AP_DEBUG
+#define H2_STRM_MAGIC_OK 0x5354524d
+#define H2_STRM_MAGIC_SDEL 0x5344454c
+#define H2_STRM_MAGIC_PDEL 0x5044454c
+
+#define H2_STRM_ASSIGN_MAGIC(s,m) ((s)->magic = m)
+#define H2_STRM_ASSERT_MAGIC(s,m) ap_assert((s)->magic == m)
+#else
+#define H2_STRM_ASSIGN_MAGIC(s,m) ((void)0)
+#define H2_STRM_ASSERT_MAGIC(s,m) ((void)0)
+#endif
+
struct h2_stream {
+#ifdef AP_DEBUG
+ uint32_t magic;
+#endif
int id; /* http2 stream identifier */
int initiated_on; /* initiating stream id (PUSH) or 0 */
apr_pool_t *pool; /* the memory pool for this stream */
@@ -73,9 +89,16 @@ struct h2_stream {
const struct h2_request *request; /* the request made in this stream */
struct h2_request *rtmp; /* request being assembled */
- apr_table_t *trailers; /* optional incoming trailers */
+ apr_table_t *trailers_in; /* optional, incoming trailers */
int request_headers_added; /* number of request headers added */
-
+ int request_headers_failed; /* number of request headers failed to add */
+
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_response *response; /* the final, non-interim response or NULL */
+#else
+ struct h2_headers *response; /* the final, non-interim response or NULL */
+#endif
+
struct h2_bucket_beam *input;
apr_bucket_brigade *in_buffer;
int in_window_size;
@@ -83,17 +106,16 @@ struct h2_stream {
struct h2_bucket_beam *output;
apr_bucket_brigade *out_buffer;
- apr_size_t max_mem; /* maximum amount of data buffered */
int rst_error; /* stream error for RST_STREAM */
unsigned int aborted : 1; /* was aborted */
unsigned int scheduled : 1; /* stream has been scheduled */
- unsigned int has_response : 1; /* response headers are known */
- unsigned int input_eof : 1; /* no more request data coming */
- unsigned int out_checked : 1; /* output eof was double checked */
+ unsigned int input_closed : 1; /* no more request data/trailers coming */
unsigned int push_policy; /* which push policy to use for this request */
-
- struct h2_task *task; /* assigned task to fullfill request */
+ unsigned int sent_trailers : 1; /* trailers have been submitted */
+ unsigned int output_eos : 1; /* output EOS in buffer/sent */
+
+ conn_rec *c2; /* connection processing stream */
const h2_priority *pref_priority; /* preferred priority for this stream */
apr_off_t out_frames; /* # of frames sent out */
@@ -132,13 +154,9 @@ h2_stream *h2_stream_create(int id, apr_pool_t *pool,
void h2_stream_destroy(h2_stream *stream);
/**
- * Prepare the stream so that processing may start.
- *
- * This is the time to allocated resources not needed before.
- *
- * @param stream the stream to prep
+ * Perform any late initialization before stream starts processing.
*/
-apr_status_t h2_stream_prep_processing(h2_stream *stream);
+apr_status_t h2_stream_prepare_processing(h2_stream *stream);
/*
* Set a new monitor for this stream, replacing any existing one. Can
@@ -153,6 +171,22 @@ void h2_stream_set_monitor(h2_stream *stream, h2_stream_monitor *monitor);
*/
void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev);
+/**
+ * Determine if stream is at given state.
+ * @param stream the stream to check
+ * @param state the state to look for
+ * @return != 0 iff stream is at given state.
+ */
+int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state);
+
+/**
+ * Determine if stream is reached given state or is past this state.
+ * @param stream the stream to check
+ * @param state the state to look for
+ * @return != 0 iff stream is at or past given state.
+ */
+int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state);
+
/**
* Cleanup references into requst processing.
*
@@ -198,6 +232,10 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream,
apr_status_t h2_stream_add_header(h2_stream *stream,
const char *name, size_t nlen,
const char *value, size_t vlen);
+
+/* End the construction of request headers */
+apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes);
+
apr_status_t h2_stream_send_frame(h2_stream *stream, int frame_type, int flags, size_t frame_len);
apr_status_t h2_stream_recv_frame(h2_stream *stream, int frame_type, int flags, size_t frame_len);
@@ -213,8 +251,6 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int frame_type, int flags,
apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
const uint8_t *data, size_t len);
-apr_status_t h2_stream_flush_input(h2_stream *stream);
-
/**
* Reset the stream. Stream write/reads will return errors afterwards.
*
@@ -224,31 +260,16 @@ apr_status_t h2_stream_flush_input(h2_stream *stream);
void h2_stream_rst(h2_stream *stream, int error_code);
/**
- * Determine if stream was closed already. This is true for
- * states H2_SS_CLOSED, H2_SS_CLEANUP. But not true
- * for H2_SS_CLOSED_L and H2_SS_CLOSED_R.
- *
- * @param stream the stream to check on
- * @return != 0 iff stream has been closed
+ * Stream input signals change. Take necessary actions.
+ * @param stream the stream to read output for
*/
-int h2_stream_was_closed(const h2_stream *stream);
+void h2_stream_on_input_change(h2_stream *stream);
/**
- * Do a speculative read on the stream output to determine the
- * amount of data that can be read.
- *
- * @param stream the stream to speculatively read from
- * @param plen (in-/out) number of bytes requested and on return amount of bytes that
- * may be read without blocking
- * @param peos (out) != 0 iff end of stream will be reached when reading plen
- * bytes (out value).
- * @param presponse (out) the response of one became available
- * @return APR_SUCCESS if out information was computed successfully.
- * APR_EAGAIN if not data is available and end of stream has not been
- * reached yet.
+ * Stream output signals change. Take necessary actions.
+ * @param stream the stream to read output for
*/
-apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
- int *peos, h2_headers **presponse);
+void h2_stream_on_output_change(h2_stream *stream);
/**
* Read a maximum number of bytes into the bucket brigade.
@@ -277,23 +298,34 @@ apr_table_t *h2_stream_get_trailers(h2_stream *stream);
/**
* Submit any server push promises on this stream and schedule
- * the tasks connection with these.
+ * the streams for these.
*
* @param stream the stream for which to submit
*/
-apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response);
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_stream_submit_pushes(h2_stream *stream,
+ ap_bucket_response *response);
+#else
+apr_status_t h2_stream_submit_pushes(h2_stream *stream,
+ struct h2_headers *response);
+#endif
/**
* Get priority information set for this stream.
*/
-const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
- h2_headers *response);
+#if AP_HAS_RESPONSE_BUCKETS
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
+ ap_bucket_response *response);
+#else
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
+ struct h2_headers *response);
+#endif
/**
* Return a textual representation of the stream state as in RFC 7540
* nomenclator, all caps, underscores.
*/
-const char *h2_stream_state_str(h2_stream *stream);
+const char *h2_stream_state_str(const h2_stream *stream);
/**
* Determine if stream is ready for submitting a response or a RST
@@ -301,8 +333,11 @@ const char *h2_stream_state_str(h2_stream *stream);
*/
int h2_stream_is_ready(h2_stream *stream);
+int h2_stream_wants_send_data(h2_stream *stream);
+
#define H2_STRM_MSG(s, msg) \
- "h2_stream(%ld-%d,%s): "msg, s->session->id, s->id, h2_stream_state_str(s)
+ "h2_stream(%d-%lu-%d,%s): "msg, s->session->child_num, \
+ (unsigned long)s->session->id, s->id, h2_stream_state_str(s)
#define H2_STRM_LOG(aplogno, s, msg) aplogno H2_STRM_MSG(s, msg)
diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c
index 5e73568..3799701 100644
--- a/modules/http2/h2_switch.c
+++ b/modules/http2/h2_switch.c
@@ -25,14 +25,17 @@
#include
#include
#include
+#include
#include
#include "h2_private.h"
+#include "h2.h"
#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_conn.h"
-#include "h2_h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_c2.h"
+#include "h2_protocol.h"
#include "h2_switch.h"
/*******************************************************************************
@@ -52,10 +55,9 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
apr_array_header_t *proposals)
{
int proposed = 0;
- int is_tls = h2_h2_is_tls(c);
- const char **protos = is_tls? h2_tls_protos : h2_clear_protos;
+ int is_tls = ap_ssl_conn_is_ssl(c);
+ const char **protos = is_tls? h2_protocol_ids_tls : h2_protocol_ids_clear;
- (void)s;
if (!h2_mpm_supported()) {
return DECLINED;
}
@@ -68,7 +70,7 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
return DECLINED;
}
- if (!h2_is_acceptable_connection(c, 0)) {
+ if (!h2_protocol_is_acceptable_c1(c, r, 0)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03084)
"protocol propose: connection requirements not met");
return DECLINED;
@@ -81,7 +83,7 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
*/
const char *p;
- if (!h2_allows_h2_upgrade(c)) {
+ if (!h2_c1_can_upgrade(r)) {
return DECLINED;
}
@@ -102,9 +104,10 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
/* We also allow switching only for requests that have no body.
*/
p = apr_table_get(r->headers_in, "Content-Length");
- if (p && strcmp(p, "0")) {
+ if ((p && strcmp(p, "0"))
+ || (!p && apr_table_get(r->headers_in, "Transfer-Encoding"))) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03087)
- "upgrade with content-length: %s, declined", p);
+ "upgrade with body declined");
return DECLINED;
}
}
@@ -124,11 +127,35 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
return proposed? DECLINED : OK;
}
+#if AP_HAS_RESPONSE_BUCKETS
+static void remove_output_filters_below(ap_filter_t *f, ap_filter_type ftype)
+{
+ ap_filter_t *fnext;
+
+ while (f && f->frec->ftype < ftype) {
+ fnext = f->next;
+ ap_remove_output_filter(f);
+ f = fnext;
+ }
+}
+
+static void remove_input_filters_below(ap_filter_t *f, ap_filter_type ftype)
+{
+ ap_filter_t *fnext;
+
+ while (f && f->frec->ftype < ftype) {
+ fnext = f->next;
+ ap_remove_input_filter(f);
+ f = fnext;
+ }
+}
+#endif
+
static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
const char *protocol)
{
int found = 0;
- const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
+ const char **protos = ap_ssl_conn_is_ssl(c)? h2_protocol_ids_tls : h2_protocol_ids_clear;
const char **p = protos;
(void)s;
@@ -145,35 +172,41 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
}
if (found) {
- h2_ctx *ctx = h2_ctx_get(c, 1);
-
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"switching protocol to '%s'", protocol);
- h2_ctx_protocol_set(ctx, protocol);
- h2_ctx_server_set(ctx, s);
-
+ h2_conn_ctx_create_for_c1(c, s, protocol);
+
if (r != NULL) {
apr_status_t status;
+#if AP_HAS_RESPONSE_BUCKETS
+ /* Switching in the middle of a request means that
+ * we have to send out the response to this one in h2
+ * format. So we need to take over the connection
+ * and remove all old filters with type up to the
+ * CONNEDCTION/NETWORK ones.
+ */
+ remove_input_filters_below(r->input_filters, AP_FTYPE_CONNECTION);
+ remove_output_filters_below(r->output_filters, AP_FTYPE_CONNECTION);
+#else
/* Switching in the middle of a request means that
* we have to send out the response to this one in h2
* format. So we need to take over the connection
* right away.
*/
ap_remove_input_filter_byhandle(r->input_filters, "http_in");
- ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
-
+#endif
/* Ok, start an h2_conn on this one. */
- h2_ctx_server_set(ctx, r->server);
- status = h2_conn_setup(ctx, r->connection, r);
+ status = h2_c1_setup(c, r, s);
+
if (status != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088)
"session setup");
- h2_ctx_clear(c);
+ h2_conn_ctx_detach(c);
return !OK;
}
- h2_conn_run(ctx, c);
+ h2_c1_run(c);
}
return OK;
}
@@ -183,7 +216,13 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
static const char *h2_protocol_get(const conn_rec *c)
{
- return h2_ctx_protocol_get(c);
+ h2_conn_ctx_t *ctx;
+
+ if (c->master) {
+ c = c->master;
+ }
+ ctx = h2_conn_ctx_get(c);
+ return ctx? ctx->protocol : NULL;
}
void h2_switch_register_hooks(void)
diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c
deleted file mode 100644
index 86fb026..0000000
--- a/modules/http2/h2_task.c
+++ /dev/null
@@ -1,769 +0,0 @@
-/* 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
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_bucket_beam.h"
-#include "h2_conn.h"
-#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_from_h1.h"
-#include "h2_h2.h"
-#include "h2_mplx.h"
-#include "h2_request.h"
-#include "h2_headers.h"
-#include "h2_session.h"
-#include "h2_stream.h"
-#include "h2_task.h"
-#include "h2_util.h"
-
-static void H2_TASK_OUT_LOG(int lvl, h2_task *task, apr_bucket_brigade *bb,
- const char *tag)
-{
- if (APLOG_C_IS_LEVEL(task->c, lvl)) {
- conn_rec *c = task->c;
- char buffer[4 * 1024];
- const char *line = "(null)";
- apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
-
- len = h2_util_bb_print(buffer, bmax, tag, "", bb);
- ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%s): %s",
- task->id, len? buffer : line);
- }
-}
-
-/*******************************************************************************
- * task input handling
- ******************************************************************************/
-
-static int input_ser_header(void *ctx, const char *name, const char *value)
-{
- h2_task *task = ctx;
- apr_brigade_printf(task->input.bb, NULL, NULL, "%s: %s\r\n", name, value);
- return 1;
-}
-
-/*******************************************************************************
- * task output handling
- ******************************************************************************/
-
-static apr_status_t open_output(h2_task *task)
-{
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03348)
- "h2_task(%s): open output to %s %s %s",
- task->id, task->request->method,
- task->request->authority,
- task->request->path);
- task->output.opened = 1;
- return h2_mplx_out_open(task->mplx, task->stream_id, task->output.beam);
-}
-
-static apr_status_t send_out(h2_task *task, apr_bucket_brigade* bb, int block)
-{
- apr_off_t written, left;
- apr_status_t status;
-
- apr_brigade_length(bb, 0, &written);
- H2_TASK_OUT_LOG(APLOG_TRACE2, task, bb, "h2_task send_out");
- h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(before)");
- /* engines send unblocking */
- status = h2_beam_send(task->output.beam, bb,
- block? APR_BLOCK_READ : APR_NONBLOCK_READ);
- h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(after)");
-
- if (APR_STATUS_IS_EAGAIN(status)) {
- apr_brigade_length(bb, 0, &left);
- written -= left;
- status = APR_SUCCESS;
- }
- if (status == APR_SUCCESS) {
- if (h2_task_logio_add_bytes_out) {
- h2_task_logio_add_bytes_out(task->c, written);
- }
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
- "h2_task(%s): send_out done", task->id);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c,
- "h2_task(%s): send_out (%ld bytes)",
- task->id, (long)written);
- }
- return status;
-}
-
-/* Bring the data from the brigade (which represents the result of the
- * request_rec out filter chain) into the h2_mplx for further sending
- * on the master connection.
- */
-static apr_status_t slave_out(h2_task *task, ap_filter_t* f,
- apr_bucket_brigade* bb)
-{
- apr_bucket *b;
- apr_status_t rv = APR_SUCCESS;
- int flush = 0, blocking;
-
- if (task->frozen) {
- h2_util_bb_log(task->c, task->stream_id, APLOG_TRACE2,
- "frozen task output write, ignored", bb);
- while (!APR_BRIGADE_EMPTY(bb)) {
- b = APR_BRIGADE_FIRST(bb);
- if (AP_BUCKET_IS_EOR(b)) {
- APR_BUCKET_REMOVE(b);
- task->eor = b;
- }
- else {
- apr_bucket_delete(b);
- }
- }
- return APR_SUCCESS;
- }
-
-send:
- /* we send block once we opened the output, so someone is there
- * reading it *and* the task is not assigned to a h2_req_engine */
- blocking = (!task->assigned && task->output.opened);
- for (b = APR_BRIGADE_FIRST(bb);
- b != APR_BRIGADE_SENTINEL(bb);
- b = APR_BUCKET_NEXT(b)) {
- if (APR_BUCKET_IS_FLUSH(b) || APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
- flush = 1;
- break;
- }
- }
-
- if (task->output.bb && !APR_BRIGADE_EMPTY(task->output.bb)) {
- /* still have data buffered from previous attempt.
- * setaside and append new data and try to pass the complete data */
- if (!APR_BRIGADE_EMPTY(bb)) {
- if (APR_SUCCESS != (rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool))) {
- goto out;
- }
- }
- rv = send_out(task, task->output.bb, blocking);
- }
- else {
- /* no data buffered previously, pass brigade directly */
- rv = send_out(task, bb, blocking);
-
- if (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
- /* output refused to buffer it all, time to open? */
- if (!task->output.opened && APR_SUCCESS == (rv = open_output(task))) {
- /* Make another attempt to send the data. With the output open,
- * the call might be blocking and send all data, so we do not need
- * to save the brigade */
- goto send;
- }
- else if (blocking && flush) {
- /* Need to keep on doing this. */
- goto send;
- }
-
- if (APR_SUCCESS == rv) {
- /* could not write all, buffer the rest */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, task->c, APLOGNO(03405)
- "h2_slave_out(%s): saving brigade", task->id);
- ap_assert(NULL);
- rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool);
- flush = 1;
- }
- }
- }
-
- if (APR_SUCCESS == rv && !task->output.opened && flush) {
- /* got a flush or could not write all, time to tell someone to read */
- rv = open_output(task);
- }
-out:
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, task->c,
- "h2_slave_out(%s): slave_out leave", task->id);
- return rv;
-}
-
-static apr_status_t output_finish(h2_task *task)
-{
- if (!task->output.opened) {
- return open_output(task);
- }
- return APR_SUCCESS;
-}
-
-/*******************************************************************************
- * task slave connection filters
- ******************************************************************************/
-
-static apr_status_t h2_filter_slave_in(ap_filter_t* f,
- apr_bucket_brigade* bb,
- ap_input_mode_t mode,
- apr_read_type_e block,
- apr_off_t readbytes)
-{
- h2_task *task;
- apr_status_t status = APR_SUCCESS;
- apr_bucket *b, *next;
- apr_off_t bblen;
- const int trace1 = APLOGctrace1(f->c);
- apr_size_t rmax = ((readbytes <= APR_SIZE_MAX)?
- (apr_size_t)readbytes : APR_SIZE_MAX);
-
- task = h2_ctx_cget_task(f->c);
- ap_assert(task);
-
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_slave_in(%s): read, mode=%d, block=%d, readbytes=%ld",
- task->id, mode, block, (long)readbytes);
- }
-
- if (mode == AP_MODE_INIT) {
- return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
- }
-
- if (f->c->aborted) {
- return APR_ECONNABORTED;
- }
-
- if (!task->input.bb) {
- return APR_EOF;
- }
-
- /* Cleanup brigades from those nasty 0 length non-meta buckets
- * that apr_brigade_split_line() sometimes produces. */
- for (b = APR_BRIGADE_FIRST(task->input.bb);
- b != APR_BRIGADE_SENTINEL(task->input.bb); b = next) {
- next = APR_BUCKET_NEXT(b);
- if (b->length == 0 && !APR_BUCKET_IS_METADATA(b)) {
- apr_bucket_delete(b);
- }
- }
-
- while (APR_BRIGADE_EMPTY(task->input.bb)) {
- /* Get more input data for our request. */
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
- "h2_slave_in(%s): get more data from mplx, block=%d, "
- "readbytes=%ld", task->id, block, (long)readbytes);
- }
- if (task->input.beam) {
- status = h2_beam_receive(task->input.beam, task->input.bb, block,
- 128*1024);
- }
- else {
- status = APR_EOF;
- }
-
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
- "h2_slave_in(%s): read returned", task->id);
- }
- if (APR_STATUS_IS_EAGAIN(status)
- && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
- /* chunked input handling does not seem to like it if we
- * return with APR_EAGAIN from a GETLINE read...
- * upload 100k test on test-ser.example.org hangs */
- status = APR_SUCCESS;
- }
- else if (APR_STATUS_IS_EOF(status)) {
- break;
- }
- else if (status != APR_SUCCESS) {
- return status;
- }
-
- if (trace1) {
- h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2,
- "input.beam recv raw", task->input.bb);
- }
- if (h2_task_logio_add_bytes_in) {
- apr_brigade_length(bb, 0, &bblen);
- h2_task_logio_add_bytes_in(f->c, bblen);
- }
- }
-
- /* Nothing there, no more data to get. Return APR_EAGAIN on
- * speculative reads, this is ap_check_pipeline()'s trick to
- * see if the connection needs closing. */
- if (status == APR_EOF && APR_BRIGADE_EMPTY(task->input.bb)) {
- return (mode == AP_MODE_SPECULATIVE)? APR_EAGAIN : APR_EOF;
- }
-
- if (trace1) {
- h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2,
- "task_input.bb", task->input.bb);
- }
-
- if (APR_BRIGADE_EMPTY(task->input.bb)) {
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
- "h2_slave_in(%s): no data", task->id);
- }
- return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
- }
-
- if (mode == AP_MODE_EXHAUSTIVE) {
- /* return all we have */
- APR_BRIGADE_CONCAT(bb, task->input.bb);
- }
- else if (mode == AP_MODE_READBYTES) {
- status = h2_brigade_concat_length(bb, task->input.bb, rmax);
- }
- else if (mode == AP_MODE_SPECULATIVE) {
- status = h2_brigade_copy_length(bb, task->input.bb, rmax);
- }
- 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, task->input.bb, 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;
- if (trace1) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
- "h2_slave_in(%s): getline: %s",
- task->id, buffer);
- }
- }
- }
- 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(03472)
- "h2_slave_in(%s), unsupported READ mode %d",
- task->id, mode);
- status = APR_ENOTIMPL;
- }
-
- if (trace1) {
- apr_brigade_length(bb, 0, &bblen);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
- "h2_slave_in(%s): %ld data bytes", task->id, (long)bblen);
- }
- return status;
-}
-
-static apr_status_t h2_filter_slave_output(ap_filter_t* filter,
- apr_bucket_brigade* brigade)
-{
- h2_task *task = h2_ctx_cget_task(filter->c);
- apr_status_t status;
-
- ap_assert(task);
- status = slave_out(task, filter, brigade);
- if (status != APR_SUCCESS) {
- h2_task_rst(task, H2_ERR_INTERNAL_ERROR);
- }
- return status;
-}
-
-static apr_status_t h2_filter_parse_h1(ap_filter_t* f, apr_bucket_brigade* bb)
-{
- h2_task *task = h2_ctx_cget_task(f->c);
- apr_status_t status;
-
- ap_assert(task);
- /* There are cases where we need to parse a serialized http/1.1
- * response. One example is a 100-continue answer in serialized mode
- * or via a mod_proxy setup */
- while (bb && !task->output.sent_response) {
- status = h2_from_h1_parse_response(task, f, bb);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
- "h2_task(%s): parsed response", task->id);
- if (APR_BRIGADE_EMPTY(bb) || status != APR_SUCCESS) {
- return status;
- }
- }
-
- return ap_pass_brigade(f->next, bb);
-}
-
-/*******************************************************************************
- * task things
- ******************************************************************************/
-
-int h2_task_can_redo(h2_task *task) {
- if (task->input.beam && h2_beam_was_received(task->input.beam)) {
- /* cannot repeat that. */
- return 0;
- }
- return (!strcmp("GET", task->request->method)
- || !strcmp("HEAD", task->request->method)
- || !strcmp("OPTIONS", task->request->method));
-}
-
-void h2_task_redo(h2_task *task)
-{
- task->rst_error = 0;
-}
-
-void h2_task_rst(h2_task *task, int error)
-{
- task->rst_error = error;
- if (task->input.beam) {
- h2_beam_leave(task->input.beam);
- }
- if (!task->worker_done) {
- h2_beam_abort(task->output.beam);
- }
- if (task->c) {
- task->c->aborted = 1;
- }
-}
-
-/*******************************************************************************
- * Register various hooks
- */
-static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
-static int h2_task_pre_conn(conn_rec* c, void *arg);
-static int h2_task_process_conn(conn_rec* c);
-
-APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_task_logio_add_bytes_in;
-APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_task_logio_add_bytes_out;
-
-void h2_task_register_hooks(void)
-{
- /* This hook runs on new connections before mod_ssl has a say.
- * Its purpose is to prevent mod_ssl from touching our pseudo-connections
- * for streams.
- */
- ap_hook_pre_connection(h2_task_pre_conn,
- NULL, mod_ssl, APR_HOOK_FIRST);
- /* When the connection processing actually starts, we might
- * take over, if the connection is for a task.
- */
- ap_hook_process_connection(h2_task_process_conn,
- NULL, NULL, APR_HOOK_FIRST);
-
- ap_register_input_filter("H2_SLAVE_IN", h2_filter_slave_in,
- NULL, AP_FTYPE_NETWORK);
- ap_register_output_filter("H2_SLAVE_OUT", h2_filter_slave_output,
- NULL, AP_FTYPE_NETWORK);
- ap_register_output_filter("H2_PARSE_H1", h2_filter_parse_h1,
- NULL, AP_FTYPE_NETWORK);
-
- ap_register_input_filter("H2_REQUEST", h2_filter_request_in,
- NULL, AP_FTYPE_PROTOCOL);
- ap_register_output_filter("H2_RESPONSE", h2_filter_headers_out,
- NULL, AP_FTYPE_PROTOCOL);
- ap_register_output_filter("H2_TRAILERS_OUT", h2_filter_trailers_out,
- NULL, AP_FTYPE_PROTOCOL);
-}
-
-/* post config init */
-apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s)
-{
- h2_task_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
- h2_task_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
-
- return APR_SUCCESS;
-}
-
-static int h2_task_pre_conn(conn_rec* c, void *arg)
-{
- h2_ctx *ctx;
-
- if (!c->master) {
- return OK;
- }
-
- ctx = h2_ctx_get(c, 0);
- (void)arg;
- if (h2_ctx_is_task(ctx)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
- "h2_h2, pre_connection, found stream task");
- ap_add_input_filter("H2_SLAVE_IN", NULL, NULL, c);
- ap_add_output_filter("H2_PARSE_H1", NULL, NULL, c);
- ap_add_output_filter("H2_SLAVE_OUT", NULL, NULL, c);
- }
- return OK;
-}
-
-h2_task *h2_task_create(conn_rec *slave, int stream_id,
- const h2_request *req, h2_mplx *m,
- h2_bucket_beam *input,
- apr_interval_time_t timeout,
- apr_size_t output_max_mem)
-{
- apr_pool_t *pool;
- h2_task *task;
-
- ap_assert(slave);
- ap_assert(req);
-
- apr_pool_create(&pool, slave->pool);
- task = apr_pcalloc(pool, sizeof(h2_task));
- if (task == NULL) {
- return NULL;
- }
- task->id = "000";
- task->stream_id = stream_id;
- task->c = slave;
- task->mplx = m;
- task->pool = pool;
- task->request = req;
- task->timeout = timeout;
- task->input.beam = input;
- task->output.max_buffer = output_max_mem;
-
- return task;
-}
-
-void h2_task_destroy(h2_task *task)
-{
- if (task->output.beam) {
- h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "task_destroy");
- h2_beam_destroy(task->output.beam);
- task->output.beam = NULL;
- }
-
- if (task->eor) {
- apr_bucket_destroy(task->eor);
- }
- if (task->pool) {
- apr_pool_destroy(task->pool);
- }
-}
-
-apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id)
-{
- conn_rec *c;
-
- ap_assert(task);
- c = task->c;
- task->worker_started = 1;
- task->started_at = apr_time_now();
-
- if (c->master) {
- /* Each conn_rec->id is supposed to be unique at a point in time. Since
- * some modules (and maybe external code) uses this id as an identifier
- * for the request_rec they handle, it needs to be unique for slave
- * connections also.
- * The connection id is generated by the MPM and most MPMs use the formula
- * id := (child_num * max_threads) + thread_num
- * which means that there is a maximum id of about
- * idmax := max_child_count * max_threads
- * If we assume 2024 child processes with 2048 threads max, we get
- * idmax ~= 2024 * 2048 = 2 ** 22
- * On 32 bit systems, we have not much space left, but on 64 bit systems
- * (and higher?) we can use the upper 32 bits without fear of collision.
- * 32 bits is just what we need, since a connection can only handle so
- * many streams.
- */
- int slave_id, free_bits;
-
- task->id = apr_psprintf(task->pool, "%ld-%d", c->master->id,
- task->stream_id);
- if (sizeof(unsigned long) >= 8) {
- free_bits = 32;
- slave_id = task->stream_id;
- }
- else {
- /* Assume we have a more limited number of threads/processes
- * and h2 workers on a 32-bit system. Use the worker instead
- * of the stream id. */
- free_bits = 8;
- slave_id = worker_id;
- }
- task->c->id = (c->master->id << free_bits)^slave_id;
- c->keepalive = AP_CONN_KEEPALIVE;
- }
-
- h2_beam_create(&task->output.beam, c->pool, task->stream_id, "output",
- H2_BEAM_OWNER_SEND, 0, task->timeout);
- if (!task->output.beam) {
- return APR_ENOMEM;
- }
-
- h2_beam_buffer_size_set(task->output.beam, task->output.max_buffer);
- h2_beam_send_from(task->output.beam, task->pool);
-
- h2_ctx_create_for(c, task);
- apr_table_setn(c->notes, H2_TASK_ID_NOTE, task->id);
-
- h2_slave_run_pre_connection(c, ap_get_conn_socket(c));
-
- task->input.bb = apr_brigade_create(task->pool, c->bucket_alloc);
- if (task->request->serialize) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): serialize request %s %s",
- task->id, task->request->method, task->request->path);
- apr_brigade_printf(task->input.bb, NULL,
- NULL, "%s %s HTTP/1.1\r\n",
- task->request->method, task->request->path);
- apr_table_do(input_ser_header, task, task->request->headers, NULL);
- apr_brigade_puts(task->input.bb, NULL, NULL, "\r\n");
- }
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): process connection", task->id);
-
- task->c->current_thread = thread;
- ap_run_process_connection(c);
-
- if (task->frozen) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): process_conn returned frozen task",
- task->id);
- /* cleanup delayed */
- return APR_EAGAIN;
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): processing done", task->id);
- return output_finish(task);
- }
-}
-
-static apr_status_t h2_task_process_request(h2_task *task, conn_rec *c)
-{
- const h2_request *req = task->request;
- conn_state_t *cs = c->cs;
- request_rec *r;
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): create request_rec", task->id);
- r = h2_request_create_rec(req, c);
- if (r && (r->status == HTTP_OK)) {
- /* set timeouts for virtual host of request */
- if (task->timeout != r->server->timeout) {
- task->timeout = r->server->timeout;
- h2_beam_timeout_set(task->output.beam, task->timeout);
- if (task->input.beam) {
- h2_beam_timeout_set(task->input.beam, task->timeout);
- }
- }
-
- ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
-
- if (cs) {
- cs->state = CONN_STATE_HANDLER;
- }
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): start process_request", task->id);
-
- /* Add the raw bytes of the request (e.g. header frame lengths to
- * the logio for this request. */
- if (req->raw_bytes && h2_task_logio_add_bytes_in) {
- h2_task_logio_add_bytes_in(c, req->raw_bytes);
- }
-
- ap_process_request(r);
-
- if (task->frozen) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): process_request frozen", task->id);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): process_request done", task->id);
- }
-
- /* After the call to ap_process_request, the
- * request pool may have been deleted. We set
- * r=NULL here to ensure that any dereference
- * of r that might be added later in this function
- * will result in a segfault immediately instead
- * of nondeterministic failures later.
- */
- if (cs)
- cs->state = CONN_STATE_WRITE_COMPLETION;
- r = NULL;
- }
- else if (!r) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): create request_rec failed, r=NULL", task->id);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s): create request_rec failed, r->status=%d",
- task->id, r->status);
- }
-
- return APR_SUCCESS;
-}
-
-static int h2_task_process_conn(conn_rec* c)
-{
- h2_ctx *ctx;
-
- if (!c->master) {
- return DECLINED;
- }
-
- ctx = h2_ctx_get(c, 0);
- if (h2_ctx_is_task(ctx)) {
- if (!ctx->task->request->serialize) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_h2, processing request directly");
- h2_task_process_request(ctx->task, c);
- return DONE;
- }
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "h2_task(%s), serialized handling", ctx->task->id);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
- "slave_conn(%ld): has no task", c->id);
- }
- return DECLINED;
-}
-
-apr_status_t h2_task_freeze(h2_task *task)
-{
- if (!task->frozen) {
- task->frozen = 1;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03406)
- "h2_task(%s), frozen", task->id);
- }
- return APR_SUCCESS;
-}
-
-apr_status_t h2_task_thaw(h2_task *task)
-{
- if (task->frozen) {
- task->frozen = 0;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03407)
- "h2_task(%s), thawed", task->id);
- }
- task->thawed = 1;
- return APR_SUCCESS;
-}
-
-int h2_task_has_thawed(h2_task *task)
-{
- return task->thawed;
-}
diff --git a/modules/http2/h2_task.h b/modules/http2/h2_task.h
deleted file mode 100644
index ab6a746..0000000
--- a/modules/http2/h2_task.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/* 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.
- */
-
-#ifndef __mod_h2__h2_task__
-#define __mod_h2__h2_task__
-
-#include
-
-/**
- * A h2_task fakes a HTTP/1.1 request from the data in a HTTP/2 stream
- * (HEADER+CONT.+DATA) the module recieves.
- *
- * In order to answer a HTTP/2 stream, we want all Apache httpd infrastructure
- * to be involved as usual, as if this stream can as a separate HTTP/1.1
- * request. The basic trickery to do so was derived from google's mod_spdy
- * source. Basically, we fake a new conn_rec object, even with its own
- * socket and give it to ap_process_connection().
- *
- * Since h2_task instances are executed in separate threads, we may have
- * different lifetimes than our h2_stream or h2_session instances. Basically,
- * we would like to be as standalone as possible.
- *
- * Finally, to keep certain connection level filters, such as ourselves and
- * especially mod_ssl ones, from messing with our data, we need a filter
- * of our own to disble those.
- */
-
-struct h2_bucket_beam;
-struct h2_conn;
-struct h2_mplx;
-struct h2_task;
-struct h2_req_engine;
-struct h2_request;
-struct h2_response_parser;
-struct h2_stream;
-struct h2_worker;
-
-typedef struct h2_task h2_task;
-
-struct h2_task {
- const char *id;
- int stream_id;
- conn_rec *c;
- apr_pool_t *pool;
-
- const struct h2_request *request;
- apr_interval_time_t timeout;
- int rst_error; /* h2 related stream abort error */
-
- struct {
- struct h2_bucket_beam *beam;
- unsigned int eos : 1;
- apr_bucket_brigade *bb;
- apr_bucket_brigade *bbchunk;
- apr_off_t chunked_total;
- } input;
- struct {
- struct h2_bucket_beam *beam;
- unsigned int opened : 1;
- unsigned int sent_response : 1;
- unsigned int copy_files : 1;
- struct h2_response_parser *rparser;
- apr_bucket_brigade *bb;
- apr_size_t max_buffer;
- } output;
-
- struct h2_mplx *mplx;
-
- unsigned int filters_set : 1;
- unsigned int frozen : 1;
- unsigned int thawed : 1;
- unsigned int worker_started : 1; /* h2_worker started processing */
- unsigned int worker_done : 1; /* h2_worker finished */
-
- apr_time_t started_at; /* when processing started */
- apr_time_t done_at; /* when processing was done */
- apr_bucket *eor;
-
- struct h2_req_engine *engine; /* engine hosted by this task */
- struct h2_req_engine *assigned; /* engine that task has been assigned to */
-};
-
-h2_task *h2_task_create(conn_rec *slave, int stream_id,
- const h2_request *req, struct h2_mplx *m,
- struct h2_bucket_beam *input,
- apr_interval_time_t timeout,
- apr_size_t output_max_mem);
-
-void h2_task_destroy(h2_task *task);
-
-apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id);
-
-void h2_task_redo(h2_task *task);
-int h2_task_can_redo(h2_task *task);
-
-/**
- * Reset the task with the given error code, resets all input/output.
- */
-void h2_task_rst(h2_task *task, int error);
-
-void h2_task_register_hooks(void);
-/*
- * One time, post config intialization.
- */
-apr_status_t h2_task_init(apr_pool_t *pool, server_rec *s);
-
-extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_task_logio_add_bytes_in;
-extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_task_logio_add_bytes_out;
-
-apr_status_t h2_task_freeze(h2_task *task);
-apr_status_t h2_task_thaw(h2_task *task);
-int h2_task_has_thawed(h2_task *task);
-
-#endif /* defined(__mod_h2__h2_task__) */
diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c
index 9dacd8b..8e53ceb 100644
--- a/modules/http2/h2_util.c
+++ b/modules/http2/h2_util.c
@@ -22,11 +22,13 @@
#include
#include
#include
+#include
#include
#include
#include "h2.h"
+#include "h2_headers.h"
#include "h2_util.h"
/* h2_log2(n) iff n is a power of 2 */
@@ -55,7 +57,7 @@ unsigned char h2_log2(int n)
if (!(n & 0x80000000u)) {
lz += 1;
}
-
+
return 31 - lz;
}
@@ -75,26 +77,6 @@ size_t h2_util_hex_dump(char *buffer, size_t maxlen,
return strlen(buffer);
}
-size_t h2_util_header_print(char *buffer, size_t maxlen,
- const char *name, size_t namelen,
- const char *value, size_t valuelen)
-{
- size_t offset = 0;
- size_t i;
- for (i = 0; i < namelen && offset < maxlen; ++i, ++offset) {
- buffer[offset] = name[i];
- }
- for (i = 0; i < 2 && offset < maxlen; ++i, ++offset) {
- buffer[offset] = ": "[i];
- }
- for (i = 0; i < valuelen && offset < maxlen; ++i, ++offset) {
- buffer[offset] = value[i];
- }
- buffer[offset] = '\0';
- return offset;
-}
-
-
void h2_util_camel_case_header(char *s, size_t len)
{
size_t start = 1;
@@ -104,7 +86,7 @@ void h2_util_camel_case_header(char *s, size_t len)
if (s[i] >= 'a' && s[i] <= 'z') {
s[i] -= 'a' - 'A';
}
-
+
start = 0;
}
else if (s[i] == '-') {
@@ -113,16 +95,16 @@ void h2_util_camel_case_header(char *s, size_t len)
}
}
-/* base64 url encoding ****************************************************************************/
+/* base64 url encoding */
#define N6 (unsigned int)-1
static const unsigned int BASE64URL_UINT6[] = {
/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 0 */
- N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 1 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 1 */
N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /* 2 */
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /* 3 */
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /* 3 */
N6, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /* 5 */
N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */
@@ -148,7 +130,7 @@ static const unsigned char BASE64URL_CHARS[] = {
#define BASE64URL_CHAR(x) BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
-apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
+apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
apr_pool_t *pool)
{
const unsigned char *e = (const unsigned char *)encoded;
@@ -156,14 +138,14 @@ apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
unsigned char *d;
unsigned int n;
long len, mlen, remain, i;
-
+
while (*p && BASE64URL_UINT6[ *p ] != N6) {
++p;
}
len = (int)(p - e);
mlen = (len/4)*4;
*decoded = apr_pcalloc(pool, (apr_size_t)len + 1);
-
+
i = 0;
d = (unsigned char*)*decoded;
for (; i < mlen; i += 4) {
@@ -197,14 +179,14 @@ apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
return (apr_size_t)(mlen/4*3 + remain);
}
-const char *h2_util_base64url_encode(const char *data,
+const char *h2_util_base64url_encode(const char *data,
apr_size_t dlen, apr_pool_t *pool)
{
int i, len = (int)dlen;
apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
const unsigned char *udata = (const unsigned char*)data;
unsigned char *enc, *p = apr_pcalloc(pool, slen);
-
+
enc = p;
for (i = 0; i < len-2; i+= 3) {
*p++ = BASE64URL_CHAR( (udata[i] >> 2) );
@@ -212,7 +194,7 @@ const char *h2_util_base64url_encode(const char *data,
*p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) );
*p++ = BASE64URL_CHAR( (udata[i+2]) );
}
-
+
if (i < len) {
*p++ = BASE64URL_CHAR( (udata[i] >> 2) );
if (i == (len - 1)) {
@@ -248,7 +230,7 @@ h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int)
return ih;
}
-size_t h2_ihash_count(h2_ihash_t *ih)
+unsigned int h2_ihash_count(h2_ihash_t *ih)
{
return apr_hash_count(ih->hash);
}
@@ -268,7 +250,7 @@ typedef struct {
void *ctx;
} iter_ctx;
-static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen,
+static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen,
const void *val)
{
iter_ctx *ictx = ctx;
@@ -326,7 +308,7 @@ size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max)
{
collect_ctx ctx;
size_t i;
-
+
ctx.ih = ih;
ctx.buffer = buffer;
ctx.max = max;
@@ -344,19 +326,17 @@ size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max)
static void iq_grow(h2_iqueue *q, int nlen);
static void iq_swap(h2_iqueue *q, int i, int j);
-static int iq_bubble_up(h2_iqueue *q, int i, int top,
+static int iq_bubble_up(h2_iqueue *q, int i, int top,
h2_iq_cmp *cmp, void *ctx);
-static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
+static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
h2_iq_cmp *cmp, void *ctx);
h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity)
{
h2_iqueue *q = apr_pcalloc(pool, sizeof(h2_iqueue));
- if (q) {
- q->pool = pool;
- iq_grow(q, capacity);
- q->nelts = 0;
- }
+ q->pool = pool;
+ iq_grow(q, capacity);
+ q->nelts = 0;
return q;
}
@@ -374,7 +354,7 @@ int h2_iq_count(h2_iqueue *q)
int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
{
int i;
-
+
if (h2_iq_contains(q, sid)) {
return 0;
}
@@ -384,7 +364,7 @@ int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
i = (q->head + q->nelts) % q->nalloc;
q->elts[i] = sid;
++q->nelts;
-
+
if (cmp) {
/* bubble it to the front of the queue */
iq_bubble_up(q, i, q->head, cmp, ctx);
@@ -405,7 +385,7 @@ int h2_iq_remove(h2_iqueue *q, int sid)
break;
}
}
-
+
if (i < q->nelts) {
++i;
for (; i < q->nelts; ++i) {
@@ -425,23 +405,23 @@ void h2_iq_clear(h2_iqueue *q)
void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
{
/* Assume that changes in ordering are minimal. This needs,
- * best case, q->nelts - 1 comparisions to check that nothing
+ * best case, q->nelts - 1 comparisons to check that nothing
* changed.
*/
if (q->nelts > 0) {
int i, ni, prev, last;
-
+
/* Start at the end of the queue and create a tail of sorted
* entries. Make that tail one element longer in each iteration.
*/
last = i = (q->head + q->nelts - 1) % q->nalloc;
while (i != q->head) {
prev = (q->nalloc + i - 1) % q->nalloc;
-
+
ni = iq_bubble_up(q, i, prev, cmp, ctx);
if (ni == prev) {
/* i bubbled one up, bubble the new i down, which
- * keeps all tasks below i sorted. */
+ * keeps all ints below i sorted. */
iq_bubble_down(q, i, last, cmp, ctx);
}
i = prev;
@@ -453,21 +433,21 @@ void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
int h2_iq_shift(h2_iqueue *q)
{
int sid;
-
+
if (q->nelts <= 0) {
return 0;
}
-
+
sid = q->elts[q->head];
q->head = (q->head + 1) % q->nalloc;
q->nelts--;
-
+
return sid;
}
size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max)
{
- int i;
+ size_t i;
for (i = 0; i < max; ++i) {
pint[i] = h2_iq_shift(q);
if (pint[i] == 0) {
@@ -483,7 +463,7 @@ static void iq_grow(h2_iqueue *q, int nlen)
int *nq = apr_pcalloc(q->pool, sizeof(int) * nlen);
if (q->nelts > 0) {
int l = ((q->head + q->nelts) % q->nalloc) - q->head;
-
+
memmove(nq, q->elts + q->head, sizeof(int) * l);
if (l < q->nelts) {
/* elts wrapped, append elts in [0, remain] to nq */
@@ -504,11 +484,11 @@ static void iq_swap(h2_iqueue *q, int i, int j)
q->elts[j] = x;
}
-static int iq_bubble_up(h2_iqueue *q, int i, int top,
- h2_iq_cmp *cmp, void *ctx)
+static int iq_bubble_up(h2_iqueue *q, int i, int top,
+ h2_iq_cmp *cmp, void *ctx)
{
int prev;
- while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top)
+ while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top)
&& (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) {
iq_swap(q, prev, i);
i = prev;
@@ -516,11 +496,11 @@ static int iq_bubble_up(h2_iqueue *q, int i, int top,
return i;
}
-static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
+static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
h2_iq_cmp *cmp, void *ctx)
{
int next;
- while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom)
+ while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom)
&& (*cmp)(q->elts[i], q->elts[next], ctx) > 0) {
iq_swap(q, next, i);
i = next;
@@ -545,9 +525,10 @@ int h2_iq_contains(h2_iqueue *q, int sid)
struct h2_fifo {
void **elems;
- int nelems;
+ int capacity;
int set;
- int head;
+ int in;
+ int out;
int count;
int aborted;
apr_thread_mutex_t *lock;
@@ -555,12 +536,7 @@ struct h2_fifo {
apr_thread_cond_t *not_full;
};
-static int nth_index(h2_fifo *fifo, int n)
-{
- return (fifo->head + n) % fifo->nelems;
-}
-
-static apr_status_t fifo_destroy(void *data)
+static apr_status_t fifo_destroy(void *data)
{
h2_fifo *fifo = data;
@@ -574,21 +550,21 @@ static apr_status_t fifo_destroy(void *data)
static int index_of(h2_fifo *fifo, void *elem)
{
int i;
-
- for (i = 0; i < fifo->count; ++i) {
- if (elem == fifo->elems[nth_index(fifo, i)]) {
+
+ for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
+ if (elem == fifo->elems[i]) {
return i;
}
}
return -1;
}
-static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool,
+static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool,
int capacity, int as_set)
{
apr_status_t rv;
h2_fifo *fifo;
-
+
fifo = apr_pcalloc(pool, sizeof(*fifo));
if (fifo == NULL) {
return APR_ENOMEM;
@@ -614,9 +590,9 @@ static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool,
if (fifo->elems == NULL) {
return APR_ENOMEM;
}
- fifo->nelems = capacity;
+ fifo->capacity = capacity;
fifo->set = as_set;
-
+
*pfifo = fifo;
apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
@@ -638,15 +614,6 @@ apr_status_t h2_fifo_term(h2_fifo *fifo)
apr_status_t rv;
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
fifo->aborted = 1;
- apr_thread_mutex_unlock(fifo->lock);
- }
- return rv;
-}
-
-apr_status_t h2_fifo_interrupt(h2_fifo *fifo)
-{
- apr_status_t rv;
- if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
apr_thread_cond_broadcast(fifo->not_empty);
apr_thread_cond_broadcast(fifo->not_full);
apr_thread_mutex_unlock(fifo->lock);
@@ -656,7 +623,12 @@ apr_status_t h2_fifo_interrupt(h2_fifo *fifo)
int h2_fifo_count(h2_fifo *fifo)
{
- return fifo->count;
+ int n;
+
+ apr_thread_mutex_lock(fifo->lock);
+ n = fifo->count;
+ apr_thread_mutex_unlock(fifo->lock);
+ return n;
}
static apr_status_t check_not_empty(h2_fifo *fifo, int block)
@@ -683,9 +655,9 @@ static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
/* set mode, elem already member */
return APR_EEXIST;
}
- else if (fifo->count == fifo->nelems) {
+ else if (fifo->count == fifo->capacity) {
if (block) {
- while (fifo->count == fifo->nelems) {
+ while (fifo->count == fifo->capacity) {
if (fifo->aborted) {
return APR_EOF;
}
@@ -696,12 +668,14 @@ static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
return APR_EAGAIN;
}
}
-
- ap_assert(fifo->count < fifo->nelems);
- fifo->elems[nth_index(fifo, fifo->count)] = elem;
+
+ fifo->elems[fifo->in++] = elem;
+ if (fifo->in >= fifo->capacity) {
+ fifo->in -= fifo->capacity;
+ }
++fifo->count;
if (fifo->count == 1) {
- apr_thread_cond_broadcast(fifo->not_empty);
+ apr_thread_cond_signal(fifo->not_empty);
}
return APR_SUCCESS;
}
@@ -709,10 +683,6 @@ static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
static apr_status_t fifo_push(h2_fifo *fifo, void *elem, int block)
{
apr_status_t rv;
-
- if (fifo->aborted) {
- return APR_EOF;
- }
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
rv = fifo_push_int(fifo, elem, block);
@@ -734,18 +704,20 @@ apr_status_t h2_fifo_try_push(h2_fifo *fifo, void *elem)
static apr_status_t pull_head(h2_fifo *fifo, void **pelem, int block)
{
apr_status_t rv;
-
+ int was_full;
+
if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) {
*pelem = NULL;
return rv;
}
- *pelem = fifo->elems[fifo->head];
+ *pelem = fifo->elems[fifo->out++];
+ if (fifo->out >= fifo->capacity) {
+ fifo->out -= fifo->capacity;
+ }
+ was_full = (fifo->count == fifo->capacity);
--fifo->count;
- if (fifo->count > 0) {
- fifo->head = nth_index(fifo, 1);
- if (fifo->count+1 == fifo->nelems) {
- apr_thread_cond_broadcast(fifo->not_full);
- }
+ if (was_full) {
+ apr_thread_cond_broadcast(fifo->not_full);
}
return APR_SUCCESS;
}
@@ -753,11 +725,7 @@ static apr_status_t pull_head(h2_fifo *fifo, void **pelem, int block)
static apr_status_t fifo_pull(h2_fifo *fifo, void **pelem, int block)
{
apr_status_t rv;
-
- if (fifo->aborted) {
- return APR_EOF;
- }
-
+
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
rv = pull_head(fifo, pelem, block);
apr_thread_mutex_unlock(fifo->lock);
@@ -779,11 +747,11 @@ static apr_status_t fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx, int
{
apr_status_t rv;
void *elem;
-
+
if (fifo->aborted) {
return APR_EOF;
}
-
+
if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
if (APR_SUCCESS == (rv = pull_head(fifo, &elem, block))) {
switch (fn(elem, ctx)) {
@@ -812,28 +780,58 @@ apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx)
apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
{
apr_status_t rv;
-
+
if (fifo->aborted) {
return APR_EOF;
}
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
- int i, rc;
- void *e;
-
- rc = 0;
- for (i = 0; i < fifo->count; ++i) {
- e = fifo->elems[nth_index(fifo, i)];
- if (e == elem) {
- ++rc;
- }
- else if (rc) {
- fifo->elems[nth_index(fifo, i-rc)] = e;
+ int i, last_count = fifo->count;
+
+ for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
+ if (fifo->elems[i] == elem) {
+ --fifo->count;
+ if (fifo->count == 0) {
+ fifo->out = fifo->in = 0;
+ }
+ else if (i == fifo->out) {
+ /* first element */
+ ++fifo->out;
+ if (fifo->out >= fifo->capacity) {
+ fifo->out -= fifo->capacity;
+ }
+ }
+ else if (((i + 1) % fifo->capacity) == fifo->in) {
+ /* last element */
+ --fifo->in;
+ if (fifo->in < 0) {
+ fifo->in += fifo->capacity;
+ }
+ }
+ else if (i > fifo->out) {
+ /* between out and in/capacity, move elements below up */
+ memmove(&fifo->elems[fifo->out+1], &fifo->elems[fifo->out],
+ (i - fifo->out) * sizeof(void*));
+ ++fifo->out;
+ if (fifo->out >= fifo->capacity) {
+ fifo->out -= fifo->capacity;
+ }
+ }
+ else {
+ /* we wrapped around, move elements above down */
+ AP_DEBUG_ASSERT((fifo->in - i - 1) > 0);
+ AP_DEBUG_ASSERT((fifo->in - i - 1) < fifo->capacity);
+ memmove(&fifo->elems[i], &fifo->elems[i + 1],
+ (fifo->in - i - 1) * sizeof(void*));
+ --fifo->in;
+ if (fifo->in < 0) {
+ fifo->in += fifo->capacity;
+ }
+ }
}
}
- if (rc) {
- fifo->count -= rc;
- if (fifo->count + rc == fifo->nelems) {
+ if (fifo->count != last_count) {
+ if (last_count == fifo->capacity) {
apr_thread_cond_broadcast(fifo->not_full);
}
rv = APR_SUCCESS;
@@ -841,7 +839,7 @@ apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
else {
rv = APR_EAGAIN;
}
-
+
apr_thread_mutex_unlock(fifo->lock);
}
return rv;
@@ -853,7 +851,7 @@ apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
struct h2_ififo {
int *elems;
- int nelems;
+ int capacity;
int set;
int head;
int count;
@@ -863,12 +861,12 @@ struct h2_ififo {
apr_thread_cond_t *not_full;
};
-static int inth_index(h2_ififo *fifo, int n)
+static int inth_index(h2_ififo *fifo, int n)
{
- return (fifo->head + n) % fifo->nelems;
+ return (fifo->head + n) % fifo->capacity;
}
-static apr_status_t ififo_destroy(void *data)
+static apr_status_t ififo_destroy(void *data)
{
h2_ififo *fifo = data;
@@ -882,7 +880,7 @@ static apr_status_t ififo_destroy(void *data)
static int iindex_of(h2_ififo *fifo, int id)
{
int i;
-
+
for (i = 0; i < fifo->count; ++i) {
if (id == fifo->elems[inth_index(fifo, i)]) {
return i;
@@ -891,12 +889,12 @@ static int iindex_of(h2_ififo *fifo, int id)
return -1;
}
-static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool,
+static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool,
int capacity, int as_set)
{
apr_status_t rv;
h2_ififo *fifo;
-
+
fifo = apr_pcalloc(pool, sizeof(*fifo));
if (fifo == NULL) {
return APR_ENOMEM;
@@ -922,9 +920,9 @@ static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool,
if (fifo->elems == NULL) {
return APR_ENOMEM;
}
- fifo->nelems = capacity;
+ fifo->capacity = capacity;
fifo->set = as_set;
-
+
*pfifo = fifo;
apr_pool_cleanup_register(pool, fifo, ififo_destroy, apr_pool_cleanup_null);
@@ -946,15 +944,6 @@ apr_status_t h2_ififo_term(h2_ififo *fifo)
apr_status_t rv;
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
fifo->aborted = 1;
- apr_thread_mutex_unlock(fifo->lock);
- }
- return rv;
-}
-
-apr_status_t h2_ififo_interrupt(h2_ififo *fifo)
-{
- apr_status_t rv;
- if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
apr_thread_cond_broadcast(fifo->not_empty);
apr_thread_cond_broadcast(fifo->not_full);
apr_thread_mutex_unlock(fifo->lock);
@@ -991,9 +980,9 @@ static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
/* set mode, elem already member */
return APR_EEXIST;
}
- else if (fifo->count == fifo->nelems) {
+ else if (fifo->count == fifo->capacity) {
if (block) {
- while (fifo->count == fifo->nelems) {
+ while (fifo->count == fifo->capacity) {
if (fifo->aborted) {
return APR_EOF;
}
@@ -1004,8 +993,8 @@ static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
return APR_EAGAIN;
}
}
-
- ap_assert(fifo->count < fifo->nelems);
+
+ ap_assert(fifo->count < fifo->capacity);
fifo->elems[inth_index(fifo, fifo->count)] = id;
++fifo->count;
if (fifo->count == 1) {
@@ -1017,10 +1006,6 @@ static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
static apr_status_t ififo_push(h2_ififo *fifo, int id, int block)
{
apr_status_t rv;
-
- if (fifo->aborted) {
- return APR_EOF;
- }
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
rv = ififo_push_int(fifo, id, block);
@@ -1042,7 +1027,7 @@ apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id)
static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
{
apr_status_t rv;
-
+
if ((rv = icheck_not_empty(fifo, block)) != APR_SUCCESS) {
*pi = 0;
return rv;
@@ -1051,7 +1036,7 @@ static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
--fifo->count;
if (fifo->count > 0) {
fifo->head = inth_index(fifo, 1);
- if (fifo->count+1 == fifo->nelems) {
+ if (fifo->count+1 == fifo->capacity) {
apr_thread_cond_broadcast(fifo->not_full);
}
}
@@ -1061,11 +1046,7 @@ static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
static apr_status_t ififo_pull(h2_ififo *fifo, int *pi, int block)
{
apr_status_t rv;
-
- if (fifo->aborted) {
- return APR_EOF;
- }
-
+
if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
rv = ipull_head(fifo, pi, block);
apr_thread_mutex_unlock(fifo->lock);
@@ -1087,11 +1068,7 @@ static apr_status_t ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx,
{
apr_status_t rv;
int id;
-
- if (fifo->aborted) {
- return APR_EOF;
- }
-
+
if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
if (APR_SUCCESS == (rv = ipull_head(fifo, &id, block))) {
switch (fn(id, ctx)) {
@@ -1117,39 +1094,40 @@ apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx)
return ififo_peek(fifo, fn, ctx, 0);
}
-apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
+static apr_status_t ififo_remove(h2_ififo *fifo, int id)
{
- apr_status_t rv;
-
+ int rc, i;
+
if (fifo->aborted) {
return APR_EOF;
}
- if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
- int i, rc;
- int e;
-
- rc = 0;
- for (i = 0; i < fifo->count; ++i) {
- e = fifo->elems[inth_index(fifo, i)];
- if (e == id) {
- ++rc;
- }
- else if (rc) {
- fifo->elems[inth_index(fifo, i-rc)] = e;
- }
- }
- if (rc) {
- fifo->count -= rc;
- if (fifo->count + rc == fifo->nelems) {
- apr_thread_cond_broadcast(fifo->not_full);
- }
- rv = APR_SUCCESS;
+ rc = 0;
+ for (i = 0; i < fifo->count; ++i) {
+ int e = fifo->elems[inth_index(fifo, i)];
+ if (e == id) {
+ ++rc;
}
- else {
- rv = APR_EAGAIN;
+ else if (rc) {
+ fifo->elems[inth_index(fifo, i-rc)] = e;
}
-
+ }
+ if (!rc) {
+ return APR_EAGAIN;
+ }
+ fifo->count -= rc;
+ if (fifo->count + rc == fifo->capacity) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ rv = ififo_remove(fifo, id);
apr_thread_mutex_unlock(fifo->lock);
}
return rv;
@@ -1158,7 +1136,7 @@ apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
/*******************************************************************************
* h2_util for apt_table_t
******************************************************************************/
-
+
typedef struct {
apr_size_t bytes;
apr_size_t pair_extra;
@@ -1180,7 +1158,7 @@ static int count_bytes(void *x, const char *key, const char *value)
apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra)
{
table_bytes_ctx ctx;
-
+
ctx.bytes = 0;
ctx.pair_extra = pair_extra;
apr_table_do(count_bytes, &ctx, t, NULL);
@@ -1192,287 +1170,108 @@ apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra)
* h2_util for bucket brigades
******************************************************************************/
-static apr_status_t last_not_included(apr_bucket_brigade *bb,
- apr_off_t maxlen,
- int same_alloc,
- apr_size_t *pfile_buckets_allowed,
- apr_bucket **pend)
+static void fit_bucket_into(apr_bucket *b, apr_off_t *plen)
{
- apr_bucket *b;
- apr_status_t status = APR_SUCCESS;
- int files_allowed = pfile_buckets_allowed? (int)*pfile_buckets_allowed : 0;
-
- if (maxlen >= 0) {
- /* Find the bucket, up to which we reach maxlen/mem bytes */
- for (b = APR_BRIGADE_FIRST(bb);
- (b != APR_BRIGADE_SENTINEL(bb));
- b = APR_BUCKET_NEXT(b)) {
-
- if (APR_BUCKET_IS_METADATA(b)) {
- /* included */
- }
- else {
- if (b->length == ((apr_size_t)-1)) {
- const char *ign;
- apr_size_t ilen;
- status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
- if (status != APR_SUCCESS) {
- return status;
- }
- }
-
- if (maxlen == 0 && b->length > 0) {
- *pend = b;
- return status;
- }
-
- if (same_alloc && APR_BUCKET_IS_FILE(b)) {
- /* we like it move it, always */
- }
- else if (files_allowed > 0 && APR_BUCKET_IS_FILE(b)) {
- /* this has no memory footprint really unless
- * it is read, disregard it in length count,
- * unless we do not move the file buckets */
- --files_allowed;
- }
- else if (maxlen < (apr_off_t)b->length) {
- apr_bucket_split(b, (apr_size_t)maxlen);
- maxlen = 0;
- }
- else {
- maxlen -= b->length;
- }
- }
- }
+ /* signed apr_off_t is at least as large as unsigned apr_size_t.
+ * Problems may arise when they are both the same size. Then
+ * the bucket length *may* be larger than a value we can hold
+ * in apr_off_t. Before casting b->length to apr_off_t we must
+ * check the limitations.
+ * After we resized the bucket, it is safe to cast and substract.
+ */
+ if ((sizeof(apr_off_t) == sizeof(apr_int64_t)
+ && b->length > APR_INT64_MAX)
+ || (sizeof(apr_off_t) == sizeof(apr_int32_t)
+ && b->length > APR_INT32_MAX)
+ || *plen < (apr_off_t)b->length) {
+ /* bucket is longer the *plen */
+ apr_bucket_split(b, *plen);
}
- *pend = APR_BRIGADE_SENTINEL(bb);
- return status;
+ *plen -= (apr_off_t)b->length;
}
-apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
+apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
apr_bucket_brigade *src,
apr_off_t length)
{
apr_bucket *b;
apr_off_t remain = length;
apr_status_t status = APR_SUCCESS;
-
+
while (!APR_BRIGADE_EMPTY(src)) {
- b = APR_BRIGADE_FIRST(src);
-
+ b = APR_BRIGADE_FIRST(src);
+
if (APR_BUCKET_IS_METADATA(b)) {
APR_BUCKET_REMOVE(b);
APR_BRIGADE_INSERT_TAIL(dest, b);
}
else {
- if (remain == b->length) {
- /* fall through */
- }
- else if (remain <= 0) {
+ if (remain <= 0) {
return status;
}
- else {
- if (b->length == ((apr_size_t)-1)) {
- const char *ign;
- apr_size_t ilen;
- status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
- if (status != APR_SUCCESS) {
- return status;
- }
- }
-
- if (remain < b->length) {
- apr_bucket_split(b, remain);
+ if (b->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ return status;
}
}
+ fit_bucket_into(b, &remain);
APR_BUCKET_REMOVE(b);
APR_BRIGADE_INSERT_TAIL(dest, b);
- remain -= b->length;
}
}
return status;
}
-apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
+apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
apr_bucket_brigade *src,
apr_off_t length)
{
apr_bucket *b, *next;
apr_off_t remain = length;
apr_status_t status = APR_SUCCESS;
-
- for (b = APR_BRIGADE_FIRST(src);
+
+ for (b = APR_BRIGADE_FIRST(src);
b != APR_BRIGADE_SENTINEL(src);
b = next) {
next = APR_BUCKET_NEXT(b);
-
+
if (APR_BUCKET_IS_METADATA(b)) {
/* fall through */
}
else {
- if (remain == b->length) {
- /* fall through */
- }
- else if (remain <= 0) {
+ if (remain <= 0) {
return status;
}
- else {
- if (b->length == ((apr_size_t)-1)) {
- const char *ign;
- apr_size_t ilen;
- status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
- if (status != APR_SUCCESS) {
- return status;
- }
- }
-
- if (remain < b->length) {
- apr_bucket_split(b, remain);
+ if (b->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ return status;
}
}
+ fit_bucket_into(b, &remain);
}
status = apr_bucket_copy(b, &b);
if (status != APR_SUCCESS) {
return status;
}
APR_BRIGADE_INSERT_TAIL(dest, b);
- remain -= b->length;
- }
- return status;
-}
-
-int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len)
-{
- apr_bucket *b, *end;
-
- apr_status_t status = last_not_included(bb, len, 0, 0, &end);
- if (status != APR_SUCCESS) {
- return status;
- }
-
- for (b = APR_BRIGADE_FIRST(bb);
- b != APR_BRIGADE_SENTINEL(bb) && b != end;
- b = APR_BUCKET_NEXT(b))
- {
- if (APR_BUCKET_IS_EOS(b)) {
- return 1;
- }
- }
- return 0;
-}
-
-apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos)
-{
- apr_status_t status;
- apr_off_t blen = 0;
-
- /* test read to determine available length */
- status = apr_brigade_length(bb, 1, &blen);
- if (status != APR_SUCCESS) {
- return status;
- }
- else if (blen == 0) {
- /* brigade without data, does it have an EOS bucket somwhere? */
- *plen = 0;
- *peos = h2_util_has_eos(bb, -1);
- }
- else {
- /* data in the brigade, limit the length returned. Check for EOS
- * bucket only if we indicate data. This is required since plen == 0
- * means "the whole brigade" for h2_util_hash_eos()
- */
- if (blen < *plen || *plen < 0) {
- *plen = blen;
- }
- *peos = h2_util_has_eos(bb, *plen);
- }
- return APR_SUCCESS;
-}
-
-apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb,
- h2_util_pass_cb *cb, void *ctx,
- apr_off_t *plen, int *peos)
-{
- apr_status_t status = APR_SUCCESS;
- int consume = (cb != NULL);
- apr_off_t written = 0;
- apr_off_t avail = *plen;
- apr_bucket *next, *b;
-
- /* Pass data in our brigade through the callback until the length
- * is satisfied or we encounter an EOS.
- */
- *peos = 0;
- for (b = APR_BRIGADE_FIRST(bb);
- (status == APR_SUCCESS) && (b != APR_BRIGADE_SENTINEL(bb));
- b = next) {
-
- if (APR_BUCKET_IS_METADATA(b)) {
- if (APR_BUCKET_IS_EOS(b)) {
- *peos = 1;
- }
- else {
- /* ignore */
- }
- }
- else if (avail <= 0) {
- break;
- }
- else {
- const char *data = NULL;
- apr_size_t data_len;
-
- if (b->length == ((apr_size_t)-1)) {
- /* read to determine length */
- status = apr_bucket_read(b, &data, &data_len, APR_NONBLOCK_READ);
- }
- else {
- data_len = b->length;
- }
-
- if (data_len > avail) {
- apr_bucket_split(b, avail);
- data_len = (apr_size_t)avail;
- }
-
- if (consume) {
- if (!data) {
- status = apr_bucket_read(b, &data, &data_len,
- APR_NONBLOCK_READ);
- }
- if (status == APR_SUCCESS) {
- status = cb(ctx, data, data_len);
- }
- }
- else {
- data_len = b->length;
- }
- avail -= data_len;
- written += data_len;
- }
-
- next = APR_BUCKET_NEXT(b);
- if (consume) {
- apr_bucket_delete(b);
- }
- }
-
- *plen = written;
- if (status == APR_SUCCESS && !*peos && !*plen) {
- return APR_EAGAIN;
}
return status;
}
-apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
+apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
apr_bucket *b, const char *sep)
{
apr_size_t off = 0;
if (sep && *sep) {
off += apr_snprintf(buffer+off, bmax-off, "%s", sep);
}
-
+
if (bmax <= off) {
return off;
}
@@ -1480,30 +1279,30 @@ apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
off += apr_snprintf(buffer+off, bmax-off, "%s", b->type->name);
}
else if (bmax > off) {
- off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
- b->type->name,
- (long)(b->length == ((apr_size_t)-1)?
- -1 : b->length));
+ off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
+ b->type->name,
+ (b->length == ((apr_size_t)-1)?
+ -1 : (long)b->length));
}
return off;
}
-apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
- const char *tag, const char *sep,
+apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
+ const char *tag, const char *sep,
apr_bucket_brigade *bb)
{
apr_size_t off = 0;
const char *sp = "";
apr_bucket *b;
-
+
if (bmax > 1) {
if (bb) {
memset(buffer, 0, bmax--);
off += apr_snprintf(buffer+off, bmax-off, "%s(", tag);
- for (b = APR_BRIGADE_FIRST(bb);
+ for (b = APR_BRIGADE_FIRST(bb);
(bmax > off) && (b != APR_BRIGADE_SENTINEL(bb));
b = APR_BUCKET_NEXT(b)) {
-
+
off += h2_util_bucket_print(buffer+off, bmax-off, b, sp);
sp = " ";
}
@@ -1519,20 +1318,21 @@ apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
}
apr_status_t h2_append_brigade(apr_bucket_brigade *to,
- apr_bucket_brigade *from,
+ apr_bucket_brigade *from,
apr_off_t *plen,
int *peos,
h2_bucket_gate *should_append)
{
apr_bucket *e;
- apr_off_t len = 0, remain = *plen;
+ apr_off_t start, remain;
apr_status_t rv;
*peos = 0;
-
+ start = remain = *plen;
+
while (!APR_BRIGADE_EMPTY(from)) {
e = APR_BRIGADE_FIRST(from);
-
+
if (!should_append(e)) {
goto leave;
}
@@ -1543,8 +1343,11 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
continue;
}
}
- else {
- if (remain > 0 && e->length == ((apr_size_t)-1)) {
+ else {
+ if (remain <= 0) {
+ goto leave;
+ }
+ if (e->length == ((apr_size_t)-1)) {
const char *ign;
apr_size_t ilen;
rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ);
@@ -1552,22 +1355,13 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
return rv;
}
}
-
- if (remain < e->length) {
- if (remain <= 0) {
- goto leave;
- }
- apr_bucket_split(e, (apr_size_t)remain);
- }
+ fit_bucket_into(e, &remain);
}
-
APR_BUCKET_REMOVE(e);
APR_BRIGADE_INSERT_TAIL(to, e);
- len += e->length;
- remain -= e->length;
}
leave:
- *plen = len;
+ *plen = start - remain;
return APR_SUCCESS;
}
@@ -1595,20 +1389,10 @@ apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb)
/*******************************************************************************
* h2_ngheader
******************************************************************************/
-
-int h2_util_ignore_header(const char *name)
-{
- /* never forward, ch. 8.1.2.2 */
- return (H2_HD_MATCH_LIT_CS("connection", name)
- || H2_HD_MATCH_LIT_CS("proxy-connection", name)
- || H2_HD_MATCH_LIT_CS("upgrade", name)
- || H2_HD_MATCH_LIT_CS("keep-alive", name)
- || H2_HD_MATCH_LIT_CS("transfer-encoding", name));
-}
static int count_header(void *ctx, const char *key, const char *value)
{
- if (!h2_util_ignore_header(key)) {
+ if (!h2_util_ignore_resp_header(key)) {
(*((size_t*)ctx))++;
}
return 1;
@@ -1629,6 +1413,17 @@ static const char *inv_field_value_chr(const char *token)
return (p && *p)? p : NULL;
}
+static void strip_field_value_ws(nghttp2_nv *nv)
+{
+ while(nv->valuelen && (nv->value[0] == ' ' || nv->value[0] == '\t')) {
+ nv->value++; nv->valuelen--;
+ }
+ while(nv->valuelen && (nv->value[nv->valuelen-1] == ' '
+ || nv->value[nv->valuelen-1] == '\t')) {
+ nv->valuelen--;
+ }
+}
+
typedef struct ngh_ctx {
apr_pool_t *p;
int unsafe;
@@ -1644,14 +1439,14 @@ static int add_header(ngh_ctx *ctx, const char *key, const char *value)
if (!ctx->unsafe) {
if ((p = inv_field_name_chr(key))) {
ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
- "h2_request: head field '%s: %s' has invalid char %s",
+ "h2_request: head field '%s: %s' has invalid char %s",
key, value, p);
ctx->status = APR_EINVAL;
return 0;
}
if ((p = inv_field_value_chr(value))) {
ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
- "h2_request: head field '%s: %s' has invalid char %s",
+ "h2_request: head field '%s: %s' has invalid char %s",
key, value, p);
ctx->status = APR_EINVAL;
return 0;
@@ -1661,69 +1456,100 @@ static int add_header(ngh_ctx *ctx, const char *key, const char *value)
nv->namelen = strlen(key);
nv->value = (uint8_t*)value;
nv->valuelen = strlen(value);
-
+ strip_field_value_ws(nv);
+
return 1;
}
static int add_table_header(void *ctx, const char *key, const char *value)
{
- if (!h2_util_ignore_header(key)) {
+ if (!h2_util_ignore_resp_header(key)) {
add_header(ctx, key, value);
}
return 1;
}
-static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p,
- int unsafe, size_t key_count,
+static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p,
+ int unsafe, size_t key_count,
const char *keys[], const char *values[],
apr_table_t *headers)
{
ngh_ctx ctx;
size_t n, i;
-
+
ctx.p = p;
ctx.unsafe = unsafe;
-
+
n = key_count;
apr_table_do(count_header, &n, headers, NULL);
-
+
*ph = ctx.ngh = apr_pcalloc(p, sizeof(h2_ngheader));
if (!ctx.ngh) {
return APR_ENOMEM;
}
-
- ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
+
+ ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
if (!ctx.ngh->nv) {
return APR_ENOMEM;
}
-
+
ctx.status = APR_SUCCESS;
for (i = 0; i < key_count; ++i) {
if (!add_header(&ctx, keys[i], values[i])) {
return ctx.status;
}
}
-
+
apr_table_do(add_table_header, &ctx, headers, NULL);
return ctx.status;
}
+#if AP_HAS_RESPONSE_BUCKETS
+
+static int is_unsafe(ap_bucket_response *h)
+{
+ const char *v = h->notes? apr_table_get(h->notes, H2_HDR_CONFORMANCE) : NULL;
+ return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
+}
+
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_headers *headers)
+{
+ return ngheader_create(ph, p, 0,
+ 0, NULL, NULL, headers->headers);
+}
+
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_response *response)
+{
+ const char *keys[] = {
+ ":status"
+ };
+ const char *values[] = {
+ apr_psprintf(p, "%d", response->status)
+ };
+ return ngheader_create(ph, p, is_unsafe(response),
+ H2_ALEN(keys), keys, values, response->headers);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
static int is_unsafe(h2_headers *h)
{
- const char *v = apr_table_get(h->notes, H2_HDR_CONFORMANCE);
+ const char *v = h->notes? apr_table_get(h->notes, H2_HDR_CONFORMANCE) : NULL;
return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
}
-apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
h2_headers *headers)
{
- return ngheader_create(ph, p, is_unsafe(headers),
+ return ngheader_create(ph, p, is_unsafe(headers),
0, NULL, NULL, headers->headers);
}
-
+
apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
- h2_headers *headers)
+ h2_headers *headers)
{
const char *keys[] = {
":status"
@@ -1731,27 +1557,29 @@ apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
const char *values[] = {
apr_psprintf(p, "%d", headers->status)
};
- return ngheader_create(ph, p, is_unsafe(headers),
+ return ngheader_create(ph, p, is_unsafe(headers),
H2_ALEN(keys), keys, values, headers->headers);
}
-apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
const struct h2_request *req)
{
-
+
const char *keys[] = {
- ":scheme",
- ":authority",
- ":path",
- ":method",
+ ":scheme",
+ ":authority",
+ ":path",
+ ":method",
};
const char *values[] = {
req->scheme,
- req->authority,
- req->path,
- req->method,
+ req->authority,
+ req->path,
+ req->method,
};
-
+
ap_assert(req->scheme);
ap_assert(req->authority);
ap_assert(req->path);
@@ -1763,7 +1591,7 @@ apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
/*******************************************************************************
* header HTTP/1 <-> HTTP/2 conversions
******************************************************************************/
-
+
typedef struct {
const char *name;
@@ -1791,9 +1619,15 @@ static literal IgnoredRequestTrailers[] = { /* Ignore, see rfc7230, ch. 4.1.2 */
H2_DEF_LITERAL("max-forwards"),
H2_DEF_LITERAL("cache-control"),
H2_DEF_LITERAL("authorization"),
- H2_DEF_LITERAL("content-length"),
+ H2_DEF_LITERAL("content-length"),
H2_DEF_LITERAL("proxy-authorization"),
-};
+};
+static literal IgnoredResponseHeaders[] = {
+ H2_DEF_LITERAL("upgrade"),
+ H2_DEF_LITERAL("connection"),
+ H2_DEF_LITERAL("keep-alive"),
+ H2_DEF_LITERAL("transfer-encoding"),
+};
static literal IgnoredResponseTrailers[] = {
H2_DEF_LITERAL("age"),
H2_DEF_LITERAL("date"),
@@ -1808,93 +1642,124 @@ static literal IgnoredResponseTrailers[] = {
H2_DEF_LITERAL("proxy-authenticate"),
};
-static int ignore_header(const literal *lits, size_t llen,
- const char *name, size_t nlen)
+static int contains_name(const literal *lits, size_t llen, nghttp2_nv *nv)
{
const literal *lit;
size_t i;
-
+
for (i = 0; i < llen; ++i) {
lit = &lits[i];
- if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) {
+ if (lit->len == nv->namelen
+ && !ap_cstr_casecmp(lit->name, (const char *)nv->name)) {
return 1;
}
}
return 0;
}
-int h2_req_ignore_header(const char *name, size_t len)
+int h2_util_ignore_resp_header(const char *name)
{
- return ignore_header(H2_LIT_ARGS(IgnoredRequestHeaders), name, len);
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = strlen(name);
+ return contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv);
}
-int h2_req_ignore_trailer(const char *name, size_t len)
+
+static int h2_req_ignore_header(nghttp2_nv *nv)
{
- return (h2_req_ignore_header(name, len)
- || ignore_header(H2_LIT_ARGS(IgnoredRequestTrailers), name, len));
+ return contains_name(H2_LIT_ARGS(IgnoredRequestHeaders), nv);
}
-int h2_res_ignore_trailer(const char *name, size_t len)
+int h2_ignore_req_trailer(const char *name, size_t len)
{
- return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len);
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = strlen(name);
+ return (h2_req_ignore_header(&nv)
+ || contains_name(H2_LIT_ARGS(IgnoredRequestTrailers), &nv));
}
-apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
- const char *name, size_t nlen,
- const char *value, size_t vlen)
+int h2_ignore_resp_trailer(const char *name, size_t len)
+{
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = strlen(name);
+ return (contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv)
+ || contains_name(H2_LIT_ARGS(IgnoredResponseTrailers), &nv));
+}
+
+static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
+ nghttp2_nv *nv, size_t max_field_len,
+ int *pwas_added)
{
char *hname, *hvalue;
-
- if (h2_req_ignore_header(name, nlen)) {
+ const char *existing;
+
+ *pwas_added = 0;
+ strip_field_value_ws(nv);
+
+ if (h2_req_ignore_header(nv)) {
return APR_SUCCESS;
}
- else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
- const char *existing = apr_table_get(headers, "cookie");
+ else if (nv->namelen == sizeof("cookie")-1
+ && !ap_cstr_casecmp("cookie", (const char *)nv->name)) {
+ existing = apr_table_get(headers, "cookie");
if (existing) {
- char *nval;
-
/* Cookie header come separately in HTTP/2, but need
* to be merged by "; " (instead of default ", ")
*/
- hvalue = apr_pstrndup(pool, value, vlen);
- nval = apr_psprintf(pool, "%s; %s", existing, hvalue);
- apr_table_setn(headers, "Cookie", nval);
+ if (max_field_len
+ && strlen(existing) + nv->valuelen + nv->namelen + 4
+ > max_field_len) {
+ /* "key: oldval, nval" is too long */
+ return APR_EINVAL;
+ }
+ hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
+ apr_table_setn(headers, "Cookie",
+ apr_psprintf(pool, "%s; %s", existing, hvalue));
return APR_SUCCESS;
}
}
- else if (H2_HD_MATCH_LIT("host", name, nlen)) {
+ else if (nv->namelen == sizeof("host")-1
+ && !ap_cstr_casecmp("host", (const char *)nv->name)) {
if (apr_table_get(headers, "Host")) {
return APR_SUCCESS; /* ignore duplicate */
}
}
-
- hname = apr_pstrndup(pool, name, nlen);
- hvalue = apr_pstrndup(pool, value, vlen);
- h2_util_camel_case_header(hname, nlen);
+
+ hname = apr_pstrndup(pool, (const char*)nv->name, nv->namelen);
+ h2_util_camel_case_header(hname, nv->namelen);
+ existing = apr_table_get(headers, hname);
+ if (max_field_len) {
+ if ((existing? strlen(existing)+2 : 0) + nv->valuelen + nv->namelen + 2
+ > max_field_len) {
+ /* "key: (oldval, )?nval" is too long */
+ return APR_EINVAL;
+ }
+ }
+ if (!existing) *pwas_added = 1;
+ hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
apr_table_mergen(headers, hname, hvalue);
-
+
return APR_SUCCESS;
}
-/*******************************************************************************
- * h2 request handling
- ******************************************************************************/
-
-h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method,
- const char *scheme, const char *authority,
- const char *path, apr_table_t *header, int serialize)
+apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added)
{
- 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();
- req->serialize = serialize;
-
- return req;
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = nlen;
+ nv.value = (uint8_t*)value;
+ nv.valuelen = vlen;
+ return req_add_header(headers, pool, &nv, max_field_len, pwas_added);
}
/*******************************************************************************
@@ -1905,7 +1770,7 @@ int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
{
char scratch[128];
size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
-
+
switch (frame->hd.type) {
case NGHTTP2_DATA: {
return apr_snprintf(buffer, maxlen,
@@ -1960,16 +1825,17 @@ int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
case NGHTTP2_GOAWAY: {
size_t len = (frame->goaway.opaque_data_len < s_len)?
frame->goaway.opaque_data_len : s_len-1;
- memcpy(scratch, frame->goaway.opaque_data, len);
+ if (len)
+ memcpy(scratch, frame->goaway.opaque_data, len);
scratch[len] = '\0';
return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s', "
- "last_stream=%d]", frame->goaway.error_code,
+ "last_stream=%d]", frame->goaway.error_code,
scratch, frame->goaway.last_stream_id);
}
case NGHTTP2_WINDOW_UPDATE: {
return apr_snprintf(buffer, maxlen,
"WINDOW_UPDATE[stream=%d, incr=%d]",
- frame->hd.stream_id,
+ frame->hd.stream_id,
frame->window_update.window_size_increment);
}
default:
@@ -2013,3 +1879,60 @@ int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabl
return policy;
}
+void h2_util_drain_pipe(apr_file_t *pipe)
+{
+ char rb[512];
+ apr_size_t nr = sizeof(rb);
+ apr_interval_time_t timeout;
+ apr_status_t trv;
+
+ /* Make the pipe non-blocking if we can */
+ trv = apr_file_pipe_timeout_get(pipe, &timeout);
+ if (trv == APR_SUCCESS)
+ apr_file_pipe_timeout_set(pipe, 0);
+
+ while (apr_file_read(pipe, rb, &nr) == APR_SUCCESS) {
+ /* Although we write just one byte to the other end of the pipe
+ * during wakeup, multiple threads could call the wakeup.
+ * So simply drain out from the input side of the pipe all
+ * the data.
+ */
+ if (nr != sizeof(rb))
+ break;
+ }
+ if (trv == APR_SUCCESS)
+ apr_file_pipe_timeout_set(pipe, timeout);
+}
+
+apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe)
+{
+ char rb[512];
+ apr_size_t nr = sizeof(rb);
+
+ return apr_file_read(pipe, rb, &nr);
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static int add_header_lengths(void *ctx, const char *name, const char *value)
+{
+ apr_size_t *plen = ctx;
+ *plen += strlen(name) + strlen(value);
+ return 1;
+}
+
+apr_size_t headers_length_estimate(ap_bucket_headers *hdrs)
+{
+ apr_size_t len = 0;
+ apr_table_do(add_header_lengths, &len, hdrs->headers, NULL);
+ return len;
+}
+
+apr_size_t response_length_estimate(ap_bucket_response *resp)
+{
+ apr_size_t len = 3 + 1 + 8 + (resp->reason? strlen(resp->reason) : 10);
+ apr_table_do(add_header_lengths, &len, resp->headers, NULL);
+ return len;
+}
+
+#endif /* AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h
index 1eb262d..d2e6548 100644
--- a/modules/http2/h2_util.h
+++ b/modules/http2/h2_util.h
@@ -18,6 +18,10 @@
#define __mod_h2__h2_util__
#include
+#include
+
+#include "h2.h"
+#include "h2_headers.h"
/*******************************************************************************
* some debugging/format helpers
@@ -28,10 +32,6 @@ struct nghttp2_frame;
size_t h2_util_hex_dump(char *buffer, size_t maxlen,
const char *data, size_t datalen);
-size_t h2_util_header_print(char *buffer, size_t maxlen,
- const char *name, size_t namelen,
- const char *value, size_t valuelen);
-
void h2_util_camel_case_header(char *s, size_t len);
int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
@@ -49,7 +49,7 @@ typedef int h2_ihash_iter_t(void *ctx, void *val);
*/
h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int);
-size_t h2_ihash_count(h2_ihash_t *ih);
+unsigned int h2_ihash_count(h2_ihash_t *ih);
int h2_ihash_empty(h2_ihash_t *ih);
void *h2_ihash_get(h2_ihash_t *ih, int id);
@@ -96,13 +96,13 @@ typedef int h2_iq_cmp(int i1, int i2, void *ctx);
/**
* Allocate a new queue from the pool and initialize.
- * @param id the identifier of the queue
* @param pool the memory pool
+ * @param capacity the initial capacity of the queue
*/
h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity);
/**
- * Return != 0 iff there are no tasks in the queue.
+ * Return != 0 iff there are no ints in the queue.
* @param q the queue to check
*/
int h2_iq_empty(h2_iqueue *q);
@@ -134,11 +134,10 @@ int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
int h2_iq_append(h2_iqueue *q, int sid);
/**
- * Remove the stream id from the queue. Return != 0 iff task
- * was found in queue.
- * @param q the task queue
+ * Remove the int from the queue. Return != 0 iff it was found.
+ * @param q the queue
* @param sid the stream id to remove
- * @return != 0 iff task was found in queue
+ * @return != 0 iff int was found in queue
*/
int h2_iq_remove(h2_iqueue *q, int sid);
@@ -148,7 +147,7 @@ int h2_iq_remove(h2_iqueue *q, int sid);
void h2_iq_clear(h2_iqueue *q);
/**
- * Sort the stream idqueue again. Call if the task ordering
+ * Sort the stream idqueue again. Call if the int ordering
* has changed.
*
* @param q the queue to sort
@@ -169,7 +168,7 @@ int h2_iq_shift(h2_iqueue *q);
/**
* Get the first max ids from the queue. All these ids will be removed.
*
- * @param q the queue to get the first task from
+ * @param q the queue to get the first ids from
* @param pint the int array to receive the values
* @param max the maximum number of ids to shift
* @return the actual number of ids shifted
@@ -179,7 +178,7 @@ size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max);
/**
* Determine if int is in the queue already
*
- * @parm q the queue
+ * @param q the queue
* @param sid the integer id to check for
* @return != 0 iff sid is already in the queue
*/
@@ -209,7 +208,6 @@ apr_status_t h2_fifo_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
apr_status_t h2_fifo_set_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
apr_status_t h2_fifo_term(h2_fifo *fifo);
-apr_status_t h2_fifo_interrupt(h2_fifo *fifo);
int h2_fifo_count(h2_fifo *fifo);
@@ -229,7 +227,7 @@ apr_status_t h2_fifo_try_pull(h2_fifo *fifo, void **pelem);
typedef enum {
H2_FIFO_OP_PULL, /* pull the element from the queue, ie discard it */
- H2_FIFO_OP_REPUSH, /* pull and immediatley re-push it */
+ H2_FIFO_OP_REPUSH, /* pull and immediately re-push it */
} h2_fifo_op_t;
typedef h2_fifo_op_t h2_fifo_peek_fn(void *head, void *ctx);
@@ -280,7 +278,6 @@ apr_status_t h2_ififo_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
apr_status_t h2_ififo_set_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
apr_status_t h2_ififo_term(h2_ififo *fifo);
-apr_status_t h2_ififo_interrupt(h2_ififo *fifo);
int h2_ififo_count(h2_ififo *fifo);
@@ -345,9 +342,8 @@ apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra);
/*******************************************************************************
* HTTP/2 header helpers
******************************************************************************/
-int h2_req_ignore_header(const char *name, size_t len);
-int h2_req_ignore_trailer(const char *name, size_t len);
-int h2_res_ignore_trailer(const char *name, size_t len);
+int h2_ignore_req_trailer(const char *name, size_t len);
+int h2_ignore_resp_trailer(const char *name, size_t len);
/**
* Set the push policy for the given request. Takes request headers into
@@ -378,52 +374,37 @@ const char *h2_util_base64url_encode(const char *data,
* nghttp2 helpers
******************************************************************************/
-#define H2_HD_MATCH_LIT_CS(l, name) \
- ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
-
-#define H2_CREATE_NV_LIT_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME; \
- nv->namelen = sizeof(NAME) - 1; \
- nv->value = (uint8_t *)VALUE; \
- nv->valuelen = strlen(VALUE)
-
-#define H2_CREATE_NV_CS_LIT(nv, NAME, VALUE) nv->name = (uint8_t *)NAME; \
- nv->namelen = strlen(NAME); \
- nv->value = (uint8_t *)VALUE; \
- nv->valuelen = sizeof(VALUE) - 1
-
-#define H2_CREATE_NV_CS_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME; \
- nv->namelen = strlen(NAME); \
- nv->value = (uint8_t *)VALUE; \
- nv->valuelen = strlen(VALUE)
-
-int h2_util_ignore_header(const char *name);
-
-struct h2_headers;
+int h2_util_ignore_resp_header(const char *name);
typedef struct h2_ngheader {
nghttp2_nv *nv;
apr_size_t nvlen;
} h2_ngheader;
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_headers *headers);
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_response *response);
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ const struct h2_request *req);
+#else
apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
struct h2_headers *headers);
apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
struct h2_headers *headers);
apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
const struct h2_request *req);
+#endif
+/**
+ * Add a HTTP/2 header and return the table key if it really was added
+ * and not ignored.
+ */
apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
const char *name, size_t nlen,
- const char *value, size_t vlen);
-
-/*******************************************************************************
- * h2_request helpers
- ******************************************************************************/
-
-struct h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method,
- const char *scheme, const char *authority,
- const char *path, apr_table_t *header,
- int serialize);
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added);
/*******************************************************************************
* apr brigade helpers
@@ -445,42 +426,9 @@ apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
apr_bucket_brigade *src,
apr_off_t length);
-/**
- * Return != 0 iff there is a FLUSH or EOS bucket in the brigade.
- * @param bb the brigade to check on
- * @return != 0 iff brigade holds FLUSH or EOS bucket (or both)
- */
-int h2_util_has_eos(apr_bucket_brigade *bb, apr_off_t len);
-
-/**
- * Check how many bytes of the desired amount are available and if the
- * end of stream is reached by that amount.
- * @param bb the brigade to check
- * @param plen the desired length and, on return, the available length
- * @param on return, if eos has been reached
- */
-apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos);
-
-typedef apr_status_t h2_util_pass_cb(void *ctx,
+typedef apr_status_t h2_util_pass_cb(void *ctx,
const char *data, apr_off_t len);
-/**
- * Read at most *plen bytes from the brigade and pass them into the
- * given callback. If cb is NULL, just return the amount of data that
- * could have been read.
- * If an EOS was/would be encountered, set *peos != 0.
- * @param bb the brigade to read from
- * @param cb the callback to invoke for the read data
- * @param ctx optional data passed to callback
- * @param plen inout, as input gives the maximum number of bytes to read,
- * on return specifies the actual/would be number of bytes
- * @param peos != 0 iff an EOS bucket was/would be encountered.
- */
-apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb,
- h2_util_pass_cb *cb, void *ctx,
- apr_off_t *plen, int *peos);
-
/**
* Print a bucket's meta data (type and length) to the buffer.
* @return number of characters printed
@@ -506,14 +454,16 @@ apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
* @param bb the brigade to log
*/
#define h2_util_bb_log(c, sid, level, tag, bb) \
-do { \
- char buffer[4 * 1024]; \
- const char *line = "(null)"; \
- apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
- len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
- ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
- ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
-} while(0)
+if (APLOG_C_IS_LEVEL(c, level)) { \
+ do { \
+ char buffer[4 * 1024]; \
+ const char *line = "(null)"; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
+ ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
+ ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
+ } while(0); \
+}
typedef int h2_bucket_gate(apr_bucket *b);
@@ -541,4 +491,29 @@ apr_status_t h2_append_brigade(apr_bucket_brigade *to,
*/
apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb);
+/**
+ * Drain a pipe used for notification.
+ */
+void h2_util_drain_pipe(apr_file_t *pipe);
+
+/**
+ * Wait on data arriving on a pipe.
+ */
+apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe);
+
+
+#if AP_HAS_RESPONSE_BUCKETS
+/**
+ * Give an estimate of the length of the header fields,
+ * without compression or other formatting decorations.
+ */
+apr_size_t headers_length_estimate(ap_bucket_headers *hdrs);
+
+/**
+ * Give an estimate of the length of the response meta data size,
+ * without compression or other formatting decorations.
+ */
+apr_size_t response_length_estimate(ap_bucket_response *resp);
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+
#endif /* defined(__mod_h2__h2_util__) */
diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h
index 7079437..7e7da21 100644
--- a/modules/http2/h2_version.h
+++ b/modules/http2/h2_version.h
@@ -27,7 +27,7 @@
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.11.4"
+#define MOD_HTTP2_VERSION "2.0.22"
/**
* @macro
@@ -35,7 +35,7 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x010b04
+#define MOD_HTTP2_VERSION_NUM 0x020016
#endif /* mod_h2_h2_version_h */
diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c
index 699f533..e7e2039 100644
--- a/modules/http2/h2_workers.c
+++ b/modules/http2/h2_workers.c
@@ -15,285 +15,440 @@
*/
#include
-#include
+#include
#include