diff --git a/src/http2.cc b/src/http2.cc index e978a58f..acac147a 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -132,6 +132,108 @@ std::string get_status_string(unsigned int status_code) { } } +const char *stringify_status(unsigned int status_code) { + switch (status_code) { + case 100: + return "100"; + case 101: + return "101"; + case 200: + return "200"; + case 201: + return "201"; + case 202: + return "202"; + case 203: + return "203"; + case 204: + return "204"; + case 205: + return "205"; + case 206: + return "206"; + case 300: + return "300"; + case 301: + return "301"; + case 302: + return "302"; + case 303: + return "303"; + case 304: + return "304"; + case 305: + return "305"; + // case 306: return "306"; + case 307: + return "307"; + case 308: + return "308"; + case 400: + return "400"; + case 401: + return "401"; + case 402: + return "402"; + case 403: + return "403"; + case 404: + return "404"; + case 405: + return "405"; + case 406: + return "406"; + case 407: + return "407"; + case 408: + return "408"; + case 409: + return "409"; + case 410: + return "410"; + case 411: + return "411"; + case 412: + return "412"; + case 413: + return "413"; + case 414: + return "414"; + case 415: + return "415"; + case 416: + return "416"; + case 417: + return "417"; + case 421: + return "421"; + case 426: + return "426"; + case 428: + return "428"; + case 429: + return "429"; + case 431: + return "431"; + case 500: + return "500"; + case 501: + return "501"; + case 502: + return "502"; + case 503: + return "503"; + case 504: + return "504"; + case 505: + return "505"; + case 511: + return "511"; + default: + return nullptr; + } +} + void capitalize(DefaultMemchunks *buf, const std::string &s) { buf->append(util::upcase(s[0])); for (size_t i = 1; i < s.size(); ++i) { @@ -207,17 +309,34 @@ bool non_empty_value(const Headers::value_type *nv) { return nv && !nv->value.empty(); } -nghttp2_nv make_nv(const std::string &name, const std::string &value, - bool no_index) { +namespace { +nghttp2_nv make_nv_internal(const std::string &name, const std::string &value, + bool no_index, uint8_t nv_flags) { uint8_t flags; - flags = no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE; + flags = + nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE); return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), value.size(), flags}; } +} // namespace -void copy_headers_to_nva(std::vector &nva, const Headers &headers) { +nghttp2_nv make_nv(const std::string &name, const std::string &value, + bool no_index) { + return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE); +} + +nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool no_index) { + return make_nv_internal(name, value, no_index, + NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE); +} + +namespace { +void copy_headers_to_nva_internal(std::vector &nva, + const Headers &headers, uint8_t nv_flags) { for (auto &kv : headers) { if (kv.name.empty() || kv.name[0] == ':') { continue; @@ -238,9 +357,20 @@ void copy_headers_to_nva(std::vector &nva, const Headers &headers) { case HD_X_FORWARDED_PROTO: continue; } - nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); + nva.push_back(make_nv_internal(kv.name, kv.value, kv.no_index, nv_flags)); } } +} // namespace + +void copy_headers_to_nva(std::vector &nva, const Headers &headers) { + copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE); +} + +void copy_headers_to_nva_nocopy(std::vector &nva, + const Headers &headers) { + copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE); +} void build_http1_headers_from_headers(DefaultMemchunks *buf, const Headers &headers) { diff --git a/src/http2.h b/src/http2.h index e313f79f..2669dd49 100644 --- a/src/http2.h +++ b/src/http2.h @@ -70,6 +70,10 @@ namespace http2 { std::string get_status_string(unsigned int status_code); +// Returns string version of |status_code|. This function can handle +// only predefined status code. Otherwise, returns nullptr. +const char *stringify_status(unsigned int status_code); + void capitalize(DefaultMemchunks *buf, const std::string &s); // Returns true if |value| is LWS @@ -110,18 +114,27 @@ bool non_empty_value(const Headers::value_type *nv); nghttp2_nv make_nv(const std::string &name, const std::string &value, bool no_index = false); +nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool no_index = false); + // Create nghttp2_nv from string literal |name| and |value|. template constexpr nghttp2_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) { return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1, - NGHTTP2_NV_FLAG_NONE}; + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; } // Create nghttp2_nv from string literal |name| and c-string |value|. template nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) { return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), - NGHTTP2_NV_FLAG_NONE}; + NGHTTP2_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp2_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; } // Create nghttp2_nv from string literal |name| and std::string @@ -129,7 +142,13 @@ nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) { template nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) { return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), - NGHTTP2_NV_FLAG_NONE}; + NGHTTP2_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; } // Appends headers in |headers| to |nv|. |headers| must be indexed @@ -138,6 +157,11 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) { // which require special handling (i.e. via), are not copied. void copy_headers_to_nva(std::vector &nva, const Headers &headers); +// Just like copy_headers_to_nva(), but this adds +// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE. +void copy_headers_to_nva_nocopy(std::vector &nva, + const Headers &headers); + // Appends HTTP/1.1 style header lines to |buf| from headers in // |headers|. |headers| must be indexed before this call (its // element's token field is assigned). Certain headers, which diff --git a/src/http2_test.cc b/src/http2_test.cc index ae4325b8..8f3aab35 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -151,10 +151,26 @@ auto headers = } // namespace void test_http2_copy_headers_to_nva(void) { + auto ans = std::vector{0, 1, 4, 5, 6, 7, 12}; std::vector nva; + + http2::copy_headers_to_nva_nocopy(nva, headers); + CU_ASSERT(7 == nva.size()); + for (size_t i = 0; i < ans.size(); ++i) { + check_nv(headers[ans[i]], &nva[i]); + + if (ans[i] == 0) { + CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE | + NGHTTP2_NV_FLAG_NO_INDEX) == nva[i].flags); + } else { + CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE) == nva[i].flags); + } + } + + nva.clear(); http2::copy_headers_to_nva(nva, headers); CU_ASSERT(7 == nva.size()); - auto ans = std::vector{0, 1, 4, 5, 6, 7, 12}; for (size_t i = 0; i < ans.size(); ++i) { check_nv(headers[ans[i]], &nva[i]); diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 247b2368..a2e35abf 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -288,7 +288,12 @@ std::pair parse_header(const char *optarg) { for (; *value == '\t' || *value == ' '; ++value) ; - return {std::string(optarg, colon), std::string(value, strlen(value))}; + auto p = std::make_pair(std::string(optarg, colon), + std::string(value, strlen(value))); + util::inp_strlower(p.first); + util::inp_strlower(p.second); + + return p; } template diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index f4d5498c..65f3e970 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -277,8 +277,33 @@ void Downstream::assemble_request_cookie() { } } -Headers Downstream::crumble_request_cookie() { - Headers cookie_hdrs; +size_t Downstream::count_crumble_request_cookie() { + size_t n = 0; + for (auto &kv : request_headers_) { + if (kv.name.size() != 6 || kv.name[5] != 'e' || + !util::streq_l("cooki", kv.name.c_str(), 5)) { + continue; + } + size_t last = kv.value.size(); + + for (size_t j = 0; j < last;) { + j = kv.value.find_first_not_of("\t ;", j); + if (j == std::string::npos) { + break; + } + + j = kv.value.find(';', j); + if (j == std::string::npos) { + j = last; + } + + ++n; + } + } + return n; +} + +void Downstream::crumble_request_cookie(std::vector &nva) { for (auto &kv : request_headers_) { if (kv.name.size() != 6 || kv.name[5] != 'e' || !util::streq_l("cooki", kv.name.c_str(), 5)) { @@ -298,11 +323,13 @@ Headers Downstream::crumble_request_cookie() { j = last; } - cookie_hdrs.push_back( - Header("cookie", kv.value.substr(first, j - first), kv.no_index)); + nva.push_back({(uint8_t *)"cookie", (uint8_t *)kv.value.c_str() + first, + str_size("cookie"), j - first, + (uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE | + (kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))}); } } - return cookie_hdrs; } const std::string &Downstream::get_assembled_request_cookie() const { diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 6960a236..ad659637 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -97,9 +97,11 @@ public: // downstream request API const Headers &get_request_headers() const; Headers &get_request_headers(); - // Crumbles (split cookie by ";") in request_headers_ and returns - // them. Headers::no_index is inherited. - Headers crumble_request_cookie(); + // Count number of crumbled cookies + size_t count_crumble_request_cookie(); + // Crumbles (split cookie by ";") in request_headers_ and adds them + // in |nva|. Headers::no_index is inherited. + void crumble_request_cookie(std::vector &nva); void assemble_request_cookie(); const std::string &get_assembled_request_cookie() const; // Lower the request header field names and indexes request headers. diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc index 2e7a6960..871cac70 100644 --- a/src/shrpx_downstream_test.cc +++ b/src/shrpx_downstream_test.cc @@ -108,13 +108,29 @@ void test_downstream_crumble_request_cookie(void) { reinterpret_cast(val), strlen(val), true, -1); d.add_request_header("cookie", ";delta"); d.add_request_header("cookie", "echo"); - auto cookies = d.crumble_request_cookie(); + + std::vector nva; + d.crumble_request_cookie(nva); + + auto num_cookies = d.count_crumble_request_cookie(); + + CU_ASSERT(5 == nva.size()); + CU_ASSERT(5 == num_cookies); + + Headers cookies; + std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies), + [](const nghttp2_nv &nv) { + return Header(std::string(nv.name, nv.name + nv.namelen), + std::string(nv.value, nv.value + nv.valuelen), + nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); + }); Headers ans = {{"cookie", "alpha"}, {"cookie", "bravo"}, {"cookie", "charlie"}, {"cookie", "delta"}, {"cookie", "echo"}}; + CU_ASSERT(ans == cookies); CU_ASSERT(cookies[0].no_index); CU_ASSERT(cookies[1].no_index); diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index 86e200f6..8a9a216f 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -205,6 +205,8 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, if (!trailers.empty()) { std::vector nva; nva.reserve(trailers.size()); + // We cannot use nocopy version, since nva may be touched after + // Downstream object is deleted. http2::copy_headers_to_nva(nva, trailers); if (!nva.empty()) { rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); @@ -273,17 +275,13 @@ int Http2DownstreamConnection::push_request_headers() { authority = req_authority.c_str(); } - if (!authority) { - authority = downstream_hostport; - } - downstream_->set_request_downstream_host(authority); auto nheader = downstream_->get_request_headers().size(); - Headers cookies; + size_t num_cookies = 0; if (!get_config()->http2_no_cookie_crumbling) { - cookies = downstream_->crumble_request_cookie(); + num_cookies = downstream_->count_crumble_request_cookie(); } // 8 means: @@ -296,29 +294,30 @@ int Http2DownstreamConnection::push_request_headers() { // 7. x-forwarded-proto (optional) // 8. te (optional) auto nva = std::vector(); - nva.reserve(nheader + 8 + cookies.size() + + nva.reserve(nheader + 8 + num_cookies + get_config()->add_request_headers.size()); - nva.push_back(http2::make_nv_lc(":method", http2::to_method_string(method))); + nva.push_back( + http2::make_nv_lc_nocopy(":method", http2::to_method_string(method))); auto &scheme = downstream_->get_request_http2_scheme(); - nva.push_back(http2::make_nv_lc(":authority", authority)); + nva.push_back(http2::make_nv_lc_nocopy(":authority", authority)); if (method != HTTP_CONNECT) { assert(!scheme.empty()); - nva.push_back(http2::make_nv_ls(":scheme", scheme)); + nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme)); auto &path = downstream_->get_request_path(); if (method == HTTP_OPTIONS && path.empty()) { nva.push_back(http2::make_nv_ll(":path", "*")); } else { - nva.push_back(http2::make_nv_ls(":path", path)); + nva.push_back(http2::make_nv_ls_nocopy(":path", path)); } } - http2::copy_headers_to_nva(nva, downstream_->get_request_headers()); + http2::copy_headers_to_nva_nocopy(nva, downstream_->get_request_headers()); bool chunked_encoding = false; auto transfer_encoding = @@ -328,8 +327,8 @@ int Http2DownstreamConnection::push_request_headers() { chunked_encoding = true; } - for (auto &nv : cookies) { - nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index)); + if (!get_config()->http2_no_cookie_crumbling) { + downstream_->crumble_request_cookie(nva); } std::string xff_value; @@ -343,20 +342,20 @@ int Http2DownstreamConnection::push_request_headers() { downstream_->get_upstream()->get_client_handler()->get_ipaddr(); nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value)); } else if (xff && !get_config()->strip_incoming_x_forwarded_for) { - nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value)); + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value)); } if (!get_config()->http2_proxy && !get_config()->client_proxy && downstream_->get_request_method() != HTTP_CONNECT) { // We use same protocol with :scheme header field - nva.push_back(http2::make_nv_ls("x-forwarded-proto", scheme)); + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", scheme)); } std::string via_value; auto via = downstream_->get_request_header(http2::HD_VIA); if (get_config()->no_via) { if (via) { - nva.push_back(http2::make_nv_ls("via", (*via).value)); + nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); } } else { if (via) { @@ -377,7 +376,7 @@ int Http2DownstreamConnection::push_request_headers() { } for (auto &p : get_config()->add_request_headers) { - nva.push_back(http2::make_nv(p.first, p.second)); + nva.push_back(http2::make_nv_nocopy(p.first, p.second)); } if (LOG_ENABLED(INFO)) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 529f4b81..97d297c3 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -1259,7 +1259,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session, if (!trailers.empty()) { std::vector nva; nva.reserve(trailers.size()); - http2::copy_headers_to_nva(nva, trailers); + http2::copy_headers_to_nva_nocopy(nva, trailers); if (!nva.empty()) { rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); @@ -1295,13 +1295,20 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body, data_prd_ptr = &data_prd; } - auto status_code_str = util::utos(downstream->get_response_http_status()); auto &headers = downstream->get_response_headers(); auto nva = std::vector(); // 2 for :status and server nva.reserve(2 + headers.size()); - nva.push_back(http2::make_nv_ls(":status", status_code_str)); + std::string status_code_str; + auto response_status_const = + http2::stringify_status(downstream->get_response_http_status()); + if (response_status_const) { + nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const)); + } else { + status_code_str = util::utos(downstream->get_response_http_status()); + nva.push_back(http2::make_nv_ls(":status", status_code_str)); + } for (auto &kv : headers) { if (kv.name.empty() || kv.name[0] == ':') { @@ -1316,11 +1323,12 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body, case http2::HD_UPGRADE: continue; } - nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index)); } if (!downstream->get_response_header(http2::HD_SERVER)) { - nva.push_back(http2::make_nv_lc("server", get_config()->server_name)); + nva.push_back( + http2::make_nv_lc_nocopy("server", get_config()->server_name)); } rv = nghttp2_submit_response(session_, downstream->get_stream_id(), @@ -1356,14 +1364,20 @@ int Http2Upstream::error_reply(Downstream *downstream, auto lgconf = log_config(); lgconf->update_tstamp(std::chrono::system_clock::now()); + auto response_status_const = http2::stringify_status(status_code); auto content_length = util::utos(html.size()); - auto status_code_str = util::utos(status_code); - auto nva = - make_array(http2::make_nv_ls(":status", status_code_str), - http2::make_nv_ll("content-type", "text/html; charset=UTF-8"), - http2::make_nv_lc("server", get_config()->server_name), - http2::make_nv_ls("content-length", content_length), - http2::make_nv_ls("date", lgconf->time_http_str)); + + std::string status_code_str; + + auto nva = make_array( + response_status_const + ? http2::make_nv_lc_nocopy(":status", response_status_const) + : http2::make_nv_ls(":status", + (status_code_str = util::utos(status_code))), + http2::make_nv_ll("content-type", "text/html; charset=UTF-8"), + http2::make_nv_lc_nocopy("server", get_config()->server_name), + http2::make_nv_ls("content-length", content_length), + http2::make_nv_ls("date", lgconf->time_http_str)); rv = nghttp2_submit_response(session_, downstream->get_stream_id(), nva.data(), nva.size(), &data_prd); @@ -1445,10 +1459,18 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { // field. nva.reserve(nheader + 4 + get_config()->add_response_headers.size()); std::string via_value; - auto response_status = util::utos(downstream->get_response_http_status()); - nva.push_back(http2::make_nv_ls(":status", response_status)); + std::string response_status; - http2::copy_headers_to_nva(nva, downstream->get_response_headers()); + auto response_status_const = + http2::stringify_status(downstream->get_response_http_status()); + if (response_status_const) { + nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const)); + } else { + response_status = util::utos(downstream->get_response_http_status()); + nva.push_back(http2::make_nv_ls(":status", response_status)); + } + + http2::copy_headers_to_nva_nocopy(nva, downstream->get_response_headers()); if (downstream->get_non_final_response()) { if (LOG_ENABLED(INFO)) { @@ -1470,18 +1492,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { } if (!get_config()->http2_proxy && !get_config()->client_proxy) { - nva.push_back(http2::make_nv_lc("server", get_config()->server_name)); + nva.push_back( + http2::make_nv_lc_nocopy("server", get_config()->server_name)); } else { auto server = downstream->get_response_header(http2::HD_SERVER); if (server) { - nva.push_back(http2::make_nv_ls("server", (*server).value)); + nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value)); } } auto via = downstream->get_response_header(http2::HD_VIA); if (get_config()->no_via) { if (via) { - nva.push_back(http2::make_nv_ls("via", (*via).value)); + nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); } } else { if (via) { @@ -1494,7 +1517,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { } for (auto &p : get_config()->add_response_headers) { - nva.push_back(http2::make_nv(p.first, p.second)); + nva.push_back(http2::make_nv_nocopy(p.first, p.second)); } if (downstream->get_stream_id() % 2 == 0) { @@ -1797,7 +1820,7 @@ int Http2Upstream::submit_push_promise(const std::string &scheme, case http2::HD_CACHE_CONTROL: case http2::HD_HOST: case http2::HD_USER_AGENT: - nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index)); break; } }