nghttpx: Add --http2-altsvc option

This commit is contained in:
Tatsuhiro Tsujikawa 2021-08-26 20:59:32 +09:00
parent d88eadff13
commit 51bf79bb8c
15 changed files with 366 additions and 85 deletions

View File

@ -179,6 +179,7 @@ OPTIONS = [
"no-strip-incoming-early-data", "no-strip-incoming-early-data",
"bpf-program-file", "bpf-program-file",
"no-bpf", "no-bpf",
"http2-altsvc",
] ]
LOGVARS = [ LOGVARS = [

View File

@ -135,6 +135,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_http_create_via_header_value) || shrpx::test_shrpx_http_create_via_header_value) ||
!CU_add_test(pSuite, "http_create_affinity_cookie", !CU_add_test(pSuite, "http_create_affinity_cookie",
shrpx::test_shrpx_http_create_affinity_cookie) || shrpx::test_shrpx_http_create_affinity_cookie) ||
!CU_add_test(pSuite, "http_create_atlsvc_header_field_value",
shrpx::test_shrpx_http_create_altsvc_header_value) ||
!CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) || !CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
!CU_add_test(pSuite, "router_match_wildcard", !CU_add_test(pSuite, "router_match_wildcard",
shrpx::test_shrpx_router_match_wildcard) || shrpx::test_shrpx_router_match_wildcard) ||
@ -197,6 +199,7 @@ int main(int argc, char *argv[]) {
shrpx::test_util_extract_host) || shrpx::test_util_extract_host) ||
!CU_add_test(pSuite, "util_split_hostport", !CU_add_test(pSuite, "util_split_hostport",
shrpx::test_util_split_hostport) || shrpx::test_util_split_hostport) ||
!CU_add_test(pSuite, "util_split_str", shrpx::test_util_split_str) ||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) || !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) || !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||

View File

@ -87,6 +87,7 @@
#include "shrpx_signal.h" #include "shrpx_signal.h"
#include "shrpx_connection.h" #include "shrpx_connection.h"
#include "shrpx_log.h" #include "shrpx_log.h"
#include "shrpx_http.h"
#include "util.h" #include "util.h"
#include "app_helper.h" #include "app_helper.h"
#include "tls.h" #include "tls.h"
@ -2723,13 +2724,18 @@ HTTP:
Rewrite host and :authority header fields in default Rewrite host and :authority header fields in default
mode. When --http2-proxy is used, these headers will mode. When --http2-proxy is used, these headers will
not be altered regardless of this option. not be altered regardless of this option.
--altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]> --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
Specify protocol ID, port, host and origin of Specify protocol ID, port, host and origin of
alternative service. <HOST> and <ORIGIN> are optional. alternative service. <HOST>, <ORIGIN> and <PARAMS> are
They are advertised in alt-svc header field only in optional. Empty <HOST> and <ORIGIN> are allowed and
HTTP/1.1 frontend. This option can be used multiple they are treated as nothing is specified. They are
times to specify multiple alternative services. advertised in alt-svc header field only in HTTP/1.1
Example: --altsvc=h2,443 frontend. This option can be used multiple times to
specify multiple alternative services.
Example: --altsvc="h2,443,,,ma=3600; persist=1'
--http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
Just like --altsvc option, but this altsvc is only sent
in HTTP/2 frontend.
--add-request-header=<HEADER> --add-request-header=<HEADER>
Specify additional header field to add to request header Specify additional header field to add to request header
set. This option just appends header field and won't set. This option just appends header field and won't
@ -3183,6 +3189,16 @@ int process_options(Config *config,
config->http2.upstream.callbacks = create_http2_upstream_callbacks(); config->http2.upstream.callbacks = create_http2_upstream_callbacks();
config->http2.downstream.callbacks = create_http2_downstream_callbacks(); config->http2.downstream.callbacks = create_http2_downstream_callbacks();
if (!config->http.altsvcs.empty()) {
config->http.altsvc_header_value =
http::create_altsvc_header_value(config->balloc, config->http.altsvcs);
}
if (!config->http.http2_altsvcs.empty()) {
config->http.http2_altsvc_header_value = http::create_altsvc_header_value(
config->balloc, config->http.http2_altsvcs);
}
return 0; return 0;
} }
} // namespace } // namespace
@ -3571,6 +3587,7 @@ int main(int argc, char **argv) {
&flag, 168}, &flag, 168},
{SHRPX_OPT_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, 169}, {SHRPX_OPT_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, 169},
{SHRPX_OPT_NO_BPF.c_str(), no_argument, &flag, 170}, {SHRPX_OPT_NO_BPF.c_str(), no_argument, &flag, 170},
{SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -4382,6 +4399,10 @@ int main(int argc, char **argv) {
// --no-bpf // --no-bpf
cmdcfgs.emplace_back(SHRPX_OPT_NO_BPF, StringRef::from_lit("yes")); cmdcfgs.emplace_back(SHRPX_OPT_NO_BPF, StringRef::from_lit("yes"));
break; break;
case 171:
// --http2-altsvc
cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg});
break;
default: default:
break; break;
} }

