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_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; }