diff --git a/src/http2.cc b/src/http2.cc index c5b596f4..64d700f0 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -1841,6 +1841,53 @@ StringRef normalize_path(BlockAllocator &balloc, const StringRef &path, query); } +StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path, + const StringRef &query) { + // First, decode %XX for unreserved characters and ':', then do + // http2::path_join + + // We won't find %XX if length is less than 3. + if (path.size() < 3 || + std::find(std::begin(path), std::end(path), '%') == std::end(path)) { + return path_join(balloc, StringRef{}, StringRef{}, path, query); + } + + // includes last terminal NULL. + auto result = make_byte_ref(balloc, path.size() + 1); + auto p = result.base; + + auto it = std::begin(path); + for (; it + 2 < std::end(path);) { + if (*it == '%') { + if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) { + auto c = + (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2)); + if (util::in_rfc3986_unreserved_chars(c) || c == ':') { + *p++ = c; + + it += 3; + + continue; + } + *p++ = '%'; + *p++ = util::upcase(*(it + 1)); + *p++ = util::upcase(*(it + 2)); + + it += 3; + + continue; + } + } + *p++ = *it++; + } + + p = std::copy(it, std::end(path), p); + *p = '\0'; + + return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p}, + query); +} + std::string normalize_path(const StringRef &path, const StringRef &query) { BlockAllocator balloc(1024, 1024); diff --git a/src/http2.h b/src/http2.h index b0b10651..f5c07136 100644 --- a/src/http2.h +++ b/src/http2.h @@ -410,6 +410,12 @@ StringRef to_method_string(int method_token); StringRef normalize_path(BlockAllocator &balloc, const StringRef &path, const StringRef &query); +// normalize_path_colon is like normalize_path, but it additionally +// does percent-decoding %3A in order to workaround the issue that ':' +// cannot be included in backend pattern. +StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path, + const StringRef &query); + std::string normalize_path(const StringRef &path, const StringRef &query); StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src); diff --git a/src/shrpx.cc b/src/shrpx.cc index 363d564c..fa935cb6 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1877,8 +1877,11 @@ Connections: affinity is enabled. Since ";" and ":" are used as delimiter, must - not contain these characters. Since ";" has special - meaning in shell, the option value must be quoted. + not contain these characters. In order to include ":" + in , one has to specify "%3A" (which is + percent-encoded from of ":") instead. Since ";" has + special meaning in shell, the option value must be + quoted. Default: )" << DEFAULT_DOWNSTREAM_HOST << "," << DEFAULT_DOWNSTREAM_PORT << R"( diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 8f5cd507..6a097d43 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -1109,9 +1109,9 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr, *p = '\0'; pattern = StringRef{iov.base, p}; } else { - auto path = http2::normalize_path(downstreamconf.balloc, - StringRef{slash, std::end(raw_pattern)}, - StringRef{}); + auto path = http2::normalize_path_colon( + downstreamconf.balloc, StringRef{slash, std::end(raw_pattern)}, + StringRef{}); auto iov = make_byte_ref(downstreamconf.balloc, std::distance(std::begin(raw_pattern), slash) + path.size() + 1);