View File

@ -372,6 +372,57 @@ int parse_int(T *dest, const StringRef &opt, const char *optarg) {
return 0; return 0;
} }
namespace {
int parse_altsvc(AltSvc &altsvc, const StringRef &opt,
const StringRef &optarg) {
// PROTOID, PORT, HOST, ORIGIN, PARAMS.
auto tokens = util::split_str(optarg, ',', 5);
if (tokens.size() < 2) {
// Requires at least protocol_id and port
LOG(ERROR) << opt << ": too few parameters: " << optarg;
return -1;
}
int port;
if (parse_uint(&port, opt, tokens[1]) != 0) {
return -1;
}
if (port < 1 ||
port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
return -1;
}
altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]);
altsvc.port = port;
altsvc.service = make_string_ref(config->balloc, tokens[1]);
if (tokens.size() > 2) {
if (!tokens[2].empty()) {
altsvc.host = make_string_ref(config->balloc, tokens[2]);
}
if (tokens.size() > 3) {
if (!tokens[3].empty()) {
altsvc.origin = make_string_ref(config->balloc, tokens[3]);
}
if (tokens.size() > 4) {
if (!tokens[4].empty()) {
altsvc.params = make_string_ref(config->balloc, tokens[4]);
}
}
}
}
return 0;
}
} // namespace
namespace { namespace {
// generated by gennghttpxfun.py // generated by gennghttpxfun.py
LogFragmentType log_var_lookup_token(const char *name, size_t namelen) { LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
@ -1863,6 +1914,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_BACKEND_IPV6; return SHRPX_OPTID_BACKEND_IPV6;
} }
break; break;
case 'c':
if (util::strieq_l("http2-altsv", name, 11)) {
return SHRPX_OPTID_HTTP2_ALTSVC;
}
break;
case 'e': case 'e':
if (util::strieq_l("host-rewrit", name, 11)) { if (util::strieq_l("host-rewrit", name, 11)) {
return SHRPX_OPTID_HOST_REWRITE; return SHRPX_OPTID_HOST_REWRITE;
@ -3186,45 +3242,10 @@ int parse_config(Config *config, int optid, const StringRef &opt,
case SHRPX_OPTID_PADDING: case SHRPX_OPTID_PADDING:
return parse_uint(&config->padding, opt, optarg); return parse_uint(&config->padding, opt, optarg);
case SHRPX_OPTID_ALTSVC: { case SHRPX_OPTID_ALTSVC: {
auto tokens = util::split_str(optarg, ',');
if (tokens.size() < 2) {
// Requires at least protocol_id and port
LOG(ERROR) << opt << ": too few parameters: " << optarg;
return -1;
}
if (tokens.size() > 4) {
// We only need protocol_id, port, host and origin
LOG(ERROR) << opt << ": too many parameters: " << optarg;
return -1;
}
int port;
if (parse_uint(&port, opt, tokens[1]) != 0) {
return -1;
}
if (port < 1 ||
port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
return -1;
}
AltSvc altsvc{}; AltSvc altsvc{};
altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]); if (parse_altsvc(altsvc, opt, optarg) != 0) {
return -1;
altsvc.port = port;
altsvc.service = make_string_ref(config->balloc, tokens[1]);
if (tokens.size() > 2) {
altsvc.host = make_string_ref(config->balloc, tokens[2]);
if (tokens.size() > 3) {
altsvc.origin = make_string_ref(config->balloc, tokens[3]);
}
} }
config->http.altsvcs.push_back(std::move(altsvc)); config->http.altsvcs.push_back(std::move(altsvc));
@ -3841,6 +3862,17 @@ int parse_config(Config *config, int optid, const StringRef &opt,
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
return 0; return 0;
case SHRPX_OPTID_HTTP2_ALTSVC: {
AltSvc altsvc{};
if (parse_altsvc(altsvc, opt, optarg) != 0) {
return -1;
}
config->http.http2_altsvcs.push_back(std::move(altsvc));
return 0;
}
case SHRPX_OPTID_CONF: case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";

View File

@ -366,6 +366,7 @@ constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA =
constexpr auto SHRPX_OPT_BPF_PROGRAM_FILE = constexpr auto SHRPX_OPT_BPF_PROGRAM_FILE =
StringRef::from_lit("bpf-program-file"); StringRef::from_lit("bpf-program-file");
constexpr auto SHRPX_OPT_NO_BPF = StringRef::from_lit("no-bpf"); constexpr auto SHRPX_OPT_NO_BPF = StringRef::from_lit("no-bpf");
constexpr auto SHRPX_OPT_HTTP2_ALTSVC = StringRef::from_lit("http2-altsvc");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -426,7 +427,7 @@ enum class ForwardedNode {
}; };
struct AltSvc { struct AltSvc {
StringRef protocol_id, host, origin, service; StringRef protocol_id, host, origin, service, params;
uint16_t port; uint16_t port;
}; };
@ -771,6 +772,11 @@ struct HttpConfig {
bool strip_incoming; bool strip_incoming;
} early_data; } early_data;
std::vector<AltSvc> altsvcs; std::vector<AltSvc> altsvcs;
// altsvcs serialized in a wire format.
StringRef altsvc_header_value;
std::vector<AltSvc> http2_altsvcs;
// http2_altsvcs serialized in a wire format.
StringRef http2_altsvc_header_value;
std::vector<ErrorPage> error_pages; std::vector<ErrorPage> error_pages;
HeaderRefs add_request_headers; HeaderRefs add_request_headers;
HeaderRefs add_response_headers; HeaderRefs add_response_headers;
@ -1161,6 +1167,7 @@ enum {
SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT, SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
SHRPX_OPTID_HEADER_FIELD_BUFFER, SHRPX_OPTID_HEADER_FIELD_BUFFER,
SHRPX_OPTID_HOST_REWRITE, SHRPX_OPTID_HOST_REWRITE,
SHRPX_OPTID_HTTP2_ALTSVC,
SHRPX_OPTID_HTTP2_BRIDGE, SHRPX_OPTID_HTTP2_BRIDGE,
SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS, SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS,
SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING, SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING,

View File

@ -220,6 +220,57 @@ bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
} }
} }
StringRef create_altsvc_header_value(BlockAllocator &balloc,
const std::vector<AltSvc> &altsvcs) {
// <PROTOID>="<HOST>:<SERVICE>"; <PARAMS>
size_t len = 0;
if (altsvcs.empty()) {
return StringRef{};
}
for (auto &altsvc : altsvcs) {
len += util::percent_encode_tokenlen(altsvc.protocol_id);
len += str_size("=\"");
len += util::quote_stringlen(altsvc.host);
len += str_size(":");
len += altsvc.service.size();
len += str_size("\"");
if (!altsvc.params.empty()) {
len += str_size("; ");
len += altsvc.params.size();
}
}
// ", " between items.
len += (altsvcs.size() - 1) * 2;
// We will write additional ", " at the end, and cut it later.
auto iov = make_byte_ref(balloc, len + 2);
auto p = iov.base;
for (auto &altsvc : altsvcs) {
p = util::percent_encode_token(p, altsvc.protocol_id);
p = util::copy_lit(p, "=\"");
p = util::quote_string(p, altsvc.host);
*p++ = ':';
p = std::copy(std::begin(altsvc.service), std::end(altsvc.service), p);
*p++ = '"';
if (!altsvc.params.empty()) {
p = util::copy_lit(p, "; ");
p = std::copy(std::begin(altsvc.params), std::end(altsvc.params), p);
}
p = util::copy_lit(p, ", ");
}
p -= 2;
*p = '\0';
assert(static_cast<size_t>(p - iov.base) == len);
return StringRef{iov.base, p};
}
} // namespace http } // namespace http
} // namespace shrpx } // namespace shrpx

