From b485aab7e71c1625cfc27e0f92c9509f42378458 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 5 May 2024 13:19:16 +0200 Subject: Adding upstream version 1.45.3+dfsg. Signed-off-by: Daniel Baumann --- src/web/server/h2o/libh2o/lib/core/config.c | 325 ++++++ src/web/server/h2o/libh2o/lib/core/configurator.c | 1102 +++++++++++++++++++++ src/web/server/h2o/libh2o/lib/core/context.c | 201 ++++ src/web/server/h2o/libh2o/lib/core/headers.c | 155 +++ src/web/server/h2o/libh2o/lib/core/logconf.c | 793 +++++++++++++++ src/web/server/h2o/libh2o/lib/core/proxy.c | 610 ++++++++++++ src/web/server/h2o/libh2o/lib/core/request.c | 696 +++++++++++++ src/web/server/h2o/libh2o/lib/core/token.c | 28 + src/web/server/h2o/libh2o/lib/core/token_table.h | 408 ++++++++ src/web/server/h2o/libh2o/lib/core/util.c | 562 +++++++++++ 10 files changed, 4880 insertions(+) create mode 100644 src/web/server/h2o/libh2o/lib/core/config.c create mode 100644 src/web/server/h2o/libh2o/lib/core/configurator.c create mode 100644 src/web/server/h2o/libh2o/lib/core/context.c create mode 100644 src/web/server/h2o/libh2o/lib/core/headers.c create mode 100644 src/web/server/h2o/libh2o/lib/core/logconf.c create mode 100644 src/web/server/h2o/libh2o/lib/core/proxy.c create mode 100644 src/web/server/h2o/libh2o/lib/core/request.c create mode 100644 src/web/server/h2o/libh2o/lib/core/token.c create mode 100644 src/web/server/h2o/libh2o/lib/core/token_table.h create mode 100644 src/web/server/h2o/libh2o/lib/core/util.c (limited to 'src/web/server/h2o/libh2o/lib/core') diff --git a/src/web/server/h2o/libh2o/lib/core/config.c b/src/web/server/h2o/libh2o/lib/core/config.c new file mode 100644 index 000000000..ce1d32018 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/config.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2014-2016 DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include "h2o.h" +#include "h2o/configurator.h" +#include "h2o/http1.h" +#include "h2o/http2.h" + +static h2o_hostconf_t *create_hostconf(h2o_globalconf_t *globalconf) +{ + h2o_hostconf_t *hostconf = h2o_mem_alloc(sizeof(*hostconf)); + *hostconf = (h2o_hostconf_t){globalconf}; + hostconf->http2.push_preload = 1; /* enabled by default */ + h2o_config_init_pathconf(&hostconf->fallback_path, globalconf, NULL, globalconf->mimemap); + hostconf->mimemap = globalconf->mimemap; + h2o_mem_addref_shared(hostconf->mimemap); + return hostconf; +} + +static void destroy_hostconf(h2o_hostconf_t *hostconf) +{ + size_t i; + + if (hostconf->authority.hostport.base != hostconf->authority.host.base) + free(hostconf->authority.hostport.base); + free(hostconf->authority.host.base); + for (i = 0; i != hostconf->paths.size; ++i) { + h2o_pathconf_t *pathconf = hostconf->paths.entries + i; + h2o_config_dispose_pathconf(pathconf); + } + free(hostconf->paths.entries); + h2o_config_dispose_pathconf(&hostconf->fallback_path); + h2o_mem_release_shared(hostconf->mimemap); + + free(hostconf); +} + +static void on_dispose_envconf(void *_envconf) +{ + h2o_envconf_t *envconf = _envconf; + size_t i; + + if (envconf->parent != NULL) + h2o_mem_release_shared(envconf->parent); + + for (i = 0; i != envconf->unsets.size; ++i) + h2o_mem_release_shared(envconf->unsets.entries[i].base); + free(envconf->unsets.entries); + for (i = 0; i != envconf->sets.size; ++i) + h2o_mem_release_shared(envconf->sets.entries[i].base); + free(envconf->sets.entries); +} + +h2o_envconf_t *h2o_config_create_envconf(h2o_envconf_t *parent) +{ + h2o_envconf_t *envconf = h2o_mem_alloc_shared(NULL, sizeof(*envconf), on_dispose_envconf); + *envconf = (h2o_envconf_t){NULL}; + + if (parent != NULL) { + envconf->parent = parent; + h2o_mem_addref_shared(parent); + } + return envconf; +} + +void h2o_config_setenv(h2o_envconf_t *envconf, const char *name, const char *value) +{ + size_t name_len = strlen(name), i; + h2o_iovec_t *value_slot; + + /* remove from the list of unsets */ + for (i = 0; i != envconf->unsets.size; ++i) { + if (h2o_memis(envconf->unsets.entries[i].base, envconf->unsets.entries[i].len, name, name_len)) { + h2o_mem_release_shared(envconf->unsets.entries[i].base); + h2o_vector_erase(&envconf->unsets, i); + break; + } + } + /* find the slot */ + for (i = 0; i != envconf->sets.size; i += 2) { + if (h2o_memis(envconf->sets.entries[i].base, envconf->sets.entries[i].len, name, name_len)) { + value_slot = envconf->sets.entries + i + 1; + h2o_mem_release_shared(value_slot->base); + goto SetValue; + } + } + /* name not found in existing sets */ + h2o_vector_reserve(NULL, &envconf->sets, envconf->sets.size + 2); + envconf->sets.entries[envconf->sets.size++] = h2o_strdup_shared(NULL, name, name_len); + value_slot = envconf->sets.entries + envconf->sets.size++; +SetValue: + *value_slot = h2o_strdup_shared(NULL, value, SIZE_MAX); +} + +void h2o_config_unsetenv(h2o_envconf_t *envconf, const char *name) +{ + size_t i, name_len = strlen(name); + + /* do nothing if already set */ + for (i = 0; i != envconf->unsets.size; ++i) + if (h2o_memis(envconf->unsets.entries[i].base, envconf->unsets.entries[i].len, name, name_len)) + return; + /* register */ + h2o_vector_reserve(NULL, &envconf->unsets, envconf->unsets.size + 1); + envconf->unsets.entries[envconf->unsets.size++] = h2o_strdup_shared(NULL, name, name_len); +} + +void h2o_config_init_pathconf(h2o_pathconf_t *pathconf, h2o_globalconf_t *globalconf, const char *path, h2o_mimemap_t *mimemap) +{ + memset(pathconf, 0, sizeof(*pathconf)); + pathconf->global = globalconf; + h2o_chunked_register(pathconf); + if (path != NULL) + pathconf->path = h2o_strdup(NULL, path, SIZE_MAX); + h2o_mem_addref_shared(mimemap); + pathconf->mimemap = mimemap; + pathconf->error_log.emit_request_errors = 1; +} + +void h2o_config_dispose_pathconf(h2o_pathconf_t *pathconf) +{ +#define DESTROY_LIST(type, list) \ + do { \ + size_t i; \ + for (i = 0; i != list.size; ++i) { \ + type *e = list.entries[i]; \ + if (e->dispose != NULL) \ + e->dispose(e); \ + free(e); \ + } \ + free(list.entries); \ + } while (0) + DESTROY_LIST(h2o_handler_t, pathconf->handlers); + DESTROY_LIST(h2o_filter_t, pathconf->filters); + DESTROY_LIST(h2o_logger_t, pathconf->loggers); +#undef DESTROY_LIST + + free(pathconf->path.base); + if (pathconf->mimemap != NULL) + h2o_mem_release_shared(pathconf->mimemap); + if (pathconf->env != NULL) + h2o_mem_release_shared(pathconf->env); +} + +void h2o_config_init(h2o_globalconf_t *config) +{ + memset(config, 0, sizeof(*config)); + config->hosts = h2o_mem_alloc(sizeof(config->hosts[0])); + config->hosts[0] = NULL; + h2o_linklist_init_anchor(&config->configurators); + config->server_name = h2o_iovec_init(H2O_STRLIT("h2o/" H2O_VERSION)); + config->max_request_entity_size = H2O_DEFAULT_MAX_REQUEST_ENTITY_SIZE; + config->max_delegations = H2O_DEFAULT_MAX_DELEGATIONS; + config->handshake_timeout = H2O_DEFAULT_HANDSHAKE_TIMEOUT; + config->http1.req_timeout = H2O_DEFAULT_HTTP1_REQ_TIMEOUT; + config->http1.upgrade_to_http2 = H2O_DEFAULT_HTTP1_UPGRADE_TO_HTTP2; + config->http1.callbacks = H2O_HTTP1_CALLBACKS; + config->http2.idle_timeout = H2O_DEFAULT_HTTP2_IDLE_TIMEOUT; + config->http2.graceful_shutdown_timeout = H2O_DEFAULT_HTTP2_GRACEFUL_SHUTDOWN_TIMEOUT; + config->proxy.io_timeout = H2O_DEFAULT_PROXY_IO_TIMEOUT; + config->proxy.emit_x_forwarded_headers = 1; + config->proxy.emit_via_header = 1; + config->http2.max_concurrent_requests_per_connection = H2O_HTTP2_SETTINGS_HOST.max_concurrent_streams; + config->http2.max_streams_for_priority = 16; + config->http2.latency_optimization.min_rtt = 50; // milliseconds + config->http2.latency_optimization.max_additional_delay = 10; + config->http2.latency_optimization.max_cwnd = 65535; + config->http2.callbacks = H2O_HTTP2_CALLBACKS; + config->mimemap = h2o_mimemap_create(); + + h2o_configurator__init_core(config); +} + +h2o_pathconf_t *h2o_config_register_path(h2o_hostconf_t *hostconf, const char *path, int flags) +{ + h2o_pathconf_t *pathconf; + + h2o_vector_reserve(NULL, &hostconf->paths, hostconf->paths.size + 1); + pathconf = hostconf->paths.entries + hostconf->paths.size++; + + h2o_config_init_pathconf(pathconf, hostconf->global, path, hostconf->mimemap); + + return pathconf; +} + +void h2o_config_register_status_handler(h2o_globalconf_t *config, h2o_status_handler_t status_handler) +{ + h2o_vector_reserve(NULL, &config->statuses, config->statuses.size + 1); + config->statuses.entries[config->statuses.size++] = status_handler; +} + +void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, final_status_handler_cb status_handler) +{ + h2o_status_handler_t *sh; + + h2o_vector_reserve(NULL, &config->statuses, config->statuses.size + 1); + sh = &config->statuses.entries[config->statuses.size++]; + memset(sh, 0, sizeof(*sh)); + sh->name = h2o_strdup(NULL, name.base, name.len); + sh->final = status_handler; +} + +h2o_hostconf_t *h2o_config_register_host(h2o_globalconf_t *config, h2o_iovec_t host, uint16_t port) +{ + h2o_hostconf_t *hostconf = NULL; + h2o_iovec_t host_lc; + + assert(host.len != 0); + + /* convert hostname to lowercase */ + host_lc = h2o_strdup(NULL, host.base, host.len); + h2o_strtolower(host_lc.base, host_lc.len); + + { /* return NULL if given authority is already registered */ + h2o_hostconf_t **p; + for (p = config->hosts; *p != NULL; ++p) + if (h2o_memis((*p)->authority.host.base, (*p)->authority.host.len, host_lc.base, host_lc.len) && + (*p)->authority.port == port) + goto Exit; + } + + /* create hostconf */ + hostconf = create_hostconf(config); + hostconf->authority.host = host_lc; + host_lc = (h2o_iovec_t){NULL}; + hostconf->authority.port = port; + if (hostconf->authority.port == 65535) { + hostconf->authority.hostport = hostconf->authority.host; + } else { + hostconf->authority.hostport.base = h2o_mem_alloc(hostconf->authority.host.len + sizeof("[]:" H2O_UINT16_LONGEST_STR)); + if (strchr(hostconf->authority.host.base, ':') != NULL) { + hostconf->authority.hostport.len = + sprintf(hostconf->authority.hostport.base, "[%s]:%" PRIu16, hostconf->authority.host.base, port); + } else { + hostconf->authority.hostport.len = + sprintf(hostconf->authority.hostport.base, "%s:%" PRIu16, hostconf->authority.host.base, port); + } + } + + /* append to the list */ + h2o_append_to_null_terminated_list((void *)&config->hosts, hostconf); + +Exit: + free(host_lc.base); + return hostconf; +} + +void h2o_config_dispose(h2o_globalconf_t *config) +{ + size_t i; + + for (i = 0; config->hosts[i] != NULL; ++i) { + h2o_hostconf_t *hostconf = config->hosts[i]; + destroy_hostconf(hostconf); + } + free(config->hosts); + + h2o_mem_release_shared(config->mimemap); + h2o_configurator__dispose_configurators(config); +} + +h2o_handler_t *h2o_create_handler(h2o_pathconf_t *conf, size_t sz) +{ + h2o_handler_t *handler = h2o_mem_alloc(sz); + + memset(handler, 0, sz); + handler->_config_slot = conf->global->_num_config_slots++; + + h2o_vector_reserve(NULL, &conf->handlers, conf->handlers.size + 1); + conf->handlers.entries[conf->handlers.size++] = handler; + + return handler; +} + +h2o_filter_t *h2o_create_filter(h2o_pathconf_t *conf, size_t sz) +{ + h2o_filter_t *filter = h2o_mem_alloc(sz); + + memset(filter, 0, sz); + filter->_config_slot = conf->global->_num_config_slots++; + + h2o_vector_reserve(NULL, &conf->filters, conf->filters.size + 1); + memmove(conf->filters.entries + 1, conf->filters.entries, conf->filters.size * sizeof(conf->filters.entries[0])); + conf->filters.entries[0] = filter; + ++conf->filters.size; + + return filter; +} + +h2o_logger_t *h2o_create_logger(h2o_pathconf_t *conf, size_t sz) +{ + h2o_logger_t *logger = h2o_mem_alloc(sz); + + memset(logger, 0, sz); + logger->_config_slot = conf->global->_num_config_slots++; + + h2o_vector_reserve(NULL, &conf->loggers, conf->loggers.size + 1); + conf->loggers.entries[conf->loggers.size++] = logger; + + return logger; +} diff --git a/src/web/server/h2o/libh2o/lib/core/configurator.c b/src/web/server/h2o/libh2o/lib/core/configurator.c new file mode 100644 index 000000000..891770cc2 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/configurator.c @@ -0,0 +1,1102 @@ +/* + * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Fastly, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include "h2o.h" +#include "h2o/configurator.h" + +struct st_core_config_vars_t { + struct { + unsigned reprioritize_blocking_assets : 1; + unsigned push_preload : 1; + h2o_casper_conf_t casper; + } http2; + struct { + unsigned emit_request_errors : 1; + } error_log; +}; + +struct st_core_configurator_t { + h2o_configurator_t super; + struct st_core_config_vars_t *vars, _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1]; +}; + +static h2o_configurator_context_t *create_context(h2o_configurator_context_t *parent, int is_custom_handler) +{ + h2o_configurator_context_t *ctx = h2o_mem_alloc(sizeof(*ctx)); + if (parent == NULL) { + *ctx = (h2o_configurator_context_t){NULL}; + return ctx; + } + *ctx = *parent; + if (ctx->env != NULL) + h2o_mem_addref_shared(ctx->env); + ctx->parent = parent; + return ctx; +} + +static void destroy_context(h2o_configurator_context_t *ctx) +{ + if (ctx->env != NULL) { + if (ctx->pathconf != NULL) + ctx->pathconf->env = ctx->env; + else + h2o_mem_release_shared(ctx->env); + } + free(ctx); +} + +static int on_core_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node) +{ + struct st_core_configurator_t *self = (void *)_self; + + ++self->vars; + self->vars[0] = self->vars[-1]; + return 0; +} + +static int on_core_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node) +{ + struct st_core_configurator_t *self = (void *)_self; + + if (ctx->hostconf != NULL && ctx->pathconf == NULL) { + /* 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.casper = self->vars->http2.casper; + } else if (ctx->pathconf != NULL) { + /* exitting from path or extension-level configuration */ + ctx->pathconf->error_log.emit_request_errors = self->vars->error_log.emit_request_errors; + } + + --self->vars; + return 0; +} + +static void destroy_configurator(h2o_configurator_t *configurator) +{ + if (configurator->dispose != NULL) + configurator->dispose(configurator); + free(configurator->commands.entries); + free(configurator); +} + +static int setup_configurators(h2o_configurator_context_t *ctx, int is_enter, yoml_t *node) +{ + h2o_linklist_t *n; + + for (n = ctx->globalconf->configurators.next; n != &ctx->globalconf->configurators; n = n->next) { + h2o_configurator_t *c = H2O_STRUCT_FROM_MEMBER(h2o_configurator_t, _link, n); + if (is_enter) { + if (c->enter != NULL && c->enter(c, ctx, node) != 0) + return -1; + } else { + if (c->exit != NULL && c->exit(c, ctx, node) != 0) + return -1; + } + } + + return 0; +} + +static int config_timeout(h2o_configurator_command_t *cmd, yoml_t *node, uint64_t *slot) +{ + uint64_t timeout_in_secs; + + if (h2o_configurator_scanf(cmd, node, "%" SCNu64, &timeout_in_secs) != 0) + return -1; + + *slot = timeout_in_secs * 1000; + return 0; +} + +int h2o_configurator_apply_commands(h2o_configurator_context_t *ctx, yoml_t *node, int flags_mask, const char **ignore_commands) +{ + struct st_cmd_value_t { + h2o_configurator_command_t *cmd; + yoml_t *value; + }; + H2O_VECTOR(struct st_cmd_value_t) deferred = {NULL}, semi_deferred = {NULL}; + int ret = -1; + + if (node != NULL && node->type != YOML_TYPE_MAPPING) { + h2o_configurator_errprintf(NULL, node, "node must be a MAPPING"); + goto Exit; + } + + /* call on_enter of every configurator */ + if (setup_configurators(ctx, 1, node) != 0) + goto Exit; + + /* handle the configuration commands */ + if (node != NULL) { + size_t i; + for (i = 0; i != node->data.mapping.size; ++i) { + yoml_t *key = node->data.mapping.elements[i].key, *value = node->data.mapping.elements[i].value; + h2o_configurator_command_t *cmd; + /* obtain the target command */ + if (key->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(NULL, key, "command must be a string"); + goto Exit; + } + if (ignore_commands != NULL) { + size_t i; + for (i = 0; ignore_commands[i] != NULL; ++i) + if (strcmp(ignore_commands[i], key->data.scalar) == 0) + goto SkipCommand; + } + if ((cmd = h2o_configurator_get_command(ctx->globalconf, key->data.scalar)) == NULL) { + h2o_configurator_errprintf(NULL, key, "unknown command: %s", key->data.scalar); + goto Exit; + } + if ((cmd->flags & flags_mask) == 0) { + h2o_configurator_errprintf(cmd, key, "the command cannot be used at this level"); + goto Exit; + } + /* check value type */ + if ((cmd->flags & (H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR | H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE | + H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING)) != 0) { + switch (value->type) { + case YOML_TYPE_SCALAR: + if ((cmd->flags & H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR) == 0) { + h2o_configurator_errprintf(cmd, value, "argument cannot be a scalar"); + goto Exit; + } + break; + case YOML_TYPE_SEQUENCE: + if ((cmd->flags & H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE) == 0) { + h2o_configurator_errprintf(cmd, value, "argument cannot be a sequence"); + goto Exit; + } + break; + case YOML_TYPE_MAPPING: + if ((cmd->flags & H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING) == 0) { + h2o_configurator_errprintf(cmd, value, "argument cannot be a mapping"); + goto Exit; + } + break; + default: + assert(!"unreachable"); + break; + } + } + /* handle the command (or keep it for later execution) */ + if ((cmd->flags & H2O_CONFIGURATOR_FLAG_SEMI_DEFERRED) != 0) { + h2o_vector_reserve(NULL, &semi_deferred, semi_deferred.size + 1); + semi_deferred.entries[semi_deferred.size++] = (struct st_cmd_value_t){cmd, value}; + } else if ((cmd->flags & H2O_CONFIGURATOR_FLAG_DEFERRED) != 0) { + h2o_vector_reserve(NULL, &deferred, deferred.size + 1); + deferred.entries[deferred.size++] = (struct st_cmd_value_t){cmd, value}; + } else { + if (cmd->cb(cmd, ctx, value) != 0) + goto Exit; + } + SkipCommand:; + } + for (i = 0; i != semi_deferred.size; ++i) { + struct st_cmd_value_t *pair = semi_deferred.entries + i; + if (pair->cmd->cb(pair->cmd, ctx, pair->value) != 0) + goto Exit; + } + for (i = 0; i != deferred.size; ++i) { + struct st_cmd_value_t *pair = deferred.entries + i; + if (pair->cmd->cb(pair->cmd, ctx, pair->value) != 0) + goto Exit; + } + } + + /* call on_exit of every configurator */ + if (setup_configurators(ctx, 0, node) != 0) + goto Exit; + + ret = 0; +Exit: + free(deferred.entries); + free(semi_deferred.entries); + return ret; +} + +static int sort_from_longer_paths(const yoml_mapping_element_t *x, const yoml_mapping_element_t *y) +{ + size_t xlen = strlen(x->key->data.scalar), ylen = strlen(y->key->data.scalar); + if (xlen < ylen) + return 1; + else if (xlen > ylen) + return -1; + /* apply strcmp for stable sort */ + return strcmp(x->key->data.scalar, y->key->data.scalar); +} + +static yoml_t *convert_path_config_node(h2o_configurator_command_t *cmd, yoml_t *node) +{ + size_t i, j; + + switch (node->type) { + case YOML_TYPE_MAPPING: + break; + case YOML_TYPE_SEQUENCE: { + /* convert to mapping */ + yoml_t *map = h2o_mem_alloc(sizeof(yoml_t)); + *map = (yoml_t){YOML_TYPE_MAPPING}; + if (node->filename != NULL) + map->filename = h2o_strdup(NULL, node->filename, SIZE_MAX).base; + map->line = node->line; + map->column = node->column; + if (node->anchor != NULL) + map->anchor = h2o_strdup(NULL, node->anchor, SIZE_MAX).base; + map->_refcnt = 1; + + for (i = 0; i != node->data.sequence.size; ++i) { + yoml_t *elem = node->data.sequence.elements[i]; + if (elem->type != YOML_TYPE_MAPPING) { + yoml_free(map, NULL); + goto Error; + } + for (j = 0; j != elem->data.mapping.size; ++j) { + yoml_t *elemkey = elem->data.mapping.elements[j].key; + yoml_t *elemvalue = elem->data.mapping.elements[j].value; + map = h2o_mem_realloc(map, offsetof(yoml_t, data.mapping.elements) + + sizeof(yoml_mapping_element_t) * (map->data.mapping.size + 1)); + map->data.mapping.elements[map->data.mapping.size].key = elemkey; + map->data.mapping.elements[map->data.mapping.size].value = elemvalue; + ++map->data.mapping.size; + ++elemkey->_refcnt; + ++elemvalue->_refcnt; + } + } + return map; + } break; + default: + Error: + h2o_configurator_errprintf(cmd, node, "value must be a mapping or sequence of mapping"); + return NULL; + } + + ++node->_refcnt; + return node; +} + +static int config_path(h2o_configurator_context_t *parent_ctx, h2o_pathconf_t *pathconf, yoml_t *node) +{ + h2o_configurator_context_t *path_ctx = create_context(parent_ctx, 0); + path_ctx->pathconf = pathconf; + path_ctx->mimemap = &pathconf->mimemap; + + int ret = h2o_configurator_apply_commands(path_ctx, node, H2O_CONFIGURATOR_FLAG_PATH, NULL); + + destroy_context(path_ctx); + return ret; +} + +static int on_config_paths(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + size_t i; + + /* sort by the length of the path (descending) */ + for (i = 0; i != node->data.mapping.size; ++i) { + yoml_t *key = node->data.mapping.elements[i].key; + if (key->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, key, "key (representing the virtual path) must be a string"); + return -1; + } + } + qsort(node->data.mapping.elements, node->data.mapping.size, sizeof(node->data.mapping.elements[0]), + (int (*)(const void *, const void *))sort_from_longer_paths); + + for (i = 0; i != node->data.mapping.size; ++i) { + yoml_t *key = node->data.mapping.elements[i].key, *value; + if ((value = convert_path_config_node(cmd, node->data.mapping.elements[i].value)) == NULL) + return -1; + h2o_pathconf_t *pathconf = h2o_config_register_path(ctx->hostconf, key->data.scalar, 0); + int cmd_ret = config_path(ctx, pathconf, value); + yoml_free(value, NULL); + if (cmd_ret != 0) + return cmd_ret; + } + + /* configure fallback path along with ordinary paths */ + return config_path(ctx, &ctx->hostconf->fallback_path, NULL); +} + +static int on_config_hosts(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + size_t i; + + if (node->data.mapping.size == 0) { + h2o_configurator_errprintf(cmd, node, "the mapping cannot be empty"); + return -1; + } + + for (i = 0; i != node->data.mapping.size; ++i) { + yoml_t *key = node->data.mapping.elements[i].key; + yoml_t *value = node->data.mapping.elements[i].value; + h2o_iovec_t hostname; + uint16_t port; + if (key->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, key, "key (representing the hostname) must be a string"); + return -1; + } + if (h2o_url_parse_hostport(key->data.scalar, strlen(key->data.scalar), &hostname, &port) == NULL) { + h2o_configurator_errprintf(cmd, key, "invalid key (must be either `host` or `host:port`)"); + return -1; + } + assert(hostname.len != 0); + if ((hostname.base[0] == '*' && !(hostname.len == 1 || hostname.base[1] == '.')) || + memchr(hostname.base + 1, '*', hostname.len - 1) != NULL) { + h2o_configurator_errprintf(cmd, key, "wildcard (*) can only be used at the start of the hostname"); + return -1; + } + h2o_configurator_context_t *host_ctx = create_context(ctx, 0); + if ((host_ctx->hostconf = h2o_config_register_host(host_ctx->globalconf, hostname, port)) == NULL) { + h2o_configurator_errprintf(cmd, key, "duplicate host entry"); + destroy_context(host_ctx); + return -1; + } + host_ctx->mimemap = &host_ctx->hostconf->mimemap; + int cmd_ret = h2o_configurator_apply_commands(host_ctx, value, H2O_CONFIGURATOR_FLAG_HOST, NULL); + destroy_context(host_ctx); + if (cmd_ret != 0) + return -1; + if (yoml_get(value, "paths") == NULL) { + h2o_configurator_errprintf(NULL, value, "mandatory configuration directive `paths` is missing"); + return -1; + } + } + + return 0; +} + +static int on_config_limit_request_body(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + return h2o_configurator_scanf(cmd, node, "%zu", &ctx->globalconf->max_request_entity_size); +} + +static int on_config_max_delegations(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + return h2o_configurator_scanf(cmd, node, "%u", &ctx->globalconf->max_delegations); +} + +static int on_config_handshake_timeout(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + return config_timeout(cmd, node, &ctx->globalconf->handshake_timeout); +} + +static int on_config_http1_request_timeout(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + return config_timeout(cmd, node, &ctx->globalconf->http1.req_timeout); +} + +static int on_config_http1_upgrade_to_http2(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON"); + if (ret == -1) + return -1; + ctx->globalconf->http1.upgrade_to_http2 = (int)ret; + return 0; +} + +static int on_config_http2_idle_timeout(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + return config_timeout(cmd, node, &ctx->globalconf->http2.idle_timeout); +} + +static int on_config_http2_graceful_shutdown_timeout(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + return config_timeout(cmd, node, &ctx->globalconf->http2.graceful_shutdown_timeout); +} + +static int on_config_http2_max_concurrent_requests_per_connection(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, + yoml_t *node) +{ + return h2o_configurator_scanf(cmd, node, "%zu", &ctx->globalconf->http2.max_concurrent_requests_per_connection); +} + +static int on_config_http2_latency_optimization_min_rtt(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, + yoml_t *node) +{ + return h2o_configurator_scanf(cmd, node, "%u", &ctx->globalconf->http2.latency_optimization.min_rtt); +} + +static int on_config_http2_latency_optimization_max_additional_delay(h2o_configurator_command_t *cmd, + h2o_configurator_context_t *ctx, yoml_t *node) +{ + double ratio; + if (h2o_configurator_scanf(cmd, node, "%lf", &ratio) != 0) + return -1; + if (!(0.0 < ratio)) { + h2o_configurator_errprintf(cmd, node, "ratio must be a positive number"); + return -1; + } + ctx->globalconf->http2.latency_optimization.max_additional_delay = 100 * ratio; + return 0; +} + +static int on_config_http2_latency_optimization_max_cwnd(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, + yoml_t *node) +{ + return h2o_configurator_scanf(cmd, node, "%u", &ctx->globalconf->http2.latency_optimization.max_cwnd); +} + +static int on_config_http2_reprioritize_blocking_assets(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.reprioritize_blocking_assets = (int)on; + + return 0; +} + +static int on_config_http2_push_preload(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.push_preload = (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 = { + 13, /* casper_bits: default (2^13 ~= 100 assets * 1/0.01 collision probability) */ + 0 /* track blocking assets only */ + }; + + struct st_core_configurator_t *self = (void *)cmd->configurator; + + switch (node->type) { + case YOML_TYPE_SCALAR: + if (strcasecmp(node->data.scalar, "OFF") == 0) { + self->vars->http2.casper = (h2o_casper_conf_t){0}; + } else if (strcasecmp(node->data.scalar, "ON") == 0) { + self->vars->http2.casper = defaults; + } + break; + case YOML_TYPE_MAPPING: { + /* set to default */ + self->vars->http2.casper = defaults; + /* override the attributes defined */ + yoml_t *t; + if ((t = yoml_get(node, "capacity-bits")) != NULL) { + if (!(t->type == YOML_TYPE_SCALAR && sscanf(t->data.scalar, "%u", &self->vars->http2.casper.capacity_bits) == 1 && + self->vars->http2.casper.capacity_bits < 16)) { + h2o_configurator_errprintf(cmd, t, "value of `capacity-bits` must be an integer between 0 to 15"); + return -1; + } + } + if ((t = yoml_get(node, "tracking-types")) != NULL) { + if (t->type == YOML_TYPE_SCALAR && strcasecmp(t->data.scalar, "blocking-assets") == 0) { + self->vars->http2.casper.track_all_types = 0; + } else if (t->type == YOML_TYPE_SCALAR && strcasecmp(t->data.scalar, "all") == 0) { + self->vars->http2.casper.track_all_types = 1; + } else { + h2o_configurator_errprintf(cmd, t, "value of `tracking-types` must be either of: `blocking-assets` or `all`"); + return -1; + } + } + } break; + default: + h2o_configurator_errprintf(cmd, node, "value must be `OFF`,`ON` or a mapping containing the necessary attributes"); + return -1; + } + + return 0; +} + +static int assert_is_mimetype(h2o_configurator_command_t *cmd, yoml_t *node) +{ + if (node->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, node, "expected a scalar (mime-type)"); + return -1; + } + if (strchr(node->data.scalar, '/') == NULL) { + h2o_configurator_errprintf(cmd, node, "the string \"%s\" does not look like a mime-type", node->data.scalar); + return -1; + } + return 0; +} + +static int assert_is_extension(h2o_configurator_command_t *cmd, yoml_t *node) +{ + if (node->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, node, "expected a scalar (extension)"); + return -1; + } + if (node->data.scalar[0] != '.') { + h2o_configurator_errprintf(cmd, node, "given extension \"%s\" does not start with a \".\"", node->data.scalar); + return -1; + } + return 0; +} + +static int set_mimetypes(h2o_configurator_command_t *cmd, h2o_mimemap_t *mimemap, yoml_t *node) +{ + size_t i, j; + + assert(node->type == YOML_TYPE_MAPPING); + + for (i = 0; i != node->data.mapping.size; ++i) { + yoml_t *key = node->data.mapping.elements[i].key; + yoml_t *value = node->data.mapping.elements[i].value; + if (assert_is_mimetype(cmd, key) != 0) + return -1; + switch (value->type) { + case YOML_TYPE_SCALAR: + if (assert_is_extension(cmd, value) != 0) + return -1; + h2o_mimemap_define_mimetype(mimemap, value->data.scalar + 1, key->data.scalar, NULL); + break; + case YOML_TYPE_SEQUENCE: + for (j = 0; j != value->data.sequence.size; ++j) { + yoml_t *ext_node = value->data.sequence.elements[j]; + if (assert_is_extension(cmd, ext_node) != 0) + return -1; + h2o_mimemap_define_mimetype(mimemap, ext_node->data.scalar + 1, key->data.scalar, NULL); + } + break; + case YOML_TYPE_MAPPING: { + yoml_t *t; + h2o_mime_attributes_t attr; + h2o_mimemap_get_default_attributes(key->data.scalar, &attr); + if ((t = yoml_get(value, "is_compressible")) != NULL) { + if (t->type == YOML_TYPE_SCALAR && strcasecmp(t->data.scalar, "YES") == 0) { + attr.is_compressible = 1; + } else if (t->type == YOML_TYPE_SCALAR && strcasecmp(t->data.scalar, "NO") == 0) { + attr.is_compressible = 0; + } else { + h2o_configurator_errprintf(cmd, t, "`is_compressible` attribute must be either of: `YES` or `NO`"); + return -1; + } + } + if ((t = yoml_get(value, "priority")) != NULL) { + if (t->type == YOML_TYPE_SCALAR && strcasecmp(t->data.scalar, "normal") == 0) { + attr.priority = H2O_MIME_ATTRIBUTE_PRIORITY_NORMAL; + } else if (t->type == YOML_TYPE_SCALAR && strcasecmp(t->data.scalar, "highest") == 0) { + attr.priority = H2O_MIME_ATTRIBUTE_PRIORITY_HIGHEST; + } else { + h2o_configurator_errprintf(cmd, t, "`priority` attribute must be either of: `normal` or `highest`"); + return -1; + } + } + if ((t = yoml_get(value, "extensions")) == NULL) { + h2o_configurator_errprintf(cmd, value, "cannot find mandatory attribute `extensions`"); + return -1; + } + if (t->type != YOML_TYPE_SEQUENCE) { + h2o_configurator_errprintf(cmd, t, "`extensions` attribute must be a sequence of scalars"); + return -1; + } + for (j = 0; j != t->data.sequence.size; ++j) { + yoml_t *ext_node = t->data.sequence.elements[j]; + if (assert_is_extension(cmd, ext_node) != 0) + return -1; + h2o_mimemap_define_mimetype(mimemap, ext_node->data.scalar + 1, key->data.scalar, &attr); + } + } break; + default: + fprintf(stderr, "logic flaw at %s:%d\n", __FILE__, __LINE__); + abort(); + } + } + + return 0; +} + +static int on_config_mime_settypes(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + h2o_mimemap_t *newmap = h2o_mimemap_create(); + h2o_mimemap_clear_types(newmap); + h2o_mimemap_set_default_type(newmap, h2o_mimemap_get_default_type(*ctx->mimemap)->data.mimetype.base, NULL); + if (set_mimetypes(cmd, newmap, node) != 0) { + h2o_mem_release_shared(newmap); + return -1; + } + + h2o_mem_release_shared(*ctx->mimemap); + *ctx->mimemap = newmap; + return 0; +} + +static void clone_mimemap_if_clean(h2o_configurator_context_t *ctx) +{ + if (ctx->parent == NULL) + return; + if (*ctx->mimemap != *ctx->parent->mimemap) + return; + h2o_mem_release_shared(*ctx->mimemap); + /* even after release, ctx->mimemap is still retained by the parent and therefore we can use it as the argument to clone */ + *ctx->mimemap = h2o_mimemap_clone(*ctx->mimemap); +} + +static int on_config_mime_addtypes(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + clone_mimemap_if_clean(ctx); + return set_mimetypes(cmd, *ctx->mimemap, node); +} + +static int on_config_mime_removetypes(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + size_t i; + + clone_mimemap_if_clean(ctx); + for (i = 0; i != node->data.sequence.size; ++i) { + yoml_t *ext_node = node->data.sequence.elements[i]; + if (assert_is_extension(cmd, ext_node) != 0) + return -1; + h2o_mimemap_remove_type(*ctx->mimemap, ext_node->data.scalar + 1); + } + + return 0; +} + +static int on_config_mime_setdefaulttype(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + if (assert_is_mimetype(cmd, node) != 0) + return -1; + + clone_mimemap_if_clean(ctx); + h2o_mimemap_set_default_type(*ctx->mimemap, node->data.scalar, NULL); + + return 0; +} + +static int on_config_custom_handler(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + static const char *ignore_commands[] = {"extension", NULL}; + yoml_t *ext_node; + const char **exts; + h2o_mimemap_type_t *type = NULL; + + if (node->type != YOML_TYPE_MAPPING) { + h2o_configurator_errprintf(cmd, node, "argument must be a MAPPING"); + return -1; + } + if ((ext_node = yoml_get(node, "extension")) == NULL) { + h2o_configurator_errprintf(cmd, node, "mandatory key `extension` is missing"); + return -1; + } + + /* create dynamic type */ + switch (ext_node->type) { + case YOML_TYPE_SCALAR: + if (assert_is_extension(cmd, ext_node) != 0) + return -1; + exts = alloca(2 * sizeof(*exts)); + exts[0] = ext_node->data.scalar + 1; + exts[1] = NULL; + break; + case YOML_TYPE_SEQUENCE: { + exts = alloca((ext_node->data.sequence.size + 1) * sizeof(*exts)); + size_t i; + for (i = 0; i != ext_node->data.sequence.size; ++i) { + yoml_t *n = ext_node->data.sequence.elements[i]; + if (assert_is_extension(cmd, n) != 0) + return -1; + exts[i] = n->data.scalar + 1; + } + exts[i] = NULL; + } break; + default: + h2o_configurator_errprintf(cmd, ext_node, "`extensions` must be a scalar or sequence of scalar"); + return -1; + } + clone_mimemap_if_clean(ctx); + type = h2o_mimemap_define_dynamic(*ctx->mimemap, exts, ctx->globalconf); + + /* apply the configuration commands */ + h2o_configurator_context_t *ext_ctx = create_context(ctx, 1); + ext_ctx->pathconf = &type->data.dynamic.pathconf; + ext_ctx->mimemap = NULL; + int cmd_ret = h2o_configurator_apply_commands(ext_ctx, node, H2O_CONFIGURATOR_FLAG_EXTENSION, ignore_commands); + destroy_context(ext_ctx); + if (cmd_ret != 0) + return cmd_ret; + switch (type->data.dynamic.pathconf.handlers.size) { + case 1: + break; + case 0: + h2o_configurator_errprintf(cmd, node, "no handler declared for given extension"); + return -1; + default: + h2o_configurator_errprintf(cmd, node, "cannot assign more than one handler for given extension"); + return -1; + } + + return 0; +} + +static void inherit_env_if_necessary(h2o_configurator_context_t *ctx) +{ + if (ctx->env == (ctx->parent != NULL ? ctx->parent->env : NULL)) + ctx->env = h2o_config_create_envconf(ctx->env); +} + +static int on_config_setenv(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + size_t i; + + inherit_env_if_necessary(ctx); + + for (i = 0; i != node->data.mapping.size; ++i) { + yoml_t *key = node->data.mapping.elements[i].key, *value = node->data.mapping.elements[i].value; + if (key->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, key, "key must be a scalar"); + return -1; + } + if (value->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, value, "value must be a scalar"); + return -1; + } + h2o_config_setenv(ctx->env, key->data.scalar, value->data.scalar); + } + + return 0; +} + +static int on_config_unsetenv(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + inherit_env_if_necessary(ctx); + + switch (node->type) { + case YOML_TYPE_SCALAR: + h2o_config_unsetenv(ctx->env, node->data.scalar); + break; + case YOML_TYPE_SEQUENCE: { + size_t i; + for (i = 0; i != node->data.sequence.size; ++i) { + yoml_t *element = node->data.sequence.elements[i]; + if (element->type != YOML_TYPE_SCALAR) { + h2o_configurator_errprintf(cmd, element, "element of a sequence passed to unsetenv must be a scalar"); + return -1; + } + h2o_config_unsetenv(ctx->env, element->data.scalar); + } + } break; + default: + h2o_configurator_errprintf(cmd, node, "argument to unsetenv must be either a scalar or a sequence"); + return -1; + } + + return 0; +} + +static int on_config_server_name(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + ctx->globalconf->server_name = h2o_strdup(NULL, node->data.scalar, SIZE_MAX); + return 0; +} + +static int on_config_send_server_name(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) +{ + switch(h2o_configurator_get_one_of(cmd, node, "OFF,ON,preserve")) { + case 0: /* off */ + ctx->globalconf->server_name = h2o_iovec_init(H2O_STRLIT("")); + break; + case 1: /* on */ + break; + case 2: /* preserve */ + ctx->globalconf->server_name = h2o_iovec_init(H2O_STRLIT("")); + ctx->globalconf->proxy.preserve_server_header = 1; + break; + default: + return -1; + } + return 0; +} + +static int on_config_error_log_emit_request_errors(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->error_log.emit_request_errors = (int)on; + return 0; +} + +void h2o_configurator__init_core(h2o_globalconf_t *conf) +{ + /* check if already initialized */ + if (h2o_configurator_get_command(conf, "files") != NULL) + return; + + { /* `hosts` and `paths` */ + h2o_configurator_t *c = h2o_configurator_create(conf, sizeof(*c)); + h2o_configurator_define_command(c, "hosts", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING | + H2O_CONFIGURATOR_FLAG_DEFERRED, + on_config_hosts); + h2o_configurator_define_command(c, "paths", H2O_CONFIGURATOR_FLAG_HOST | H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING | + H2O_CONFIGURATOR_FLAG_DEFERRED, + on_config_paths); + }; + + { /* setup global configurators */ + struct st_core_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c)); + c->super.enter = on_core_enter; + c->super.exit = on_core_exit; + c->vars = c->_vars_stack; + c->vars->http2.reprioritize_blocking_assets = 1; /* defaults to ON */ + c->vars->http2.push_preload = 1; /* defaults to ON */ + c->vars->error_log.emit_request_errors = 1; /* defaults to ON */ + h2o_configurator_define_command(&c->super, "limit-request-body", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_limit_request_body); + h2o_configurator_define_command(&c->super, "max-delegations", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_max_delegations); + h2o_configurator_define_command(&c->super, "handshake-timeout", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_handshake_timeout); + h2o_configurator_define_command(&c->super, "http1-request-timeout", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http1_request_timeout); + h2o_configurator_define_command(&c->super, "http1-upgrade-to-http2", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http1_upgrade_to_http2); + h2o_configurator_define_command(&c->super, "http2-idle-timeout", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_idle_timeout); + h2o_configurator_define_command(&c->super, "http2-graceful-shutdown-timeout", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_graceful_shutdown_timeout); + h2o_configurator_define_command(&c->super, "http2-max-concurrent-requests-per-connection", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_max_concurrent_requests_per_connection); + h2o_configurator_define_command(&c->super, "http2-latency-optimization-min-rtt", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_latency_optimization_min_rtt); + h2o_configurator_define_command(&c->super, "http2-latency-optimization-max-additional-delay", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_latency_optimization_max_additional_delay); + h2o_configurator_define_command(&c->super, "http2-latency-optimization-max-cwnd", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_latency_optimization_max_cwnd); + h2o_configurator_define_command(&c->super, "http2-reprioritize-blocking-assets", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST | + H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_http2_reprioritize_blocking_assets); + h2o_configurator_define_command(&c->super, "http2-push-preload", 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-casper", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_HOST, + on_config_http2_casper); + h2o_configurator_define_command(&c->super, "file.mime.settypes", + (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) | + H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING, + on_config_mime_settypes); + h2o_configurator_define_command(&c->super, "file.mime.addtypes", + (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) | + H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING, + on_config_mime_addtypes); + h2o_configurator_define_command(&c->super, "file.mime.removetypes", + (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) | + H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE, + on_config_mime_removetypes); + h2o_configurator_define_command(&c->super, "file.mime.setdefaulttype", + (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) | + H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_mime_setdefaulttype); + h2o_configurator_define_command(&c->super, "file.custom-handler", + (H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) | + H2O_CONFIGURATOR_FLAG_SEMI_DEFERRED, + on_config_custom_handler); + h2o_configurator_define_command(&c->super, "setenv", + H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING, on_config_setenv); + h2o_configurator_define_command(&c->super, "unsetenv", H2O_CONFIGURATOR_FLAG_ALL_LEVELS, on_config_unsetenv); + h2o_configurator_define_command(&c->super, "server-name", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_server_name); + h2o_configurator_define_command(&c->super, "send-server-name", + H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR | + H2O_CONFIGURATOR_FLAG_DEFERRED, + on_config_send_server_name); + h2o_configurator_define_command(&c->super, "error-log.emit-request-errors", + H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, + on_config_error_log_emit_request_errors); + } +} + +void h2o_configurator__dispose_configurators(h2o_globalconf_t *conf) +{ + while (!h2o_linklist_is_empty(&conf->configurators)) { + h2o_configurator_t *c = H2O_STRUCT_FROM_MEMBER(h2o_configurator_t, _link, conf->configurators.next); + h2o_linklist_unlink(&c->_link); + if (c->dispose != NULL) + c->dispose(c); + destroy_configurator(c); + } +} + +h2o_configurator_t *h2o_configurator_create(h2o_globalconf_t *conf, size_t sz) +{ + h2o_configurator_t *c; + + assert(sz >= sizeof(*c)); + + c = h2o_mem_alloc(sz); + memset(c, 0, sz); + h2o_linklist_insert(&conf->configurators, &c->_link); + + return c; +} + +void h2o_configurator_define_command(h2o_configurator_t *configurator, const char *name, int flags, h2o_configurator_command_cb cb) +{ + h2o_configurator_command_t *cmd; + + h2o_vector_reserve(NULL, &configurator->commands, configurator->commands.size + 1); + cmd = configurator->commands.entries + configurator->commands.size++; + cmd->configurator = configurator; + cmd->flags = flags; + cmd->name = name; + cmd->cb = cb; +} + +h2o_configurator_command_t *h2o_configurator_get_command(h2o_globalconf_t *conf, const char *name) +{ + h2o_linklist_t *node; + size_t i; + + for (node = conf->configurators.next; node != &conf->configurators; node = node->next) { + h2o_configurator_t *configurator = H2O_STRUCT_FROM_MEMBER(h2o_configurator_t, _link, node); + for (i = 0; i != configurator->commands.size; ++i) { + h2o_configurator_command_t *cmd = configurator->commands.entries + i; + if (strcmp(cmd->name, name) == 0) { + return cmd; + } + } + } + + return NULL; +} + +int h2o_configurator_apply(h2o_globalconf_t *config, yoml_t *node, int dry_run) +{ + h2o_configurator_context_t *ctx = create_context(NULL, 0); + ctx->globalconf = config; + ctx->mimemap = &ctx->globalconf->mimemap; + ctx->dry_run = dry_run; + int cmd_ret = h2o_configurator_apply_commands(ctx, node, H2O_CONFIGURATOR_FLAG_GLOBAL, NULL); + destroy_context(ctx); + + if (cmd_ret != 0) + return cmd_ret; + if (config->hosts[0] == NULL) { + h2o_configurator_errprintf(NULL, node, "mandatory configuration directive `hosts` is missing"); + return -1; + } + return 0; +} + +void h2o_configurator_errprintf(h2o_configurator_command_t *cmd, yoml_t *node, const char *reason, ...) +{ + va_list args; + + fprintf(stderr, "[%s:%zu] ", node->filename ? node->filename : "-", node->line + 1); + if (cmd != NULL) + fprintf(stderr, "in command %s, ", cmd->name); + va_start(args, reason); + vfprintf(stderr, reason, args); + va_end(args); + fputc('\n', stderr); +} + +int h2o_configurator_scanf(h2o_configurator_command_t *cmd, yoml_t *node, const char *fmt, ...) +{ + va_list args; + int sscan_ret; + + if (node->type != YOML_TYPE_SCALAR) + goto Error; + va_start(args, fmt); + sscan_ret = vsscanf(node->data.scalar, fmt, args); + va_end(args); + if (sscan_ret != 1) + goto Error; + + return 0; +Error: + h2o_configurator_errprintf(cmd, node, "argument must match the format: %s", fmt); + return -1; +} + +ssize_t h2o_configurator_get_one_of(h2o_configurator_command_t *cmd, yoml_t *node, const char *candidates) +{ + const char *config_str, *cand_str; + ssize_t config_str_len, cand_index; + + if (node->type != YOML_TYPE_SCALAR) + goto Error; + + config_str = node->data.scalar; + config_str_len = strlen(config_str); + + cand_str = candidates; + for (cand_index = 0;; ++cand_index) { + if (strncasecmp(cand_str, config_str, config_str_len) == 0 && + (cand_str[config_str_len] == '\0' || cand_str[config_str_len] == ',')) { + /* found */ + return cand_index; + } + cand_str = strchr(cand_str, ','); + if (cand_str == NULL) + goto Error; + cand_str += 1; /* skip ',' */ + } +/* not reached */ + +Error: + h2o_configurator_errprintf(cmd, node, "argument must be one of: %s", candidates); + return -1; +} + +char *h2o_configurator_get_cmd_path(const char *cmd) +{ + char *root, *cmd_fullpath; + + /* just return the cmd (being strdup'ed) in case we do not need to prefix the value */ + if (cmd[0] == '/' || strchr(cmd, '/') == NULL) + goto ReturnOrig; + + /* obtain root */ + if ((root = getenv("H2O_ROOT")) == NULL) { + root = H2O_TO_STR(H2O_ROOT); + } + + /* build full-path and return */ + cmd_fullpath = h2o_mem_alloc(strlen(root) + strlen(cmd) + 2); + sprintf(cmd_fullpath, "%s/%s", root, cmd); + return cmd_fullpath; + +ReturnOrig: + return h2o_strdup(NULL, cmd, SIZE_MAX).base; +} diff --git a/src/web/server/h2o/libh2o/lib/core/context.c b/src/web/server/h2o/libh2o/lib/core/context.c new file mode 100644 index 000000000..8d1101381 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/context.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2014 DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include "h2o.h" +#include "h2o/memcached.h" + +void h2o_context_init_pathconf_context(h2o_context_t *ctx, h2o_pathconf_t *pathconf) +{ + /* add pathconf to the inited list (or return if already inited) */ + size_t i; + for (i = 0; i != ctx->_pathconfs_inited.size; ++i) + if (ctx->_pathconfs_inited.entries[i] == pathconf) + return; + h2o_vector_reserve(NULL, &ctx->_pathconfs_inited, ctx->_pathconfs_inited.size + 1); + ctx->_pathconfs_inited.entries[ctx->_pathconfs_inited.size++] = pathconf; + +#define DOIT(type, list) \ + do { \ + size_t i; \ + for (i = 0; i != pathconf->list.size; ++i) { \ + type *o = pathconf->list.entries[i]; \ + if (o->on_context_init != NULL) \ + o->on_context_init(o, ctx); \ + } \ + } while (0) + + DOIT(h2o_handler_t, handlers); + DOIT(h2o_filter_t, filters); + DOIT(h2o_logger_t, loggers); + +#undef DOIT +} + +void h2o_context_dispose_pathconf_context(h2o_context_t *ctx, h2o_pathconf_t *pathconf) +{ + /* nullify pathconf in the inited list (or return if already disposed) */ + size_t i; + for (i = 0; i != ctx->_pathconfs_inited.size; ++i) + if (ctx->_pathconfs_inited.entries[i] == pathconf) + break; + if (i == ctx->_pathconfs_inited.size) + return; + ctx->_pathconfs_inited.entries[i] = NULL; + +#define DOIT(type, list) \ + do { \ + size_t i; \ + for (i = 0; i != pathconf->list.size; ++i) { \ + type *o = pathconf->list.entries[i]; \ + if (o->on_context_dispose != NULL) \ + o->on_context_dispose(o, ctx); \ + } \ + } while (0) + + DOIT(h2o_handler_t, handlers); + DOIT(h2o_filter_t, filters); + DOIT(h2o_logger_t, loggers); + +#undef DOIT +} + +void h2o_context_init(h2o_context_t *ctx, h2o_loop_t *loop, h2o_globalconf_t *config) +{ + size_t i, j; + + assert(config->hosts[0] != NULL); + + memset(ctx, 0, sizeof(*ctx)); + ctx->loop = loop; + ctx->globalconf = config; + h2o_timeout_init(ctx->loop, &ctx->zero_timeout, 0); + h2o_timeout_init(ctx->loop, &ctx->one_sec_timeout, 1000); + h2o_timeout_init(ctx->loop, &ctx->hundred_ms_timeout, 100); + ctx->queue = h2o_multithread_create_queue(loop); + h2o_multithread_register_receiver(ctx->queue, &ctx->receivers.hostinfo_getaddr, h2o_hostinfo_getaddr_receiver); + ctx->filecache = h2o_filecache_create(config->filecache.capacity); + + h2o_timeout_init(ctx->loop, &ctx->handshake_timeout, config->handshake_timeout); + h2o_timeout_init(ctx->loop, &ctx->http1.req_timeout, config->http1.req_timeout); + h2o_linklist_init_anchor(&ctx->http1._conns); + h2o_timeout_init(ctx->loop, &ctx->http2.idle_timeout, config->http2.idle_timeout); + h2o_timeout_init(ctx->loop, &ctx->http2.graceful_shutdown_timeout, config->http2.graceful_shutdown_timeout); + h2o_linklist_init_anchor(&ctx->http2._conns); + ctx->proxy.client_ctx.loop = loop; + h2o_timeout_init(ctx->loop, &ctx->proxy.io_timeout, config->proxy.io_timeout); + ctx->proxy.client_ctx.getaddr_receiver = &ctx->receivers.hostinfo_getaddr; + ctx->proxy.client_ctx.io_timeout = &ctx->proxy.io_timeout; + ctx->proxy.client_ctx.ssl_ctx = config->proxy.ssl_ctx; + + ctx->_module_configs = h2o_mem_alloc(sizeof(*ctx->_module_configs) * config->_num_config_slots); + memset(ctx->_module_configs, 0, sizeof(*ctx->_module_configs) * config->_num_config_slots); + + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&mutex); + for (i = 0; config->hosts[i] != NULL; ++i) { + h2o_hostconf_t *hostconf = config->hosts[i]; + for (j = 0; j != hostconf->paths.size; ++j) { + h2o_pathconf_t *pathconf = hostconf->paths.entries + j; + h2o_context_init_pathconf_context(ctx, pathconf); + } + h2o_context_init_pathconf_context(ctx, &hostconf->fallback_path); + } + pthread_mutex_unlock(&mutex); +} + +void h2o_context_dispose(h2o_context_t *ctx) +{ + h2o_globalconf_t *config = ctx->globalconf; + size_t i, j; + + for (i = 0; config->hosts[i] != NULL; ++i) { + h2o_hostconf_t *hostconf = config->hosts[i]; + for (j = 0; j != hostconf->paths.size; ++j) { + h2o_pathconf_t *pathconf = hostconf->paths.entries + j; + h2o_context_dispose_pathconf_context(ctx, pathconf); + } + h2o_context_dispose_pathconf_context(ctx, &hostconf->fallback_path); + } + free(ctx->_pathconfs_inited.entries); + free(ctx->_module_configs); + h2o_timeout_dispose(ctx->loop, &ctx->zero_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->one_sec_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->hundred_ms_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->handshake_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->http1.req_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->http2.idle_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->http2.graceful_shutdown_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->proxy.io_timeout); + /* what should we do here? assert(!h2o_linklist_is_empty(&ctx->http2._conns); */ + + h2o_filecache_destroy(ctx->filecache); + ctx->filecache = NULL; + + /* clear storage */ + for (i = 0; i != ctx->storage.size; ++i) { + h2o_context_storage_item_t *item = ctx->storage.entries + i; + if (item->dispose != NULL) { + item->dispose(item->data); + } + } + free(ctx->storage.entries); + + /* TODO assert that the all the getaddrinfo threads are idle */ + h2o_multithread_unregister_receiver(ctx->queue, &ctx->receivers.hostinfo_getaddr); + h2o_multithread_destroy_queue(ctx->queue); + + if (ctx->_timestamp_cache.value != NULL) + h2o_mem_release_shared(ctx->_timestamp_cache.value); + +#if H2O_USE_LIBUV + /* make sure the handles released by h2o_timeout_dispose get freed */ + uv_run(ctx->loop, UV_RUN_NOWAIT); +#endif +} + +void h2o_context_request_shutdown(h2o_context_t *ctx) +{ + ctx->shutdown_requested = 1; + if (ctx->globalconf->http1.callbacks.request_shutdown != NULL) + ctx->globalconf->http1.callbacks.request_shutdown(ctx); + if (ctx->globalconf->http2.callbacks.request_shutdown != NULL) + ctx->globalconf->http2.callbacks.request_shutdown(ctx); +} + +void h2o_context_update_timestamp_cache(h2o_context_t *ctx) +{ + time_t prev_sec = ctx->_timestamp_cache.tv_at.tv_sec; + ctx->_timestamp_cache.uv_now_at = h2o_now(ctx->loop); + gettimeofday(&ctx->_timestamp_cache.tv_at, NULL); + if (ctx->_timestamp_cache.tv_at.tv_sec != prev_sec) { + struct tm gmt; + /* update the string cache */ + if (ctx->_timestamp_cache.value != NULL) + h2o_mem_release_shared(ctx->_timestamp_cache.value); + ctx->_timestamp_cache.value = h2o_mem_alloc_shared(NULL, sizeof(h2o_timestamp_string_t), NULL); + gmtime_r(&ctx->_timestamp_cache.tv_at.tv_sec, &gmt); + h2o_time2str_rfc1123(ctx->_timestamp_cache.value->rfc1123, &gmt); + h2o_time2str_log(ctx->_timestamp_cache.value->log, ctx->_timestamp_cache.tv_at.tv_sec); + } +} diff --git a/src/web/server/h2o/libh2o/lib/core/headers.c b/src/web/server/h2o/libh2o/lib/core/headers.c new file mode 100644 index 000000000..d31183623 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/headers.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2014 DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include "h2o.h" + +static void add_header(h2o_mem_pool_t *pool, h2o_headers_t *headers, h2o_iovec_t *name, const char *orig_name, const char *value, + size_t value_len) +{ + h2o_header_t *slot; + + h2o_vector_reserve(pool, headers, headers->size + 1); + slot = headers->entries + headers->size++; + + slot->name = name; + slot->value.base = (char *)value; + slot->value.len = value_len; + slot->orig_name = orig_name ? h2o_strdup(pool, orig_name, name->len).base : NULL; +} + +ssize_t h2o_find_header(const h2o_headers_t *headers, const h2o_token_t *token, ssize_t cursor) +{ + for (++cursor; cursor < headers->size; ++cursor) { + if (headers->entries[cursor].name == &token->buf) { + return cursor; + } + } + return -1; +} + +ssize_t h2o_find_header_by_str(const h2o_headers_t *headers, const char *name, size_t name_len, ssize_t cursor) +{ + for (++cursor; cursor < headers->size; ++cursor) { + h2o_header_t *t = headers->entries + cursor; + if (h2o_memis(t->name->base, t->name->len, name, name_len)) { + return cursor; + } + } + return -1; +} + +void h2o_add_header(h2o_mem_pool_t *pool, h2o_headers_t *headers, const h2o_token_t *token, const char *orig_name, + const char *value, size_t value_len) +{ + add_header(pool, headers, (h2o_iovec_t *)&token->buf, orig_name, value, value_len); +} + +void h2o_add_header_by_str(h2o_mem_pool_t *pool, h2o_headers_t *headers, const char *name, size_t name_len, int maybe_token, + const char *orig_name, const char *value, size_t value_len) +{ + h2o_iovec_t *name_buf; + + if (maybe_token) { + const h2o_token_t *token = h2o_lookup_token(name, name_len); + if (token != NULL) { + add_header(pool, headers, (h2o_iovec_t *)token, orig_name, value, value_len); + return; + } + } + name_buf = h2o_mem_alloc_pool(pool, sizeof(h2o_iovec_t)); + name_buf->base = (char *)name; + name_buf->len = name_len; + add_header(pool, headers, name_buf, orig_name, value, value_len); +} + +void h2o_set_header(h2o_mem_pool_t *pool, h2o_headers_t *headers, const h2o_token_t *token, const char *value, size_t value_len, + int overwrite_if_exists) +{ + ssize_t cursor = h2o_find_header(headers, token, -1); + if (cursor != -1) { + if (overwrite_if_exists) { + h2o_iovec_t *slot = &headers->entries[cursor].value; + slot->base = (char *)value; + slot->len = value_len; + } + } else { + h2o_add_header(pool, headers, token, NULL, value, value_len); + } +} + +void h2o_set_header_by_str(h2o_mem_pool_t *pool, h2o_headers_t *headers, const char *name, size_t name_len, int maybe_token, + const char *value, size_t value_len, int overwrite_if_exists) +{ + ssize_t cursor; + + if (maybe_token) { + const h2o_token_t *token = h2o_lookup_token(name, name_len); + if (token != NULL) { + h2o_set_header(pool, headers, token, value, value_len, overwrite_if_exists); + return; + } + } + + cursor = h2o_find_header_by_str(headers, name, name_len, -1); + if (cursor != -1) { + if (overwrite_if_exists) { + h2o_iovec_t *slot = &headers->entries[cursor].value; + slot->base = (char *)value; + slot->len = value_len; + } + } else { + h2o_iovec_t *name_buf = h2o_mem_alloc_pool(pool, sizeof(h2o_iovec_t)); + name_buf->base = (char *)name; + name_buf->len = name_len; + add_header(pool, headers, name_buf, NULL, value, value_len); + } +} + +void h2o_set_header_token(h2o_mem_pool_t *pool, h2o_headers_t *headers, const h2o_token_t *token, const char *value, + size_t value_len) +{ + h2o_header_t *dest = NULL; + size_t i; + for (i = 0; i != headers->size; ++i) { + if (headers->entries[i].name == &token->buf) { + if (h2o_contains_token(headers->entries[i].value.base, headers->entries[i].value.len, value, value_len, ',')) + return; + dest = headers->entries + i; + } + } + if (dest != NULL) { + dest->value = h2o_concat(pool, dest->value, h2o_iovec_init(H2O_STRLIT(", ")), h2o_iovec_init(value, value_len)); + } else { + h2o_add_header(pool, headers, token, NULL, value, value_len); + } +} + +ssize_t h2o_delete_header(h2o_headers_t *headers, ssize_t cursor) +{ + assert(cursor != -1); + + --headers->size; + memmove(headers->entries + cursor, headers->entries + cursor + 1, sizeof(h2o_header_t) * (headers->size - cursor)); + + return cursor; +} diff --git a/src/web/server/h2o/libh2o/lib/core/logconf.c b/src/web/server/h2o/libh2o/lib/core/logconf.c new file mode 100644 index 000000000..4d79736cc --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/logconf.c @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include "h2o.h" + +enum { + ELEMENT_TYPE_EMPTY, /* empty element (with suffix only) */ + ELEMENT_TYPE_LOCAL_ADDR, /* %A */ + ELEMENT_TYPE_BYTES_SENT, /* %b */ + ELEMENT_TYPE_PROTOCOL, /* %H */ + ELEMENT_TYPE_REMOTE_ADDR, /* %h */ + ELEMENT_TYPE_LOGNAME, /* %l */ + ELEMENT_TYPE_METHOD, /* %m */ + ELEMENT_TYPE_LOCAL_PORT, /* %p, %{local}p */ + ELEMENT_TYPE_REMOTE_PORT, /* %{remote}p */ + ELEMENT_TYPE_ENV_VAR, /* %{..}e */ + ELEMENT_TYPE_QUERY, /* %q */ + ELEMENT_TYPE_REQUEST_LINE, /* %r */ + ELEMENT_TYPE_STATUS, /* %s */ + ELEMENT_TYPE_TIMESTAMP, /* %t */ + ELEMENT_TYPE_TIMESTAMP_STRFTIME, /* %{...}t */ + ELEMENT_TYPE_TIMESTAMP_SEC_SINCE_EPOCH, /* %{sec}t */ + ELEMENT_TYPE_TIMESTAMP_MSEC_SINCE_EPOCH, /* %{msec}t */ + ELEMENT_TYPE_TIMESTAMP_USEC_SINCE_EPOCH, /* %{usec}t */ + ELEMENT_TYPE_TIMESTAMP_MSEC_FRAC, /* %{msec_frac}t */ + ELEMENT_TYPE_TIMESTAMP_USEC_FRAC, /* %{usec_frac}t */ + ELEMENT_TYPE_URL_PATH, /* %U */ + ELEMENT_TYPE_REMOTE_USER, /* %u */ + ELEMENT_TYPE_AUTHORITY, /* %V */ + ELEMENT_TYPE_HOSTCONF, /* %v */ + ELEMENT_TYPE_IN_HEADER_TOKEN, /* %{data.header_token}i */ + ELEMENT_TYPE_IN_HEADER_STRING, /* %{data.name}i */ + ELEMENT_TYPE_OUT_HEADER_TOKEN, /* %{data.header_token}o */ + ELEMENT_TYPE_OUT_HEADER_STRING, /* %{data.name}o */ + ELEMENT_TYPE_OUT_HEADER_TOKEN_CONCATENATED, /* %{data.header_token}o */ + ELEMENT_TYPE_EXTENDED_VAR, /* %{data.name}x */ + ELEMENT_TYPE_CONNECTION_ID, /* %{connection-id}x */ + ELEMENT_TYPE_CONNECT_TIME, /* %{connect-time}x */ + ELEMENT_TYPE_REQUEST_HEADER_TIME, /* %{request-header-time}x */ + ELEMENT_TYPE_REQUEST_BODY_TIME, /* %{request-body-time}x */ + ELEMENT_TYPE_REQUEST_TOTAL_TIME, /* %{request-total-time}x */ + ELEMENT_TYPE_PROCESS_TIME, /* %{process-time}x */ + ELEMENT_TYPE_RESPONSE_TIME, /* %{response-total-time}x */ + ELEMENT_TYPE_DURATION, /* %{duration}x */ + ELEMENT_TYPE_ERROR, /* %{error}x */ + ELEMENT_TYPE_PROTOCOL_SPECIFIC, /* %{protocol-specific...}x */ + NUM_ELEMENT_TYPES +}; + +struct log_element_t { + unsigned type; + h2o_iovec_t suffix; + union { + const h2o_token_t *header_token; + h2o_iovec_t name; + size_t protocol_specific_callback_index; + } data; + unsigned magically_quoted_json : 1; /* whether to omit surrounding doublequotes when the output is null */ + unsigned original_response : 1; +}; + +struct st_h2o_logconf_t { + H2O_VECTOR(struct log_element_t) elements; + int escape; +}; + +static h2o_iovec_t strdup_lowercased(const char *s, size_t len) +{ + h2o_iovec_t v = h2o_strdup(NULL, s, len); + h2o_strtolower(v.base, v.len); + return v; +} + +static int determine_magicquote_nodes(h2o_logconf_t *logconf, char *errbuf) +{ + size_t element_index; + int quote_char = '\0'; /* the quote char being used if the state machine is within a string literal */ + int just_in = 0; /* if we just went into the string literal */ + + for (element_index = 0; element_index < logconf->elements.size; ++element_index) { + h2o_iovec_t suffix = logconf->elements.entries[element_index].suffix; + logconf->elements.entries[element_index].magically_quoted_json = just_in && suffix.len != 0 && suffix.base[0] == quote_char; + + just_in = 0; + + size_t i; + for (i = 0; i < suffix.len; ++i) { + just_in = 0; + if (quote_char != '\0') { + if (quote_char == suffix.base[i]) { + /* out of quote? */ + size_t j, num_bs = 0; + for (j = i; j != 0; ++num_bs) + if (suffix.base[--j] != '\\') + break; + if (num_bs % 2 == 0) + quote_char = '\0'; + } + } else { + if (suffix.base[i] == '"' || suffix.base[i] == '\'') { + quote_char = suffix.base[i]; + just_in = 1; + } + } + } + } + + return 1; +} + +h2o_logconf_t *h2o_logconf_compile(const char *fmt, int escape, char *errbuf) +{ + h2o_logconf_t *logconf = h2o_mem_alloc(sizeof(*logconf)); + const char *pt = fmt; + size_t fmt_len = strlen(fmt); + + *logconf = (h2o_logconf_t){{NULL}, escape}; + +#define LAST_ELEMENT() (logconf->elements.entries + logconf->elements.size - 1) +/* suffix buffer is always guaranteed to be larger than the fmt + (sizeof('\n') - 1) (so that they would be no buffer overruns) */ +#define NEW_ELEMENT(ty) \ + do { \ + h2o_vector_reserve(NULL, &logconf->elements, logconf->elements.size + 1); \ + logconf->elements.size++; \ + *LAST_ELEMENT() = (struct log_element_t){0}; \ + LAST_ELEMENT()->type = ty; \ + LAST_ELEMENT()->suffix.base = h2o_mem_alloc(fmt_len + 1); \ + } while (0) + + while (*pt != '\0') { + if (memcmp(pt, "%%", 2) == 0) { + ++pt; /* emit % */ + } else if (*pt == '%') { + ++pt; + /* handle < and > */ + int log_original = 0; + for (;; ++pt) { + if (*pt == '<') { + log_original = 1; + } else if (*pt == '>') { + log_original = 0; + } else { + break; + } + } + /* handle {...}n */ + if (*pt == '{') { + const h2o_token_t *token; + const char *quote_end = strchr(++pt, '}'); + if (quote_end == NULL) { + sprintf(errbuf, "failed to compile log format: unterminated header name starting at: \"%16s\"", pt); + goto Error; + } + const char modifier = quote_end[1]; + switch (modifier) { + case 'i': + case 'o': { + h2o_iovec_t name = strdup_lowercased(pt, quote_end - pt); + token = h2o_lookup_token(name.base, name.len); + if (token != NULL) { + free(name.base); + if (modifier == 'o' && token == H2O_TOKEN_SET_COOKIE) { + NEW_ELEMENT(ELEMENT_TYPE_OUT_HEADER_TOKEN_CONCATENATED); + LAST_ELEMENT()->data.header_token = token; + } else { + NEW_ELEMENT(modifier == 'i' ? ELEMENT_TYPE_IN_HEADER_TOKEN : ELEMENT_TYPE_OUT_HEADER_TOKEN); + LAST_ELEMENT()->data.header_token = token; + } + } else { + NEW_ELEMENT(modifier == 'i' ? ELEMENT_TYPE_IN_HEADER_STRING : ELEMENT_TYPE_OUT_HEADER_STRING); + LAST_ELEMENT()->data.name = name; + } + LAST_ELEMENT()->original_response = log_original; + } break; + case 'p': + if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("local"))) { + NEW_ELEMENT(ELEMENT_TYPE_LOCAL_PORT); + } else if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("remote"))) { + NEW_ELEMENT(ELEMENT_TYPE_REMOTE_PORT); + } else { + sprintf(errbuf, "failed to compile log format: unknown specifier for %%{...}p"); + goto Error; + } + break; + case 'e': + { + h2o_iovec_t name = h2o_strdup(NULL, pt, quote_end - pt); + NEW_ELEMENT(ELEMENT_TYPE_ENV_VAR); + LAST_ELEMENT()->data.name = name; + } + break; + case 't': + if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("sec"))) { + NEW_ELEMENT(ELEMENT_TYPE_TIMESTAMP_SEC_SINCE_EPOCH); + } else if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("msec"))) { + NEW_ELEMENT(ELEMENT_TYPE_TIMESTAMP_MSEC_SINCE_EPOCH); + } else if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("usec"))) { + NEW_ELEMENT(ELEMENT_TYPE_TIMESTAMP_USEC_SINCE_EPOCH); + } else if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("msec_frac"))) { + NEW_ELEMENT(ELEMENT_TYPE_TIMESTAMP_MSEC_FRAC); + } else if (h2o_memis(pt, quote_end - pt, H2O_STRLIT("usec_frac"))) { + NEW_ELEMENT(ELEMENT_TYPE_TIMESTAMP_USEC_FRAC); + } else { + h2o_iovec_t name = h2o_strdup(NULL, pt, quote_end - pt); + NEW_ELEMENT(ELEMENT_TYPE_TIMESTAMP_STRFTIME); + LAST_ELEMENT()->data.name = name; + } + break; + case 'x': +#define MAP_EXT_TO_TYPE(name, id) \ + if (h2o_lcstris(pt, quote_end - pt, H2O_STRLIT(name))) { \ + NEW_ELEMENT(id); \ + goto MAP_EXT_Found; \ + } +#define MAP_EXT_TO_PROTO(name, cb) \ + if (h2o_lcstris(pt, quote_end - pt, H2O_STRLIT(name))) { \ + h2o_conn_callbacks_t dummy_; \ + NEW_ELEMENT(ELEMENT_TYPE_PROTOCOL_SPECIFIC); \ + LAST_ELEMENT()->data.protocol_specific_callback_index = \ + &dummy_.log_.cb - dummy_.log_.callbacks; \ + goto MAP_EXT_Found; \ + } + MAP_EXT_TO_TYPE("connection-id", ELEMENT_TYPE_CONNECTION_ID); + MAP_EXT_TO_TYPE("connect-time", ELEMENT_TYPE_CONNECT_TIME); + MAP_EXT_TO_TYPE("request-total-time", ELEMENT_TYPE_REQUEST_TOTAL_TIME); + MAP_EXT_TO_TYPE("request-header-time", ELEMENT_TYPE_REQUEST_HEADER_TIME); + MAP_EXT_TO_TYPE("request-body-time", ELEMENT_TYPE_REQUEST_BODY_TIME); + MAP_EXT_TO_TYPE("process-time", ELEMENT_TYPE_PROCESS_TIME); + MAP_EXT_TO_TYPE("response-time", ELEMENT_TYPE_RESPONSE_TIME); + MAP_EXT_TO_TYPE("duration", ELEMENT_TYPE_DURATION); + MAP_EXT_TO_TYPE("error", ELEMENT_TYPE_ERROR); + MAP_EXT_TO_PROTO("http1.request-index", http1.request_index); + MAP_EXT_TO_PROTO("http2.stream-id", http2.stream_id); + MAP_EXT_TO_PROTO("http2.priority.received", http2.priority_received); + MAP_EXT_TO_PROTO("http2.priority.received.exclusive", http2.priority_received_exclusive); + MAP_EXT_TO_PROTO("http2.priority.received.parent", http2.priority_received_parent); + MAP_EXT_TO_PROTO("http2.priority.received.weight", http2.priority_received_weight); + MAP_EXT_TO_PROTO("http2.priority.actual", http2.priority_actual); + MAP_EXT_TO_PROTO("http2.priority.actual.parent", http2.priority_actual_parent); + MAP_EXT_TO_PROTO("http2.priority.actual.weight", http2.priority_actual_weight); + MAP_EXT_TO_PROTO("ssl.protocol-version", ssl.protocol_version); + MAP_EXT_TO_PROTO("ssl.session-reused", ssl.session_reused); + MAP_EXT_TO_PROTO("ssl.cipher", ssl.cipher); + MAP_EXT_TO_PROTO("ssl.cipher-bits", ssl.cipher_bits); + MAP_EXT_TO_PROTO("ssl.session-id", ssl.session_id); + { /* not found */ + h2o_iovec_t name = strdup_lowercased(pt, quote_end - pt); + NEW_ELEMENT(ELEMENT_TYPE_EXTENDED_VAR); + LAST_ELEMENT()->data.name = name; + } + MAP_EXT_Found: +#undef MAP_EXT_TO_TYPE +#undef MAP_EXT_TO_PROTO + break; + default: + sprintf(errbuf, "failed to compile log format: header name is not followed by either `i`, `o`, `x`, `e`"); + goto Error; + } + pt = quote_end + 2; + continue; + } else { + unsigned type = NUM_ELEMENT_TYPES; + switch (*pt++) { +#define TYPE_MAP(ch, ty) \ + case ch: \ + type = ty; \ + break + TYPE_MAP('A', ELEMENT_TYPE_LOCAL_ADDR); + TYPE_MAP('b', ELEMENT_TYPE_BYTES_SENT); + TYPE_MAP('H', ELEMENT_TYPE_PROTOCOL); + TYPE_MAP('h', ELEMENT_TYPE_REMOTE_ADDR); + TYPE_MAP('l', ELEMENT_TYPE_LOGNAME); + TYPE_MAP('m', ELEMENT_TYPE_METHOD); + TYPE_MAP('p', ELEMENT_TYPE_LOCAL_PORT); + TYPE_MAP('e', ELEMENT_TYPE_ENV_VAR); + TYPE_MAP('q', ELEMENT_TYPE_QUERY); + TYPE_MAP('r', ELEMENT_TYPE_REQUEST_LINE); + TYPE_MAP('s', ELEMENT_TYPE_STATUS); + TYPE_MAP('t', ELEMENT_TYPE_TIMESTAMP); + TYPE_MAP('U', ELEMENT_TYPE_URL_PATH); + TYPE_MAP('u', ELEMENT_TYPE_REMOTE_USER); + TYPE_MAP('V', ELEMENT_TYPE_AUTHORITY); + TYPE_MAP('v', ELEMENT_TYPE_HOSTCONF); +#undef TYPE_MAP + default: + sprintf(errbuf, "failed to compile log format: unknown escape sequence: %%%c", pt[-1]); + goto Error; + } + NEW_ELEMENT(type); + LAST_ELEMENT()->original_response = log_original; + continue; + } + } + /* emit current char */ + if (logconf->elements.size == 0) + NEW_ELEMENT(ELEMENT_TYPE_EMPTY); + LAST_ELEMENT()->suffix.base[LAST_ELEMENT()->suffix.len++] = *pt++; + } + + /* emit end-of-line */ + if (logconf->elements.size == 0) + NEW_ELEMENT(ELEMENT_TYPE_EMPTY); + LAST_ELEMENT()->suffix.base[LAST_ELEMENT()->suffix.len++] = '\n'; + +#undef NEW_ELEMENT +#undef LAST_ELEMENT + + if (escape == H2O_LOGCONF_ESCAPE_JSON) { + if (!determine_magicquote_nodes(logconf, errbuf)) + goto Error; + } + + return logconf; + +Error: + h2o_logconf_dispose(logconf); + return NULL; +} + +void h2o_logconf_dispose(h2o_logconf_t *logconf) +{ + size_t i; + + for (i = 0; i != logconf->elements.size; ++i) { + free(logconf->elements.entries[i].suffix.base); + switch (logconf->elements.entries[i].type) { + case ELEMENT_TYPE_EXTENDED_VAR: + case ELEMENT_TYPE_IN_HEADER_STRING: + case ELEMENT_TYPE_OUT_HEADER_STRING: + case ELEMENT_TYPE_TIMESTAMP_STRFTIME: + free(logconf->elements.entries[i].data.name.base); + break; + default: + break; + } + } + free(logconf->elements.entries); + free(logconf); +} + +static inline char *append_safe_string(char *pos, const char *src, size_t len) +{ + memcpy(pos, src, len); + return pos + len; +} + +static char *append_unsafe_string_apache(char *pos, const char *src, size_t len) +{ + const char *src_end = src + len; + + for (; src != src_end; ++src) { + if (' ' <= *src && *src < 0x7d && *src != '"') { + *pos++ = *src; + } else { + *pos++ = '\\'; + *pos++ = 'x'; + *pos++ = ("0123456789abcdef")[(*src >> 4) & 0xf]; + *pos++ = ("0123456789abcdef")[*src & 0xf]; + } + } + + return pos; +} + +static char *append_unsafe_string_json(char *pos, const char *src, size_t len) +{ + const char *src_end = src + len; + + for (; src != src_end; ++src) { + if (' ' <= *src && *src < 0x7e) { + if (*src == '"' || *src == '\\') + *pos++ = '\\'; + *pos++ = *src; + } else { + *pos++ = '\\'; + *pos++ = 'u'; + *pos++ = '0'; + *pos++ = '0'; + *pos++ = ("0123456789abcdef")[(*src >> 4) & 0xf]; + *pos++ = ("0123456789abcdef")[*src & 0xf]; + } + } + + return pos; +} + +static char *append_addr(char *pos, socklen_t (*cb)(h2o_conn_t *conn, struct sockaddr *sa), h2o_conn_t *conn, h2o_iovec_t nullexpr) +{ + struct sockaddr_storage ss; + socklen_t sslen; + + if ((sslen = cb(conn, (void *)&ss)) == 0) + goto Fail; + size_t l = h2o_socket_getnumerichost((void *)&ss, sslen, pos); + if (l == SIZE_MAX) + goto Fail; + pos += l; + return pos; + +Fail: + memcpy(pos, nullexpr.base, nullexpr.len); + pos += nullexpr.len; + return pos; +} + +static char *append_port(char *pos, socklen_t (*cb)(h2o_conn_t *conn, struct sockaddr *sa), h2o_conn_t *conn, h2o_iovec_t nullexpr) +{ + struct sockaddr_storage ss; + socklen_t sslen; + + if ((sslen = cb(conn, (void *)&ss)) == 0) + goto Fail; + int32_t port = h2o_socket_getport((void *)&ss); + if (port == -1) + goto Fail; + pos += sprintf(pos, "%" PRIu16, (uint16_t)port); + return pos; + +Fail: + memcpy(pos, nullexpr.base, nullexpr.len); + pos += nullexpr.len; + return pos; +} + +#define APPEND_DURATION(pos, name) \ + do { \ + int64_t delta_usec; \ + if (!h2o_time_compute_##name(req, &delta_usec)) \ + goto EmitNull; \ + int32_t delta_sec = (int32_t)(delta_usec / (1000 * 1000)); \ + delta_usec -= ((int64_t)delta_sec * (1000 * 1000)); \ + RESERVE(sizeof(H2O_INT32_LONGEST_STR ".999999") - 1); \ + pos += sprintf(pos, "%" PRId32, delta_sec); \ + if (delta_usec != 0) { \ + int i; \ + *pos++ = '.'; \ + for (i = 5; i >= 0; --i) { \ + pos[i] = '0' + delta_usec % 10; \ + delta_usec /= 10; \ + } \ + pos += 6; \ + } \ + } while (0); + +static char *expand_line_buf(char *line, size_t *cur_size, size_t required, int should_realloc) +{ + size_t new_size = *cur_size; + + /* determine the new size */ + do { + new_size *= 2; + } while (new_size < required); + + /* reallocate */ + if (!should_realloc) { + char *newpt = h2o_mem_alloc(new_size); + memcpy(newpt, line, *cur_size); + line = newpt; + } else { + line = h2o_mem_realloc(line, new_size); + } + *cur_size = new_size; + + return line; +} + +char *h2o_log_request(h2o_logconf_t *logconf, h2o_req_t *req, size_t *len, char *buf) +{ + char *line = buf, *pos = line, *line_end = line + *len; + h2o_iovec_t nullexpr; + char *(*append_unsafe_string)(char *pos, const char *src, size_t len); + size_t element_index, unsafe_factor; + struct tm localt = {0}; + int should_realloc_on_expand = 0; + + switch (logconf->escape) { + case H2O_LOGCONF_ESCAPE_APACHE: + nullexpr = h2o_iovec_init(H2O_STRLIT("-")); + append_unsafe_string = append_unsafe_string_apache; + unsafe_factor = 4; + break; + case H2O_LOGCONF_ESCAPE_JSON: + nullexpr = h2o_iovec_init(H2O_STRLIT("null")); + append_unsafe_string = append_unsafe_string_json; + unsafe_factor = 6; + break; + default: + h2o_fatal("unexpected escape mode"); + break; + } + + for (element_index = 0; element_index != logconf->elements.size; ++element_index) { + struct log_element_t *element = logconf->elements.entries + element_index; + +/* reserve capacity + suffix.len */ +#define RESERVE(capacity) \ + do { \ + if ((capacity) + element->suffix.len > line_end - pos) { \ + size_t off = pos - line; \ + size_t size = line_end - line; \ + line = expand_line_buf(line, &size, off + (capacity) + element->suffix.len, should_realloc_on_expand); \ + pos = line + off; \ + line_end = line + size; \ + should_realloc_on_expand = 1; \ + } \ + } while (0) + + switch (element->type) { + case ELEMENT_TYPE_EMPTY: + RESERVE(0); + break; + case ELEMENT_TYPE_LOCAL_ADDR: /* %A */ + RESERVE(NI_MAXHOST); + pos = append_addr(pos, req->conn->callbacks->get_sockname, req->conn, nullexpr); + break; + case ELEMENT_TYPE_BYTES_SENT: /* %b */ + RESERVE(sizeof(H2O_UINT64_LONGEST_STR) - 1); + pos += sprintf(pos, "%" PRIu64, (uint64_t)req->bytes_sent); + break; + case ELEMENT_TYPE_PROTOCOL: /* %H */ + RESERVE(sizeof("HTTP/1.1")); + pos += h2o_stringify_protocol_version(pos, req->version); + break; + case ELEMENT_TYPE_REMOTE_ADDR: /* %h */ + RESERVE(NI_MAXHOST); + pos = append_addr(pos, req->conn->callbacks->get_peername, req->conn, nullexpr); + break; + case ELEMENT_TYPE_METHOD: /* %m */ + RESERVE(req->input.method.len * unsafe_factor); + pos = append_unsafe_string(pos, req->input.method.base, req->input.method.len); + break; + case ELEMENT_TYPE_LOCAL_PORT: /* %p */ + RESERVE(sizeof(H2O_UINT16_LONGEST_STR) - 1); + pos = append_port(pos, req->conn->callbacks->get_sockname, req->conn, nullexpr); + break; + case ELEMENT_TYPE_REMOTE_PORT: /* %{remote}p */ + RESERVE(sizeof(H2O_UINT16_LONGEST_STR) - 1); + pos = append_port(pos, req->conn->callbacks->get_peername, req->conn, nullexpr); + break; + case ELEMENT_TYPE_ENV_VAR: /* %{..}e */ { + h2o_iovec_t *env_var = h2o_req_getenv(req, element->data.name.base, element->data.name.len, 0); + if (env_var == NULL) + goto EmitNull; + RESERVE(env_var->len * unsafe_factor); + pos = append_safe_string(pos, env_var->base, env_var->len); + } break; + case ELEMENT_TYPE_QUERY: /* %q */ + if (req->input.query_at != SIZE_MAX) { + size_t len = req->input.path.len - req->input.query_at; + RESERVE(len * unsafe_factor); + pos = append_unsafe_string(pos, req->input.path.base + req->input.query_at, len); + } + break; + case ELEMENT_TYPE_REQUEST_LINE: /* %r */ + RESERVE((req->input.method.len + req->input.path.len) * unsafe_factor + sizeof(" HTTP/1.1")); + pos = append_unsafe_string(pos, req->input.method.base, req->input.method.len); + *pos++ = ' '; + pos = append_unsafe_string(pos, req->input.path.base, req->input.path.len); + *pos++ = ' '; + pos += h2o_stringify_protocol_version(pos, req->version); + break; + case ELEMENT_TYPE_STATUS: /* %s */ + RESERVE(sizeof(H2O_INT32_LONGEST_STR) - 1); + pos += sprintf(pos, "%" PRId32, (int32_t)(element->original_response ? req->res.original.status : req->res.status)); + break; + case ELEMENT_TYPE_TIMESTAMP: /* %t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + RESERVE(H2O_TIMESTR_LOG_LEN + 2); + *pos++ = '['; + pos = append_safe_string(pos, req->processed_at.str->log, H2O_TIMESTR_LOG_LEN); + *pos++ = ']'; + break; + case ELEMENT_TYPE_TIMESTAMP_STRFTIME: /* %{...}t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + { + size_t bufsz, len; + if (localt.tm_year == 0) + localtime_r(&req->processed_at.at.tv_sec, &localt); + for (bufsz = 128;; bufsz *= 2) { + RESERVE(bufsz); + if ((len = strftime(pos, bufsz, element->data.name.base, &localt)) != 0) + break; + } + pos += len; + } + break; + case ELEMENT_TYPE_TIMESTAMP_SEC_SINCE_EPOCH: /* %{sec}t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + RESERVE(sizeof(H2O_UINT32_LONGEST_STR) - 1); + pos += sprintf(pos, "%" PRIu32, (uint32_t)req->processed_at.at.tv_sec); + break; + case ELEMENT_TYPE_TIMESTAMP_MSEC_SINCE_EPOCH: /* %{msec}t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + RESERVE(sizeof(H2O_UINT64_LONGEST_STR) - 1); + pos += sprintf(pos, "%" PRIu64, + (uint64_t)req->processed_at.at.tv_sec * 1000 + (uint64_t)req->processed_at.at.tv_usec / 1000); + break; + case ELEMENT_TYPE_TIMESTAMP_USEC_SINCE_EPOCH: /* %{usec}t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + RESERVE(sizeof(H2O_UINT64_LONGEST_STR) - 1); + pos += + sprintf(pos, "%" PRIu64, (uint64_t)req->processed_at.at.tv_sec * 1000000 + (uint64_t)req->processed_at.at.tv_usec); + break; + case ELEMENT_TYPE_TIMESTAMP_MSEC_FRAC: /* %{msec_frac}t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + RESERVE(3); + pos += sprintf(pos, "%03u", (unsigned)(req->processed_at.at.tv_usec / 1000)); + break; + case ELEMENT_TYPE_TIMESTAMP_USEC_FRAC: /* %{usec_frac}t */ + if (h2o_timeval_is_null(&req->processed_at.at)) + goto EmitNull; + RESERVE(6); + pos += sprintf(pos, "%06u", (unsigned)req->processed_at.at.tv_usec); + break; + case ELEMENT_TYPE_URL_PATH: /* %U */ { + size_t path_len = req->input.query_at == SIZE_MAX ? req->input.path.len : req->input.query_at; + RESERVE(path_len * unsafe_factor); + pos = append_unsafe_string(pos, req->input.path.base, path_len); + } break; + case ELEMENT_TYPE_REMOTE_USER: /* %u */ { + h2o_iovec_t *remote_user = h2o_req_getenv(req, H2O_STRLIT("REMOTE_USER"), 0); + if (remote_user == NULL) + goto EmitNull; + RESERVE(remote_user->len * unsafe_factor); + pos = append_unsafe_string(pos, remote_user->base, remote_user->len); + } break; + case ELEMENT_TYPE_AUTHORITY: /* %V */ + RESERVE(req->input.authority.len * unsafe_factor); + pos = append_unsafe_string(pos, req->input.authority.base, req->input.authority.len); + break; + case ELEMENT_TYPE_HOSTCONF: /* %v */ + RESERVE(req->hostconf->authority.hostport.len * unsafe_factor); + pos = append_unsafe_string(pos, req->hostconf->authority.hostport.base, req->hostconf->authority.hostport.len); + break; + +#define EMIT_HEADER(__headers, concat, findfunc, ...) \ + do { \ + h2o_headers_t *headers = (__headers); \ + ssize_t index = -1; \ + int found = 0; \ + while ((index = (findfunc)(headers, __VA_ARGS__, index)) != -1) { \ + if (found) { \ + RESERVE(2); \ + *pos++ = ','; \ + *pos++ = ' '; \ + } else { \ + found = 1; \ + } \ + const h2o_header_t *header = headers->entries + index; \ + RESERVE(header->value.len *unsafe_factor); \ + pos = append_unsafe_string(pos, header->value.base, header->value.len); \ + if (!concat) \ + break; \ + } \ + if (!found) \ + goto EmitNull; \ + } while (0) + + case ELEMENT_TYPE_IN_HEADER_TOKEN: + EMIT_HEADER(&req->headers, 0, h2o_find_header, element->data.header_token); + break; + case ELEMENT_TYPE_IN_HEADER_STRING: + EMIT_HEADER(&req->headers, 0, h2o_find_header_by_str, element->data.name.base, element->data.name.len); + break; + case ELEMENT_TYPE_OUT_HEADER_TOKEN: + EMIT_HEADER(element->original_response ? &req->res.original.headers : &req->res.headers, 0, h2o_find_header, + element->data.header_token); + break; + case ELEMENT_TYPE_OUT_HEADER_STRING: + EMIT_HEADER(element->original_response ? &req->res.original.headers : &req->res.headers, 0, h2o_find_header_by_str, + element->data.name.base, element->data.name.len); + break; + case ELEMENT_TYPE_OUT_HEADER_TOKEN_CONCATENATED: + EMIT_HEADER(element->original_response ? &req->res.original.headers : &req->res.headers, 1, h2o_find_header, + element->data.header_token); + break; + +#undef EMIT_HEADER + + case ELEMENT_TYPE_CONNECTION_ID: + RESERVE(sizeof(H2O_UINT64_LONGEST_STR) - 1); + pos += sprintf(pos, "%" PRIu64, req->conn->id); + break; + + case ELEMENT_TYPE_CONNECT_TIME: + APPEND_DURATION(pos, connect_time); + break; + + case ELEMENT_TYPE_REQUEST_HEADER_TIME: + APPEND_DURATION(pos, header_time); + break; + + case ELEMENT_TYPE_REQUEST_BODY_TIME: + APPEND_DURATION(pos, body_time); + break; + + case ELEMENT_TYPE_REQUEST_TOTAL_TIME: + APPEND_DURATION(pos, request_total_time); + break; + + case ELEMENT_TYPE_PROCESS_TIME: + APPEND_DURATION(pos, process_time); + break; + + case ELEMENT_TYPE_RESPONSE_TIME: + APPEND_DURATION(pos, response_time); + break; + + case ELEMENT_TYPE_DURATION: + APPEND_DURATION(pos, duration); + break; + + case ELEMENT_TYPE_ERROR: { + size_t i; + for (i = 0; i != req->error_logs.size; ++i) { + h2o_req_error_log_t *log = req->error_logs.entries + i; + size_t module_len = strlen(log->module); + RESERVE(sizeof("[] ") - 1 + module_len + log->msg.len * unsafe_factor); + *pos++ = '['; + pos = append_safe_string(pos, log->module, module_len); + *pos++ = ']'; + *pos++ = ' '; + pos = append_unsafe_string(pos, log->msg.base, log->msg.len); + } + } break; + + case ELEMENT_TYPE_PROTOCOL_SPECIFIC: { + h2o_iovec_t (*cb)(h2o_req_t *) = req->conn->callbacks->log_.callbacks[element->data.protocol_specific_callback_index]; + if (cb != NULL) { + h2o_iovec_t s = cb(req); + if (s.base == NULL) + goto EmitNull; + RESERVE(s.len); + pos = append_safe_string(pos, s.base, s.len); + } else { + goto EmitNull; + } + } break; + + case ELEMENT_TYPE_LOGNAME: /* %l */ + case ELEMENT_TYPE_EXTENDED_VAR: /* %{...}x */ + EmitNull: + RESERVE(nullexpr.len); + /* special case that trims surrounding quotes */ + if (element->magically_quoted_json) { + --pos; + pos = append_safe_string(pos, nullexpr.base, nullexpr.len); + pos = append_safe_string(pos, element->suffix.base + 1, element->suffix.len - 1); + continue; + } + pos = append_safe_string(pos, nullexpr.base, nullexpr.len); + break; + + default: + assert(!"unknown type"); + break; + } + +#undef RESERVE + + pos = append_safe_string(pos, element->suffix.base, element->suffix.len); + } + + *len = pos - line; + return line; +} diff --git a/src/web/server/h2o/libh2o/lib/core/proxy.c b/src/web/server/h2o/libh2o/lib/core/proxy.c new file mode 100644 index 000000000..edb4baf9d --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/proxy.c @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2014,2015 DeNA Co., Ltd., Kazuho Oku, Masahiro Nagano + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include "picohttpparser.h" +#include "h2o.h" +#include "h2o/http1.h" +#include "h2o/http1client.h" +#include "h2o/tunnel.h" + +struct rp_generator_t { + h2o_generator_t super; + h2o_req_t *src_req; + h2o_http1client_t *client; + struct { + h2o_iovec_t bufs[2]; /* first buf is the request line and headers, the second is the POST content */ + int is_head; + } up_req; + h2o_buffer_t *last_content_before_send; + h2o_doublebuffer_t sending; + int is_websocket_handshake; + int had_body_error; /* set if an error happened while fetching the body so that we can propagate the error */ +}; + +struct rp_ws_upgrade_info_t { + h2o_context_t *ctx; + h2o_timeout_t *timeout; + h2o_socket_t *upstream_sock; +}; + +static h2o_http1client_ctx_t *get_client_ctx(h2o_req_t *req) +{ + h2o_req_overrides_t *overrides = req->overrides; + if (overrides != NULL && overrides->client_ctx != NULL) + return overrides->client_ctx; + return &req->conn->ctx->proxy.client_ctx; +} + +static h2o_iovec_t rewrite_location(h2o_mem_pool_t *pool, const char *location, size_t location_len, h2o_url_t *match, + const h2o_url_scheme_t *req_scheme, h2o_iovec_t req_authority, h2o_iovec_t req_basepath) +{ + h2o_url_t loc_parsed; + + if (h2o_url_parse(location, location_len, &loc_parsed) != 0) + goto NoRewrite; + if (loc_parsed.scheme != &H2O_URL_SCHEME_HTTP) + goto NoRewrite; + if (!h2o_url_hosts_are_equal(&loc_parsed, match)) + goto NoRewrite; + if (h2o_url_get_port(&loc_parsed) != h2o_url_get_port(match)) + goto NoRewrite; + if (loc_parsed.path.len < match->path.len) + goto NoRewrite; + if (memcmp(loc_parsed.path.base, match->path.base, match->path.len) != 0) + goto NoRewrite; + + return h2o_concat(pool, req_scheme->name, h2o_iovec_init(H2O_STRLIT("://")), req_authority, req_basepath, + h2o_iovec_init(loc_parsed.path.base + match->path.len, loc_parsed.path.len - match->path.len)); + +NoRewrite: + return (h2o_iovec_t){NULL}; +} + +static h2o_iovec_t build_request_merge_headers(h2o_mem_pool_t *pool, h2o_iovec_t merged, h2o_iovec_t added, int seperator) +{ + if (added.len == 0) + return merged; + if (merged.len == 0) + return added; + + size_t newlen = merged.len + 2 + added.len; + char *buf = h2o_mem_alloc_pool(pool, newlen); + memcpy(buf, merged.base, merged.len); + buf[merged.len] = seperator; + buf[merged.len + 1] = ' '; + memcpy(buf + merged.len + 2, added.base, added.len); + merged.base = buf; + merged.len = newlen; + return merged; +} + +/* + * A request without neither Content-Length or Transfer-Encoding header implies a zero-length request body (see 6th rule of RFC 7230 + * 3.3.3). + * OTOH, section 3.3.3 states: + * + * A user agent SHOULD send a Content-Length in a request message when + * no Transfer-Encoding is sent and the request method defines a meaning + * for an enclosed payload body. For example, a Content-Length header + * field is normally sent in a POST request even when the value is 0 + * (indicating an empty payload body). A user agent SHOULD NOT send a + * Content-Length header field when the request message does not contain + * a payload body and the method semantics do not anticipate such a + * body. + * + * PUT and POST define a meaning for the payload body, let's emit a + * Content-Length header if it doesn't exist already, since the server + * might send a '411 Length Required' response. + * + * see also: ML thread starting at https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0580.html + */ +static int req_requires_content_length(h2o_req_t *req) +{ + int is_put_or_post = + (req->method.len >= 1 && req->method.base[0] == 'P' && (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST")) || + h2o_memis(req->method.base, req->method.len, H2O_STRLIT("PUT")))); + + return is_put_or_post && h2o_find_header(&req->res.headers, H2O_TOKEN_TRANSFER_ENCODING, -1) == -1; +} + +static h2o_iovec_t build_request(h2o_req_t *req, int keepalive, int is_websocket_handshake, int use_proxy_protocol) +{ + h2o_iovec_t buf; + size_t offset = 0, remote_addr_len = SIZE_MAX; + char remote_addr[NI_MAXHOST]; + struct sockaddr_storage ss; + socklen_t sslen; + h2o_iovec_t cookie_buf = {NULL}, xff_buf = {NULL}, via_buf = {NULL}; + int preserve_x_forwarded_proto = req->conn->ctx->globalconf->proxy.preserve_x_forwarded_proto; + int emit_x_forwarded_headers = req->conn->ctx->globalconf->proxy.emit_x_forwarded_headers; + int emit_via_header = req->conn->ctx->globalconf->proxy.emit_via_header; + + /* for x-f-f */ + if ((sslen = req->conn->callbacks->get_peername(req->conn, (void *)&ss)) != 0) + remote_addr_len = h2o_socket_getnumerichost((void *)&ss, sslen, remote_addr); + + /* build response */ + buf.len = req->method.len + req->path.len + req->authority.len + 512; + if (use_proxy_protocol) + buf.len += H2O_PROXY_HEADER_MAX_LENGTH; + buf.base = h2o_mem_alloc_pool(&req->pool, buf.len); + +#define RESERVE(sz) \ + do { \ + size_t required = offset + sz + 4 /* for "\r\n\r\n" */; \ + if (required > buf.len) { \ + do { \ + buf.len *= 2; \ + } while (required > buf.len); \ + char *newp = h2o_mem_alloc_pool(&req->pool, buf.len); \ + memcpy(newp, buf.base, offset); \ + buf.base = newp; \ + } \ + } while (0) +#define APPEND(s, l) \ + do { \ + memcpy(buf.base + offset, (s), (l)); \ + offset += (l); \ + } while (0) +#define APPEND_STRLIT(lit) APPEND((lit), sizeof(lit) - 1) +#define FLATTEN_PREFIXED_VALUE(prefix, value, add_size) \ + do { \ + RESERVE(sizeof(prefix) - 1 + value.len + 2 + add_size); \ + APPEND_STRLIT(prefix); \ + if (value.len != 0) { \ + APPEND(value.base, value.len); \ + if (add_size != 0) { \ + buf.base[offset++] = ','; \ + buf.base[offset++] = ' '; \ + } \ + } \ + } while (0) + + if (use_proxy_protocol) + offset += h2o_stringify_proxy_header(req->conn, buf.base + offset); + + APPEND(req->method.base, req->method.len); + buf.base[offset++] = ' '; + APPEND(req->path.base, req->path.len); + APPEND_STRLIT(" HTTP/1.1\r\nconnection: "); + if (is_websocket_handshake) { + APPEND_STRLIT("upgrade\r\nupgrade: websocket\r\nhost: "); + } else if (keepalive) { + APPEND_STRLIT("keep-alive\r\nhost: "); + } else { + APPEND_STRLIT("close\r\nhost: "); + } + APPEND(req->authority.base, req->authority.len); + buf.base[offset++] = '\r'; + buf.base[offset++] = '\n'; + assert(offset <= buf.len); + if (req->entity.base != NULL || req_requires_content_length(req)) { + RESERVE(sizeof("content-length: " H2O_UINT64_LONGEST_STR) - 1); + offset += sprintf(buf.base + offset, "content-length: %zu\r\n", req->entity.len); + } + + /* rewrite headers if necessary */ + h2o_headers_t req_headers = req->headers; + if (req->overrides != NULL && req->overrides->headers_cmds != NULL) { + req_headers.entries = NULL; + req_headers.size = 0; + req_headers.capacity = 0; + h2o_headers_command_t *cmd; + h2o_vector_reserve(&req->pool, &req_headers, req->headers.capacity); + memcpy(req_headers.entries, req->headers.entries, sizeof(req->headers.entries[0]) * req->headers.size); + req_headers.size = req->headers.size; + for (cmd = req->overrides->headers_cmds; cmd->cmd != H2O_HEADERS_CMD_NULL; ++cmd) + h2o_rewrite_headers(&req->pool, &req_headers, cmd); + } + + { + const h2o_header_t *h, *h_end; + for (h = req_headers.entries, h_end = h + req_headers.size; h != h_end; ++h) { + if (h2o_iovec_is_token(h->name)) { + const h2o_token_t *token = (void *)h->name; + if (token->proxy_should_drop_for_req) { + continue; + } else if (token == H2O_TOKEN_COOKIE) { + /* merge the cookie headers; see HTTP/2 8.1.2.5 and HTTP/1 (RFC6265 5.4) */ + /* FIXME current algorithm is O(n^2) against the number of cookie headers */ + cookie_buf = build_request_merge_headers(&req->pool, cookie_buf, h->value, ';'); + continue; + } else if (token == H2O_TOKEN_VIA) { + if (!emit_via_header) { + goto AddHeader; + } + via_buf = build_request_merge_headers(&req->pool, via_buf, h->value, ','); + continue; + } else if (token == H2O_TOKEN_X_FORWARDED_FOR) { + if (!emit_x_forwarded_headers) { + goto AddHeader; + } + xff_buf = build_request_merge_headers(&req->pool, xff_buf, h->value, ','); + continue; + } + } + if (!preserve_x_forwarded_proto && h2o_lcstris(h->name->base, h->name->len, H2O_STRLIT("x-forwarded-proto"))) + continue; + AddHeader: + RESERVE(h->name->len + h->value.len + 2); + APPEND(h->orig_name ? h->orig_name : h->name->base, h->name->len); + buf.base[offset++] = ':'; + buf.base[offset++] = ' '; + APPEND(h->value.base, h->value.len); + buf.base[offset++] = '\r'; + buf.base[offset++] = '\n'; + } + } + if (cookie_buf.len != 0) { + FLATTEN_PREFIXED_VALUE("cookie: ", cookie_buf, 0); + buf.base[offset++] = '\r'; + buf.base[offset++] = '\n'; + } + if (emit_x_forwarded_headers) { + if (!preserve_x_forwarded_proto) { + FLATTEN_PREFIXED_VALUE("x-forwarded-proto: ", req->input.scheme->name, 0); + buf.base[offset++] = '\r'; + buf.base[offset++] = '\n'; + } + if (remote_addr_len != SIZE_MAX) { + FLATTEN_PREFIXED_VALUE("x-forwarded-for: ", xff_buf, remote_addr_len); + APPEND(remote_addr, remote_addr_len); + } else { + FLATTEN_PREFIXED_VALUE("x-forwarded-for: ", xff_buf, 0); + } + buf.base[offset++] = '\r'; + buf.base[offset++] = '\n'; + } + if (emit_via_header) { + FLATTEN_PREFIXED_VALUE("via: ", via_buf, sizeof("1.1 ") - 1 + req->input.authority.len); + if (req->version < 0x200) { + buf.base[offset++] = '1'; + buf.base[offset++] = '.'; + buf.base[offset++] = '0' + (0x100 <= req->version && req->version <= 0x109 ? req->version - 0x100 : 0); + } else { + buf.base[offset++] = '2'; + } + buf.base[offset++] = ' '; + APPEND(req->input.authority.base, req->input.authority.len); + buf.base[offset++] = '\r'; + buf.base[offset++] = '\n'; + } + APPEND_STRLIT("\r\n"); + +#undef RESERVE +#undef APPEND +#undef APPEND_STRLIT +#undef FLATTEN_PREFIXED_VALUE + + /* set the length */ + assert(offset <= buf.len); + buf.len = offset; + + return buf; +} + +static void do_close(h2o_generator_t *generator, h2o_req_t *req) +{ + struct rp_generator_t *self = (void *)generator; + + if (self->client != NULL) { + h2o_http1client_cancel(self->client); + self->client = NULL; + } +} + +static void do_send(struct rp_generator_t *self) +{ + h2o_iovec_t vecs[1]; + size_t veccnt; + h2o_send_state_t ststate; + + assert(self->sending.bytes_inflight == 0); + + vecs[0] = h2o_doublebuffer_prepare(&self->sending, + self->client != NULL ? &self->client->sock->input : &self->last_content_before_send, + self->src_req->preferred_chunk_size); + + if (self->client == NULL && vecs[0].len == self->sending.buf->size && self->last_content_before_send->size == 0) { + veccnt = vecs[0].len != 0 ? 1 : 0; + ststate = H2O_SEND_STATE_FINAL; + } else { + if (vecs[0].len == 0) + return; + veccnt = 1; + ststate = H2O_SEND_STATE_IN_PROGRESS; + } + + if (self->had_body_error) + ststate = H2O_SEND_STATE_ERROR; + + h2o_send(self->src_req, vecs, veccnt, ststate); +} + +static void do_proceed(h2o_generator_t *generator, h2o_req_t *req) +{ + struct rp_generator_t *self = (void *)generator; + + h2o_doublebuffer_consume(&self->sending); + do_send(self); +} + +static void on_websocket_upgrade_complete(void *_info, h2o_socket_t *sock, size_t reqsize) +{ + struct rp_ws_upgrade_info_t *info = _info; + + if (sock != NULL) { + h2o_buffer_consume(&sock->input, reqsize);//It is detached from conn. Let's trash unused data. + h2o_tunnel_establish(info->ctx, sock, info->upstream_sock, info->timeout); + } else { + h2o_socket_close(info->upstream_sock); + } + free(info); +} + +static inline void on_websocket_upgrade(struct rp_generator_t *self, h2o_timeout_t *timeout, int rlen) +{ + h2o_req_t *req = self->src_req; + h2o_socket_t *sock = h2o_http1client_steal_socket(self->client); + h2o_buffer_consume(&sock->input, rlen);//trash data after stealing sock. + struct rp_ws_upgrade_info_t *info = h2o_mem_alloc(sizeof(*info)); + info->upstream_sock = sock; + info->timeout = timeout; + info->ctx = req->conn->ctx; + h2o_http1_upgrade(req, NULL, 0, on_websocket_upgrade_complete, info); +} + +static int on_body(h2o_http1client_t *client, const char *errstr) +{ + struct rp_generator_t *self = client->data; + + if (errstr != NULL) { + /* detach the content */ + self->last_content_before_send = self->client->sock->input; + h2o_buffer_init(&self->client->sock->input, &h2o_socket_buffer_prototype); + self->client = NULL; + if (errstr != h2o_http1client_error_is_eos) { + h2o_req_log_error(self->src_req, "lib/core/proxy.c", "%s", errstr); + self->had_body_error = 1; + } + } + if (self->sending.bytes_inflight == 0) + do_send(self); + + return 0; +} + +static char compress_hint_to_enum(const char *val, size_t len) +{ + if (h2o_lcstris(val, len, H2O_STRLIT("on"))) { + return H2O_COMPRESS_HINT_ENABLE; + } + if (h2o_lcstris(val, len, H2O_STRLIT("off"))) { + return H2O_COMPRESS_HINT_DISABLE; + } + return H2O_COMPRESS_HINT_AUTO; +} + +static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *errstr, int minor_version, int status, + h2o_iovec_t msg, h2o_header_t *headers, size_t num_headers, int rlen) +{ + struct rp_generator_t *self = client->data; + h2o_req_t *req = self->src_req; + size_t i; + + if (errstr != NULL && errstr != h2o_http1client_error_is_eos) { + self->client = NULL; + h2o_req_log_error(req, "lib/core/proxy.c", "%s", errstr); + h2o_send_error_502(req, "Gateway Error", errstr, 0); + return NULL; + } + + /* copy the response (note: all the headers must be copied; http1client discards the input once we return from this callback) */ + req->res.status = status; + req->res.reason = h2o_strdup(&req->pool, msg.base, msg.len).base; + for (i = 0; i != num_headers; ++i) { + if (h2o_iovec_is_token(headers[i].name)) { + const h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, headers[i].name); + h2o_iovec_t value; + if (token->proxy_should_drop_for_res) { + goto Skip; + } + if (token == H2O_TOKEN_CONTENT_LENGTH) { + if (req->res.content_length != SIZE_MAX || + (req->res.content_length = h2o_strtosize(headers[i].value.base, headers[i].value.len)) == SIZE_MAX) { + self->client = NULL; + h2o_req_log_error(req, "lib/core/proxy.c", "%s", "invalid response from upstream (malformed content-length)"); + h2o_send_error_502(req, "Gateway Error", "invalid response from upstream", 0); + return NULL; + } + goto Skip; + } else if (token == H2O_TOKEN_LOCATION) { + if (req->res_is_delegated && (300 <= status && status <= 399) && status != 304) { + self->client = NULL; + h2o_iovec_t method = h2o_get_redirect_method(req->method, status); + h2o_send_redirect_internal(req, method, headers[i].value.base, headers[i].value.len, 1); + return NULL; + } + if (req->overrides != NULL && req->overrides->location_rewrite.match != NULL) { + value = rewrite_location(&req->pool, headers[i].value.base, headers[i].value.len, + req->overrides->location_rewrite.match, req->input.scheme, req->input.authority, + req->overrides->location_rewrite.path_prefix); + if (value.base != NULL) + goto AddHeader; + } + goto AddHeaderDuped; + } else if (token == H2O_TOKEN_LINK) { + h2o_iovec_t new_value; + new_value = h2o_push_path_in_link_header(req, headers[i].value.base, headers[i].value.len); + if (!new_value.len) + goto Skip; + headers[i].value.base = new_value.base; + headers[i].value.len = new_value.len; + } else if (token == H2O_TOKEN_SERVER) { + if (!req->conn->ctx->globalconf->proxy.preserve_server_header) + goto Skip; + } else if (token == H2O_TOKEN_X_COMPRESS_HINT) { + req->compress_hint = compress_hint_to_enum(headers[i].value.base, headers[i].value.len); + goto Skip; + } + /* default behaviour, transfer the header downstream */ + AddHeaderDuped: + value = h2o_strdup(&req->pool, headers[i].value.base, headers[i].value.len); + AddHeader: + h2o_add_header(&req->pool, &req->res.headers, token, headers[i].orig_name, value.base, value.len); + Skip:; + } else { + h2o_iovec_t name = h2o_strdup(&req->pool, headers[i].name->base, headers[i].name->len); + h2o_iovec_t value = h2o_strdup(&req->pool, headers[i].value.base, headers[i].value.len); + h2o_add_header_by_str(&req->pool, &req->res.headers, name.base, name.len, 0, headers[i].orig_name, value.base, + value.len); + } + } + + if (self->is_websocket_handshake && req->res.status == 101) { + h2o_http1client_ctx_t *client_ctx = get_client_ctx(req); + assert(client_ctx->websocket_timeout != NULL); + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_UPGRADE, NULL, H2O_STRLIT("websocket")); + on_websocket_upgrade(self, client_ctx->websocket_timeout, rlen); + self->client = NULL; + return NULL; + } + /* declare the start of the response */ + h2o_start_response(req, &self->super); + + if (errstr == h2o_http1client_error_is_eos) { + self->client = NULL; + h2o_send(req, NULL, 0, H2O_SEND_STATE_FINAL); + return NULL; + } + + return on_body; +} + +static int on_1xx(h2o_http1client_t *client, int minor_version, int status, h2o_iovec_t msg, h2o_header_t *headers, + size_t num_headers) +{ + struct rp_generator_t *self = client->data; + size_t i; + + for (i = 0; i != num_headers; ++i) { + if (headers[i].name == &H2O_TOKEN_LINK->buf) + h2o_push_path_in_link_header(self->src_req, headers[i].value.base, headers[i].value.len); + } + + return 0; +} + +static h2o_http1client_head_cb on_connect(h2o_http1client_t *client, const char *errstr, h2o_iovec_t **reqbufs, size_t *reqbufcnt, + int *method_is_head) +{ + struct rp_generator_t *self = client->data; + + if (errstr != NULL) { + self->client = NULL; + h2o_req_log_error(self->src_req, "lib/core/proxy.c", "%s", errstr); + h2o_send_error_502(self->src_req, "Gateway Error", errstr, 0); + return NULL; + } + + *reqbufs = self->up_req.bufs; + *reqbufcnt = self->up_req.bufs[1].base != NULL ? 2 : 1; + *method_is_head = self->up_req.is_head; + self->client->informational_cb = on_1xx; + return on_head; +} + +static void on_generator_dispose(void *_self) +{ + struct rp_generator_t *self = _self; + + if (self->client != NULL) { + h2o_http1client_cancel(self->client); + self->client = NULL; + } + h2o_buffer_dispose(&self->last_content_before_send); + h2o_doublebuffer_dispose(&self->sending); +} + +static struct rp_generator_t *proxy_send_prepare(h2o_req_t *req, int keepalive, int use_proxy_protocol) +{ + struct rp_generator_t *self = h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose); + h2o_http1client_ctx_t *client_ctx = get_client_ctx(req); + + self->super.proceed = do_proceed; + self->super.stop = do_close; + self->src_req = req; + if (client_ctx->websocket_timeout != NULL && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("websocket"))) { + self->is_websocket_handshake = 1; + } else { + self->is_websocket_handshake = 0; + } + self->had_body_error = 0; + self->up_req.bufs[0] = build_request(req, keepalive, self->is_websocket_handshake, use_proxy_protocol); + self->up_req.bufs[1] = req->entity; + self->up_req.is_head = h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD")); + h2o_buffer_init(&self->last_content_before_send, &h2o_socket_buffer_prototype); + h2o_doublebuffer_init(&self->sending, &h2o_socket_buffer_prototype); + + return self; +} + +void h2o__proxy_process_request(h2o_req_t *req) +{ + h2o_req_overrides_t *overrides = req->overrides; + h2o_http1client_ctx_t *client_ctx = get_client_ctx(req); + struct rp_generator_t *self; + + if (overrides != NULL) { + if (overrides->socketpool != NULL) { + if (overrides->use_proxy_protocol) + assert(!"proxy protocol cannot be used for a persistent upstream connection"); + self = proxy_send_prepare(req, 1, 0); + h2o_http1client_connect_with_pool(&self->client, self, client_ctx, overrides->socketpool, on_connect); + return; + } else if (overrides->hostport.host.base != NULL) { + self = proxy_send_prepare(req, 0, overrides->use_proxy_protocol); + h2o_http1client_connect(&self->client, self, client_ctx, req->overrides->hostport.host, req->overrides->hostport.port, + 0, on_connect); + return; + } + } + { /* default logic */ + h2o_iovec_t host; + uint16_t port; + if (h2o_url_parse_hostport(req->authority.base, req->authority.len, &host, &port) == NULL) { + h2o_req_log_error(req, "lib/core/proxy.c", "invalid URL supplied for internal redirection:%s://%.*s%.*s", + req->scheme->name.base, (int)req->authority.len, req->authority.base, (int)req->path.len, + req->path.base); + h2o_send_error_502(req, "Gateway Error", "internal error", 0); + return; + } + if (port == 65535) + port = req->scheme->default_port; + self = proxy_send_prepare(req, 0, overrides != NULL && overrides->use_proxy_protocol); + h2o_http1client_connect(&self->client, self, client_ctx, host, port, req->scheme == &H2O_URL_SCHEME_HTTPS, on_connect); + return; + } +} diff --git a/src/web/server/h2o/libh2o/lib/core/request.c b/src/web/server/h2o/libh2o/lib/core/request.c new file mode 100644 index 000000000..96aabb22d --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/request.c @@ -0,0 +1,696 @@ +/* + * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include "h2o.h" + +#ifndef IOV_MAX +#define IOV_MAX UIO_MAXIOV +#endif + +#define INITIAL_INBUFSZ 8192 + +struct st_deferred_request_action_t { + h2o_timeout_entry_t timeout; + h2o_req_t *req; +}; + +struct st_delegate_request_deferred_t { + struct st_deferred_request_action_t super; + h2o_handler_t *current_handler; +}; + +struct st_reprocess_request_deferred_t { + struct st_deferred_request_action_t super; + h2o_iovec_t method; + const h2o_url_scheme_t *scheme; + h2o_iovec_t authority; + h2o_iovec_t path; + h2o_req_overrides_t *overrides; + int is_delegated; +}; + +struct st_send_error_deferred_t { + h2o_req_t *req; + int status; + const char *reason; + const char *body; + int flags; + h2o_timeout_entry_t _timeout; +}; + +static void on_deferred_action_dispose(void *_action) +{ + struct st_deferred_request_action_t *action = _action; + if (h2o_timeout_is_linked(&action->timeout)) + h2o_timeout_unlink(&action->timeout); +} + +static struct st_deferred_request_action_t *create_deferred_action(h2o_req_t *req, size_t sz, h2o_timeout_cb cb) +{ + struct st_deferred_request_action_t *action = h2o_mem_alloc_shared(&req->pool, sz, on_deferred_action_dispose); + *action = (struct st_deferred_request_action_t){{0, cb}, req}; + h2o_timeout_link(req->conn->ctx->loop, &req->conn->ctx->zero_timeout, &action->timeout); + return action; +} + +static h2o_hostconf_t *find_hostconf(h2o_hostconf_t **hostconfs, h2o_iovec_t authority, uint16_t default_port) +{ + h2o_iovec_t hostname; + uint16_t port; + char *hostname_lc; + + /* safe-guard for alloca */ + if (authority.len >= 65536) + return NULL; + + /* extract the specified hostname and port */ + if (h2o_url_parse_hostport(authority.base, authority.len, &hostname, &port) == NULL) + return NULL; + if (port == 65535) + port = default_port; + + /* convert supplied hostname to lower-case */ + hostname_lc = alloca(hostname.len); + memcpy(hostname_lc, hostname.base, hostname.len); + h2o_strtolower(hostname_lc, hostname.len); + + do { + h2o_hostconf_t *hostconf = *hostconfs; + if (hostconf->authority.port == port || (hostconf->authority.port == 65535 && port == default_port)) { + if (hostconf->authority.host.base[0] == '*') { + /* matching against "*.foo.bar" */ + size_t cmplen = hostconf->authority.host.len - 1; + if (cmplen < hostname.len && + memcmp(hostconf->authority.host.base + 1, hostname_lc + hostname.len - cmplen, cmplen) == 0) + return hostconf; + } else { + /* exact match */ + if (h2o_memis(hostconf->authority.host.base, hostconf->authority.host.len, hostname_lc, hostname.len)) + return hostconf; + } + } + } while (*++hostconfs != NULL); + + return NULL; +} + +static h2o_hostconf_t *setup_before_processing(h2o_req_t *req) +{ + h2o_context_t *ctx = req->conn->ctx; + h2o_hostconf_t *hostconf; + + h2o_get_timestamp(ctx, &req->pool, &req->processed_at); + + /* find the host context */ + if (req->input.authority.base != NULL) { + if (req->conn->hosts[1] == NULL || + (hostconf = find_hostconf(req->conn->hosts, req->input.authority, req->input.scheme->default_port)) == NULL) + hostconf = *req->conn->hosts; + } else { + /* set the authority name to the default one */ + hostconf = *req->conn->hosts; + req->input.authority = hostconf->authority.hostport; + } + + req->scheme = req->input.scheme; + req->method = req->input.method; + req->authority = req->input.authority; + req->path = req->input.path; + req->path_normalized = + h2o_url_normalize_path(&req->pool, req->input.path.base, req->input.path.len, &req->query_at, &req->norm_indexes); + req->input.query_at = req->query_at; /* we can do this since input.path == path */ + + return hostconf; +} + +static void call_handlers(h2o_req_t *req, h2o_handler_t **handler) +{ + h2o_handler_t **end = req->pathconf->handlers.entries + req->pathconf->handlers.size; + + for (; handler != end; ++handler) + if ((*handler)->on_req(*handler, req) == 0) + return; + + h2o_send_error_404(req, "File Not Found", "not found", 0); +} + +static void process_hosted_request(h2o_req_t *req, h2o_hostconf_t *hostconf) +{ + h2o_pathconf_t *selected_pathconf = &hostconf->fallback_path; + size_t i; + + /* setup pathconf, or redirect to "path/" */ + for (i = 0; i != hostconf->paths.size; ++i) { + h2o_pathconf_t *candidate = hostconf->paths.entries + i; + if (req->path_normalized.len >= candidate->path.len && + memcmp(req->path_normalized.base, candidate->path.base, candidate->path.len) == 0 && + (candidate->path.base[candidate->path.len - 1] == '/' || req->path_normalized.len == candidate->path.len || + req->path_normalized.base[candidate->path.len] == '/')) { + selected_pathconf = candidate; + break; + } + } + h2o_req_bind_conf(req, hostconf, selected_pathconf); + + call_handlers(req, req->pathconf->handlers.entries); +} + +static void deferred_proceed_cb(h2o_timeout_entry_t *entry) +{ + h2o_req_t *req = H2O_STRUCT_FROM_MEMBER(h2o_req_t, _timeout_entry, entry); + h2o_proceed_response(req); +} + +static void close_generator_and_filters(h2o_req_t *req) +{ + /* close the generator if it is still open */ + if (req->_generator != NULL) { + /* close generator */ + if (req->_generator->stop != NULL) + req->_generator->stop(req->_generator, req); + req->_generator = NULL; + } + /* close the ostreams still open */ + while (req->_ostr_top->next != NULL) { + if (req->_ostr_top->stop != NULL) + req->_ostr_top->stop(req->_ostr_top, req); + req->_ostr_top = req->_ostr_top->next; + } +} + +static void reset_response(h2o_req_t *req) +{ + req->res = (h2o_res_t){0, NULL, SIZE_MAX}; + req->res.reason = "OK"; + req->_next_filter_index = 0; + req->bytes_sent = 0; +} + +static void retain_original_response(h2o_req_t *req) +{ + if (req->res.original.status != 0) + return; + + req->res.original.status = req->res.status; + h2o_vector_reserve(&req->pool, &req->res.original.headers, req->res.headers.size); + h2o_memcpy(req->res.original.headers.entries, req->res.headers.entries, + sizeof(req->res.headers.entries[0]) * req->res.headers.size); + req->res.original.headers.size = req->res.headers.size; +} + +void h2o_init_request(h2o_req_t *req, h2o_conn_t *conn, h2o_req_t *src) +{ + /* clear all memory (expect memory pool, since it is large) */ + memset(req, 0, offsetof(h2o_req_t, pool)); + + /* init memory pool (before others, since it may be used) */ + h2o_mem_init_pool(&req->pool); + + /* init properties that should be initialized to non-zero */ + req->conn = conn; + req->_timeout_entry.cb = deferred_proceed_cb; + req->res.reason = "OK"; /* default to "OK" regardless of the status value, it's not important after all (never sent in HTTP2) */ + req->res.content_length = SIZE_MAX; + req->preferred_chunk_size = SIZE_MAX; + + if (src != NULL) { + size_t i; +#define COPY(buf) \ + do { \ + req->buf.base = h2o_mem_alloc_pool(&req->pool, src->buf.len); \ + memcpy(req->buf.base, src->buf.base, src->buf.len); \ + req->buf.len = src->buf.len; \ + } while (0) + COPY(input.authority); + COPY(input.method); + COPY(input.path); + req->input.scheme = src->input.scheme; + req->version = src->version; + req->entity = src->entity; + req->http1_is_persistent = src->http1_is_persistent; + req->timestamps = src->timestamps; + if (src->upgrade.base != NULL) { + COPY(upgrade); + } else { + req->upgrade.base = NULL; + req->upgrade.len = 0; + } +#undef COPY + h2o_vector_reserve(&req->pool, &req->headers, src->headers.size); + req->headers.size = src->headers.size; + for (i = 0; i != src->headers.size; ++i) { + h2o_header_t *dst_header = req->headers.entries + i, *src_header = src->headers.entries + i; + if (h2o_iovec_is_token(src_header->name)) { + dst_header->name = src_header->name; + } else { + dst_header->name = h2o_mem_alloc_pool(&req->pool, sizeof(*dst_header->name)); + *dst_header->name = h2o_strdup(&req->pool, src_header->name->base, src_header->name->len); + } + dst_header->value = h2o_strdup(&req->pool, src_header->value.base, src_header->value.len); + if (!src_header->orig_name) + dst_header->orig_name = NULL; + else + dst_header->orig_name = h2o_strdup(&req->pool, src_header->orig_name, src_header->name->len).base; + } + if (src->env.size != 0) { + h2o_vector_reserve(&req->pool, &req->env, src->env.size); + req->env.size = src->env.size; + for (i = 0; i != req->env.size; ++i) + req->env.entries[i] = h2o_strdup(&req->pool, src->env.entries[i].base, src->env.entries[i].len); + } + } +} + +void h2o_dispose_request(h2o_req_t *req) +{ + close_generator_and_filters(req); + + h2o_timeout_unlink(&req->_timeout_entry); + + if (req->version != 0 && req->pathconf != NULL) { + h2o_logger_t **logger = req->pathconf->loggers.entries, **end = logger + req->pathconf->loggers.size; + for (; logger != end; ++logger) { + (*logger)->log_access((*logger), req); + } + } + + h2o_mem_clear_pool(&req->pool); +} + +void h2o_process_request(h2o_req_t *req) +{ + h2o_hostconf_t *hostconf = setup_before_processing(req); + process_hosted_request(req, hostconf); +} + +void h2o_delegate_request(h2o_req_t *req, h2o_handler_t *current_handler) +{ + h2o_handler_t **handler = req->pathconf->handlers.entries, **end = handler + req->pathconf->handlers.size; + + for (; handler != end; ++handler) { + if (*handler == current_handler) { + ++handler; + break; + } + } + call_handlers(req, handler); +} + +static void on_delegate_request_cb(h2o_timeout_entry_t *entry) +{ + struct st_delegate_request_deferred_t *args = + H2O_STRUCT_FROM_MEMBER(struct st_delegate_request_deferred_t, super.timeout, entry); + h2o_delegate_request(args->super.req, args->current_handler); +} + +void h2o_delegate_request_deferred(h2o_req_t *req, h2o_handler_t *current_handler) +{ + struct st_delegate_request_deferred_t *args = + (struct st_delegate_request_deferred_t *)create_deferred_action(req, sizeof(*args), on_delegate_request_cb); + args->current_handler = current_handler; +} + +void h2o_reprocess_request(h2o_req_t *req, h2o_iovec_t method, const h2o_url_scheme_t *scheme, h2o_iovec_t authority, + h2o_iovec_t path, h2o_req_overrides_t *overrides, int is_delegated) +{ + h2o_hostconf_t *hostconf; + + retain_original_response(req); + + /* close generators and filters that are already running */ + close_generator_and_filters(req); + + /* setup the request/response parameters */ + req->method = method; + req->scheme = scheme; + req->authority = authority; + req->path = path; + req->path_normalized = h2o_url_normalize_path(&req->pool, req->path.base, req->path.len, &req->query_at, &req->norm_indexes); + req->overrides = overrides; + req->res_is_delegated |= is_delegated; + reset_response(req); + + /* check the delegation (or reprocess) counter */ + if (req->res_is_delegated) { + if (req->num_delegated == req->conn->ctx->globalconf->max_delegations) { + /* TODO log */ + h2o_send_error_502(req, "Gateway Error", "too many internal delegations", 0); + return; + } + ++req->num_delegated; + } else { + if (req->num_reprocessed >= 5) { + /* TODO log */ + h2o_send_error_502(req, "Gateway Error", "too many internal reprocesses", 0); + return; + } + ++req->num_reprocessed; + } + + /* handle the response using the handlers, if hostconf exists */ + h2o_hostconf_t **hosts = is_delegated ? req->conn->ctx->globalconf->hosts : req->conn->hosts; + if (req->overrides == NULL && (hostconf = find_hostconf(hosts, req->authority, req->scheme->default_port)) != NULL) { + req->pathconf = NULL; + process_hosted_request(req, hostconf); + return; + } + + /* uses the current pathconf, in other words, proxy uses the previous pathconf for building filters */ + h2o__proxy_process_request(req); +} + +static void on_reprocess_request_cb(h2o_timeout_entry_t *entry) +{ + struct st_reprocess_request_deferred_t *args = + H2O_STRUCT_FROM_MEMBER(struct st_reprocess_request_deferred_t, super.timeout, entry); + h2o_reprocess_request(args->super.req, args->method, args->scheme, args->authority, args->path, args->overrides, + args->is_delegated); +} + +void h2o_reprocess_request_deferred(h2o_req_t *req, h2o_iovec_t method, const h2o_url_scheme_t *scheme, h2o_iovec_t authority, + h2o_iovec_t path, h2o_req_overrides_t *overrides, int is_delegated) +{ + struct st_reprocess_request_deferred_t *args = + (struct st_reprocess_request_deferred_t *)create_deferred_action(req, sizeof(*args), on_reprocess_request_cb); + args->method = method; + args->scheme = scheme; + args->authority = authority; + args->path = path; + args->overrides = overrides; + args->is_delegated = is_delegated; +} + +void h2o_start_response(h2o_req_t *req, h2o_generator_t *generator) +{ + retain_original_response(req); + + /* set generator */ + assert(req->_generator == NULL); + req->_generator = generator; + + /* setup response filters */ + if (req->prefilters != NULL) { + req->prefilters->on_setup_ostream(req->prefilters, req, &req->_ostr_top); + } else { + h2o_setup_next_ostream(req, &req->_ostr_top); + } +} + +void h2o_send(h2o_req_t *req, h2o_iovec_t *bufs, size_t bufcnt, h2o_send_state_t state) +{ + assert(req->_generator != NULL); + + if (!h2o_send_state_is_in_progress(state)) + req->_generator = NULL; + + req->_ostr_top->do_send(req->_ostr_top, req, bufs, bufcnt, state); +} + +h2o_req_prefilter_t *h2o_add_prefilter(h2o_req_t *req, size_t sz) +{ + h2o_req_prefilter_t *prefilter = h2o_mem_alloc_pool(&req->pool, sz); + prefilter->next = req->prefilters; + req->prefilters = prefilter; + return prefilter; +} + +h2o_ostream_t *h2o_add_ostream(h2o_req_t *req, size_t sz, h2o_ostream_t **slot) +{ + h2o_ostream_t *ostr = h2o_mem_alloc_pool(&req->pool, sz); + ostr->next = *slot; + ostr->do_send = NULL; + ostr->stop = NULL; + ostr->start_pull = NULL; + + *slot = ostr; + + return ostr; +} + +static void apply_env(h2o_req_t *req, h2o_envconf_t *env) +{ + size_t i; + + if (env->parent != NULL) + apply_env(req, env->parent); + for (i = 0; i != env->unsets.size; ++i) + h2o_req_unsetenv(req, env->unsets.entries[i].base, env->unsets.entries[i].len); + for (i = 0; i != env->sets.size; i += 2) + *h2o_req_getenv(req, env->sets.entries[i].base, env->sets.entries[i].len, 1) = env->sets.entries[i + 1]; +} + +void h2o_req_bind_conf(h2o_req_t *req, h2o_hostconf_t *hostconf, h2o_pathconf_t *pathconf) +{ + req->hostconf = hostconf; + req->pathconf = pathconf; + if (pathconf->env != NULL) + apply_env(req, pathconf->env); +} + +void h2o_ostream_send_next(h2o_ostream_t *ostream, h2o_req_t *req, h2o_iovec_t *bufs, size_t bufcnt, h2o_send_state_t state) +{ + if (!h2o_send_state_is_in_progress(state)) { + assert(req->_ostr_top == ostream); + req->_ostr_top = ostream->next; + } else if (bufcnt == 0) { + h2o_timeout_link(req->conn->ctx->loop, &req->conn->ctx->zero_timeout, &req->_timeout_entry); + return; + } + ostream->next->do_send(ostream->next, req, bufs, bufcnt, state); +} + +void h2o_req_fill_mime_attributes(h2o_req_t *req) +{ + ssize_t content_type_index; + h2o_mimemap_type_t *mime; + + if (req->res.mime_attr != NULL) + return; + + if ((content_type_index = h2o_find_header(&req->res.headers, H2O_TOKEN_CONTENT_TYPE, -1)) != -1 && + (mime = h2o_mimemap_get_type_by_mimetype(req->pathconf->mimemap, req->res.headers.entries[content_type_index].value, 0)) != + NULL) + req->res.mime_attr = &mime->data.attr; + else + req->res.mime_attr = &h2o_mime_attributes_as_is; +} + +void h2o_send_inline(h2o_req_t *req, const char *body, size_t len) +{ + static h2o_generator_t generator = {NULL, NULL}; + + h2o_iovec_t buf = h2o_strdup(&req->pool, body, len); + /* the function intentionally does not set the content length, since it may be used for generating 304 response, etc. */ + /* req->res.content_length = buf.len; */ + + h2o_start_response(req, &generator); + + if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD"))) + h2o_send(req, NULL, 0, H2O_SEND_STATE_FINAL); + else + h2o_send(req, &buf, 1, H2O_SEND_STATE_FINAL); +} + +void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, const char *body, int flags) +{ + if (req->pathconf == NULL) { + h2o_hostconf_t *hostconf = setup_before_processing(req); + h2o_req_bind_conf(req, hostconf, &hostconf->fallback_path); + } + + if ((flags & H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION) != 0) + req->http1_is_persistent = 0; + + req->res.status = status; + req->res.reason = reason; + req->res.content_length = strlen(body); + + if ((flags & H2O_SEND_ERROR_KEEP_HEADERS) == 0) + memset(&req->res.headers, 0, sizeof(req->res.headers)); + + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/plain; charset=utf-8")); + + h2o_send_inline(req, body, SIZE_MAX); +} + +#define DECL_SEND_ERROR_DEFERRED(status_) \ + static void send_error_deferred_cb_##status_(h2o_timeout_entry_t *entry) \ + { \ + struct st_send_error_deferred_t *args = H2O_STRUCT_FROM_MEMBER(struct st_send_error_deferred_t, _timeout, entry); \ + reset_response(args->req); \ + args->req->conn->ctx->emitted_error_status[H2O_STATUS_ERROR_##status_]++; \ + h2o_send_error_generic(args->req, args->status, args->reason, args->body, args->flags); \ + } \ + \ + static void h2o_send_error_deferred_##status_(h2o_req_t *req, const char *reason, const char *body, int flags) \ + { \ + struct st_send_error_deferred_t *args = h2o_mem_alloc_pool(&req->pool, sizeof(*args)); \ + *args = (struct st_send_error_deferred_t){req, status_, reason, body, flags}; \ + args->_timeout.cb = send_error_deferred_cb_##status_; \ + h2o_timeout_link(req->conn->ctx->loop, &req->conn->ctx->zero_timeout, &args->_timeout); \ + } + +DECL_SEND_ERROR_DEFERRED(502) + +#undef DECL_SEND_ERROR_DEFERRED + +void h2o_req_log_error(h2o_req_t *req, const char *module, const char *fmt, ...) +{ +#define INITIAL_BUF_SIZE 256 + + char *errbuf = h2o_mem_alloc_pool(&req->pool, INITIAL_BUF_SIZE); + int errlen; + va_list args; + + va_start(args, fmt); + errlen = vsnprintf(errbuf, INITIAL_BUF_SIZE, fmt, args); + va_end(args); + + if (errlen >= INITIAL_BUF_SIZE) { + errbuf = h2o_mem_alloc_pool(&req->pool, errlen + 1); + va_start(args, fmt); + errlen = vsnprintf(errbuf, errlen + 1, fmt, args); + va_end(args); + } + +#undef INITIAL_BUF_SIZE + + /* save the log */ + h2o_vector_reserve(&req->pool, &req->error_logs, req->error_logs.size + 1); + req->error_logs.entries[req->error_logs.size++] = (h2o_req_error_log_t){module, h2o_iovec_init(errbuf, errlen)}; + + if (req->pathconf->error_log.emit_request_errors) { + /* build prefix */ + char *prefix = alloca(sizeof("[] in request::") + 32 + strlen(module)), *p = prefix; + p += sprintf(p, "[%s] in request:", module); + if (req->path.len < 32) { + memcpy(p, req->path.base, req->path.len); + p += req->path.len; + } else { + memcpy(p, req->path.base, 29); + p += 29; + memcpy(p, "...", 3); + p += 3; + } + *p++ = ':'; + /* use writev(2) to emit error atomically */ + struct iovec vecs[] = {{prefix, p - prefix}, {errbuf, errlen}, {"\n", 1}}; + H2O_BUILD_ASSERT(sizeof(vecs) / sizeof(vecs[0]) < IOV_MAX); + writev(2, vecs, sizeof(vecs) / sizeof(vecs[0])); + } +} + +void h2o_send_redirect(h2o_req_t *req, int status, const char *reason, const char *url, size_t url_len) +{ + if (req->res_is_delegated) { + h2o_iovec_t method = h2o_get_redirect_method(req->method, status); + h2o_send_redirect_internal(req, method, url, url_len, 0); + return; + } + + static h2o_generator_t generator = {NULL, NULL}; + static const h2o_iovec_t body_prefix = {H2O_STRLIT("Moved

