diff --git a/src/asio_server_serve_mux.cc b/src/asio_server_serve_mux.cc index c77a4f38..94a0a322 100644 --- a/src/asio_server_serve_mux.cc +++ b/src/asio_server_serve_mux.cc @@ -49,22 +49,15 @@ std::string create_html(int status_code) { } // namespace namespace { -request_cb redirect_handler(int status_code, std::string path) { - return [status_code, path](const request &req, const response &res) { - auto &uref = req.impl().uri(); - auto newloc = uref.scheme; - newloc += "://"; - newloc += uref.host; - newloc += path; - if (!uref.raw_query.empty()) { - newloc += "?"; - newloc += uref.raw_query; - } - auto html = create_html(status_code); +request_cb redirect_handler(int status_code, std::string uri) { + return [status_code, uri](const request &req, const response &res) { header_map h; - h.emplace("location", header_value{std::move(newloc)}); + h.emplace("location", header_value{std::move(uri)}); + std::string html; + if (req.method() == "GET") { + html = create_html(status_code); + } h.emplace("content-length", header_value{util::utos(html.size())}); - h.emplace("content-type", header_value{"text/html; charset=utf-8"}); res.write_head(status_code, std::move(h)); res.end(std::move(html)); @@ -131,7 +124,14 @@ request_cb serve_mux::handler(request_impl &req) const { auto clean_path = ::nghttp2::http2::path_join( nullptr, 0, nullptr, 0, path.c_str(), path.size(), nullptr, 0); if (clean_path != path) { - return redirect_handler(301, std::move(clean_path)); + auto new_uri = util::percent_encode_path(clean_path); + auto &uref = req.uri(); + if (!uref.raw_query.empty()) { + new_uri += "?"; + new_uri += uref.raw_query; + } + + return redirect_handler(301, std::move(new_uri)); } } auto &host = req.uri().host; diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 86d601c4..34428e07 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -126,6 +126,8 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "util_to_token68", shrpx::test_util_to_token68) || !CU_add_test(pSuite, "util_percent_encode_token", shrpx::test_util_percent_encode_token) || + !CU_add_test(pSuite, "util_percent_encode_path", + shrpx::test_util_percent_encode_path) || !CU_add_test(pSuite, "util_percent_decode", shrpx::test_util_percent_decode) || !CU_add_test(pSuite, "util_quote_string", diff --git a/src/util.cc b/src/util.cc index 199bc27a..5683ded7 100644 --- a/src/util.cc +++ b/src/util.cc @@ -70,6 +70,13 @@ bool inRFC3986UnreservedChars(const char c) { std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4]; } +bool in_rfc3986_sub_delims(const char c) { + static const char sub_delims[] = {'!', '$', '&', '\'', '(', ')', + '*', '+', ',', ';', '='}; + return std::find(std::begin(sub_delims), std::end(sub_delims), c) != + std::end(sub_delims); +} + std::string percentEncode(const unsigned char *target, size_t len) { std::string dest; for (size_t i = 0; i < len; ++i) { @@ -91,6 +98,21 @@ std::string percentEncode(const std::string &target) { target.size()); } +std::string percent_encode_path(const std::string &s) { + std::string dest; + for (auto c : s) { + if (inRFC3986UnreservedChars(c) || in_rfc3986_sub_delims(c) || c == '/') { + dest += c; + continue; + } + + dest += "%"; + dest += UPPER_XDIGITS[(c >> 4) & 0x0f]; + dest += UPPER_XDIGITS[(c & 0x0f)]; + } + return dest; +} + bool in_token(char c) { static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}; diff --git a/src/util.h b/src/util.h index c0be68ac..3f68acc3 100644 --- a/src/util.h +++ b/src/util.h @@ -162,6 +162,8 @@ bool isHexDigit(const char c); bool inRFC3986UnreservedChars(const char c); +bool in_rfc3986_sub_delims(const char c); + // Returns true if |c| is in token (HTTP-p1, Section 3.2.6) bool in_token(char c); @@ -175,6 +177,9 @@ std::string percentEncode(const unsigned char *target, size_t len); std::string percentEncode(const std::string &target); +// percent-encode path component of URI |s|. +std::string percent_encode_path(const std::string &s); + template std::string percentDecode(InputIt first, InputIt last) { std::string result; diff --git a/src/util_test.cc b/src/util_test.cc index d724d923..3e1b4028 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -139,6 +139,11 @@ void test_util_percent_encode_token(void) { CU_ASSERT("http%202" == util::percent_encode_token("http 2")); } +void test_util_percent_encode_path(void) { + CU_ASSERT("/foo1/bar%3F&/%0A" == util::percent_encode_path("/foo1/bar?&/" + "\x0a")); +} + void test_util_percent_decode(void) { { std::string s = "%66%6F%6f%62%61%72"; diff --git a/src/util_test.h b/src/util_test.h index 8edb9c6d..d8f7dd47 100644 --- a/src/util_test.h +++ b/src/util_test.h @@ -33,6 +33,7 @@ void test_util_inp_strlower(void); void test_util_to_base64(void); void test_util_to_token68(void); void test_util_percent_encode_token(void); +void test_util_percent_encode_path(void); void test_util_percent_decode(void); void test_util_quote_string(void); void test_util_utox(void);