From 85f7e4bdc0d1078d90b6889014d7aeb50d97c631 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Fri, 22 Jun 2018 13:57:38 -0700 Subject: [PATCH 1/2] http2-allow-cross-origin-push This toggle allows H2O to push resources even if the authority doesn't match the one in the pushed stream. It's up to the administrator to determine whether this is a safe setting or not. --- include/h2o.h | 6 ++++- include/h2o/socket/uv-binding.h | 1 + lib/core/configurator.c | 18 +++++++++++++ lib/core/request.c | 3 ++- lib/core/util.c | 10 ++++--- srcdoc/configure/http2_directives.mt | 13 +++++++++ t/00unit/lib/core/util.c | 2 +- t/40server-push.t | 40 ++++++++++++++++++++++++++++ 8 files changed, 86 insertions(+), 7 deletions(-) diff --git a/include/h2o.h b/include/h2o.h index 44b30a09be..7e3834768d 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -302,6 +302,10 @@ struct st_h2o_hostconf_t { * if server push should be used */ unsigned push_preload : 1; + /** + * if cross origin pushes should be authorized + */ + unsigned allow_cross_origin_push : 1; /** * casper settings */ @@ -1306,7 +1310,7 @@ void h2o_extract_push_path_from_link_header(h2o_mem_pool_t *pool, const char *va const h2o_url_scheme_t *input_scheme, h2o_iovec_t input_authority, const h2o_url_scheme_t *base_scheme, h2o_iovec_t *base_authority, void (*cb)(void *ctx, const char *path, size_t path_len, int is_critical), void *cb_ctx, - h2o_iovec_t *filtered_value); + h2o_iovec_t *filtered_value, int allow_cross_origin_push); /** * return a bitmap of compressible types, by parsing the `accept-encoding` header */ diff --git a/include/h2o/socket/uv-binding.h b/include/h2o/socket/uv-binding.h index a76fb965ec..7c26ddf8c7 100644 --- a/include/h2o/socket/uv-binding.h +++ b/include/h2o/socket/uv-binding.h @@ -23,6 +23,7 @@ #define h2o__uv_binding_h #include +#include #if !(defined(UV_VERSION_MAJOR) && UV_VERSION_MAJOR == 1) #error "libh2o (libuv binding) requires libuv version 1.x.y" diff --git a/lib/core/configurator.c b/lib/core/configurator.c index 41d12c1f8f..f519d77548 100644 --- a/lib/core/configurator.c +++ b/lib/core/configurator.c @@ -30,6 +30,7 @@ struct st_core_config_vars_t { struct { unsigned reprioritize_blocking_assets : 1; unsigned push_preload : 1; + unsigned allow_cross_origin_push : 1; h2o_casper_conf_t casper; } http2; struct { @@ -84,6 +85,7 @@ static int on_core_exit(h2o_configurator_t *_self, h2o_configurator_context_t *c /* exitting from host-level configuration */ ctx->hostconf->http2.reprioritize_blocking_assets = self->vars->http2.reprioritize_blocking_assets; ctx->hostconf->http2.push_preload = self->vars->http2.push_preload; + ctx->hostconf->http2.allow_cross_origin_push = self->vars->http2.allow_cross_origin_push; ctx->hostconf->http2.casper = self->vars->http2.casper; } else if (ctx->pathconf != NULL) { /* exitting from path or extension-level configuration */ @@ -501,6 +503,18 @@ static int on_config_http2_push_preload(h2o_configurator_command_t *cmd, h2o_con return 0; } +static int on_config_http2_allow_cross_origin_push(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + struct st_core_configurator_t *self = (void *)cmd->configurator; + ssize_t on; + + if ((on = h2o_configurator_get_one_of(cmd, node, "OFF,ON")) == -1) + return -1; + self->vars->http2.allow_cross_origin_push = (int)on; + + return 0; +} + static int on_config_http2_casper(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) { static const h2o_casper_conf_t defaults = { @@ -964,6 +978,10 @@ void h2o_configurator__init_core(h2o_globalconf_t *conf) H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_http2_push_preload); + h2o_configurator_define_command(&c->super, "http2-allow-cross-origin-push", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST | + H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_allow_cross_origin_push); h2o_configurator_define_command(&c->super, "http2-casper", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST, on_config_http2_casper); h2o_configurator_define_command(&c->super, "file.mime.settypes", diff --git a/lib/core/request.c b/lib/core/request.c index 897cb3ca68..449c2cd5be 100644 --- a/lib/core/request.c +++ b/lib/core/request.c @@ -751,7 +751,8 @@ h2o_iovec_t h2o_push_path_in_link_header(h2o_req_t *req, const char *value, size h2o_extract_push_path_from_link_header(&req->pool, value, value_len, req->path_normalized, req->input.scheme, req->input.authority, req->res_is_delegated ? req->scheme : NULL, - req->res_is_delegated ? &req->authority : NULL, do_push_path, req, &ret); + req->res_is_delegated ? &req->authority : NULL, do_push_path, req, &ret, + req->hostconf->http2.allow_cross_origin_push); return ret; } diff --git a/lib/core/util.c b/lib/core/util.c index a8262ba2a2..aa96b37c28 100644 --- a/lib/core/util.c +++ b/lib/core/util.c @@ -580,7 +580,8 @@ size_t h2o_stringify_proxy_header(h2o_conn_t *conn, char *buf) } static h2o_iovec_t to_push_path(h2o_mem_pool_t *pool, h2o_iovec_t url, h2o_iovec_t base_path, const h2o_url_scheme_t *input_scheme, - h2o_iovec_t input_authority, const h2o_url_scheme_t *base_scheme, h2o_iovec_t *base_authority) + h2o_iovec_t input_authority, const h2o_url_scheme_t *base_scheme, h2o_iovec_t *base_authority, + int allow_cross_origin_push) { h2o_url_t parsed, resolved; @@ -602,7 +603,8 @@ static h2o_iovec_t to_push_path(h2o_mem_pool_t *pool, h2o_iovec_t url, h2o_iovec h2o_url_resolve(pool, &base, &parsed, &resolved); if (input_scheme != resolved.scheme) goto Invalid; - if (!h2o_lcstris(input_authority.base, input_authority.len, resolved.authority.base, resolved.authority.len)) + if (!allow_cross_origin_push && + !h2o_lcstris(input_authority.base, input_authority.len, resolved.authority.base, resolved.authority.len)) goto Invalid; return resolved.path; @@ -615,7 +617,7 @@ void h2o_extract_push_path_from_link_header(h2o_mem_pool_t *pool, const char *va const h2o_url_scheme_t *input_scheme, h2o_iovec_t input_authority, const h2o_url_scheme_t *base_scheme, h2o_iovec_t *base_authority, void (*cb)(void *ctx, const char *path, size_t path_len, int is_critical), void *cb_ctx, - h2o_iovec_t *filtered_value) + h2o_iovec_t *filtered_value, int allow_cross_origin_push) { h2o_iovec_t iter = h2o_iovec_init(value, value_len), token_value; const char *token; @@ -658,7 +660,7 @@ void h2o_extract_push_path_from_link_header(h2o_mem_pool_t *pool, const char *va /* push the path */ if (!nopush && preload) { h2o_iovec_t path = to_push_path(pool, h2o_iovec_init(url_with_brackets.base + 1, url_with_brackets.len - 2), base_path, - input_scheme, input_authority, base_scheme, base_authority); + input_scheme, input_authority, base_scheme, base_authority, allow_cross_origin_push); if (path.len != 0) (*cb)(cb_ctx, path.base, path.len, critical); } diff --git a/srcdoc/configure/http2_directives.mt b/srcdoc/configure/http2_directives.mt index 03deee4788..3b88e9cfea 100644 --- a/srcdoc/configure/http2_directives.mt +++ b/srcdoc/configure/http2_directives.mt @@ -369,6 +369,19 @@ EOT )->(sub {}); ?> +{directive}->( + name => "http2-allow-cross-origin-push", + levels => [ qw(global host) ], + since => '2.3', + default => 'http2-allow-cross-origin-push: OFF', + desc => << 'EOT', +A boolean flag (ON or OFF) indicating whether if the server should push resources belonging to a different authority. +EOT +)->(sub { +?> +? }) + ? }) ? }) diff --git a/t/00unit/lib/core/util.c b/t/00unit/lib/core/util.c index 6549e758af..24dce676e4 100644 --- a/t/00unit/lib/core/util.c +++ b/t/00unit/lib/core/util.c @@ -93,7 +93,7 @@ static void test_extract_push_path_from_link_header(void) h2o_iovec_t input = h2o_iovec_init(_input, strlen(_input)), filtered; \ struct expected_t expected[] = {__VA_ARGS__, {NULL}}, *e = expected; \ h2o_extract_push_path_from_link_header(&pool, input.base, input.len, base_path, &H2O_URL_SCHEME_HTTP, input_authority, \ - _base_scheme, _base_authority, check_path, &e, &filtered); \ + _base_scheme, _base_authority, check_path, &e, &filtered, 0); \ ok(e->path == NULL); \ if (_filtered_expected != NULL) { \ ok(h2o_memis(filtered.base, filtered.len, _filtered_expected, strlen(_filtered_expected))); \ diff --git a/t/40server-push.t b/t/40server-push.t index eda355939a..9d86eab777 100644 --- a/t/40server-push.t +++ b/t/40server-push.t @@ -253,4 +253,44 @@ EOT like $resp, qr{\nid\s*responseEnd\s.*\s/index\.js\n.*\s/index.txt}is, "receives index.js then /index.txt"; }; +subtest "cross-origin push" => sub { + sub test { + my ($allow_cross_origin, $must_match) = @_; + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +http2-allow-cross-origin-push: $allow_cross_origin +hosts: + "127.0.0.1:$tls_port": + paths: + /: + mruby.handler: | + Proc.new do |env| + case env["PATH_INFO"] + when "/index.txt" + push_paths = [] + push_paths << "https://127.0.0.1.xip.io/index.js" + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload"}.join("\\n")}, []] + else + [399, {}, []] + end + end + file.dir: t/assets/doc_root + "127.0.0.1.xip.io:$tls_port": + paths: + /: + file.dir: t/assets/doc_root +EOT + }); + my $resp = `nghttp -v -m 2 -n --stat https://127.0.0.1:$server->{tls_port}/index.txt`; + if ($must_match) { + like $resp, qr{\s+200\s+16\s+/index\.js\n}is, "receives index.js"; + } else { + unlike $resp, qr{\s+200\s+16\s+/index\.js\n}is, "does not receive index.js"; + } + }; + test("ON", 1); + test("OFF", 0); +}; + done_testing; From 22ece12bdc8af577309ba913e65510abc30b882f Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Wed, 4 Jul 2018 10:15:43 -0700 Subject: [PATCH 2/2] Make the parameter a path level setting --- lib/core/configurator.c | 2 +- srcdoc/configure/http2_directives.mt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/configurator.c b/lib/core/configurator.c index f519d77548..1341bf59e5 100644 --- a/lib/core/configurator.c +++ b/lib/core/configurator.c @@ -979,7 +979,7 @@ void h2o_configurator__init_core(h2o_globalconf_t *conf) H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_http2_push_preload); h2o_configurator_define_command(&c->super, "http2-allow-cross-origin-push", - H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST | + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_http2_allow_cross_origin_push); h2o_configurator_define_command(&c->super, "http2-casper", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST, diff --git a/srcdoc/configure/http2_directives.mt b/srcdoc/configure/http2_directives.mt index 3b88e9cfea..6b81aa2f89 100644 --- a/srcdoc/configure/http2_directives.mt +++ b/srcdoc/configure/http2_directives.mt @@ -372,7 +372,7 @@ EOT {directive}->( name => "http2-allow-cross-origin-push", - levels => [ qw(global host) ], + levels => [ qw(global path) ], since => '2.3', default => 'http2-allow-cross-origin-push: OFF', desc => << 'EOT',