The document has moved here")}; + + /* build and send response */ + h2o_iovec_t bufs[3]; + size_t bufcnt; + if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD"))) { + req->res.content_length = SIZE_MAX; + bufcnt = 0; + } else { + bufs[0] = body_prefix; + bufs[1] = h2o_htmlescape(&req->pool, url, url_len); + bufs[2] = body_suffix; + bufcnt = 3; + req->res.content_length = body_prefix.len + bufs[1].len + body_suffix.len; + } + req->res.status = status; + req->res.reason = reason; + req->res.headers = (h2o_headers_t){NULL}; + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_LOCATION, NULL, url, url_len); + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/html; charset=utf-8")); + h2o_start_response(req, &generator); + h2o_send(req, bufs, bufcnt, H2O_SEND_STATE_FINAL); +} + +void h2o_send_redirect_internal(h2o_req_t *req, h2o_iovec_t method, const char *url_str, size_t url_len, int preserve_overrides) +{ + h2o_url_t url; + + /* parse the location URL */ + if (h2o_url_parse_relative(url_str, url_len, &url) != 0) { + /* TODO log fprintf(stderr, "[proxy] cannot handle location header: %.*s\n", (int)url_len, url); */ + h2o_send_error_deferred_502(req, "Gateway Error", "internal error", 0); + return; + } + /* convert the location to absolute (while creating copies of the values passed to the deferred call) */ + if (url.scheme == NULL) + url.scheme = req->scheme; + if (url.authority.base == NULL) { + if (req->hostconf != NULL) + url.authority = req->hostconf->authority.hostport; + else + url.authority = req->authority; + } else { + if (h2o_lcstris(url.authority.base, url.authority.len, req->authority.base, req->authority.len)) { + url.authority = req->authority; + } else { + url.authority = h2o_strdup(&req->pool, url.authority.base, url.authority.len); + preserve_overrides = 0; + } + } + h2o_iovec_t base_path = req->path; + h2o_url_resolve_path(&base_path, &url.path); + url.path = h2o_concat(&req->pool, base_path, url.path); + + h2o_reprocess_request_deferred(req, method, url.scheme, url.authority, url.path, preserve_overrides ? req->overrides : NULL, 1); +} + +h2o_iovec_t h2o_get_redirect_method(h2o_iovec_t method, int status) +{ + if (h2o_memis(method.base, method.len, H2O_STRLIT("POST")) && !(status == 307 || status == 308)) + method = h2o_iovec_init(H2O_STRLIT("GET")); + return method; +} + +h2o_iovec_t h2o_push_path_in_link_header(h2o_req_t *req, const char *value, size_t value_len) +{ + int i; + h2o_iovec_t ret = h2o_iovec_init(value, value_len); + if (req->conn->callbacks->push_path == NULL) + return ret; + + h2o_iovec_vector_t paths = 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, &ret); + if (paths.size == 0) + return ret; + + for (i = 0; i < paths.size; i++) { + req->conn->callbacks->push_path(req, paths.entries[i].base, paths.entries[i].len); + } + return ret; +} diff --git a/src/web/server/h2o/libh2o/lib/core/token.c b/src/web/server/h2o/libh2o/lib/core/token.c new file mode 100644 index 000000000..e21ce2383 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/token.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014 DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "h2o.h" +#include "token_table.h" + +int h2o_iovec_is_token(const h2o_iovec_t *buf) +{ + return &h2o__tokens[0].buf <= buf && buf <= &h2o__tokens[H2O_MAX_TOKENS - 1].buf; +} diff --git a/src/web/server/h2o/libh2o/lib/core/token_table.h b/src/web/server/h2o/libh2o/lib/core/token_table.h new file mode 100644 index 000000000..ae26aa6c4 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/token_table.h @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2014 DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* DO NOT EDIT! generated by tokens.pl */ +h2o_token_t h2o__tokens[] = {{{H2O_STRLIT(":authority")}, 1, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT(":method")}, 2, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT(":path")}, 4, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT(":scheme")}, 6, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT(":status")}, 8, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("accept")}, 19, 0, 0, 0, 0, 1, 0}, + {{H2O_STRLIT("accept-charset")}, 15, 0, 0, 0, 0, 1, 0}, + {{H2O_STRLIT("accept-encoding")}, 16, 0, 0, 0, 0, 1, 0}, + {{H2O_STRLIT("accept-language")}, 17, 0, 0, 0, 0, 1, 0}, + {{H2O_STRLIT("accept-ranges")}, 18, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("access-control-allow-origin")}, 20, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("age")}, 21, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("allow")}, 22, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("authorization")}, 23, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("cache-control")}, 24, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("cache-digest")}, 0, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("connection")}, 0, 1, 1, 0, 1, 0, 0}, + {{H2O_STRLIT("content-disposition")}, 25, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("content-encoding")}, 26, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("content-language")}, 27, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("content-length")}, 28, 0, 0, 1, 0, 0, 0}, + {{H2O_STRLIT("content-location")}, 29, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("content-range")}, 30, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("content-type")}, 31, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("cookie")}, 32, 0, 0, 0, 0, 0, 1}, + {{H2O_STRLIT("date")}, 33, 0, 1, 0, 0, 0, 0}, + {{H2O_STRLIT("etag")}, 34, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("expect")}, 35, 0, 0, 1, 0, 0, 0}, + {{H2O_STRLIT("expires")}, 36, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("from")}, 37, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("host")}, 38, 0, 0, 1, 1, 0, 0}, + {{H2O_STRLIT("http2-settings")}, 0, 1, 0, 0, 1, 0, 0}, + {{H2O_STRLIT("if-match")}, 39, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("if-modified-since")}, 40, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("if-none-match")}, 41, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("if-range")}, 42, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("if-unmodified-since")}, 43, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("keep-alive")}, 0, 1, 1, 0, 0, 0, 0}, + {{H2O_STRLIT("last-modified")}, 44, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("link")}, 45, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("location")}, 46, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("max-forwards")}, 47, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("proxy-authenticate")}, 48, 1, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("proxy-authorization")}, 49, 1, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("range")}, 50, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("referer")}, 51, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("refresh")}, 52, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("retry-after")}, 53, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("server")}, 54, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("set-cookie")}, 55, 0, 0, 0, 0, 0, 1}, + {{H2O_STRLIT("strict-transport-security")}, 56, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("te")}, 0, 1, 0, 0, 1, 0, 0}, + {{H2O_STRLIT("transfer-encoding")}, 57, 1, 1, 1, 1, 0, 0}, + {{H2O_STRLIT("upgrade")}, 0, 1, 1, 1, 1, 0, 0}, + {{H2O_STRLIT("user-agent")}, 58, 0, 0, 0, 0, 1, 0}, + {{H2O_STRLIT("vary")}, 59, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("via")}, 60, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("www-authenticate")}, 61, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("x-compress-hint")}, 0, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("x-forwarded-for")}, 0, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("x-reproxy-url")}, 0, 0, 0, 0, 0, 0, 0}, + {{H2O_STRLIT("x-traffic")}, 0, 0, 0, 0, 0, 0, 0}}; +size_t h2o__num_tokens = 62; + +const h2o_token_t *h2o_lookup_token(const char *name, size_t len) +{ + switch (len) { + case 2: + switch (name[1]) { + case 'e': + if (memcmp(name, "t", 1) == 0) + return H2O_TOKEN_TE; + break; + } + break; + case 3: + switch (name[2]) { + case 'a': + if (memcmp(name, "vi", 2) == 0) + return H2O_TOKEN_VIA; + break; + case 'e': + if (memcmp(name, "ag", 2) == 0) + return H2O_TOKEN_AGE; + break; + } + break; + case 4: + switch (name[3]) { + case 'e': + if (memcmp(name, "dat", 3) == 0) + return H2O_TOKEN_DATE; + break; + case 'g': + if (memcmp(name, "eta", 3) == 0) + return H2O_TOKEN_ETAG; + break; + case 'k': + if (memcmp(name, "lin", 3) == 0) + return H2O_TOKEN_LINK; + break; + case 'm': + if (memcmp(name, "fro", 3) == 0) + return H2O_TOKEN_FROM; + break; + case 't': + if (memcmp(name, "hos", 3) == 0) + return H2O_TOKEN_HOST; + break; + case 'y': + if (memcmp(name, "var", 3) == 0) + return H2O_TOKEN_VARY; + break; + } + break; + case 5: + switch (name[4]) { + case 'e': + if (memcmp(name, "rang", 4) == 0) + return H2O_TOKEN_RANGE; + break; + case 'h': + if (memcmp(name, ":pat", 4) == 0) + return H2O_TOKEN_PATH; + break; + case 'w': + if (memcmp(name, "allo", 4) == 0) + return H2O_TOKEN_ALLOW; + break; + } + break; + case 6: + switch (name[5]) { + case 'e': + if (memcmp(name, "cooki", 5) == 0) + return H2O_TOKEN_COOKIE; + break; + case 'r': + if (memcmp(name, "serve", 5) == 0) + return H2O_TOKEN_SERVER; + break; + case 't': + if (memcmp(name, "accep", 5) == 0) + return H2O_TOKEN_ACCEPT; + if (memcmp(name, "expec", 5) == 0) + return H2O_TOKEN_EXPECT; + break; + } + break; + case 7: + switch (name[6]) { + case 'd': + if (memcmp(name, ":metho", 6) == 0) + return H2O_TOKEN_METHOD; + break; + case 'e': + if (memcmp(name, ":schem", 6) == 0) + return H2O_TOKEN_SCHEME; + if (memcmp(name, "upgrad", 6) == 0) + return H2O_TOKEN_UPGRADE; + break; + case 'h': + if (memcmp(name, "refres", 6) == 0) + return H2O_TOKEN_REFRESH; + break; + case 'r': + if (memcmp(name, "refere", 6) == 0) + return H2O_TOKEN_REFERER; + break; + case 's': + if (memcmp(name, ":statu", 6) == 0) + return H2O_TOKEN_STATUS; + if (memcmp(name, "expire", 6) == 0) + return H2O_TOKEN_EXPIRES; + break; + } + break; + case 8: + switch (name[7]) { + case 'e': + if (memcmp(name, "if-rang", 7) == 0) + return H2O_TOKEN_IF_RANGE; + break; + case 'h': + if (memcmp(name, "if-matc", 7) == 0) + return H2O_TOKEN_IF_MATCH; + break; + case 'n': + if (memcmp(name, "locatio", 7) == 0) + return H2O_TOKEN_LOCATION; + break; + } + break; + case 9: + switch (name[8]) { + case 'c': + if (memcmp(name, "x-traffi", 8) == 0) + return H2O_TOKEN_X_TRAFFIC; + break; + } + break; + case 10: + switch (name[9]) { + case 'e': + if (memcmp(name, "keep-aliv", 9) == 0) + return H2O_TOKEN_KEEP_ALIVE; + if (memcmp(name, "set-cooki", 9) == 0) + return H2O_TOKEN_SET_COOKIE; + break; + case 'n': + if (memcmp(name, "connectio", 9) == 0) + return H2O_TOKEN_CONNECTION; + break; + case 't': + if (memcmp(name, "user-agen", 9) == 0) + return H2O_TOKEN_USER_AGENT; + break; + case 'y': + if (memcmp(name, ":authorit", 9) == 0) + return H2O_TOKEN_AUTHORITY; + break; + } + break; + case 11: + switch (name[10]) { + case 'r': + if (memcmp(name, "retry-afte", 10) == 0) + return H2O_TOKEN_RETRY_AFTER; + break; + } + break; + case 12: + switch (name[11]) { + case 'e': + if (memcmp(name, "content-typ", 11) == 0) + return H2O_TOKEN_CONTENT_TYPE; + break; + case 's': + if (memcmp(name, "max-forward", 11) == 0) + return H2O_TOKEN_MAX_FORWARDS; + break; + case 't': + if (memcmp(name, "cache-diges", 11) == 0) + return H2O_TOKEN_CACHE_DIGEST; + break; + } + break; + case 13: + switch (name[12]) { + case 'd': + if (memcmp(name, "last-modifie", 12) == 0) + return H2O_TOKEN_LAST_MODIFIED; + break; + case 'e': + if (memcmp(name, "content-rang", 12) == 0) + return H2O_TOKEN_CONTENT_RANGE; + break; + case 'h': + if (memcmp(name, "if-none-matc", 12) == 0) + return H2O_TOKEN_IF_NONE_MATCH; + break; + case 'l': + if (memcmp(name, "cache-contro", 12) == 0) + return H2O_TOKEN_CACHE_CONTROL; + if (memcmp(name, "x-reproxy-ur", 12) == 0) + return H2O_TOKEN_X_REPROXY_URL; + break; + case 'n': + if (memcmp(name, "authorizatio", 12) == 0) + return H2O_TOKEN_AUTHORIZATION; + break; + case 's': + if (memcmp(name, "accept-range", 12) == 0) + return H2O_TOKEN_ACCEPT_RANGES; + break; + } + break; + case 14: + switch (name[13]) { + case 'h': + if (memcmp(name, "content-lengt", 13) == 0) + return H2O_TOKEN_CONTENT_LENGTH; + break; + case 's': + if (memcmp(name, "http2-setting", 13) == 0) + return H2O_TOKEN_HTTP2_SETTINGS; + break; + case 't': + if (memcmp(name, "accept-charse", 13) == 0) + return H2O_TOKEN_ACCEPT_CHARSET; + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (memcmp(name, "accept-languag", 14) == 0) + return H2O_TOKEN_ACCEPT_LANGUAGE; + break; + case 'g': + if (memcmp(name, "accept-encodin", 14) == 0) + return H2O_TOKEN_ACCEPT_ENCODING; + break; + case 'r': + if (memcmp(name, "x-forwarded-fo", 14) == 0) + return H2O_TOKEN_X_FORWARDED_FOR; + break; + case 't': + if (memcmp(name, "x-compress-hin", 14) == 0) + return H2O_TOKEN_X_COMPRESS_HINT; + break; + } + break; + case 16: + switch (name[15]) { + case 'e': + if (memcmp(name, "content-languag", 15) == 0) + return H2O_TOKEN_CONTENT_LANGUAGE; + if (memcmp(name, "www-authenticat", 15) == 0) + return H2O_TOKEN_WWW_AUTHENTICATE; + break; + case 'g': + if (memcmp(name, "content-encodin", 15) == 0) + return H2O_TOKEN_CONTENT_ENCODING; + break; + case 'n': + if (memcmp(name, "content-locatio", 15) == 0) + return H2O_TOKEN_CONTENT_LOCATION; + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (memcmp(name, "if-modified-sinc", 16) == 0) + return H2O_TOKEN_IF_MODIFIED_SINCE; + break; + case 'g': + if (memcmp(name, "transfer-encodin", 16) == 0) + return H2O_TOKEN_TRANSFER_ENCODING; + break; + } + break; + case 18: + switch (name[17]) { + case 'e': + if (memcmp(name, "proxy-authenticat", 17) == 0) + return H2O_TOKEN_PROXY_AUTHENTICATE; + break; + } + break; + case 19: + switch (name[18]) { + case 'e': + if (memcmp(name, "if-unmodified-sinc", 18) == 0) + return H2O_TOKEN_IF_UNMODIFIED_SINCE; + break; + case 'n': + if (memcmp(name, "content-dispositio", 18) == 0) + return H2O_TOKEN_CONTENT_DISPOSITION; + if (memcmp(name, "proxy-authorizatio", 18) == 0) + return H2O_TOKEN_PROXY_AUTHORIZATION; + break; + } + break; + case 25: + switch (name[24]) { + case 'y': + if (memcmp(name, "strict-transport-securit", 24) == 0) + return H2O_TOKEN_STRICT_TRANSPORT_SECURITY; + break; + } + break; + case 27: + switch (name[26]) { + case 'n': + if (memcmp(name, "access-control-allow-origi", 26) == 0) + return H2O_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN; + break; + } + break; + } + + return NULL; +} diff --git a/src/web/server/h2o/libh2o/lib/core/util.c b/src/web/server/h2o/libh2o/lib/core/util.c new file mode 100644 index 000000000..50d2b2493 --- /dev/null +++ b/src/web/server/h2o/libh2o/lib/core/util.c @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Satoh Hiroh + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include "h2o.h" +#include "h2o/http1.h" +#include "h2o/http2.h" + +struct st_h2o_accept_data_t { + h2o_accept_ctx_t *ctx; + h2o_socket_t *sock; + h2o_timeout_entry_t timeout; + h2o_memcached_req_t *async_resumption_get_req; + struct timeval connected_at; +}; + +static void on_accept_timeout(h2o_timeout_entry_t *entry); + +static struct st_h2o_accept_data_t *create_accept_data(h2o_accept_ctx_t *ctx, h2o_socket_t *sock, struct timeval connected_at) +{ + struct st_h2o_accept_data_t *data = h2o_mem_alloc(sizeof(*data)); + + data->ctx = ctx; + data->sock = sock; + data->timeout = (h2o_timeout_entry_t){0}; + data->timeout.cb = on_accept_timeout; + h2o_timeout_link(ctx->ctx->loop, &ctx->ctx->handshake_timeout, &data->timeout); + data->async_resumption_get_req = NULL; + data->connected_at = connected_at; + + sock->data = data; + return data; +} + +static void free_accept_data(struct st_h2o_accept_data_t *data) +{ + assert(data->async_resumption_get_req == NULL); + h2o_timeout_unlink(&data->timeout); + free(data); +} + +static struct { + h2o_memcached_context_t *memc; + unsigned expiration; +} async_resumption_context; + +static void async_resumption_on_get(h2o_iovec_t session_data, void *_accept_data) +{ + struct st_h2o_accept_data_t *accept_data = _accept_data; + accept_data->async_resumption_get_req = NULL; + h2o_socket_ssl_resume_server_handshake(accept_data->sock, session_data); +} + +static void async_resumption_get(h2o_socket_t *sock, h2o_iovec_t session_id) +{ + struct st_h2o_accept_data_t *data = sock->data; + + data->async_resumption_get_req = + h2o_memcached_get(async_resumption_context.memc, data->ctx->libmemcached_receiver, session_id, async_resumption_on_get, + data, H2O_MEMCACHED_ENCODE_KEY | H2O_MEMCACHED_ENCODE_VALUE); +} + +static void async_resumption_new(h2o_iovec_t session_id, h2o_iovec_t session_data) +{ + h2o_memcached_set(async_resumption_context.memc, session_id, session_data, + (uint32_t)time(NULL) + async_resumption_context.expiration, + H2O_MEMCACHED_ENCODE_KEY | H2O_MEMCACHED_ENCODE_VALUE); +} + +void h2o_accept_setup_async_ssl_resumption(h2o_memcached_context_t *memc, unsigned expiration) +{ + async_resumption_context.memc = memc; + async_resumption_context.expiration = expiration; + h2o_socket_ssl_async_resumption_init(async_resumption_get, async_resumption_new); +} + +void on_accept_timeout(h2o_timeout_entry_t *entry) +{ + /* TODO log */ + struct st_h2o_accept_data_t *data = H2O_STRUCT_FROM_MEMBER(struct st_h2o_accept_data_t, timeout, entry); + if (data->async_resumption_get_req != NULL) { + h2o_memcached_cancel_get(async_resumption_context.memc, data->async_resumption_get_req); + data->async_resumption_get_req = NULL; + } + h2o_socket_t *sock = data->sock; + free_accept_data(data); + h2o_socket_close(sock); +} + +static void on_ssl_handshake_complete(h2o_socket_t *sock, const char *err) +{ + struct st_h2o_accept_data_t *data = sock->data; + sock->data = NULL; + + if (err != NULL) { + h2o_socket_close(sock); + goto Exit; + } + + h2o_iovec_t proto = h2o_socket_ssl_get_selected_protocol(sock); + const h2o_iovec_t *ident; + for (ident = h2o_http2_alpn_protocols; ident->len != 0; ++ident) { + if (proto.len == ident->len && memcmp(proto.base, ident->base, proto.len) == 0) { + /* connect as http2 */ + h2o_http2_accept(data->ctx, sock, data->connected_at); + goto Exit; + } + } + /* connect as http1 */ + h2o_http1_accept(data->ctx, sock, data->connected_at); + +Exit: + free_accept_data(data); +} + +static ssize_t parse_proxy_line(char *src, size_t len, struct sockaddr *sa, socklen_t *salen) +{ +#define CHECK_EOF() \ + if (p == end) \ + return -2 +#define EXPECT_CHAR(ch) \ + do { \ + CHECK_EOF(); \ + if (*p++ != ch) \ + return -1; \ + } while (0) +#define SKIP_TO_WS() \ + do { \ + do { \ + CHECK_EOF(); \ + } while (*p++ != ' '); \ + --p; \ + } while (0) + + char *p = src, *end = p + len; + void *addr; + in_port_t *port; + + /* "PROXY "*/ + EXPECT_CHAR('P'); + EXPECT_CHAR('R'); + EXPECT_CHAR('O'); + EXPECT_CHAR('X'); + EXPECT_CHAR('Y'); + EXPECT_CHAR(' '); + + /* "TCP[46] " */ + CHECK_EOF(); + if (*p++ != 'T') { + *salen = 0; /* indicate that no data has been obtained */ + goto SkipToEOL; + } + EXPECT_CHAR('C'); + EXPECT_CHAR('P'); + CHECK_EOF(); + switch (*p++) { + case '4': + *salen = sizeof(struct sockaddr_in); + memset(sa, 0, sizeof(struct sockaddr_in)); + sa->sa_family = AF_INET; + addr = &((struct sockaddr_in *)sa)->sin_addr; + port = &((struct sockaddr_in *)sa)->sin_port; + break; + case '6': + *salen = sizeof(struct sockaddr_in6); + memset(sa, 0, sizeof(struct sockaddr_in6)); + sa->sa_family = AF_INET6; + addr = &((struct sockaddr_in6 *)sa)->sin6_addr; + port = &((struct sockaddr_in6 *)sa)->sin6_port; + break; + default: + return -1; + } + EXPECT_CHAR(' '); + + /* parse peer address */ + char *addr_start = p; + SKIP_TO_WS(); + *p = '\0'; + if (inet_pton(sa->sa_family, addr_start, addr) != 1) + return -1; + *p++ = ' '; + + /* skip local address */ + SKIP_TO_WS(); + ++p; + + /* parse peer port */ + char *port_start = p; + SKIP_TO_WS(); + *p = '\0'; + unsigned short usval; + if (sscanf(port_start, "%hu", &usval) != 1) + return -1; + *port = htons(usval); + *p++ = ' '; + +SkipToEOL: + do { + CHECK_EOF(); + } while (*p++ != '\r'); + CHECK_EOF(); + if (*p++ != '\n') + return -2; + return p - src; + +#undef CHECK_EOF +#undef EXPECT_CHAR +#undef SKIP_TO_WS +} + +static void on_read_proxy_line(h2o_socket_t *sock, const char *err) +{ + struct st_h2o_accept_data_t *data = sock->data; + + if (err != NULL) { + free_accept_data(data); + h2o_socket_close(sock); + return; + } + + struct sockaddr_storage addr; + socklen_t addrlen; + ssize_t r = parse_proxy_line(sock->input->bytes, sock->input->size, (void *)&addr, &addrlen); + switch (r) { + case -1: /* error, just pass the input to the next handler */ + break; + case -2: /* incomplete */ + return; + default: + h2o_buffer_consume(&sock->input, r); + if (addrlen != 0) + h2o_socket_setpeername(sock, (void *)&addr, addrlen); + break; + } + + if (data->ctx->ssl_ctx != NULL) { + h2o_socket_ssl_handshake(sock, data->ctx->ssl_ctx, NULL, on_ssl_handshake_complete); + } else { + struct st_h2o_accept_data_t *data = sock->data; + sock->data = NULL; + h2o_http1_accept(data->ctx, sock, data->connected_at); + free_accept_data(data); + } +} + +void h2o_accept(h2o_accept_ctx_t *ctx, h2o_socket_t *sock) +{ + struct timeval connected_at = *h2o_get_timestamp(ctx->ctx, NULL, NULL); + + if (ctx->expect_proxy_line || ctx->ssl_ctx != NULL) { + create_accept_data(ctx, sock, connected_at); + if (ctx->expect_proxy_line) { + h2o_socket_read_start(sock, on_read_proxy_line); + } else { + h2o_socket_ssl_handshake(sock, ctx->ssl_ctx, NULL, on_ssl_handshake_complete); + } + } else { + h2o_http1_accept(ctx, sock, connected_at); + } +} + +size_t h2o_stringify_protocol_version(char *dst, int version) +{ + char *p = dst; + + if (version < 0x200) { + assert(version <= 0x109); +#define PREFIX "HTTP/1." + memcpy(p, PREFIX, sizeof(PREFIX) - 1); + p += sizeof(PREFIX) - 1; +#undef PREFIX + *p++ = '0' + (version & 0xff); + } else { +#define PROTO "HTTP/2" + memcpy(p, PROTO, sizeof(PROTO) - 1); + p += sizeof(PROTO) - 1; +#undef PROTO + } + + *p = '\0'; + return p - dst; +} + +size_t h2o_stringify_proxy_header(h2o_conn_t *conn, char *buf) +{ + struct sockaddr_storage ss; + socklen_t sslen; + size_t strlen; + uint16_t peerport; + char *dst = buf; + + if ((sslen = conn->callbacks->get_peername(conn, (void *)&ss)) == 0) + goto Unknown; + switch (ss.ss_family) { + case AF_INET: + memcpy(dst, "PROXY TCP4 ", 11); + dst += 11; + break; + case AF_INET6: + memcpy(dst, "PROXY TCP6 ", 11); + dst += 11; + break; + default: + goto Unknown; + } + if ((strlen = h2o_socket_getnumerichost((void *)&ss, sslen, dst)) == SIZE_MAX) + goto Unknown; + dst += strlen; + *dst++ = ' '; + + peerport = h2o_socket_getport((void *)&ss); + + if ((sslen = conn->callbacks->get_sockname(conn, (void *)&ss)) == 0) + goto Unknown; + if ((strlen = h2o_socket_getnumerichost((void *)&ss, sslen, dst)) == SIZE_MAX) + goto Unknown; + dst += strlen; + *dst++ = ' '; + + dst += sprintf(dst, "%" PRIu16 " %" PRIu16 "\r\n", peerport, (uint16_t)h2o_socket_getport((void *)&ss)); + + return dst - buf; + +Unknown: + memcpy(buf, "PROXY UNKNOWN\r\n", 15); + return 15; +} + +static void push_one_path(h2o_mem_pool_t *pool, h2o_iovec_vector_t *paths_to_push, 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_url_t parsed, resolved; + + /* check the authority, and extract absolute path */ + if (h2o_url_parse_relative(url.base, url.len, &parsed) != 0) + return; + + /* fast-path for abspath form */ + if (base_scheme == NULL && parsed.scheme == NULL && parsed.authority.base == NULL && url.len != 0 && url.base[0] == '/') { + h2o_vector_reserve(pool, paths_to_push, paths_to_push->size + 1); + paths_to_push->entries[paths_to_push->size++] = h2o_strdup(pool, url.base, url.len); + return; + } + + /* check scheme and authority if given URL contains either of the two, or if base is specified */ + h2o_url_t base = {input_scheme, input_authority, {NULL}, base_path, 65535}; + if (base_scheme != NULL) { + base.scheme = base_scheme; + base.authority = *base_authority; + } + h2o_url_resolve(pool, &base, &parsed, &resolved); + if (input_scheme != resolved.scheme) + return; + if (!h2o_lcstris(input_authority.base, input_authority.len, resolved.authority.base, resolved.authority.len)) + return; + + h2o_vector_reserve(pool, paths_to_push, paths_to_push->size + 1); + paths_to_push->entries[paths_to_push->size++] = resolved.path; +} + +h2o_iovec_vector_t h2o_extract_push_path_from_link_header(h2o_mem_pool_t *pool, const char *value, size_t value_len, + 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 *filtered_value) +{ + h2o_iovec_vector_t paths_to_push = {NULL}; + h2o_iovec_t iter = h2o_iovec_init(value, value_len), token_value; + const char *token; + size_t token_len; + *filtered_value = h2o_iovec_init(NULL, 0); + +#define PUSH_FILTERED_VALUE(s, e) \ + do { \ + if (filtered_value->len != 0) { \ + memcpy(filtered_value->base + filtered_value->len, ", ", 2); \ + filtered_value->len += 2; \ + } \ + memcpy(filtered_value->base + filtered_value->len, (s), (e) - (s)); \ + filtered_value->len += (e) - (s); \ + } while (0) + + /* extract URL values from Link: ; rel=preload */ + do { + if ((token = h2o_next_token(&iter, ';', &token_len, NULL)) == NULL) + break; + /* first element should be */ + if (!(token_len >= 2 && token[0] == '<' && token[token_len - 1] == '>')) + break; + h2o_iovec_t url_with_brackets = h2o_iovec_init(token, token_len); + /* find rel=preload */ + int preload = 0, nopush = 0, push_only = 0; + while ((token = h2o_next_token(&iter, ';', &token_len, &token_value)) != NULL && + !h2o_memis(token, token_len, H2O_STRLIT(","))) { + if (h2o_lcstris(token, token_len, H2O_STRLIT("rel")) && + h2o_lcstris(token_value.base, token_value.len, H2O_STRLIT("preload"))) { + preload++; + } else if (h2o_lcstris(token, token_len, H2O_STRLIT("nopush"))) { + nopush++; + } else if (h2o_lcstris(token, token_len, H2O_STRLIT("x-http2-push-only"))) { + push_only++; + } + } + /* push the path */ + if (!nopush && preload) + push_one_path(pool, &paths_to_push, h2o_iovec_init(url_with_brackets.base + 1, url_with_brackets.len - 2), base_path, + input_scheme, input_authority, base_scheme, base_authority); + /* store the elements that needs to be preserved to filtered_value */ + if (push_only) { + if (filtered_value->base == NULL) { + /* the max. size of filtered_value would be x2 in the worst case, when "," is converted to ", " */ + filtered_value->base = h2o_mem_alloc_pool(pool, value_len * 2); + const char *prev_comma = h2o_memrchr(value, ',', url_with_brackets.base - value); + if (prev_comma != NULL) + PUSH_FILTERED_VALUE(value, prev_comma); + } + } else if (filtered_value->base != NULL) { + PUSH_FILTERED_VALUE(url_with_brackets.base, token != NULL ? token : value + value_len); + } + } while (token != NULL); + + if (filtered_value->base != NULL) { + if (token != NULL) + PUSH_FILTERED_VALUE(token, value + value_len); + } else { + *filtered_value = h2o_iovec_init(value, value_len); + } + + return paths_to_push; + +#undef PUSH_FILTERED_VALUE +} + +int h2o_get_compressible_types(const h2o_headers_t *headers) +{ + size_t header_index; + int compressible_types = 0; + + for (header_index = 0; header_index != headers->size; ++header_index) { + const h2o_header_t *header = headers->entries + header_index; + if (H2O_UNLIKELY(header->name == &H2O_TOKEN_ACCEPT_ENCODING->buf)) { + h2o_iovec_t iter = h2o_iovec_init(header->value.base, header->value.len); + const char *token = NULL; + size_t token_len = 0; + while ((token = h2o_next_token(&iter, ',', &token_len, NULL)) != NULL) { + if (h2o_lcstris(token, token_len, H2O_STRLIT("gzip"))) + compressible_types |= H2O_COMPRESSIBLE_GZIP; + else if (h2o_lcstris(token, token_len, H2O_STRLIT("br"))) + compressible_types |= H2O_COMPRESSIBLE_BROTLI; + } + } + } + + return compressible_types; +} + +h2o_iovec_t h2o_build_destination(h2o_req_t *req, const char *prefix, size_t prefix_len, int use_path_normalized) +{ + h2o_iovec_t parts[4]; + size_t num_parts = 0; + int conf_ends_with_slash = req->pathconf->path.base[req->pathconf->path.len - 1] == '/'; + int prefix_ends_with_slash = prefix[prefix_len - 1] == '/'; + + /* destination starts with given prefix */ + parts[num_parts++] = h2o_iovec_init(prefix, prefix_len); + + /* make adjustments depending on the trailing slashes */ + if (conf_ends_with_slash != prefix_ends_with_slash) { + if (conf_ends_with_slash) { + parts[num_parts++] = h2o_iovec_init(H2O_STRLIT("/")); + } else { + if (req->path_normalized.len != req->pathconf->path.len) + parts[num_parts - 1].len -= 1; + } + } + + /* append suffix path and query */ + + if (use_path_normalized) { + parts[num_parts++] = h2o_uri_escape(&req->pool, req->path_normalized.base + req->pathconf->path.len, + req->path_normalized.len - req->pathconf->path.len, "/@:"); + if (req->query_at != SIZE_MAX) { + parts[num_parts++] = h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at); + } + } else { + if (req->path.len > 1) { + /* + * When proxying, we want to modify the input URL as little + * as possible. We use norm_indexes to find the start of + * the path we want to forward. + */ + size_t next_unnormalized; + if (req->norm_indexes && req->pathconf->path.len > 1) { + next_unnormalized = req->norm_indexes[req->pathconf->path.len - 1]; + } else { + next_unnormalized = req->pathconf->path.len; + } + + /* + * Special case: the input path didn't have any '/' including the first, + * so the first character is actually found at '0' + */ + if (req->path.base[0] != '/' && next_unnormalized == 1) { + next_unnormalized = 0; + } + parts[num_parts++] = (h2o_iovec_t){req->path.base + next_unnormalized, req->path.len - next_unnormalized}; + } + } + + return h2o_concat_list(&req->pool, parts, num_parts); +} + +/* h2-14 and h2-16 are kept for backwards compatibility, as they are often used */ +#define ALPN_ENTRY(s) \ + { \ + H2O_STRLIT(s) \ + } +#define ALPN_PROTOCOLS_CORE ALPN_ENTRY("h2"), ALPN_ENTRY("h2-16"), ALPN_ENTRY("h2-14") +#define NPN_PROTOCOLS_CORE \ + "\x02" \ + "h2" \ + "\x05" \ + "h2-16" \ + "\x05" \ + "h2-14" + +static const h2o_iovec_t http2_alpn_protocols[] = {ALPN_PROTOCOLS_CORE, {NULL}}; +const h2o_iovec_t *h2o_http2_alpn_protocols = http2_alpn_protocols; + +static const h2o_iovec_t alpn_protocols[] = {ALPN_PROTOCOLS_CORE, {H2O_STRLIT("http/1.1")}, {NULL}}; +const h2o_iovec_t *h2o_alpn_protocols = alpn_protocols; + +const char *h2o_http2_npn_protocols = NPN_PROTOCOLS_CORE; +const char *h2o_npn_protocols = NPN_PROTOCOLS_CORE "\x08" + "http/1.1"; + +uint64_t h2o_connection_id = 0; -- cgit v1.2.3