static ssize_t fixup_request(struct st_h2o_http1_conn_t *conn, struct phr_header *headers, size_t num_headers, int minor_version, h2o_iovec_t *expect) { ssize_t entity_header_index; h2o_iovec_t connection = {NULL, 0}, host = {NULL, 0}, upgrade = {NULL, 0}; expect->base = NULL; expect->len = 0; conn->req.input.scheme = conn->sock->ssl != NULL ? &H2O_URL_SCHEME_HTTPS : &H2O_URL_SCHEME_HTTP; conn->req.version = 0x100 | (minor_version != 0); /* init headers */ entity_header_index = init_headers(&conn->req.pool, &conn->req.headers, headers, num_headers, &connection, &host, &upgrade, expect); /* copy the values to pool, since the buffer pointed by the headers may get realloced */ if (entity_header_index != -1) { size_t i; conn->req.input.method = h2o_strdup(&conn->req.pool, conn->req.input.method.base, conn->req.input.method.len); conn->req.input.path = h2o_strdup(&conn->req.pool, conn->req.input.path.base, conn->req.input.path.len); for (i = 0; i != conn->req.headers.size; ++i) { h2o_header_t *header = conn->req.headers.entries + i; if (!h2o_iovec_is_token(header->name)) { *header->name = h2o_strdup(&conn->req.pool, header->name->base, header->name->len); } header->value = h2o_strdup(&conn->req.pool, header->value.base, header->value.len); } if (host.base != NULL) host = h2o_strdup(&conn->req.pool, host.base, host.len); if (upgrade.base != NULL) upgrade = h2o_strdup(&conn->req.pool, upgrade.base, upgrade.len); } /* move host header to req->authority */ if (host.base != NULL) conn->req.input.authority = host; /* setup persistent flag (and upgrade info) */ if (connection.base != NULL) { /* TODO contains_token function can be faster */ if (h2o_contains_token(connection.base, connection.len, H2O_STRLIT("keep-alive"), ',')) { conn->req.http1_is_persistent = 1; } if (upgrade.base != NULL && h2o_contains_token(connection.base, connection.len, H2O_STRLIT("upgrade"), ',')) { conn->req.upgrade = upgrade; } } else if (conn->req.version >= 0x101) { /* defaults to keep-alive if >= HTTP/1.1 */ conn->req.http1_is_persistent = 1; } /* disable keep-alive if shutdown is requested */ if (conn->req.http1_is_persistent && conn->super.ctx->shutdown_requested) conn->req.http1_is_persistent = 0; return entity_header_index; }
static h2o_header_t *find_header(h2o_headers_t *headers, h2o_headers_command_t *cmd) { size_t index; if (h2o_iovec_is_token(cmd->name)) { index = h2o_find_header(headers, (void *)cmd->name, SIZE_MAX); } else { index = h2o_find_header_by_str(headers, cmd->name->base, cmd->name->len, SIZE_MAX); } if (index == SIZE_MAX) return NULL; return headers->entries + index; }
void h2o_rewrite_headers(h2o_mem_pool_t *pool, h2o_headers_t *headers, h2o_headers_command_t *cmd) { h2o_header_t *target; switch (cmd->cmd) { case H2O_HEADERS_CMD_ADD: goto AddHeader; case H2O_HEADERS_CMD_APPEND: if ((target = find_header(headers, cmd)) == NULL) goto AddHeader; goto AppendToken; case H2O_HEADERS_CMD_MERGE: if ((target = find_header(headers, cmd)) == NULL) goto AddHeader; if (h2o_contains_token(target->value.base, target->value.len, cmd->value.base, cmd->value.len, ',')) return; goto AppendToken; case H2O_HEADERS_CMD_SET: remove_header(headers, cmd); goto AddHeader; case H2O_HEADERS_CMD_SETIFEMPTY: if (find_header(headers, cmd) != NULL) return; goto AddHeader; case H2O_HEADERS_CMD_UNSET: remove_header(headers, cmd); return; } assert(!"FIXME"); return; AddHeader: if (h2o_iovec_is_token(cmd->name)) { h2o_add_header(pool, headers, (void *)cmd->name, NULL, cmd->value.base, cmd->value.len); } else { h2o_add_header_by_str(pool, headers, cmd->name->base, cmd->name->len, 0, NULL, cmd->value.base, cmd->value.len); } return; AppendToken: if (target->value.len != 0) { h2o_iovec_t v; v.len = target->value.len + 2 + cmd->value.len; v.base = h2o_mem_alloc_pool(pool, char, v.len); memcpy(v.base, target->value.base, target->value.len); v.base[target->value.len] = ','; v.base[target->value.len + 1] = ' '; memcpy(v.base + target->value.len + 2, cmd->value.base, cmd->value.len); target->value = v; } else {
static h2o_iovec_t build_request(h2o_req_t *req, h2o_proxy_location_t *upstream, int keepalive) { h2o_iovec_t buf; size_t bufsz; const h2o_header_t *h, * h_end; char *p; /* calc buffer length */ bufsz = sizeof(" HTTP/1.1\r\nhost: :65535\r\nconnection: keep-alive\r\ncontent-length: 18446744073709551615\r\n\r\n") + req->method.len + req->path.len - req->pathconf->path.len + upstream->path.len + upstream->host.len; for (h = req->headers.entries, h_end = h + req->headers.size; h != h_end; ++h) bufsz += h->name->len + h->value.len + 4; /* allocate */ buf.base = h2o_mem_alloc_pool(&req->pool, bufsz); /* build response */ p = buf.base; p += sprintf(p, "%.*s %.*s%.*s HTTP/1.1\r\nconnection: %s\r\n", (int)req->method.len, req->method.base, (int)upstream->path.len, upstream->path.base, (int)(req->path.len - req->pathconf->path.len), req->path.base + req->pathconf->path.len, keepalive ? "keep-alive" : "close"); if (upstream->port == 80) p += sprintf(p, "host: %.*s\r\n", (int)upstream->host.len, upstream->host.base); else p += sprintf(p, "host: %.*s:%u\r\n", (int)upstream->host.len, upstream->host.base, (unsigned)upstream->port); if (req->entity.base != NULL) { p += sprintf(p, "content-length: %zu\r\n", req->entity.len); } for (h = req->headers.entries, h_end = h + req->headers.size; h != h_end; ++h) { if (h2o_iovec_is_token(h->name) && ((h2o_token_t*)h->name)->is_connection_specific) continue; p += sprintf(p, "%.*s: %.*s\r\n", (int)h->name->len, h->name->base, (int)h->value.len, h->value.base); } *p++ = '\r'; *p++ = '\n'; /* set the length */ buf.len = p - buf.base; assert(buf.len < bufsz); return buf; }
static int add_cmd(h2o_configurator_command_t *cmd, yoml_t *node, int cmd_id, h2o_iovec_t *name, h2o_iovec_t value) { struct headers_configurator_t *self = (void *)cmd->configurator; if (h2o_iovec_is_token(name)) { const h2o_token_t *token = (void *)name; if (h2o_headers_is_prohibited_name(token)) { h2o_configurator_errprintf(cmd, node, "the named header cannot be rewritten"); return -1; } } h2o_vector_reserve(NULL, (h2o_vector_t *)self->cmds, sizeof(self->cmds->entries[0]), self->cmds->size + 1); self->cmds->entries[self->cmds->size++] = (h2o_headers_command_t){cmd_id, name, value}; return 0; }
static void remove_header(h2o_headers_t *headers, h2o_headers_command_t *cmd) { size_t src, dst = 0; for (src = 0; src != headers->size; ++src) { if (h2o_iovec_is_token(cmd->name)) { if (headers->entries[src].name == cmd->name) continue; } else { if (h2o_memis(headers->entries[src].name->base, headers->entries[src].name->len, cmd->name->base, cmd->name->len)) continue; } /* not matched */ if (dst != src) headers->entries[dst] = headers->entries[src]; ++dst; } headers->size = dst; }
static h2o_iovec_t build_request(h2o_req_t *req, int keepalive, int is_websocket_handshake) { 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 = {}, xff_buf = {}, via_buf = {}; /* 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; 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) 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) { RESERVE(sizeof("content-length: 18446744073709551615") - 1); offset += sprintf(buf.base + offset, "content-length: %zu\r\n", req->entity.len); } { 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) { 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) { via_buf = build_request_merge_headers(&req->pool, via_buf, h->value, ','); continue; } else if (token == H2O_TOKEN_X_FORWARDED_FOR) { xff_buf = build_request_merge_headers(&req->pool, xff_buf, h->value, ','); continue; } } if (h2o_lcstris(h->name->base, h->name->len, H2O_STRLIT("x-forwarded-proto"))) continue; RESERVE(h->name->len + h->value.len + 2); APPEND(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'; } 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'; 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); APPEND_STRLIT("\r\n\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 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) { 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 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) { 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; }