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_vector_t paths_to_push = {}; h2o_iovec_t iter = h2o_iovec_init(value, value_len), token_value; const char *token; size_t token_len; /* extract URL values from Link: </pushed.css>; rel=preload */ do { if ((token = h2o_next_token(&iter, ';', &token_len, NULL)) == NULL) break; /* first element should be <URL> */ if (!(token_len >= 2 && token[0] == '<' && token[token_len - 1] == '>')) break; h2o_iovec_t url = h2o_iovec_init(token + 1, token_len - 2); /* find rel=preload */ int preload = 0, nopush = 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++; } } if (!nopush && preload) push_one_path(pool, &paths_to_push, &url, base_path, input_scheme, input_authority, base_scheme, base_authority); } while (token != NULL); return paths_to_push; }
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; }
h2o_iovec_t h2o_extract_push_path_from_link_header(h2o_mem_pool_t *pool, const char *value, size_t value_len, const h2o_url_scheme_t *base_scheme, h2o_iovec_t *base_authority, h2o_iovec_t *base_path) { h2o_iovec_t url; h2o_url_t parsed, resolved; { /* extract URL value from: Link: </pushed.css>; rel=preload */ h2o_iovec_t iter = h2o_iovec_init(value, value_len), token_value; const char *token; size_t token_len; /* first element should be <URL> */ if ((token = h2o_next_token(&iter, ';', &token_len, NULL)) == NULL) goto None; if (!(token_len >= 2 && token[0] == '<' && token[token_len - 1] == '>')) goto None; url = h2o_iovec_init(token + 1, token_len - 2); /* find rel=preload */ while ((token = h2o_next_token(&iter, ';', &token_len, &token_value)) != NULL) { if (h2o_lcstris(token, token_len, H2O_STRLIT("rel")) && h2o_lcstris(token_value.base, token_value.len, H2O_STRLIT("preload"))) break; } if (token == NULL) goto None; } /* check the authority, and extract absolute path */ if (h2o_url_parse_relative(url.base, url.len, &parsed) != 0) goto None; /* return the URL found in Link header, if it is an absolute path-only URL */ if (parsed.scheme == NULL && parsed.authority.base == NULL && url.len != 0 && url.base[0] == '/') return h2o_strdup(pool, url.base, url.len); /* check scheme and authority if given URL contains either of the two */ h2o_url_t base = {base_scheme, *base_authority, {}, *base_path, 65535}; h2o_url_resolve(pool, &base, &parsed, &resolved); if (base.scheme != resolved.scheme) goto None; if (parsed.authority.base != NULL && !h2o_lcstris(base.authority.base, base.authority.len, resolved.authority.base, resolved.authority.len)) goto None; return resolved.path; None: return (h2o_iovec_t){}; }
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, {}, 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; }
int h2o_is_websocket_handshake(h2o_req_t *req, const char **ws_client_key) { ssize_t key_header_index; *ws_client_key = NULL; /* method */ if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("GET"))) { /* ok */ } else { return 0; } /* upgrade header */ if (req->upgrade.base != NULL && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("websocket"))) { /* ok */ } else { return 0; } /* sec-websocket-key header */ if ((key_header_index = h2o_find_header_by_str(&req->headers, H2O_STRLIT("sec-websocket-key"), -1)) != -1) { if (req->headers.entries[key_header_index].value.len != 24) { return -1; } } else { return 0; } *ws_client_key = req->headers.entries[key_header_index].value.base; return 0; }
static int check_header(h2o_res_t *res, const h2o_token_t *header_name, const char *expected) { size_t index = h2o_find_header(&res->headers, header_name, SIZE_MAX); if (index == SIZE_MAX) return 0; return h2o_lcstris(res->headers.entries[index].value.base, res->headers.entries[index].value.len, expected, strlen(expected)); }
static int create_entity_reader(struct st_h2o_http1_conn_t *conn, const struct phr_header *entity_header) { /* strlen("content-length") is unequal to sizeof("transfer-encoding"), and thus checking the length only is sufficient */ if (entity_header->name_len == sizeof("transfer-encoding") - 1) { /* transfer-encoding */ if (!h2o_lcstris(entity_header->value, entity_header->value_len, H2O_STRLIT("chunked"))) { entity_read_send_error(conn, 400, "Invalid Request", "unknown transfer-encoding"); return -1; } return create_chunked_entity_reader(conn); } else { /* content-length */ size_t content_length = h2o_strtosize(entity_header->value, entity_header->value_len); if (content_length == SIZE_MAX) { entity_read_send_error(conn, 400, "Invalid Request", "broken content-length header"); return -1; } if (content_length > conn->super.ctx->globalconf->max_request_entity_size) { entity_read_send_error(conn, 413, "Request Entity Too Large", "request entity is too large"); return -1; } return create_content_length_entity_reader(conn, (size_t)content_length); } /* failed */ 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_lcstris(t->name->base, t->name->len, name, name_len)) { return cursor; } } return -1; }
static void process_request(h2o_http1_conn_t *conn) { if (conn->sock->ssl == NULL && conn->req.upgrade.base != NULL && conn->super.ctx->globalconf->http1_upgrade_to_http2 && h2o_lcstris(conn->req.upgrade.base, conn->req.upgrade.len, H2O_STRLIT("h2c-14"))) { if (h2o_http2_handle_upgrade(&conn->req) == 0) { return; } } h2o_process_request(&conn->req); }
static int flatten_request_header(h2o_mruby_context_t *handler_ctx, h2o_iovec_t name, h2o_iovec_t value, void *_ctx) { struct st_h2o_mruby_http_request_context_t *ctx = _ctx; /* ignore certain headers */ if (h2o_lcstris(name.base, name.len, H2O_STRLIT("content-length")) || h2o_lcstris(name.base, name.len, H2O_STRLIT("connection")) || h2o_lcstris(name.base, name.len, H2O_STRLIT("host"))) return 0; /* mark the existence of transfer-encoding in order to prevent us from adding content-length header */ if (h2o_lcstris(name.base, name.len, H2O_STRLIT("transfer-encoding"))) ctx->req.has_transfer_encoding = 1; h2o_buffer_reserve(&ctx->req.buf, name.len + value.len + sizeof(": \r\n") - 1); append_to_buffer(&ctx->req.buf, name.base, name.len); append_to_buffer(&ctx->req.buf, H2O_STRLIT(": ")); append_to_buffer(&ctx->req.buf, value.base, value.len); append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n")); return 0; }
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; }
static void process_request(struct st_h2o_http1_conn_t *conn) { if (conn->sock->ssl == NULL && conn->req.upgrade.base != NULL && conn->super.ctx->globalconf->http1.upgrade_to_http2 && conn->req.upgrade.len >= 3 && h2o_lcstris(conn->req.upgrade.base, 3, H2O_STRLIT("h2c")) && (conn->req.upgrade.len == 3 || (conn->req.upgrade.len == 6 && (memcmp(conn->req.upgrade.base + 3, H2O_STRLIT("-14")) == 0 || memcmp(conn->req.upgrade.base + 3, H2O_STRLIT("-16")) == 0)))) { if (h2o_http2_handle_upgrade(&conn->req, conn->super.connected_at) == 0) { return; } } h2o_process_request(&conn->req); }
void h2o_cache_digests_load_header(h2o_cache_digests_t **digests, const char *value, size_t len) { h2o_iovec_t iter = h2o_iovec_init(value, len); const char *token; size_t token_len; do { const char *gcs_base64; size_t gcs_base64_len; int reset = 0, validators = 0, complete = 0, skip = 0; h2o_iovec_t token_value; if ((gcs_base64 = h2o_next_token(&iter, ';', &gcs_base64_len, NULL)) == NULL) return; 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("reset"))) { reset = 1; } else if (h2o_lcstris(token, token_len, H2O_STRLIT("validators"))) { validators = 1; } else if (h2o_lcstris(token, token_len, H2O_STRLIT("complete"))) { complete = 1; } else { skip = 1; } } if (reset && *digests != NULL) { h2o_cache_digests_destroy(*digests); *digests = NULL; } if (skip) { /* not supported for the time being */ } else { load_digest(digests, gcs_base64, gcs_base64_len, validators, complete); } } while (token != NULL); }
static int test_location_match(h2o_proxy_location_t *location, h2o_iovec_t scheme, h2o_iovec_t host, uint16_t port, h2o_iovec_t path) { if (! h2o_memis(scheme.base, scheme.len, H2O_STRLIT("http"))) return 0; if (! h2o_lcstris(host.base, host.len, location->host.base, location->host.len)) return 0; if (port != location->port) return 0; if (path.len < location->path.len) return 0; if (memcmp(path.base, location->path.base, location->path.len) != 0) return 0; return 1; }
static struct rp_generator_t *proxy_send_prepare(h2o_req_t *req, int keepalive) { 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->up_req.bufs[0] = build_request(req, keepalive, self->is_websocket_handshake); 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; }
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_lcstris(loc_parsed.host.base, loc_parsed.host.len, match->host.base, match->host.len)) 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){}; }
static void test_request(h2o_iovec_t first_req, h2o_iovec_t second_req, h2o_iovec_t third_req) { h2o_hpack_header_table_t header_table; h2o_req_t req; h2o_iovec_t in; int r, allow_psuedo; memset(&header_table, 0, sizeof(header_table)); header_table.hpack_capacity = 4096; memset(&req, 0, sizeof(req)); h2o_mem_init_pool(&req.pool); allow_psuedo = 1; in = first_req; r = h2o_hpack_parse_headers(&req, &header_table, &allow_psuedo, (const uint8_t*)in.base, in.len); ok(r == 0); ok(allow_psuedo == 1); ok(req.authority.len == 15); ok(memcmp(req.authority.base, H2O_STRLIT("www.example.com")) == 0); ok(req.method.len == 3); ok(memcmp(req.method.base, H2O_STRLIT("GET")) == 0); ok(req.path.len == 1); ok(memcmp(req.path.base, H2O_STRLIT("/")) == 0); ok(req.scheme.len == 4); ok(memcmp(req.scheme.base, H2O_STRLIT("http")) == 0); ok(req.headers.size == 0); h2o_mem_clear_pool(&req.pool); memset(&req, 0, sizeof(req)); h2o_mem_init_pool(&req.pool); allow_psuedo = 1; in = second_req; r = h2o_hpack_parse_headers(&req, &header_table, &allow_psuedo, (const uint8_t*)in.base, in.len); ok(r == 0); ok(allow_psuedo == 0); ok(req.authority.len == 15); ok(memcmp(req.authority.base, H2O_STRLIT("www.example.com")) == 0); ok(req.method.len == 3); ok(memcmp(req.method.base, H2O_STRLIT("GET")) == 0); ok(req.path.len == 1); ok(memcmp(req.path.base, H2O_STRLIT("/")) == 0); ok(req.scheme.len == 4); ok(memcmp(req.scheme.base, H2O_STRLIT("http")) == 0); ok(req.headers.size == 1); ok(h2o_lcstris(req.headers.entries[0].name->base, req.headers.entries[0].name->len, H2O_STRLIT("cache-control"))); ok(h2o_lcstris(req.headers.entries[0].value.base, req.headers.entries[0].value.len, H2O_STRLIT("no-cache"))); h2o_mem_clear_pool(&req.pool); memset(&req, 0, sizeof(req)); h2o_mem_init_pool(&req.pool); allow_psuedo = 1; in = third_req; r = h2o_hpack_parse_headers(&req, &header_table, &allow_psuedo, (const uint8_t*)in.base, in.len); ok(r == 0); ok(allow_psuedo == 0); ok(req.authority.len == 15); ok(memcmp(req.authority.base, H2O_STRLIT("www.example.com")) == 0); ok(req.method.len == 3); ok(memcmp(req.method.base, H2O_STRLIT("GET")) == 0); ok(req.path.len == 11); ok(memcmp(req.path.base, H2O_STRLIT("/index.html")) == 0); ok(req.scheme.len == 5); ok(memcmp(req.scheme.base, H2O_STRLIT("https")) == 0); ok(req.headers.size == 1); ok(h2o_lcstris(req.headers.entries[0].name->base, req.headers.entries[0].name->len, H2O_STRLIT("custom-key"))); ok(h2o_lcstris(req.headers.entries[0].value.base, req.headers.entries[0].value.len, H2O_STRLIT("custom-value"))); h2o_hpack_dispose_header_table(&header_table); h2o_mem_clear_pool(&req.pool); }
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 void handle_incoming_request(struct st_h2o_http1_conn_t *conn) { size_t inreqlen = conn->sock->input->size < H2O_MAX_REQLEN ? conn->sock->input->size : H2O_MAX_REQLEN; int reqlen, minor_version; struct phr_header headers[H2O_MAX_HEADERS]; size_t num_headers = H2O_MAX_HEADERS; ssize_t entity_body_header_index; h2o_iovec_t expect; /* need to set request_begin_at here for keep-alive connection */ if (conn->req.timestamps.request_begin_at.tv_sec == 0) conn->req.timestamps.request_begin_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); reqlen = phr_parse_request(conn->sock->input->bytes, inreqlen, (const char **)&conn->req.input.method.base, &conn->req.input.method.len, (const char **)&conn->req.input.path.base, &conn->req.input.path.len, &minor_version, headers, &num_headers, conn->_prevreqlen); conn->_prevreqlen = inreqlen; switch (reqlen) { default: // parse complete conn->_reqsize = reqlen; if ((entity_body_header_index = fixup_request(conn, headers, num_headers, minor_version, &expect)) != -1) { conn->req.timestamps.request_body_begin_at = *h2o_get_timestamp(conn->super.ctx, NULL, NULL); if (expect.base != NULL) { if (!h2o_lcstris(expect.base, expect.len, H2O_STRLIT("100-continue"))) { set_timeout(conn, NULL, NULL); h2o_socket_read_stop(conn->sock); h2o_send_error(&conn->req, 417, "Expectation Failed", "unknown expectation", H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); return; } static const h2o_iovec_t res = {H2O_STRLIT("HTTP/1.1 100 Continue\r\n\r\n")}; h2o_socket_write(conn->sock, (void *)&res, 1, on_continue_sent); } if (create_entity_reader(conn, headers + entity_body_header_index) != 0) { return; } if (expect.base != NULL) { /* processing of the incoming entity is postponed until the 100 response is sent */ h2o_socket_read_stop(conn->sock); return; } conn->_req_entity_reader->handle_incoming_entity(conn); } else { set_timeout(conn, NULL, NULL); h2o_socket_read_stop(conn->sock); process_request(conn); } return; case -2: // incomplete if (inreqlen == H2O_MAX_REQLEN) { // request is too long (TODO notify) close_connection(conn, 1); } return; case -1: // error /* upgrade to HTTP/2 if the request starts with: PRI * HTTP/2 */ if (conn->super.ctx->globalconf->http1.upgrade_to_http2) { /* should check up to the first octet that phr_parse_request returns an error */ static const h2o_iovec_t HTTP2_SIG = {H2O_STRLIT("PRI * HTTP/2")}; if (conn->sock->input->size >= HTTP2_SIG.len && memcmp(conn->sock->input->bytes, HTTP2_SIG.base, HTTP2_SIG.len) == 0) { h2o_accept_ctx_t accept_ctx = {conn->super.ctx, conn->super.hosts}; h2o_socket_t *sock = conn->sock; struct timeval connected_at = conn->super.connected_at; /* destruct the connection after detatching the socket */ conn->sock = NULL; close_connection(conn, 1); /* and accept as http2 connection */ h2o_http2_accept(&accept_ctx, sock, connected_at); return; } } close_connection(conn, 1); return; } }
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; }