View File

@ -79,6 +79,10 @@ StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name,
bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure, bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
const StringRef &scheme); const StringRef &scheme);
// Returns RFC 7838 alt-svc header field value.
StringRef create_altsvc_header_value(BlockAllocator &balloc,
const std::vector<AltSvc> &altsvcs);
} // namespace http } // namespace http
} // namespace shrpx } // namespace shrpx

View File

@ -1725,9 +1725,9 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
} }
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
// 5 means :status and possible server, via, x-http2-push, and // 6 means :status and possible server, via, x-http2-push, alt-svc,
// set-cookie (for affinity cookie) header field. // and set-cookie (for affinity cookie) header field.
nva.reserve(resp.fs.headers().size() + 5 + nva.reserve(resp.fs.headers().size() + 6 +
httpconf.add_response_headers.size()); httpconf.add_response_headers.size());
if (downstream->get_non_final_response()) { if (downstream->get_non_final_response()) {
@ -1795,6 +1795,14 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
} }
} }
if (!resp.fs.header(http2::HD_ALT_SVC)) {
// We won't change or alter alt-svc from backend for now
if (!httpconf.http2_altsvc_header_value.empty()) {
nva.push_back(http2::make_nv_ls_nocopy(
"alt-svc", httpconf.http2_altsvc_header_value));
}
}
auto via = resp.fs.header(http2::HD_VIA); auto via = resp.fs.header(http2::HD_VIA);
if (httpconf.no_via) { if (httpconf.no_via) {
if (via) { if (via) {

View File

@ -118,4 +118,40 @@ void test_shrpx_http_create_affinity_cookie(void) {
CU_ASSERT("charlie=01111111; Path=bar; Secure" == c); CU_ASSERT("charlie=01111111; Path=bar; Secure" == c);
} }
void test_shrpx_http_create_altsvc_header_value(void) {
{
BlockAllocator balloc(1024, 1024);
std::vector<AltSvc> altsvcs{
AltSvc{
.protocol_id = StringRef::from_lit("h3"),
.host = StringRef::from_lit("127.0.0.1"),
.service = StringRef::from_lit("443"),
.params = StringRef::from_lit("ma=3600"),
},
};
CU_ASSERT(R"(h3="127.0.0.1:443"; ma=3600)" ==
http::create_altsvc_header_value(balloc, altsvcs));
}
{
BlockAllocator balloc(1024, 1024);
std::vector<AltSvc> altsvcs{
AltSvc{
.protocol_id = StringRef::from_lit("h3"),
.service = StringRef::from_lit("443"),
.params = StringRef::from_lit("ma=3600"),
},
AltSvc{
.protocol_id = StringRef::from_lit("h3%"),
.host = StringRef::from_lit("\"foo\""),
.service = StringRef::from_lit("4433"),
},
};
CU_ASSERT(R"(h3=":443"; ma=3600, h3%25="\"foo\":4433")" ==
http::create_altsvc_header_value(balloc, altsvcs));
}
}
} // namespace shrpx } // namespace shrpx

View File

@ -34,6 +34,7 @@ namespace shrpx {
void test_shrpx_http_create_forwarded(void); void test_shrpx_http_create_forwarded(void);
void test_shrpx_http_create_via_header_value(void); void test_shrpx_http_create_via_header_value(void);
void test_shrpx_http_create_affinity_cookie(void); void test_shrpx_http_create_affinity_cookie(void);
void test_shrpx_http_create_altsvc_header_value(void);
} // namespace shrpx } // namespace shrpx

View File

@ -1055,18 +1055,6 @@ std::unique_ptr<Downstream> HttpsUpstream::pop_downstream() {
return std::unique_ptr<Downstream>(downstream_.release()); return std::unique_ptr<Downstream>(downstream_.release());
} }
namespace {
void write_altsvc(DefaultMemchunks *buf, BlockAllocator &balloc,
const AltSvc &altsvc) {
buf->append(util::percent_encode_token(balloc, altsvc.protocol_id));
buf->append("=\"");
buf->append(util::quote_string(balloc, altsvc.host));
buf->append(':');
buf->append(altsvc.service);
buf->append('"');
}
} // namespace
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
if (downstream->get_non_final_response()) { if (downstream->get_non_final_response()) {
@ -1225,13 +1213,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
// We won't change or alter alt-svc from backend for now // We won't change or alter alt-svc from backend for now
if (!httpconf.altsvcs.empty()) { if (!httpconf.altsvcs.empty()) {
buf->append("Alt-Svc: "); buf->append("Alt-Svc: ");
buf->append(httpconf.altsvc_header_value);
auto &altsvcs = httpconf.altsvcs;
write_altsvc(buf, downstream->get_block_allocator(), altsvcs[0]);
for (size_t i = 1; i < altsvcs.size(); ++i) {
buf->append(", ");
write_altsvc(buf, downstream->get_block_allocator(), altsvcs[i]);
}
buf->append("\r\n"); buf->append("\r\n");
} }
} }

