From 1f3b96e233e0bba6a17d8f34bee7414578862cf8 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 28 Aug 2013 00:09:46 +0900 Subject: [PATCH] nghttpx: Rewrite header handling --- lib/includes/nghttp2/nghttp2.h | 10 ++ lib/nghttp2_frame.c | 5 + src/.gitignore | 4 + src/Makefile.am | 24 ++-- src/shrpx-unittest.cc | 28 +++- src/shrpx_downstream.cc | 53 +++++++ src/shrpx_downstream.h | 18 +++ src/shrpx_downstream_test.cc | 113 +++++++++++++++ src/shrpx_downstream_test.h | 37 +++++ src/shrpx_http.cc | 182 ++++++++++++++++++++++++ src/shrpx_http.h | 42 ++++++ src/shrpx_http2_upstream.cc | 159 +++++++-------------- src/shrpx_http_downstream_connection.cc | 83 ++++------- src/shrpx_http_test.cc | 166 +++++++++++++++++++++ src/shrpx_http_test.h | 39 +++++ src/shrpx_https_upstream.cc | 44 ++---- src/shrpx_spdy_downstream_connection.cc | 93 ++++++------ src/shrpx_spdy_session.cc | 57 ++++---- src/shrpx_spdy_upstream.cc | 2 +- src/util.cc | 27 ++++ src/util.h | 7 + src/util_test.cc | 74 ++++++++++ src/util_test.h | 35 +++++ 23 files changed, 1023 insertions(+), 279 deletions(-) create mode 100644 src/shrpx_downstream_test.cc create mode 100644 src/shrpx_downstream_test.h create mode 100644 src/shrpx_http_test.cc create mode 100644 src/shrpx_http_test.h create mode 100644 src/util_test.cc create mode 100644 src/util_test.h diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 5e870c3b..2dcfc186 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1766,6 +1766,16 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t window_size_increment); +/** + * @function + * + * Compares lhs->name with lhs->namelen bytes and rhs->name with + * rhs->namelen bytes. Returns negative integer if lhs->name is found + * to be less than rhs->name; or returns positive integer if lhs->name + * is found to be greater than rhs->name; or returns 0 otherwise. + */ +int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs); + /** * @function * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 82e1d4dd..2507cc45 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -766,6 +766,11 @@ static int nghttp2_nv_name_compar(const void *lhs, const void *rhs) } } +int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs) +{ + return nghttp2_nv_name_compar(lhs, rhs); +} + void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen) { qsort(nva, nvlen, sizeof(nghttp2_nv), nghttp2_nv_name_compar); diff --git a/src/.gitignore b/src/.gitignore index 1ecb4a52..c2a7e874 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,7 @@ nghttp nghttpd nghttpx +nghttpx-unittest +nghttpx-unittest.log +nghttpx-unittest.trs +test-suite.log diff --git a/src/Makefile.am b/src/Makefile.am index d718dcdc..18b3ee85 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -91,16 +91,18 @@ endif # HAVE_SPDYLAY nghttpx_SOURCES = ${NGHTTPX_SRCS} shrpx.cc shrpx.h -# if HAVE_CUNIT -# check_PROGRAMS += shrpx-unittest -# shrpx_unittest_SOURCES = shrpx-unittest.cc \ -# shrpx_ssl_test.cc shrpx_ssl_test.h\ -# ${SHRPX_SRCS} -# shrpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\ -# -DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\" -# shrpx_unittest_LDFLAGS = -static @OPENSSL_LIBS@ @LIBEVENT_OPENSSL_LIBS@\ -# @SRC_LIBS@ @CUNIT_LIBS@ @TESTS_LIBS@ -# TESTS += shrpx-unittest -# endif # HAVE_CUNIT +if HAVE_CUNIT +check_PROGRAMS += nghttpx-unittest +nghttpx_unittest_SOURCES = shrpx-unittest.cc \ + shrpx_ssl_test.cc shrpx_ssl_test.h \ + shrpx_http_test.cc shrpx_http_test.h \ + shrpx_downstream_test.cc shrpx_downstream_test.h \ + util_test.cc util_test.h \ + ${NGHTTPX_SRCS} +nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\ + -DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\" +nghttpx_unittest_LDFLAGS = -static ${AM_LDFLAGS} @CUNIT_LIBS@ @TESTS_LIBS@ +TESTS += nghttpx-unittest +endif # HAVE_CUNIT endif # ENABLE_SRC diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 0d4231ad..8d78f23e 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -29,6 +29,9 @@ #include /* include test cases' include files here */ #include "shrpx_ssl_test.h" +#include "shrpx_http_test.h" +#include "shrpx_downstream_test.h" +#include "util_test.h" static int init_suite1(void) { @@ -65,7 +68,30 @@ int main(int argc, char* argv[]) if(!CU_add_test(pSuite, "ssl_create_lookup_tree", shrpx::test_shrpx_ssl_create_lookup_tree) || !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file", - shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file)) { + shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) || + !CU_add_test(pSuite, "http_check_http2_headers", + shrpx::test_http_check_http2_headers) || + !CU_add_test(pSuite, "http_get_unique_header", + shrpx::test_http_get_unique_header) || + !CU_add_test(pSuite, "http_get_header", + shrpx::test_http_get_header) || + !CU_add_test(pSuite, "http_value_lws", + shrpx::test_http_value_lws) || + !CU_add_test(pSuite, "http_copy_norm_headers_to_nv", + shrpx::test_http_copy_norm_headers_to_nv) || + !CU_add_test(pSuite, "http_build_http1_headers_from_norm_headers", + shrpx::test_http_build_http1_headers_from_norm_headers) || + !CU_add_test(pSuite, "downstream_normalize_request_headers", + shrpx::test_downstream_normalize_request_headers) || + !CU_add_test(pSuite, "downstream_normalize_response_headers", + shrpx::test_downstream_normalize_response_headers) || + !CU_add_test(pSuite, "downstream_get_norm_request_header", + shrpx::test_downstream_get_norm_request_header) || + !CU_add_test(pSuite, "downstream_get_norm_response_header", + shrpx::test_downstream_get_norm_response_header) || + !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || + !CU_add_test(pSuite, "util_inp_strlower", + shrpx::test_util_inp_strlower)) { CU_cleanup_registry(); return CU_get_error(); } diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index fccb3c05..c9b52933 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -157,11 +157,53 @@ void check_connection_close(bool *connection_close, } } // namespace +namespace { +auto name_less = [](const Headers::value_type& lhs, + const Headers::value_type& rhs) +{ + return lhs.first < rhs.first; +}; +} // namespace + +namespace { +void normalize_headers(Headers& headers) +{ + for(auto& kv : headers) { + util::inp_strlower(kv.first); + } + std::sort(std::begin(headers), std::end(headers), name_less); +} +} // namespace + +namespace { +Headers::const_iterator get_norm_header(const Headers& headers, + const std::string& name) +{ + auto i = std::lower_bound(std::begin(headers), std::end(headers), + std::make_pair(name, std::string()), name_less); + if(i != std::end(headers) && (*i).first == name) { + return i; + } + return std::end(headers); +} +} // namespace + const Headers& Downstream::get_request_headers() const { return request_headers_; } +void Downstream::normalize_request_headers() +{ + normalize_headers(request_headers_); +} + +Headers::const_iterator Downstream::get_norm_request_header +(const std::string& name) const +{ + return get_norm_header(request_headers_, name); +} + void Downstream::add_request_header(const std::string& name, const std::string& value) { @@ -334,6 +376,17 @@ const Headers& Downstream::get_response_headers() const return response_headers_; } +void Downstream::normalize_response_headers() +{ + normalize_headers(response_headers_); +} + +Headers::const_iterator Downstream::get_norm_response_header +(const std::string& name) const +{ + return get_norm_header(response_headers_, name); +} + void Downstream::add_response_header(const std::string& name, const std::string& value) { diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 56b6c9e1..add47649 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -84,6 +84,15 @@ public: bool http2_upgrade_request() const; // downstream request API const Headers& get_request_headers() const; + // Makes key lowercase and sort headers by name using < + void normalize_request_headers(); + // Returns iterator pointing to the request header with the name + // |name|. If multiple header have |name| as name, return first + // occurrence from the beginning. If no such header is found, + // returns std::end(get_request_headers()). This function must be + // called after calling normalize_request_headers(). + Headers::const_iterator get_norm_request_header + (const std::string& name) const; void add_request_header(const std::string& name, const std::string& value); void set_last_request_header_value(const std::string& value); @@ -120,6 +129,15 @@ public: int get_request_state() const; // downstream response API const Headers& get_response_headers() const; + // Makes key lowercase and sort headers by name using < + void normalize_response_headers(); + // Returns iterator pointing to the response header with the name + // |name|. If multiple header have |name| as name, return first + // occurrence from the beginning. If no such header is found, + // returns std::end(get_response_headers()). This function must be + // called after calling normalize_response_headers(). + Headers::const_iterator get_norm_response_header + (const std::string& name) const; void add_response_header(const std::string& name, const std::string& value); void set_last_response_header_value(const std::string& value); diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc new file mode 100644 index 00000000..ce3f2de3 --- /dev/null +++ b/src/shrpx_downstream_test.cc @@ -0,0 +1,113 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 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 "shrpx_downstream_test.h" + +#include + +#include + +#include "shrpx_downstream.h" + +namespace shrpx { + +void test_downstream_normalize_request_headers(void) +{ + Downstream d(nullptr, 0, 0); + d.add_request_header("Charlie", "0"); + d.add_request_header("Alpha", "1"); + d.add_request_header("Delta", "2"); + d.add_request_header("BravO", "3"); + d.normalize_request_headers(); + + auto ans = Headers{ + {"alpha", "1"}, + {"bravo", "3"}, + {"charlie", "0"}, + {"delta", "2"} + }; + CU_ASSERT(ans == d.get_request_headers()); +} + +void test_downstream_normalize_response_headers(void) +{ + Downstream d(nullptr, 0, 0); + d.add_response_header("Charlie", "0"); + d.add_response_header("Alpha", "1"); + d.add_response_header("Delta", "2"); + d.add_response_header("BravO", "3"); + d.normalize_response_headers(); + + auto ans = Headers{ + {"alpha", "1"}, + {"bravo", "3"}, + {"charlie", "0"}, + {"delta", "2"} + }; + CU_ASSERT(ans == d.get_response_headers()); +} + +void test_downstream_get_norm_request_header(void) +{ + Downstream d(nullptr, 0, 0); + d.add_request_header("alpha", "0"); + d.add_request_header("bravo", "1"); + d.add_request_header("bravo", "2"); + d.add_request_header("charlie", "3"); + d.add_request_header("delta", "4"); + d.add_request_header("echo", "5"); + auto i = d.get_norm_request_header("alpha"); + CU_ASSERT(std::make_pair(std::string("alpha"), std::string("0")) == *i); + i = d.get_norm_request_header("bravo"); + CU_ASSERT(std::make_pair(std::string("bravo"), std::string("1")) == *i); + i = d.get_norm_request_header("delta"); + CU_ASSERT(std::make_pair(std::string("delta"), std::string("4")) == *i); + i = d.get_norm_request_header("echo"); + CU_ASSERT(std::make_pair(std::string("echo"), std::string("5")) == *i); + i = d.get_norm_request_header("foxtrot"); + CU_ASSERT(i == std::end(d.get_request_headers())); +} + +void test_downstream_get_norm_response_header(void) +{ + Downstream d(nullptr, 0, 0); + d.add_response_header("alpha", "0"); + d.add_response_header("bravo", "1"); + d.add_response_header("bravo", "2"); + d.add_response_header("charlie", "3"); + d.add_response_header("delta", "4"); + d.add_response_header("echo", "5"); + auto i = d.get_norm_response_header("alpha"); + CU_ASSERT(std::make_pair(std::string("alpha"), std::string("0")) == *i); + i = d.get_norm_response_header("bravo"); + CU_ASSERT(std::make_pair(std::string("bravo"), std::string("1")) == *i); + i = d.get_norm_response_header("delta"); + CU_ASSERT(std::make_pair(std::string("delta"), std::string("4")) == *i); + i = d.get_norm_response_header("echo"); + CU_ASSERT(std::make_pair(std::string("echo"), std::string("5")) == *i); + i = d.get_norm_response_header("foxtrot"); + CU_ASSERT(i == std::end(d.get_response_headers())); +} + +} // namespace shrpx diff --git a/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h new file mode 100644 index 00000000..50e0b9f5 --- /dev/null +++ b/src/shrpx_downstream_test.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 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. + */ +#ifndef SHRPX_DOWNSTREAM_TEST_H +#define SHRPX_DOWNSTREAM_TEST_H + +namespace shrpx { + +void test_downstream_normalize_request_headers(void); +void test_downstream_normalize_response_headers(void); +void test_downstream_get_norm_request_header(void); +void test_downstream_get_norm_response_header(void); + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_TEST_H diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc index 87bb5b61..eefde0c2 100644 --- a/src/shrpx_http.cc +++ b/src/shrpx_http.cc @@ -188,6 +188,188 @@ bool check_http2_allowed_header(const uint8_t *name, size_t namelen) !util::strieq("upgrade", name, namelen); } +namespace { +const char *DISALLOWED_HD[] = { + "connection", + "host", + "keep-alive", + "proxy-connection", + "te", + "transfer-encoding", + "upgrade", +}; +} // namespace + +namespace { +size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]); +} // namespace + +namespace { +const char *IGN_HD[] = { + "connection", + "expect", + "host", + "http2-settings", + "keep-alive", + "proxy-connection", + "te", + "transfer-encoding", + "upgrade", + "via", + "x-forwarded-for", + "x-forwarded-proto", +}; +} // namespace + +namespace { +size_t IGN_HDLEN = sizeof(IGN_HD)/sizeof(IGN_HD[0]); +} // namespace + +namespace { +const char *HTTP1_IGN_HD[] = { + "connection", + "expect", + "http2-settings", + "keep-alive", + "proxy-connection", + "upgrade", + "via", + "x-forwarded-for", + "x-forwarded-proto", +}; +} // namespace + +namespace { +size_t HTTP1_IGN_HDLEN = sizeof(HTTP1_IGN_HD)/sizeof(HTTP1_IGN_HD[0]); +} // namespace + +namespace { +auto nv_name_less = [](const nghttp2_nv& lhs, const nghttp2_nv& rhs) +{ + return nghttp2_nv_compare_name(&lhs, &rhs) < 0; +}; +} // namespace + +bool check_http2_headers(const nghttp2_nv *nva, size_t nvlen) +{ + for(size_t i = 0; i < DISALLOWED_HDLEN; ++i) { + nghttp2_nv nv = {(uint8_t*)DISALLOWED_HD[i], nullptr, + (uint16_t)strlen(DISALLOWED_HD[i]), 0}; + if(std::binary_search(&nva[0], &nva[nvlen], nv, nv_name_less)) { + return false; + } + } + return true; +} + +const nghttp2_nv* get_unique_header(const nghttp2_nv *nva, size_t nvlen, + const char *name) +{ + size_t namelen = strlen(name); + nghttp2_nv nv = {(uint8_t*)name, nullptr, (uint16_t)namelen, 0}; + auto i = std::lower_bound(&nva[0], &nva[nvlen], nv, nv_name_less); + if(i != &nva[nvlen] && util::streq(i->name, i->namelen, + (const uint8_t*)name, namelen)) { + auto j = i + 1; + if(j == &nva[nvlen] || !util::streq(j->name, j->namelen, + (const uint8_t*)name, namelen)) { + return i; + } + } + return nullptr; +} + +const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen, + const char *name) +{ + size_t namelen = strlen(name); + nghttp2_nv nv = {(uint8_t*)name, nullptr, (uint16_t)namelen, 0}; + auto i = std::lower_bound(&nva[0], &nva[nvlen], nv, nv_name_less); + if(i != &nva[nvlen] && util::streq(i->name, i->namelen, + (const uint8_t*)name, namelen)) { + return i; + } + return nullptr; +} + +std::string name_to_str(const nghttp2_nv *nv) +{ + return std::string(reinterpret_cast(nv->name), nv->namelen); +} + +std::string value_to_str(const nghttp2_nv *nv) +{ + return std::string(reinterpret_cast(nv->value), nv->valuelen); +} + +bool value_lws(const nghttp2_nv *nv) +{ + for(size_t i = 0; i < nv->valuelen; ++i) { + switch(nv->value[i]) { + case '\t': + case ' ': + continue; + default: + return false; + } + } + return true; +} + +size_t copy_norm_headers_to_nv +(const char **nv, + const std::vector>& headers) +{ + size_t i, j, nvlen = 0; + for(i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) { + int rv = strcmp(headers[i].first.c_str(), IGN_HD[j]); + if(rv < 0) { + nv[nvlen++] = headers[i].first.c_str(); + nv[nvlen++] = headers[i].second.c_str(); + ++i; + } else if(rv > 0) { + ++j; + } else { + ++i; + } + } + for(; i < headers.size(); ++i) { + nv[nvlen++] = headers[i].first.c_str(); + nv[nvlen++] = headers[i].second.c_str(); + } + return nvlen; +} + +void build_http1_headers_from_norm_headers +(std::string& hdrs, + const std::vector>& headers) +{ + size_t i, j; + for(i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) { + int rv = strcmp(headers[i].first.c_str(), HTTP1_IGN_HD[j]); + if(rv < 0) { + hdrs += headers[i].first; + http::capitalize(hdrs, hdrs.size()-headers[i].first.size()); + hdrs += ": "; + hdrs += headers[i].second; + hdrs += "\r\n"; + ++i; + } else if(rv > 0) { + ++j; + } else { + ++i; + } + } + for(; i < headers.size(); ++i) { + hdrs += headers[i].first; + http::capitalize(hdrs, hdrs.size()-headers[i].first.size()); + hdrs += ": "; + hdrs += headers[i].second; + hdrs += "\r\n"; + } +} + } // namespace http } // namespace shrpx diff --git a/src/shrpx_http.h b/src/shrpx_http.h index b0e1fef1..3f2eff96 100644 --- a/src/shrpx_http.h +++ b/src/shrpx_http.h @@ -26,6 +26,9 @@ #define SHRPX_HTTP_H #include +#include + +#include #include "http-parser/http_parser.h" @@ -60,6 +63,45 @@ bool check_http2_allowed_header(const uint8_t *name, size_t namelen); // assuming |name| is null-terminated string. bool check_http2_allowed_header(const char *name); +// Checks that headers |nva| including |nvlen| entries do not contain +// disallowed header fields in HTTP/2.0 spec. This function returns +// true if |nva| does not contains such headers. +bool check_http2_headers(const nghttp2_nv *nva, size_t nvlen); + +// Returns the pointer to the entry in |nva| which has name |name| and +// the |name| is uinque in the |nva|. If no such entry exist, returns +// nullptr. +const nghttp2_nv* get_unique_header(const nghttp2_nv *nva, size_t nvlen, + const char *name); + +// Returns the poiter to the entry in |nva| which has name |name|. If +// more than one entries which have the name |name|, first occurrence +// in |nva| is returned. If no such entry exist, returns nullptr. +const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen, + const char *name); + +// Returns std::string version of nv->name with nv->namelen bytes. +std::string name_to_str(const nghttp2_nv *nv); +// Returns std::string version of nv->value with nv->valuelen bytes. +std::string value_to_str(const nghttp2_nv *nv); + +// Returns true if the value of |nv| includes only ' ' (0x20) or '\t'. +bool value_lws(const nghttp2_nv *nv); + +// Copies headers in |headers| to |nv|. Certain headers, including +// disallowed headers in HTTP/2.0 spec and headers which require +// special handling (i.e. via), are not copied. +size_t copy_norm_headers_to_nv +(const char **nv, + const std::vector>& headers); + +// Appends HTTP/1.1 style header lines to |hdrs| from headers in +// |headers|. Certain headers, which requires special handling +// (i.e. via), are not appended. +void build_http1_headers_from_norm_headers +(std::string& hdrs, + const std::vector>& headers); + } // namespace http } // namespace shrpx diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index ee224bcb..25a807e8 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -192,6 +192,7 @@ void on_frame_recv_callback downstream->init_response_body_buf(); auto nva = frame->headers.nva; + auto nvlen = frame->headers.nvlen; if(LOG_ENABLED(INFO)) { std::stringstream ss; @@ -208,116 +209,58 @@ void on_frame_recv_callback } // Assuming that nva is sorted by name. - const char *req_headers[] = {":host", ":method", ":path", ":scheme", - "content-length"}; - const size_t req_hdlen = sizeof(req_headers)/sizeof(req_headers[0]); - int req_hdidx[req_hdlen]; - memset(req_hdidx, -1, sizeof(req_hdidx)); - bool bad_req = false; - { - size_t i, j; - for(i = 0, j = 0; i < frame->headers.nvlen && j < req_hdlen;) { - if(!http::check_http2_allowed_header(nva[i].name, nva[i].namelen)) { - bad_req = true; - break; - } - int rv = util::strcompare(req_headers[j], nva[i].name, nva[i].namelen); - if(rv > 0) { - if(nva[i].namelen > 0 && nva[i].name[0] != ':') { - downstream->add_request_header - (std::string(reinterpret_cast(nva[i].name), - nva[i].namelen), - std::string(reinterpret_cast(nva[i].value), - nva[i].valuelen)); - } - ++i; - } else if(rv < 0) { - ++j; - } else { - if(req_hdidx[j] != -1) { - if(LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "multiple " << req_headers[j] - << " found in the request"; - } - bad_req = true; - break; - } - req_hdidx[j] = i; - ++i; - } - } - if(!bad_req) { - // Here :scheme is optional, because with CONNECT method, it - // is omitted. content-length is mandatory if END_STREAM is - // not set. - for(j = 0; j < 3; ++j) { - if(req_hdidx[j] == -1) { - bad_req = true; - break; - } - } - } - if(!bad_req && - !util::strieq("CONNECT", - nva[req_hdidx[1]].value, - nva[req_hdidx[1]].valuelen) && - (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0 && - req_hdidx[4] == -1) { - // If content-length is missing, - // Downstream::push_upload_data_chunk will fail and - // RST_STREAM will be sent. - bad_req = true; - } - if(!bad_req) { - for(; i < frame->headers.nvlen; ++i) { - if(nva[i].namelen > 0 && nva[i].name[0] != ':') { - downstream->add_request_header - (std::string(reinterpret_cast(nva[i].name), - nva[i].namelen), - std::string(reinterpret_cast(nva[i].value), - nva[i].valuelen)); - } - } - } - } - if(bad_req) { + if(!http::check_http2_headers(nva, nvlen)) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); return; } - if(req_hdidx[4] != -1) { - downstream->add_request_header - (std::string(reinterpret_cast(nva[req_hdidx[4]].name), - nva[req_hdidx[4]].namelen), - std::string(reinterpret_cast(nva[req_hdidx[4]].value), - nva[req_hdidx[4]].valuelen)); + for(size_t i = 0; i < nvlen; ++i) { + if(nva[i].namelen > 0 && nva[i].name[0] != ':') { + downstream->add_request_header(http::name_to_str(&nva[i]), + http::value_to_str(&nva[i])); + } } - std::string host(reinterpret_cast(nva[req_hdidx[0]].value), - nva[req_hdidx[0]].valuelen); - std::string method(reinterpret_cast(nva[req_hdidx[1]].value), - nva[req_hdidx[1]].valuelen); - std::string path(reinterpret_cast(nva[req_hdidx[2]].value), - nva[req_hdidx[2]].valuelen); + auto host = http::get_unique_header(nva, nvlen, ":host"); + auto path = http::get_unique_header(nva, nvlen, ":path"); + auto method = http::get_unique_header(nva, nvlen, ":method"); + auto scheme = http::get_unique_header(nva, nvlen, ":scheme"); + bool is_connect = method && + util::streq("CONNECT", method->value, method->valuelen); + if(!host || !path || !method || + http::value_lws(host) || http::value_lws(path) || + http::value_lws(method) || + (!is_connect && (!scheme || http::value_lws(scheme)))) { + upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + return; + } + if(!is_connect && + (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + auto content_length = http::get_header(nva, nvlen, "content-length"); + if(!content_length || http::value_lws(content_length)) { + // If content-length is missing, + // Downstream::push_upload_data_chunk will fail and + upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + return; + } + } - downstream->set_request_method(method); + downstream->set_request_method(http::value_to_str(method)); // SpdyDownstreamConnection examines request path to find // scheme. We construct abs URI for spdy_bridge mode as well as // spdy_proxy mode. if((get_config()->spdy_proxy || get_config()->spdy_bridge) && - req_hdidx[3] != -1 && path[0] == '/') { - std::string reqpath(reinterpret_cast(nva[req_hdidx[3]].value), - nva[req_hdidx[3]].valuelen); + scheme && path->value[0] == '/') { + std::string reqpath(http::value_to_str(scheme)); reqpath += "://"; - reqpath += host; - reqpath += path; + reqpath += http::value_to_str(host); + reqpath += http::value_to_str(path); downstream->set_request_path(reqpath); } else { - downstream->set_request_path(path); + downstream->set_request_path(http::value_to_str(path)); } - - downstream->add_request_header("host", host); + downstream->add_request_header("host", http::value_to_str(host)); downstream->check_upgrade_request(); auto dconn = upstream->get_client_handler()->get_downstream_connection(); @@ -908,6 +851,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) if(LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response header completed"; } + downstream->normalize_response_headers(); + auto end_headers = std::end(downstream->get_response_headers()); size_t nheader = downstream->get_response_headers().size(); // 4 means :status and possible via header field. const char **nv = new const char*[nheader * 2 + 4 + 1]; @@ -917,20 +862,18 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) std::to_string(downstream->get_response_http_status()); nv[hdidx++] = ":status"; nv[hdidx++] = response_status.c_str(); - for(Headers::const_iterator i = downstream->get_response_headers().begin(); - i != downstream->get_response_headers().end(); ++i) { - if(!http::check_http2_allowed_header((*i).first.c_str())) { - // These are ignored - } else if(!get_config()->no_via && - util::strieq((*i).first.c_str(), "via")) { - via_value = (*i).second; - } else { - nv[hdidx++] = (*i).first.c_str(); - nv[hdidx++] = (*i).second.c_str(); + + hdidx += http::copy_norm_headers_to_nv(&nv[hdidx], + downstream->get_response_headers()); + auto via = downstream->get_norm_response_header("via"); + if(get_config()->no_via) { + if(via != end_headers) { + nv[hdidx++] = "via"; + nv[hdidx++] = (*via).second.c_str(); } - } - if(!get_config()->no_via) { - if(!via_value.empty()) { + } else { + if(via != end_headers) { + via_value = (*via).second; via_value += ", "; } via_value += http::create_via_header_value @@ -938,7 +881,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) nv[hdidx++] = "via"; nv[hdidx++] = via_value.c_str(); } - nv[hdidx++] = 0; + nv[hdidx++] = nullptr; if(LOG_ENABLED(INFO)) { std::stringstream ss; for(size_t i = 0; nv[i]; i += 2) { diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 68fcc9c3..a79175cc 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -118,69 +118,26 @@ int HttpDownstreamConnection::push_request_headers() hdrs += downstream_->get_request_path(); hdrs += " "; hdrs += "HTTP/1.1\r\n"; - std::string connection_upgrade; - std::string via_value; - std::string xff_value; - const Headers& request_headers = downstream_->get_request_headers(); - for(Headers::const_iterator i = request_headers.begin(); - i != request_headers.end(); ++i) { - if(util::strieq((*i).first.c_str(), "connection")) { - // nghttpx handles HTTP/2.0 upgrade and does not relay it to the - // downstream. - if(util::strifind((*i).second.c_str(), "upgrade") && - !util::strifind((*i).second.c_str(), "http2-settings")) { - connection_upgrade = (*i).second; - } - continue; - } else if(util::strieq((*i).first.c_str(), "upgrade")) { - // nghttpx handles HTTP/2.0 upgrade and does not relay it to the - // downstream. - if(util::strieq((*i).second.c_str(), NGHTTP2_PROTO_VERSION_ID)) { - continue; - } - } else if(util::strieq((*i).first.c_str(), "x-forwarded-proto") || - util::strieq((*i).first.c_str(), "keep-alive") || - util::strieq((*i).first.c_str(), "proxy-connection") || - util::strieq((*i).first.c_str(), "http2-settings")) { - continue; - } else if(util::strieq((*i).first.c_str(), "via")) { - if(!get_config()->no_via) { - via_value = (*i).second; - continue; - } - } else if(util::strieq((*i).first.c_str(), "x-forwarded-for")) { - xff_value = (*i).second; - continue; - } else if(util::strieq((*i).first.c_str(), "expect")) { - if(util::strifind((*i).second.c_str(), "100-continue")) { - continue; - } - } - hdrs += (*i).first; - http::capitalize(hdrs, hdrs.size()-(*i).first.size()); - hdrs += ": "; - hdrs += (*i).second; - http::sanitize_header_value(hdrs, hdrs.size()-(*i).second.size()); - hdrs += "\r\n"; - } + downstream_->normalize_request_headers(); + auto end_headers = std::end(downstream_->get_request_headers()); + http::build_http1_headers_from_norm_headers + (hdrs, downstream_->get_request_headers()); + if(downstream_->get_request_connection_close()) { hdrs += "Connection: close\r\n"; - } else if(!connection_upgrade.empty()) { - hdrs += "Connection: "; - hdrs += connection_upgrade; - hdrs += "\r\n"; } + auto xff = downstream_->get_norm_request_header("x-forwarded-for"); if(get_config()->add_x_forwarded_for) { hdrs += "X-Forwarded-For: "; - if(!xff_value.empty()) { - hdrs += xff_value; + if(xff != end_headers) { + hdrs += (*xff).second; hdrs += ", "; } hdrs += downstream_->get_upstream()->get_client_handler()->get_ipaddr(); hdrs += "\r\n"; - } else if(!xff_value.empty()) { + } else if(xff != end_headers) { hdrs += "X-Forwarded-For: "; - hdrs += xff_value; + hdrs += (*xff).second; hdrs += "\r\n"; } if(downstream_->get_request_method() != "CONNECT") { @@ -192,10 +149,24 @@ int HttpDownstreamConnection::push_request_headers() } hdrs += "\r\n"; } - if(!get_config()->no_via) { + auto expect = downstream_->get_norm_request_header("expect"); + if(expect != end_headers && + util::strifind((*expect).second.c_str(), "100-continue")) { + hdrs += "Expect: "; + hdrs += (*expect).second; + hdrs += "\r\n"; + } + auto via = downstream_->get_norm_request_header("via"); + if(get_config()->no_via) { + if(via != end_headers) { + hdrs += "Via: "; + hdrs += (*via).second; + hdrs += "\r\n"; + } + } else { hdrs += "Via: "; - if(!via_value.empty()) { - hdrs += via_value; + if(via != end_headers) { + hdrs += (*via).second; hdrs += ", "; } hdrs += http::create_via_header_value(downstream_->get_request_major(), diff --git a/src/shrpx_http_test.cc b/src/shrpx_http_test.cc new file mode 100644 index 00000000..d6f6ef73 --- /dev/null +++ b/src/shrpx_http_test.cc @@ -0,0 +1,166 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 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 "shrpx_http_test.h" + +#include + +#include + +#include "shrpx_http.h" +#include "util.h" + +using namespace nghttp2; + +#define MAKE_NV(K, V) {(uint8_t*)K, (uint8_t*)V, strlen(K), strlen(V)} + +namespace shrpx { + +void test_http_check_http2_headers(void) +{ + nghttp2_nv nv1[] = {MAKE_NV("alpha", "1"), + MAKE_NV("bravo", "2"), + MAKE_NV("upgrade", "http2")}; + CU_ASSERT(!http::check_http2_headers(nv1, 3)); + + nghttp2_nv nv2[] = {MAKE_NV("connection", "1"), + MAKE_NV("delta", "2"), + MAKE_NV("echo", "3")}; + CU_ASSERT(!http::check_http2_headers(nv2, 3)); + + nghttp2_nv nv3[] = {MAKE_NV("alpha", "1"), + MAKE_NV("bravo", "2"), + MAKE_NV("te2", "3")}; + CU_ASSERT(http::check_http2_headers(nv3, 3)); +} + +void test_http_get_unique_header(void) +{ + nghttp2_nv nv[] = {MAKE_NV("alpha", "1"), + MAKE_NV("bravo", "2"), + MAKE_NV("bravo", "3"), + MAKE_NV("charlie", "4"), + MAKE_NV("delta", "5"), + MAKE_NV("echo", "6"),}; + size_t nvlen = sizeof(nv)/sizeof(nv[0]); + const nghttp2_nv *rv; + rv = http::get_unique_header(nv, nvlen, "delta"); + CU_ASSERT(rv != nullptr); + CU_ASSERT(util::streq("delta", rv->name, rv->namelen)); + + rv = http::get_unique_header(nv, nvlen, "bravo"); + CU_ASSERT(rv == nullptr); + + rv = http::get_unique_header(nv, nvlen, "foxtrot"); + CU_ASSERT(rv == nullptr); +} + +void test_http_get_header(void) +{ + nghttp2_nv nv[] = {MAKE_NV("alpha", "1"), + MAKE_NV("bravo", "2"), + MAKE_NV("bravo", "3"), + MAKE_NV("charlie", "4"), + MAKE_NV("delta", "5"), + MAKE_NV("echo", "6"),}; + size_t nvlen = sizeof(nv)/sizeof(nv[0]); + const nghttp2_nv *rv; + rv = http::get_header(nv, nvlen, "delta"); + CU_ASSERT(rv != nullptr); + CU_ASSERT(util::streq("delta", rv->name, rv->namelen)); + + rv = http::get_header(nv, nvlen, "bravo"); + CU_ASSERT(rv != nullptr); + CU_ASSERT(util::streq("bravo", rv->name, rv->namelen)); + + rv = http::get_header(nv, nvlen, "foxtrot"); + CU_ASSERT(rv == nullptr); +} + +void test_http_value_lws(void) +{ + nghttp2_nv nv[] = {MAKE_NV("0", "alpha"), + MAKE_NV("1", " alpha"), + MAKE_NV("2", ""), + MAKE_NV("3", " "), + MAKE_NV("4", " a ")}; + CU_ASSERT(!http::value_lws(&nv[0])); + CU_ASSERT(!http::value_lws(&nv[1])); + CU_ASSERT(http::value_lws(&nv[2])); + CU_ASSERT(http::value_lws(&nv[3])); + CU_ASSERT(!http::value_lws(&nv[4])); +} + +namespace { +auto headers = std::vector> + {{"alpha", "0"}, + {"bravo", "1"}, + {"connection", "2"}, + {"connection", "3"}, + {"delta", "4"}, + {"expect", "5"}, + {"foxtrot", "6"}, + {"tango", "7"}, + {"te", "8"}, + {"te", "9"}, + {"x-forwarded-proto", "10"}, + {"x-forwarded-proto", "11"}, + {"zulu", "12"}}; +} // namespace + +void test_http_copy_norm_headers_to_nv(void) +{ + const char* nv[30]; + size_t nvlen = http::copy_norm_headers_to_nv(nv, headers); + CU_ASSERT(12 == nvlen); + CU_ASSERT(strcmp(nv[0], "alpha") == 0); + CU_ASSERT(strcmp(nv[1], "0") == 0); + CU_ASSERT(strcmp(nv[2], "bravo") == 0); + CU_ASSERT(strcmp(nv[3], "1") == 0); + CU_ASSERT(strcmp(nv[4], "delta") == 0); + CU_ASSERT(strcmp(nv[5], "4") == 0); + CU_ASSERT(strcmp(nv[6], "foxtrot") == 0); + CU_ASSERT(strcmp(nv[7], "6") == 0); + CU_ASSERT(strcmp(nv[8], "tango") == 0); + CU_ASSERT(strcmp(nv[9], "7") == 0); + CU_ASSERT(strcmp(nv[10], "zulu") == 0); + CU_ASSERT(strcmp(nv[11], "12") == 0); +} + +void test_http_build_http1_headers_from_norm_headers(void) +{ + std::string hdrs; + http::build_http1_headers_from_norm_headers(hdrs, headers); + CU_ASSERT(hdrs == + "Alpha: 0\r\n" + "Bravo: 1\r\n" + "Delta: 4\r\n" + "Foxtrot: 6\r\n" + "Tango: 7\r\n" + "Te: 8\r\n" + "Te: 9\r\n" + "Zulu: 12\r\n"); +} + +} // namespace shrpx diff --git a/src/shrpx_http_test.h b/src/shrpx_http_test.h new file mode 100644 index 00000000..9f8fa9b5 --- /dev/null +++ b/src/shrpx_http_test.h @@ -0,0 +1,39 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 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. + */ +#ifndef SHRPX_HTTP_TEST_H +#define SHRPX_HTTP_TEST_H + +namespace shrpx { + +void test_http_check_http2_headers(void); +void test_http_get_unique_header(void); +void test_http_get_header(void); +void test_http_value_lws(void); +void test_http_copy_norm_headers_to_nv(void); +void test_http_build_http1_headers_from_norm_headers(void); + +} // namespace shrpx + +#endif // SHRPX_HTTP_TEST_H diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 36793541..439488fd 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -654,8 +654,6 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) if(LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response header completed"; } - bool connection_upgrade = false; - std::string via_value; char temp[16]; snprintf(temp, sizeof(temp), "HTTP/%d.%d ", downstream->get_request_major(), @@ -663,26 +661,10 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) std::string hdrs = temp; hdrs += http::get_status_string(downstream->get_response_http_status()); hdrs += "\r\n"; - for(Headers::const_iterator i = downstream->get_response_headers().begin(); - i != downstream->get_response_headers().end(); ++i) { - if(util::strieq((*i).first.c_str(), "connection")) { - if(util::strifind((*i).second.c_str(), "upgrade")) { - connection_upgrade = true; - } - } else if(util::strieq((*i).first.c_str(), "keep-alive") || // HTTP/1.0? - util:: strieq((*i).first.c_str(), "proxy-connection")) { - // These are ignored - } else if(!get_config()->no_via && - util::strieq((*i).first.c_str(), "via")) { - via_value = (*i).second; - } else { - hdrs += (*i).first; - http::capitalize(hdrs, hdrs.size()-(*i).first.size()); - hdrs += ": "; - hdrs += (*i).second; - hdrs += "\r\n"; - } - } + downstream->normalize_response_headers(); + auto end_headers = std::end(downstream->get_response_headers()); + http::build_http1_headers_from_norm_headers + (hdrs, downstream->get_response_headers()); // We check downstream->get_response_connection_close() in case when // the Content-Length is not available. @@ -692,23 +674,27 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) downstream->get_request_minor() <= 0) { // We add this header for HTTP/1.0 or HTTP/0.9 clients hdrs += "Connection: Keep-Alive\r\n"; - } else if(connection_upgrade) { - hdrs += "Connection: upgrade\r\n"; } - } else if(!downstream->get_upgraded()) { + } else { hdrs += "Connection: close\r\n"; } - if(!get_config()->no_via) { + auto via = downstream->get_norm_response_header("via"); + if(get_config()->no_via) { + if(via != end_headers) { + hdrs += "Via: "; + hdrs += (*via).second; + hdrs += "\r\n"; + } + } else { hdrs += "Via: "; - if(!via_value.empty()) { - hdrs += via_value; + if(via != end_headers) { + hdrs += (*via).second; hdrs += ", "; } hdrs += http::create_via_header_value (downstream->get_response_major(), downstream->get_response_minor()); hdrs += "\r\n"; } - hdrs += "\r\n"; if(LOG_ENABLED(INFO)) { const char *hdrp; diff --git a/src/shrpx_spdy_downstream_connection.cc b/src/shrpx_spdy_downstream_connection.cc index 6f6f47cb..d166951d 100644 --- a/src/shrpx_spdy_downstream_connection.cc +++ b/src/shrpx_spdy_downstream_connection.cc @@ -230,6 +230,8 @@ int SpdyDownstreamConnection::push_request_headers() return 0; } size_t nheader = downstream_->get_request_headers().size(); + downstream_->normalize_request_headers(); + auto end_headers = std::end(downstream_->get_request_headers()); // 10 means :method, :scheme, :path and possible via and // x-forwarded-for header fields. We rename host header field as // :host. @@ -280,61 +282,63 @@ int SpdyDownstreamConnection::push_request_headers() nv[hdidx++] = ":method"; nv[hdidx++] = downstream_->get_request_method().c_str(); - bool chunked_encoding = false; - bool content_length = false; - for(Headers::const_iterator i = downstream_->get_request_headers().begin(); - i != downstream_->get_request_headers().end(); ++i) { - if(util::strieq((*i).first.c_str(), "transfer-encoding")) { - if(util::strieq((*i).second.c_str(), "chunked")) { - chunked_encoding = true; - } - // Ignore transfer-encoding - continue; - } else if(util::strieq((*i).first.c_str(), "x-forwarded-proto") || - util::strieq((*i).first.c_str(), "keep-alive") || - util::strieq((*i).first.c_str(), "connection") || - util::strieq((*i).first.c_str(), "proxy-connection") || - util::strieq((*i).first.c_str(), "te") || - util::strieq((*i).first.c_str(), "upgrade") || - util::strieq((*i).first.c_str(), "http2-settings")) { - // These are ignored - continue; - } else if(!get_config()->no_via && - util::strieq((*i).first.c_str(), "via")) { - via_value = (*i).second; - continue; - } else if(util::strieq((*i).first.c_str(), "x-forwarded-for")) { - xff_value = (*i).second; - continue; - } else if(util::strieq((*i).first.c_str(), "expect") && - util::strifind((*i).second.c_str(), "100-continue")) { - // Ignore - continue; - } else if(util::strieq((*i).first.c_str(), "host")) { - nv[hdidx++] = ":host"; - nv[hdidx++] = (*i).second.c_str(); - continue; - } else if(util::strieq((*i).first.c_str(), "content-length")) { - content_length = true; + hdidx += http::copy_norm_headers_to_nv(&nv[hdidx], + downstream_->get_request_headers()); + + auto host = downstream_->get_norm_request_header("host"); + if(host == end_headers) { + if(LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "host header field missing"; } - nv[hdidx++] = (*i).first.c_str(); - nv[hdidx++] = (*i).second.c_str(); + return -1; + } + nv[hdidx++] = ":host"; + nv[hdidx++] = (*host).second.c_str(); + + bool content_length = false; + if(downstream_->get_norm_request_header("content-length") != end_headers) { + content_length = true; } + auto expect = downstream_->get_norm_request_header("expect"); + if(expect != end_headers && + util::strifind((*expect).second.c_str(), "100-continue")) { + nv[hdidx++] = "expect"; + nv[hdidx++] = (*expect).second.c_str(); + } + + bool chunked_encoding = false; + auto transfer_encoding = + downstream_->get_norm_request_header("transfer-encoding"); + if(transfer_encoding != end_headers && + util::strieq((*transfer_encoding).second.c_str(), "chunked")) { + chunked_encoding = true; + } + + auto xff = downstream_->get_norm_request_header("x-forwarded-for"); if(get_config()->add_x_forwarded_for) { nv[hdidx++] = "x-forwarded-for"; - if(!xff_value.empty()) { + if(xff != end_headers) { + xff_value = (*xff).second; xff_value += ", "; } xff_value += downstream_->get_upstream()->get_client_handler()-> get_ipaddr(); nv[hdidx++] = xff_value.c_str(); - } else if(!xff_value.empty()) { + } else if(xff != end_headers) { nv[hdidx++] = "x-forwarded-for"; - nv[hdidx++] = xff_value.c_str(); + nv[hdidx++] = (*xff).second.c_str(); } - if(!get_config()->no_via) { - if(!via_value.empty()) { + + auto via = downstream_->get_norm_request_header("via"); + if(get_config()->no_via) { + if(via != end_headers) { + nv[hdidx++] = "via"; + nv[hdidx++] = (*via).second.c_str(); + } + } else { + if(via != end_headers) { + via_value = (*via).second; via_value += ", "; } via_value += http::create_via_header_value @@ -342,7 +346,8 @@ int SpdyDownstreamConnection::push_request_headers() nv[hdidx++] = "via"; nv[hdidx++] = via_value.c_str(); } - nv[hdidx++] = 0; + nv[hdidx++] = nullptr; + if(LOG_ENABLED(INFO)) { std::stringstream ss; for(size_t i = 0; nv[i]; i += 2) { diff --git a/src/shrpx_spdy_session.cc b/src/shrpx_spdy_session.cc index 61ee89e1..c768da41 100644 --- a/src/shrpx_spdy_session.cc +++ b/src/shrpx_spdy_session.cc @@ -746,35 +746,10 @@ void on_frame_recv_callback break; } auto nva = frame->headers.nva; - std::string status, content_length; - for(size_t i = 0; i < frame->headers.nvlen; ++i) { - if(!http::check_http2_allowed_header(nva[i].name, nva[i].namelen)) { - status.clear(); - break; - } - if(util::strieq(":status", nva[i].name, nva[i].namelen)) { - status.assign(reinterpret_cast(nva[i].value), - nva[i].valuelen); - auto code = strtoul(status.c_str(), nullptr, 10); - downstream->set_response_http_status(code); - } else if(nva[i].namelen > 0 && nva[i].name[0] != ':') { - if(util::strieq("content-length", nva[i].name, nva[i].namelen)) { - content_length.assign(reinterpret_cast(nva[i].value), - nva[i].valuelen); - } - downstream->add_response_header - (std::string(reinterpret_cast(nva[i].name), - nva[i].namelen), - std::string(reinterpret_cast(nva[i].value), - nva[i].valuelen)); - } - } - // Just assume it is HTTP/1.1. But we really consider to say 2.0 - // here. - downstream->set_response_major(1); - downstream->set_response_minor(1); + auto nvlen = frame->headers.nvlen; - if(status.empty()) { + // Assuming that nva is sorted by name. + if(!http::check_http2_headers(nva, nvlen)) { nghttp2_submit_rst_stream(session, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); @@ -782,7 +757,31 @@ void on_frame_recv_callback return; } - if(content_length.empty() && downstream->get_request_method() != "HEAD" && + for(size_t i = 0; i < nvlen; ++i) { + if(nva[i].namelen > 0 && nva[i].name[0] != ':') { + downstream->add_response_header(http::name_to_str(&nva[i]), + http::value_to_str(&nva[i])); + } + } + + auto status = http::get_unique_header(nva, nvlen, ":status"); + if(!status || http::value_lws(status)) { + nghttp2_submit_rst_stream(session, frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + downstream->set_response_state(Downstream::MSG_RESET); + call_downstream_readcb(spdy, downstream); + return; + } + downstream->set_response_http_status + (strtoul(http::value_to_str(status).c_str(), nullptr, 10)); + + // Just assume it is HTTP/1.1. But we really consider to say 2.0 + // here. + downstream->set_response_major(1); + downstream->set_response_minor(1); + + auto content_length = http::get_header(nva, nvlen, "content-length"); + if(!content_length && downstream->get_request_method() != "HEAD" && downstream->get_request_method() != "CONNECT") { unsigned int status; status = downstream->get_response_http_status(); diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 466560a4..a13883ef 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -184,7 +184,7 @@ void on_ctrl_recv_callback return; } // Require content-length if FIN flag is not set. - if(!util::strieq("CONNECT", method) && + if(strcmp("CONNECT", method) == 0 && (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) == 0 && !content_length) { upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); diff --git a/src/util.cc b/src/util.cc index 1caa4e66..c699a2d5 100644 --- a/src/util.cc +++ b/src/util.cc @@ -171,6 +171,24 @@ bool strieq(const char *a, const uint8_t *b, size_t bn) return !*a && b == blast; } +bool streq(const char *a, const uint8_t *b, size_t bn) +{ + if(!a || !b) { + return false; + } + const uint8_t *blast = b + bn; + for(; *a && b != blast && *a == *b; ++a, ++b); + return !*a && b == blast; +} + +bool streq(const uint8_t *a, size_t alen, const uint8_t *b, size_t blen) +{ + if(alen != blen) { + return false; + } + return memcmp(a, b, alen) == 0; +} + int strcompare(const char *a, const uint8_t *b, size_t bn) { assert(a && b); @@ -269,6 +287,15 @@ void to_base64(std::string& token68str) return; } +void inp_strlower(std::string& s) +{ + for(auto i = std::begin(s); i != std::end(s); ++i) { + if('A' <= *i && *i <= 'Z') { + *i = (*i) - 'A' + 'a'; + } + } +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index f90c097a..89734ae0 100644 --- a/src/util.h +++ b/src/util.h @@ -299,6 +299,10 @@ bool strieq(const char *a, const char *b); bool strieq(const char *a, const uint8_t *b, size_t n); +bool streq(const char *a, const uint8_t *b, size_t bn); + +bool streq(const uint8_t *a, size_t alen, const uint8_t *b, size_t blen); + bool strifind(const char *a, const char *b); char upcase(char c); @@ -344,6 +348,9 @@ inline char lowcase(char c) return tbl[static_cast(c)]; } +// Lowercase |s| in place. +void inp_strlower(std::string& s); + template std::string utos(T n) { diff --git a/src/util_test.cc b/src/util_test.cc new file mode 100644 index 00000000..f3b74812 --- /dev/null +++ b/src/util_test.cc @@ -0,0 +1,74 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 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 "util_test.h" + +#include + +#include + +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +void test_util_streq(void) +{ + CU_ASSERT(util::streq("alpha", (const uint8_t*)"alpha", 5)); + CU_ASSERT(util::streq("alpha", (const uint8_t*)"alphabravo", 5)); + CU_ASSERT(!util::streq("alpha", (const uint8_t*)"alphabravo", 6)); + CU_ASSERT(!util::streq("alphabravo", (const uint8_t*)"alpha", 5)); + CU_ASSERT(!util::streq("alpha", (const uint8_t*)"alphA", 5)); + CU_ASSERT(!util::streq("", (const uint8_t*)"a", 1)); + CU_ASSERT(util::streq("", (const uint8_t*)"", 0)); + CU_ASSERT(!util::streq("alpha", (const uint8_t*)"", 0)); + + CU_ASSERT(util::streq((const uint8_t*)"alpha", 5, + (const uint8_t*)"alpha", 5)); + CU_ASSERT(!util::streq((const uint8_t*)"alpha", 4, + (const uint8_t*)"alpha", 5)); + CU_ASSERT(!util::streq((const uint8_t*)"alpha", 5, + (const uint8_t*)"alpha", 4)); + CU_ASSERT(!util::streq((const uint8_t*)"alpha", 5, + (const uint8_t*)"alphA", 5)); + CU_ASSERT(util::streq(nullptr, 0, nullptr, 0)); +} + +void test_util_inp_strlower(void) +{ + std::string a("alPha"); + util::inp_strlower(a); + CU_ASSERT("alpha" == a); + + a = "ALPHA123BRAVO"; + util::inp_strlower(a); + CU_ASSERT("alpha123bravo" == a); + + a = ""; + util::inp_strlower(a); + CU_ASSERT("" == a); +} + +} // namespace shrpx diff --git a/src/util_test.h b/src/util_test.h new file mode 100644 index 00000000..3472b629 --- /dev/null +++ b/src/util_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 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. + */ +#ifndef UTIL_TEST_H +#define UTIL_TEST_H + +namespace shrpx { + +void test_util_streq(void); +void test_util_inp_strlower(void); + +} // namespace shrpx + +#endif // UTIL_TEST_H