View File

@ -167,24 +167,29 @@ bool in_attr_char(char c) {
StringRef percent_encode_token(BlockAllocator &balloc, StringRef percent_encode_token(BlockAllocator &balloc,
const StringRef &target) { const StringRef &target) {
auto iov = make_byte_ref(balloc, target.size() * 3 + 1); auto iov = make_byte_ref(balloc, target.size() * 3 + 1);
auto p = iov.base; auto p = percent_encode_token(iov.base, target);
*p = '\0';
return StringRef{iov.base, p};
}
size_t percent_encode_tokenlen(const StringRef &target) {
size_t n = 0;
for (auto first = std::begin(target); first != std::end(target); ++first) { for (auto first = std::begin(target); first != std::end(target); ++first) {
uint8_t c = *first; uint8_t c = *first;
if (c != '%' && in_token(c)) { if (c != '%' && in_token(c)) {
*p++ = c; ++n;
continue; continue;
} }
*p++ = '%'; // percent-encoded character '%ff'
*p++ = UPPER_XDIGITS[c >> 4]; n += 3;
*p++ = UPPER_XDIGITS[(c & 0x0f)];
} }
*p = '\0'; return n;
return StringRef{iov.base, p};
} }
uint32_t hex_to_uint(char c) { uint32_t hex_to_uint(char c) {
@ -208,21 +213,27 @@ StringRef quote_string(BlockAllocator &balloc, const StringRef &target) {
} }
auto iov = make_byte_ref(balloc, target.size() + cnt + 1); auto iov = make_byte_ref(balloc, target.size() + cnt + 1);
auto p = iov.base; auto p = quote_string(iov.base, target);
for (auto c : target) {
if (c == '"') {
*p++ = '\\';
*p++ = '"';
} else {
*p++ = c;
}
}
*p = '\0'; *p = '\0';
return StringRef{iov.base, p}; return StringRef{iov.base, p};
} }
size_t quote_stringlen(const StringRef &target) {
size_t n = 0;
for (auto c : target) {
if (c == '"') {
n += 2;
} else {
++n;
}
}
return n;
}
namespace { namespace {
template <typename Iterator> template <typename Iterator>
Iterator cpydig(Iterator d, uint32_t n, size_t len) { Iterator cpydig(Iterator d, uint32_t n, size_t len) {
@ -871,6 +882,42 @@ std::vector<StringRef> split_str(const StringRef &s, char delim) {
return list; return list;
} }
std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n) {
if (n == 0) {
return split_str(s, delim);
}
if (n == 1) {
return {s};
}
size_t len = 1;
auto last = std::end(s);
StringRef::const_iterator d;
for (auto first = std::begin(s);
len < n && (d = std::find(first, last, delim)) != last;
++len, first = d + 1)
;
auto list = std::vector<StringRef>(len);
len = 0;
for (auto first = std::begin(s);; ++len) {
if (len == n - 1) {
list[len] = StringRef{first, last};
break;
}
auto stop = std::find(first, last, delim);
list[len] = StringRef{first, stop};
if (stop == last) {
break;
}
first = stop + 1;
}
return list;
}
std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) { std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) {
auto sublist = split_str(s, delim); auto sublist = split_str(s, delim);
auto res = std::vector<std::string>(); auto res = std::vector<std::string>();

View File

@ -74,6 +74,8 @@ constexpr size_t NGHTTP2_MAX_UINT64_DIGITS = str_size("18446744073709551615");
namespace util { namespace util {
extern const char UPPER_XDIGITS[];
inline bool is_alpha(const char c) { inline bool is_alpha(const char c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
} }
@ -136,10 +138,52 @@ StringRef percent_decode(BlockAllocator &balloc, const StringRef &src);
// Percent encode |target| if character is not in token or '%'. // Percent encode |target| if character is not in token or '%'.
StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target); StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target);
template <typename OutputIt>
OutputIt percent_encode_token(OutputIt it, const StringRef &target) {
for (auto first = std::begin(target); first != std::end(target); ++first) {
uint8_t c = *first;
if (c != '%' && in_token(c)) {
*it++ = c;
continue;
}
*it++ = '%';
*it++ = UPPER_XDIGITS[c >> 4];
*it++ = UPPER_XDIGITS[(c & 0x0f)];
}
return it;
}
// Returns the number of bytes written by percent_encode_token with
// the same |target| parameter. The return value does not include a
// terminal NUL byte.
size_t percent_encode_tokenlen(const StringRef &target);
// Returns quotedString version of |target|. Currently, this function // Returns quotedString version of |target|. Currently, this function
// just replace '"' with '\"'. // just replace '"' with '\"'.
StringRef quote_string(BlockAllocator &balloc, const StringRef &target); StringRef quote_string(BlockAllocator &balloc, const StringRef &target);
template <typename OutputIt>
OutputIt quote_string(OutputIt it, const StringRef &target) {
for (auto c : target) {
if (c == '"') {
*it++ = '\\';
*it++ = '"';
} else {
*it++ = c;
}
}
return it;
}
// Returns the number of bytes written by quote_string with the same
// |target| parameter. The return value does not include a terminal
// NUL byte.
size_t quote_stringlen(const StringRef &target);
std::string format_hex(const unsigned char *s, size_t len); std::string format_hex(const unsigned char *s, size_t len);
template <size_t N> std::string format_hex(const unsigned char (&s)[N]) { template <size_t N> std::string format_hex(const unsigned char (&s)[N]) {
@ -443,8 +487,6 @@ template <typename T> std::string utos_funit(T n) {
return dtos(static_cast<double>(n) / (1 << b)) + u; return dtos(static_cast<double>(n) / (1 << b)) + u;
} }
extern const char UPPER_XDIGITS[];
template <typename T> std::string utox(T n) { template <typename T> std::string utox(T n) {
std::string res; std::string res;
if (n == 0) { if (n == 0) {
@ -564,6 +606,11 @@ std::vector<std::string> parse_config_str_list(const StringRef &s,
// treated as a part of substring. // treated as a part of substring.
std::vector<StringRef> split_str(const StringRef &s, char delim); std::vector<StringRef> split_str(const StringRef &s, char delim);
// Behaves like split_str, but this variant splits at most |n| - 1
// times and returns at most |n| sub-strings. If |n| is zero, it
// falls back to split_str.
std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n);
// Writes given time |tp| in Common Log format (e.g., // Writes given time |tp| in Common Log format (e.g.,
// 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer // 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer
// must be at least 27 bytes, including terminal NULL byte. Expected // must be at least 27 bytes, including terminal NULL byte. Expected

View File

@ -652,4 +652,44 @@ void test_util_split_hostport(void) {
util::split_hostport(StringRef::from_lit("[::1]80"))); util::split_hostport(StringRef::from_lit("[::1]80")));
} }
void test_util_split_str(void) {
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
util::split_str(StringRef::from_lit(""), ','));
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
util::split_str(StringRef::from_lit("alpha"), ','));
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
StringRef::from_lit("")}) ==
util::split_str(StringRef::from_lit("alpha,"), ','));
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
StringRef::from_lit("bravo")}) ==
util::split_str(StringRef::from_lit("alpha,bravo"), ','));
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
StringRef::from_lit("bravo"),
StringRef::from_lit("charlie")}) ==
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ','));
CU_ASSERT(
(std::vector<StringRef>{StringRef::from_lit("alpha"),
StringRef::from_lit("bravo"),
StringRef::from_lit("charlie")}) ==
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 0));
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
util::split_str(StringRef::from_lit(""), ',', 1));
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
util::split_str(StringRef::from_lit(""), ',', 2));
CU_ASSERT(
(std::vector<StringRef>{StringRef::from_lit("alpha"),
StringRef::from_lit("bravo,charlie")}) ==
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 2));
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
util::split_str(StringRef::from_lit("alpha"), ',', 2));
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
StringRef::from_lit("")}) ==
util::split_str(StringRef::from_lit("alpha,"), ',', 2));
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
util::split_str(StringRef::from_lit("alpha"), ',', 0));
CU_ASSERT(
std::vector<StringRef>{StringRef::from_lit("alpha,bravo,charlie")} ==
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 1));
}
} // namespace shrpx } // namespace shrpx

View File

@ -68,6 +68,7 @@ void test_util_is_hex_string(void);
void test_util_decode_hex(void); void test_util_decode_hex(void);
void test_util_extract_host(void); void test_util_extract_host(void);
void test_util_split_hostport(void); void test_util_split_hostport(void);
void test_util_split_str(void);
} // namespace shrpx } // namespace shrpx