nghttpd, nghttpx: Rework incoming header handling

This commit is contained in:
Tatsuhiro Tsujikawa 2015-01-04 23:22:39 +09:00
parent 730d47f7ad
commit 3ae44ef2f3
19 changed files with 818 additions and 899 deletions

View File

@ -5,14 +5,25 @@ HEADERS = [
':method',
':path',
':scheme',
# disallowed h1 headers
'connection',
':status',
':host', # for spdy
'expect',
'host',
'if-modified-since',
"te",
"cookie",
"http2-settings",
"server",
"via",
"x-forwarded-for",
"x-forwarded-proto",
"alt-svc",
"content-length",
"location",
# disallowed h1 headers
'connection',
'keep-alive',
'proxy-connection',
'te',
'transfer-encoding',
'upgrade'
]
@ -20,9 +31,7 @@ HEADERS = [
def to_enum_hd(k):
res = 'HD_'
for c in k.upper():
if c == ':':
continue
if c == '-':
if c == ':' or c == '-':
res += '_'
continue
res += c
@ -54,7 +63,7 @@ enum {'''
def gen_index_header():
print '''\
void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
int lookup_token(const uint8_t *name, size_t namelen) {
switch (namelen) {'''
b = build_header(HEADERS)
for size in sorted(b.keys()):
@ -70,8 +79,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
for k in headers:
print '''\
if (util::streq("{}", name, {})) {{
hdidx[{}] = idx;
return;
return {};
}}'''.format(k[:-1], size - 1, to_enum_hd(k))
print '''\
break;'''
@ -80,6 +88,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
break;'''
print '''\
}
return -1;
}'''
if __name__ == '__main__':

View File

@ -89,9 +89,12 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
namespace {
void append_nv(Stream *stream, const std::vector<nghttp2_nv> &nva) {
size_t idx = 0;
for (auto &nv : nva) {
http2::index_header(stream->hdidx, nv.name, nv.namelen, idx++);
for (size_t i = 0; i < nva.size(); ++i) {
auto &nv = nva[i];
auto token = http2::lookup_token(nv.name, nv.namelen);
if (token != -1) {
http2::index_header(stream->hdidx, token, i);
}
http2::add_header(stream->headers, nv.name, nv.namelen, nv.value,
nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
}
@ -739,7 +742,7 @@ int Http2Handler::submit_non_final_response(const std::string &status,
int Http2Handler::submit_push_promise(Stream *stream,
const std::string &push_path) {
auto authority =
http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers);
http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
if (!authority) {
authority =
@ -900,9 +903,9 @@ void prepare_redirect_response(Stream *stream, Http2Handler *hd,
const std::string &path,
const std::string &status) {
auto scheme =
http2::get_header(stream->hdidx, http2::HD_SCHEME, stream->headers);
http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers);
auto authority =
http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers);
http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
if (!authority) {
authority =
http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers);
@ -924,7 +927,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
bool allow_push = true) {
int rv;
auto reqpath =
http2::get_header(stream->hdidx, http2::HD_PATH, stream->headers)->value;
http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value;
auto ims =
get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers);
@ -1038,11 +1041,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (namelen > 0 && name[0] == ':') {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if ((!stream->headers.empty() &&
stream->headers.back().name.c_str()[0] != ':') ||
!http2::check_http2_request_pseudo_header(stream->hdidx, name,
namelen)) {
!http2::check_http2_request_pseudo_header(stream->hdidx, token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
@ -1050,8 +1054,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
}
}
http2::index_header(stream->hdidx, name, namelen, stream->headers.size());
if (!http2::http2_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
http2::index_header(stream->hdidx, token, stream->headers.size());
http2::add_header(stream->headers, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
return 0;
@ -1113,7 +1122,7 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
if (!http2::check_http2_request_headers(stream->hdidx)) {
if (!http2::http2_mandatory_request_headers_presence(stream->hdidx)) {
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
return 0;
}

View File

@ -174,138 +174,6 @@ void copy_url_component(std::string &dest, const http_parser_url *u, int field,
}
}
bool check_http2_allowed_header(const char *name) {
return check_http2_allowed_header(reinterpret_cast<const uint8_t *>(name),
strlen(name));
}
bool check_http2_allowed_header(const uint8_t *name, size_t namelen) {
return !util::strieq("connection", name, namelen) &&
!util::strieq("host", name, namelen) &&
!util::strieq("keep-alive", name, namelen) &&
!util::strieq("proxy-connection", name, namelen) &&
!util::strieq("te", name, namelen) &&
!util::strieq("transfer-encoding", name, namelen) &&
!util::strieq("upgrade", name, namelen);
}
namespace {
const char *DISALLOWED_HD[] = {
"connection", "keep-alive", "proxy-connection",
"te", "transfer-encoding", "upgrade",
};
} // namespace
namespace {
auto DISALLOWED_HDLEN = util::array_size(DISALLOWED_HD);
} // namespace
namespace {
const char *REQUEST_PSEUDO_HD[] = {
":authority", ":method", ":path", ":scheme",
};
} // namespace
namespace {
auto REQUEST_PSEUDO_HDLEN = util::array_size(REQUEST_PSEUDO_HD);
} // namespace
namespace {
const char *RESPONSE_PSEUDO_HD[] = {
":status",
};
} // namespace
namespace {
auto RESPONSE_PSEUDO_HDLEN = util::array_size(RESPONSE_PSEUDO_HD);
} // namespace
namespace {
const char *IGN_HD[] = {
"connection", "http2-settings", "keep-alive", "proxy-connection",
"server", "te", "transfer-encoding", "upgrade",
"via", "x-forwarded-for", "x-forwarded-proto",
};
} // namespace
namespace {
auto IGN_HDLEN = util::array_size(IGN_HD);
} // namespace
namespace {
const char *HTTP1_IGN_HD[] = {
"connection", "cookie", "http2-settings", "keep-alive",
"proxy-connection", "server", "upgrade", "via",
"x-forwarded-for", "x-forwarded-proto",
};
} // namespace
namespace {
auto HTTP1_IGN_HDLEN = util::array_size(HTTP1_IGN_HD);
} // namespace
bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs) {
if (lhs.name.c_str()[0] == ':') {
if (rhs.name.c_str()[0] != ':') {
return true;
}
} else if (rhs.name.c_str()[0] == ':') {
return false;
}
return lhs.name < rhs.name;
}
bool check_http2_headers(const Headers &nva) {
for (size_t i = 0; i < DISALLOWED_HDLEN; ++i) {
if (std::binary_search(std::begin(nva), std::end(nva),
Header(DISALLOWED_HD[i], ""), name_less)) {
return false;
}
}
return true;
}
bool check_http2_request_headers(const Headers &nva) {
return check_http2_headers(nva);
}
bool check_http2_response_headers(const Headers &nva) {
return check_http2_headers(nva);
}
namespace {
template <typename InputIterator>
bool check_pseudo_header(const uint8_t *name, size_t namelen,
InputIterator allowed_first,
InputIterator allowed_last) {
for (auto i = allowed_first; i != allowed_last; ++i) {
if (util::streq(*i, name, namelen)) {
return true;
}
}
return false;
}
} // namespace
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen) {
return check_pseudo_header(name, namelen, REQUEST_PSEUDO_HD,
REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN);
}
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen) {
return check_pseudo_header(name, namelen, RESPONSE_PSEUDO_HD,
RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN);
}
void normalize_headers(Headers &nva) {
for (auto &kv : nva) {
util::inp_strlower(kv.name);
}
std::stable_sort(std::begin(nva), std::end(nva), name_less);
}
Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index) {
@ -328,26 +196,14 @@ void add_header(Headers &nva, const uint8_t *name, size_t namelen,
nva.push_back(to_header(name, namelen, value, valuelen, no_index));
}
const Headers::value_type *get_unique_header(const Headers &nva,
const char *name) {
auto nv = Headers::value_type(name, "");
auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less);
if (i != std::end(nva) && (*i).name == nv.name) {
auto j = i + 1;
if (j == std::end(nva) || (*j).name != nv.name) {
return &(*i);
const Headers::value_type *get_header(const Headers &nva, const char *name) {
const Headers::value_type *res = nullptr;
for (auto &nv : nva) {
if (nv.name == name) {
res = &nv;
}
}
return nullptr;
}
const Headers::value_type *get_header(const Headers &nva, const char *name) {
auto nv = Headers::value_type(name, "");
auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less);
if (i != std::end(nva) && (*i).name == nv.name) {
return &(*i);
}
return nullptr;
return res;
}
std::string value_to_str(const Headers::value_type *nv) {
@ -371,65 +227,54 @@ nghttp2_nv make_nv(const std::string &name, const std::string &value,
value.size(), flags};
}
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
const Headers &headers) {
size_t i, j;
for (i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) {
auto &kv = headers[i];
int rv = strcmp(kv.name.c_str(), IGN_HD[j]);
if (rv < 0) {
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
}
++i;
} else if (rv > 0) {
++j;
} else {
++i;
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
continue;
}
}
for (; i < headers.size(); ++i) {
auto &kv = headers[i];
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
switch (lookup_token(kv.name)) {
case HD_COOKIE:
case HD_CONNECTION:
case HD_HTTP2_SETTINGS:
case HD_KEEP_ALIVE:
case HD_PROXY_CONNECTION:
case HD_SERVER:
case HD_TRANSFER_ENCODING:
case HD_UPGRADE:
case HD_VIA:
case HD_X_FORWARDED_FOR:
case HD_X_FORWARDED_PROTO:
continue;
}
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
}
}
void build_http1_headers_from_norm_headers(std::string &hdrs,
const Headers &headers) {
size_t i, j;
for (i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) {
auto &kv = headers[i];
auto rv = strcmp(kv.name.c_str(), HTTP1_IGN_HD[j]);
if (rv < 0) {
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
hdrs += kv.name;
capitalize(hdrs, hdrs.size() - kv.name.size());
hdrs += ": ";
hdrs += kv.value;
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
hdrs += "\r\n";
}
++i;
} else if (rv > 0) {
++j;
} else {
++i;
void build_http1_headers_from_headers(std::string &hdrs,
const Headers &headers) {
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
continue;
}
}
for (; i < headers.size(); ++i) {
auto &kv = headers[i];
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
hdrs += kv.name;
capitalize(hdrs, hdrs.size() - kv.name.size());
hdrs += ": ";
hdrs += kv.value;
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
hdrs += "\r\n";
switch (lookup_token(kv.name)) {
case HD_CONNECTION:
case HD_COOKIE:
case HD_HTTP2_SETTINGS:
case HD_KEEP_ALIVE:
case HD_PROXY_CONNECTION:
case HD_SERVER:
case HD_UPGRADE:
case HD_VIA:
case HD_X_FORWARDED_FOR:
case HD_X_FORWARDED_PROTO:
continue;
}
hdrs += kv.name;
capitalize(hdrs, hdrs.size() - kv.name.size());
hdrs += ": ";
hdrs += kv.value;
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
hdrs += "\r\n";
}
}
@ -572,18 +417,29 @@ int parse_http_status_code(const std::string &src) {
return status;
}
void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); }
int lookup_token(const std::string &name) {
return lookup_token(reinterpret_cast<const uint8_t *>(name.c_str()),
name.size());
}
// This function was generated by genheaderfunc.py. Inspired by h2o
// header lookup. https://github.com/h2o/h2o
void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
int lookup_token(const uint8_t *name, size_t namelen) {
switch (namelen) {
case 2:
switch (util::lowcase(name[namelen - 1])) {
case 'e':
if (util::streq("t", name, 1)) {
hdidx[HD_TE] = idx;
return;
return HD_TE;
}
break;
}
break;
case 3:
switch (util::lowcase(name[namelen - 1])) {
case 'a':
if (util::streq("vi", name, 2)) {
return HD_VIA;
}
break;
}
@ -592,8 +448,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
switch (util::lowcase(name[namelen - 1])) {
case 't':
if (util::streq("hos", name, 3)) {
hdidx[HD_HOST] = idx;
return;
return HD_HOST;
}
break;
}
@ -602,38 +457,67 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
switch (util::lowcase(name[namelen - 1])) {
case 'h':
if (util::streq(":pat", name, 4)) {
hdidx[HD_PATH] = idx;
return;
return HD__PATH;
}
break;
case 't':
if (util::streq(":hos", name, 4)) {
return HD__HOST;
}
break;
}
break;
case 6:
switch (util::lowcase(name[namelen - 1])) {
case 'e':
if (util::streq("cooki", name, 5)) {
return HD_COOKIE;
}
break;
case 'r':
if (util::streq("serve", name, 5)) {
return HD_SERVER;
}
break;
case 't':
if (util::streq("expec", name, 5)) {
hdidx[HD_EXPECT] = idx;
return;
return HD_EXPECT;
}
break;
}
break;
case 7:
switch (util::lowcase(name[namelen - 1])) {
case 'c':
if (util::streq("alt-sv", name, 6)) {
return HD_ALT_SVC;
}
break;
case 'd':
if (util::streq(":metho", name, 6)) {
hdidx[HD_METHOD] = idx;
return;
return HD__METHOD;
}
break;
case 'e':
if (util::streq(":schem", name, 6)) {
hdidx[HD_SCHEME] = idx;
return;
return HD__SCHEME;
}
if (util::streq("upgrad", name, 6)) {
hdidx[HD_UPGRADE] = idx;
return;
return HD_UPGRADE;
}
break;
case 's':
if (util::streq(":statu", name, 6)) {
return HD__STATUS;
}
break;
}
break;
case 8:
switch (util::lowcase(name[namelen - 1])) {
case 'n':
if (util::streq("locatio", name, 7)) {
return HD_LOCATION;
}
break;
}
@ -642,20 +526,40 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
switch (util::lowcase(name[namelen - 1])) {
case 'e':
if (util::streq("keep-aliv", name, 9)) {
hdidx[HD_KEEP_ALIVE] = idx;
return;
return HD_KEEP_ALIVE;
}
break;
case 'n':
if (util::streq("connectio", name, 9)) {
hdidx[HD_CONNECTION] = idx;
return;
return HD_CONNECTION;
}
break;
case 'y':
if (util::streq(":authorit", name, 9)) {
hdidx[HD_AUTHORITY] = idx;
return;
return HD__AUTHORITY;
}
break;
}
break;
case 14:
switch (util::lowcase(name[namelen - 1])) {
case 'h':
if (util::streq("content-lengt", name, 13)) {
return HD_CONTENT_LENGTH;
}
break;
case 's':
if (util::streq("http2-setting", name, 13)) {
return HD_HTTP2_SETTINGS;
}
break;
}
break;
case 15:
switch (util::lowcase(name[namelen - 1])) {
case 'r':
if (util::streq("x-forwarded-fo", name, 14)) {
return HD_X_FORWARDED_FOR;
}
break;
}
@ -664,8 +568,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
switch (util::lowcase(name[namelen - 1])) {
case 'n':
if (util::streq("proxy-connectio", name, 15)) {
hdidx[HD_PROXY_CONNECTION] = idx;
return;
return HD_PROXY_CONNECTION;
}
break;
}
@ -674,96 +577,92 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
switch (util::lowcase(name[namelen - 1])) {
case 'e':
if (util::streq("if-modified-sinc", name, 16)) {
hdidx[HD_IF_MODIFIED_SINCE] = idx;
return;
return HD_IF_MODIFIED_SINCE;
}
break;
case 'g':
if (util::streq("transfer-encodin", name, 16)) {
hdidx[HD_TRANSFER_ENCODING] = idx;
return;
return HD_TRANSFER_ENCODING;
}
break;
case 'o':
if (util::streq("x-forwarded-prot", name, 16)) {
return HD_X_FORWARDED_PROTO;
}
break;
}
break;
}
return -1;
}
bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *s,
size_t len) {
switch (len) {
case 5:
switch (util::lowcase(s[len - 1])) {
case 'h':
if (util::streq(":pat", s, 4)) {
if (hdidx[HD_PATH] != -1) {
return false;
}
return true;
}
break;
void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); }
void index_headers(int *hdidx, const Headers &headers) {
for (size_t i = 0; i < headers.size(); ++i) {
auto &kv = headers[i];
auto token = lookup_token(
reinterpret_cast<const uint8_t *>(kv.name.c_str()), kv.name.size());
if (token >= 0) {
http2::index_header(hdidx, token, i);
}
break;
case 7:
switch (util::lowcase(s[len - 1])) {
case 'd':
if (util::streq(":metho", s, 6)) {
if (hdidx[HD_METHOD] != -1) {
return false;
}
return true;
}
break;
case 'e':
if (util::streq(":schem", s, 6)) {
if (hdidx[HD_SCHEME] != -1) {
return false;
}
return true;
}
break;
}
break;
case 10:
switch (util::lowcase(s[len - 1])) {
case 'y':
if (util::streq(":authorit", s, 9)) {
if (hdidx[HD_AUTHORITY] != -1) {
return false;
}
return true;
}
break;
}
break;
}
return false;
}
bool check_http2_headers(int *hdidx) {
if (hdidx[HD_CONNECTION] != -1 || hdidx[HD_KEEP_ALIVE] != -1 ||
hdidx[HD_PROXY_CONNECTION] != -1 || hdidx[HD_TE] != -1 ||
hdidx[HD_TRANSFER_ENCODING] != -1 || hdidx[HD_UPGRADE] != -1) {
void index_header(int *hdidx, int token, size_t idx) {
if (token == -1) {
return;
}
assert(token < HD_MAXIDX);
hdidx[token] = idx;
}
bool check_http2_request_pseudo_header(const int *hdidx, int token) {
switch (token) {
case HD__AUTHORITY:
case HD__METHOD:
case HD__PATH:
case HD__SCHEME:
return hdidx[token] == -1;
default:
return false;
}
}
bool check_http2_response_pseudo_header(const int *hdidx, int token) {
switch (token) {
case HD__STATUS:
return hdidx[token] == -1;
default:
return false;
}
}
bool http2_header_allowed(int token) {
switch (token) {
case HD_CONNECTION:
case HD_KEEP_ALIVE:
case HD_PROXY_CONNECTION:
case HD_TRANSFER_ENCODING:
case HD_UPGRADE:
return false;
default:
return true;
}
}
bool http2_mandatory_request_headers_presence(const int *hdidx) {
if (hdidx[HD__METHOD] == -1 || hdidx[HD__PATH] == -1 ||
hdidx[HD__SCHEME] == -1 ||
(hdidx[HD__AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) {
return false;
}
return true;
}
bool check_http2_request_headers(int *hdidx) {
if (!check_http2_headers(hdidx)) {
return false;
}
if (hdidx[HD_METHOD] == -1 || hdidx[HD_PATH] == -1 ||
hdidx[HD_SCHEME] == -1 ||
(hdidx[HD_AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) {
return false;
}
return true;
}
const Headers::value_type *get_header(int *hdidx, int hdkey,
const Headers::value_type *get_header(const int *hdidx, int token,
const Headers &nva) {
auto i = hdidx[hdkey];
auto i = hdidx[token];
if (i == -1) {
return nullptr;
}

View File

@ -76,35 +76,6 @@ void sanitize_header_value(std::string &s, size_t offset);
void copy_url_component(std::string &dest, const http_parser_url *u, int field,
const char *url);
// Returns true if the header field |name| with length |namelen| bytes
// is valid for HTTP/2.
bool check_http2_allowed_header(const uint8_t *name, size_t namelen);
// Calls check_http2_allowed_header with |name| and strlen(name),
// assuming |name| is null-terminated string.
bool check_http2_allowed_header(const char *name);
// Checks that headers |nva| do not contain disallowed header fields
// in HTTP/2 spec. This function returns true if |nva| does not
// contains such headers.
bool check_http2_headers(const Headers &nva);
// Calls check_http2_headers()
bool check_http2_request_headers(const Headers &nva);
// Calls check_http2_headers()
bool check_http2_response_headers(const Headers &nva);
// Returns true if |name| is allowed pusedo header for request.
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen);
// Returns true if |name| is allowed pusedo header for response.
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen);
bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs);
void normalize_headers(Headers &nva);
Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
@ -115,16 +86,9 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
void add_header(Headers &nva, const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index);
// Returns the iterator 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 Headers::value_type *get_unique_header(const Headers &nva,
const char *name);
// Returns the iterator 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.
// Returns pointer to the entry in |nva| which has name |name|. If
// more than one entries which have the name |name|, last occurrence
// in |nva| is returned. If no such entry exist, returns nullptr.
const Headers::value_type *get_header(const Headers &nva, const char *name);
// Returns nv->second if nv is not nullptr. Otherwise, returns "".
@ -165,14 +129,13 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
// Appends headers in |headers| to |nv|. Certain headers, including
// disallowed headers in HTTP/2 spec and headers which require
// special handling (i.e. via), are not copied.
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
const Headers &headers);
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
// |headers|. Certain headers, which requires special handling
// (i.e. via and cookie), are not appended.
void build_http1_headers_from_norm_headers(std::string &hdrs,
const Headers &headers);
void build_http1_headers_from_headers(std::string &hdrs,
const Headers &headers);
// Return positive window_size_increment if WINDOW_UPDATE should be
// sent for the stream |stream_id|. If |stream_id| == 0, this function
@ -218,43 +181,68 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
// Returns parsed HTTP status code. Returns -1 on failure.
int parse_http_status_code(const std::string &src);
// Header fields to be indexed, except HD_MAXIDX which is convenient
// member to get maximum value.
enum {
HD_AUTHORITY,
HD_METHOD,
HD_PATH,
HD_SCHEME,
HD__AUTHORITY,
HD__HOST,
HD__METHOD,
HD__PATH,
HD__SCHEME,
HD__STATUS,
HD_ALT_SVC,
HD_CONNECTION,
HD_CONTENT_LENGTH,
HD_COOKIE,
HD_EXPECT,
HD_HOST,
HD_HTTP2_SETTINGS,
HD_IF_MODIFIED_SINCE,
HD_KEEP_ALIVE,
HD_LOCATION,
HD_PROXY_CONNECTION,
HD_SERVER,
HD_TE,
HD_TRANSFER_ENCODING,
HD_UPGRADE,
HD_VIA,
HD_X_FORWARDED_FOR,
HD_X_FORWARDED_PROTO,
HD_MAXIDX,
};
// Looks up header token for header name |name| of length |namelen|.
// Only headers we are interested in are tokenized. If header name
// cannot be tokenized, returns -1.
int lookup_token(const uint8_t *name, size_t namelen);
int lookup_token(const std::string &name);
// Initializes |hdidx|, header index. The |hdidx| must point to the
// array containing at least HD_MAXIDX elements.
void init_hdidx(int *hdidx);
// Indexes header |name| of length |namelen| using index |idx|.
void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx);
// Indexes header |token| using index |idx|.
void index_header(int *hdidx, int token, size_t idx);
// Iterates |headers| and for each element, call index_header.
void index_headers(int *hdidx, const Headers &headers);
// Checks pseudo header |name| of length |namelen| are unique and
// allowed for HTTP/2 request.
bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *name,
size_t namelen);
// Returns true if HTTP/2 request pseudo header |token| is not indexed
// yet and not -1.
bool check_http2_request_pseudo_header(const int *hdidx, int token);
// Checks |hdidx| does not contain disallowed headers.
bool check_http2_headers(int *hdidx);
// Checks |hdidx| does not contain disallowed headers and contains
// mandatory headers. This funtions internally calls
// check_http2_headers().
bool check_http2_request_headers(int *hdidx);
// Returns true if HTTP/2 response pseudo header |token| is not
// indexed yet and not -1.
bool check_http2_response_pseudo_header(const int *hdidx, int token);
// Returns header denoted by |hdkey| using index |hdidx|.
const Headers::value_type *get_header(int *hdidx, int hdkey,
// Returns true if header field denoted by |token| is allowed for
// HTTP/2.
bool http2_header_allowed(int token);
// Returns true that |hdidx| contains mandatory HTTP/2 request
// headers.
bool http2_mandatory_request_headers_presence(const int *hdidx);
// Returns header denoted by |token| using index |hdidx|.
const Headers::value_type *get_header(const int *hdidx, int token,
const Headers &nva);
} // namespace http2

View File

@ -100,55 +100,14 @@ void test_http2_add_header(void) {
CU_ASSERT(Headers::value_type("a", "") == nva[0]);
}
void test_http2_check_http2_headers(void) {
auto nva1 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"upgrade", "http2"}};
CU_ASSERT(!http2::check_http2_headers(nva1));
auto nva2 = Headers{{"connection", "1"}, {"delta", "2"}, {"echo", "3"}};
CU_ASSERT(!http2::check_http2_headers(nva2));
auto nva3 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"te2", "3"}};
CU_ASSERT(http2::check_http2_headers(nva3));
auto n1 = ":authority";
auto n1u8 = reinterpret_cast<const uint8_t *>(n1);
CU_ASSERT(http2::check_http2_request_pseudo_header(n1u8, strlen(n1)));
CU_ASSERT(!http2::check_http2_response_pseudo_header(n1u8, strlen(n1)));
auto n2 = ":status";
auto n2u8 = reinterpret_cast<const uint8_t *>(n2);
CU_ASSERT(!http2::check_http2_request_pseudo_header(n2u8, strlen(n2)));
CU_ASSERT(http2::check_http2_response_pseudo_header(n2u8, strlen(n2)));
}
void test_http2_get_unique_header(void) {
auto nva = Headers{{"alpha", "1"},
{"bravo", "2"},
{"bravo", "3"},
{"charlie", "4"},
{"delta", "5"},
{"echo", "6"}};
const Headers::value_type *rv;
rv = http2::get_unique_header(nva, "delta");
CU_ASSERT(rv != nullptr);
CU_ASSERT("delta" == rv->name);
rv = http2::get_unique_header(nva, "bravo");
CU_ASSERT(rv == nullptr);
rv = http2::get_unique_header(nva, "foxtrot");
CU_ASSERT(rv == nullptr);
}
void test_http2_get_header(void) {
auto nva = Headers{{"alpha", "1"},
{"bravo", "2"},
{"bravo", "3"},
{"charlie", "4"},
{"delta", "5"},
{"echo", "6"}};
{"echo", "6"},
{"content-length", "7"}};
const Headers::value_type *rv;
rv = http2::get_header(nva, "delta");
CU_ASSERT(rv != nullptr);
@ -160,6 +119,12 @@ void test_http2_get_header(void) {
rv = http2::get_header(nva, "foxtrot");
CU_ASSERT(rv == nullptr);
int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx);
hdidx[http2::HD_CONTENT_LENGTH] = 6;
rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva);
CU_ASSERT("content-length" == rv->name);
}
namespace {
@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true},
{"zulu", "12"}};
} // namespace
void test_http2_copy_norm_headers_to_nva(void) {
void test_http2_copy_headers_to_nva(void) {
std::vector<nghttp2_nv> nva;
http2::copy_norm_headers_to_nva(nva, headers);
CU_ASSERT(7 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
http2::copy_headers_to_nva(nva, headers);
CU_ASSERT(9 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 8, 9, 12};
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
@ -194,9 +159,9 @@ void test_http2_copy_norm_headers_to_nva(void) {
}
}
void test_http2_build_http1_headers_from_norm_headers(void) {
void test_http2_build_http1_headers_from_headers(void) {
std::string hdrs;
http2::build_http1_headers_from_norm_headers(hdrs, headers);
http2::build_http1_headers_from_headers(hdrs, headers);
CU_ASSERT(hdrs == "Alpha: 0\r\n"
"Bravo: 1\r\n"
"Delta: 4\r\n"
@ -206,15 +171,6 @@ void test_http2_build_http1_headers_from_norm_headers(void) {
"Te: 8\r\n"
"Te: 9\r\n"
"Zulu: 12\r\n");
hdrs.clear();
// Both nghttp2 and spdylay do not allow \r and \n in header value
// now.
// auto hd2 = std::vector<std::pair<std::string, std::string>>
// {{"alpha", "bravo\r\ncharlie\r\n"}};
// http2::build_http1_headers_from_norm_headers(hdrs, hd2);
// CU_ASSERT(hdrs == "Alpha: bravo charlie \r\n");
}
void test_http2_lws(void) {
@ -274,12 +230,64 @@ void test_http2_index_header(void) {
int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx);
http2::index_header(hdidx, reinterpret_cast<const uint8_t *>(":authority"),
10, 0);
http2::index_header(hdidx, reinterpret_cast<const uint8_t *>("hos"), 3, 1);
http2::index_header(hdidx, http2::HD__AUTHORITY, 0);
http2::index_header(hdidx, -1, 1);
CU_ASSERT(0 == hdidx[http2::HD_AUTHORITY]);
CU_ASSERT(-1 == hdidx[http2::HD_HOST]);
CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]);
}
void test_http2_lookup_token(void) {
CU_ASSERT(http2::HD__AUTHORITY == http2::lookup_token(":authority"));
CU_ASSERT(-1 == http2::lookup_token(":authorit"));
CU_ASSERT(-1 == http2::lookup_token(":Authority"));
CU_ASSERT(http2::HD_EXPECT == http2::lookup_token("expect"));
}
void test_http2_check_http2_pseudo_header(void) {
int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx);
CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
hdidx[http2::HD__PATH] = 0;
CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
hdidx[http2::HD__METHOD] = 1;
CU_ASSERT(
!http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
CU_ASSERT(!http2::check_http2_request_pseudo_header(hdidx, http2::HD_VIA));
http2::init_hdidx(hdidx);
CU_ASSERT(
http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS));
hdidx[http2::HD__STATUS] = 0;
CU_ASSERT(
!http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS));
CU_ASSERT(!http2::check_http2_response_pseudo_header(hdidx, http2::HD_VIA));
}
void test_http2_http2_header_allowed(void) {
CU_ASSERT(http2::http2_header_allowed(http2::HD__PATH));
CU_ASSERT(http2::http2_header_allowed(http2::HD_CONTENT_LENGTH));
CU_ASSERT(!http2::http2_header_allowed(http2::HD_CONNECTION));
}
void test_http2_mandatory_request_headers_presence(void) {
int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx);
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
hdidx[http2::HD__AUTHORITY] = 0;
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
hdidx[http2::HD__METHOD] = 1;
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
hdidx[http2::HD__PATH] = 2;
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
hdidx[http2::HD__SCHEME] = 3;
CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx));
hdidx[http2::HD__AUTHORITY] = -1;
hdidx[http2::HD_HOST] = 0;
CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx));
}
} // namespace shrpx

View File

@ -28,15 +28,17 @@
namespace shrpx {
void test_http2_add_header(void);
void test_http2_check_http2_headers(void);
void test_http2_get_unique_header(void);
void test_http2_get_header(void);
void test_http2_copy_norm_headers_to_nva(void);
void test_http2_build_http1_headers_from_norm_headers(void);
void test_http2_copy_headers_to_nva(void);
void test_http2_build_http1_headers_from_headers(void);
void test_http2_lws(void);
void test_http2_rewrite_location_uri(void);
void test_http2_parse_http_status_code(void);
void test_http2_index_header(void);
void test_http2_lookup_token(void);
void test_http2_check_http2_pseudo_header(void);
void test_http2_http2_header_allowed(void);
void test_http2_mandatory_request_headers_presence(void);
} // namespace shrpx

View File

@ -224,6 +224,9 @@ struct Request {
int level;
// RequestPriority value defined in HtmlParser.h
int pri;
int res_hdidx[http2::HD_MAXIDX];
// used for incoming PUSH_PROMISE
int req_hdidx[http2::HD_MAXIDX];
bool expect_final_response;
// For pushed request, |uri| is empty and |u| is zero-cleared.
@ -235,7 +238,10 @@ struct Request {
data_length(data_length), data_offset(0), response_len(0),
inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
stream_id(-1), status(0), level(level), pri(pri),
expect_final_response(false) {}
expect_final_response(false) {
http2::init_hdidx(res_hdidx);
http2::init_hdidx(req_hdidx);
}
~Request() {
nghttp2_gzip_inflate_del(inflater);
@ -354,12 +360,47 @@ struct Request {
}
}
bool response_pseudo_header_allowed() const {
return res_nva.empty() || res_nva.back().name.c_str()[0] == ':';
bool response_pseudo_header_allowed(int token) const {
if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
return false;
}
switch (token) {
case http2::HD__STATUS:
return res_hdidx[token] == -1;
default:
return false;
}
}
bool push_request_pseudo_header_allowed() const {
return res_nva.empty() || req_nva.back().name.c_str()[0] == ':';
bool push_request_pseudo_header_allowed(int token) const {
if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
return false;
}
switch (token) {
case http2::HD__AUTHORITY:
case http2::HD__METHOD:
case http2::HD__PATH:
case http2::HD__SCHEME:
return req_hdidx[token] == -1;
default:
return false;
}
}
Headers::value_type *get_res_header(int token) {
auto idx = res_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &res_nva[idx];
}
Headers::value_type *get_req_header(int token) {
auto idx = req_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &req_nva[idx];
}
void record_request_time() {
@ -1537,8 +1578,6 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
build_headers.emplace_back(kv.name, kv.value, kv.no_index);
}
std::stable_sort(std::begin(build_headers), std::end(build_headers),
http2::name_less);
auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size());
@ -1690,30 +1729,29 @@ void check_response_header(nghttp2_session *session, Request *req) {
req->expect_final_response = false;
auto status_hd = req->get_res_header(http2::HD__STATUS);
if (!status_hd) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR);
return;
}
auto status = http2::parse_http_status_code(status_hd->value);
if (status == -1) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR);
return;
}
req->status = status;
for (auto &nv : req->res_nva) {
if ("content-encoding" == nv.name) {
gzip =
util::strieq("gzip", nv.value) || util::strieq("deflate", nv.value);
continue;
}
if (":status" == nv.name) {
int status;
if (req->status != 0 ||
(status = http2::parse_http_status_code(nv.value)) == -1) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR);
return;
}
req->status = status;
}
}
if (req->status == 0) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR);
return;
}
if (req->status / 100 == 1) {
@ -1797,15 +1835,17 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
break;
}
if (namelen > 0 && name[0] == ':') {
if (!req->response_pseudo_header_allowed() ||
!http2::check_http2_response_pseudo_header(name, namelen)) {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!req->response_pseudo_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
http2::index_header(req->res_hdidx, token, req->res_nva.size());
http2::add_header(req->res_nva, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
break;
@ -1818,9 +1858,10 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
break;
}
if (namelen > 0 && name[0] == ':') {
if (!req->push_request_pseudo_header_allowed() ||
!http2::check_http2_request_pseudo_header(name, namelen)) {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!req->push_request_pseudo_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR);
@ -1828,6 +1869,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
}
}
http2::index_header(req->req_hdidx, token, req->req_nva.size());
http2::add_header(req->req_nva, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
break;
@ -1895,36 +1937,27 @@ int on_frame_recv_callback2(nghttp2_session *session,
if (!req) {
break;
}
std::string scheme, authority, method, path;
for (auto &nv : req->req_nva) {
if (nv.name == ":scheme") {
scheme = nv.value;
continue;
}
if (nv.name == ":authority" || nv.name == "host") {
authority = nv.value;
continue;
}
if (nv.name == ":method") {
method = nv.value;
continue;
}
if (nv.name == ":path") {
path = nv.value;
continue;
}
auto scheme = req->get_req_header(http2::HD__SCHEME);
auto authority = req->get_req_header(http2::HD__AUTHORITY);
auto method = req->get_req_header(http2::HD__METHOD);
auto path = req->get_req_header(http2::HD__PATH);
if (!authority) {
authority = req->get_req_header(http2::HD_HOST);
}
if (scheme.empty() || authority.empty() || method.empty() || path.empty() ||
path[0] != '/') {
if (!scheme || !authority || !method || !path || scheme->value.empty() ||
authority->value.empty() || method->value.empty() ||
path->value.empty() || path->value[0] != '/') {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR);
break;
}
std::string uri = scheme;
std::string uri = scheme->value;
uri += "://";
uri += authority;
uri += path;
uri += authority->value;
uri += path->value;
http_parser_url u;
memset(&u, 0, sizeof(u));
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {

View File

@ -72,15 +72,11 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
!CU_add_test(pSuite, "http2_check_http2_headers",
shrpx::test_http2_check_http2_headers) ||
!CU_add_test(pSuite, "http2_get_unique_header",
shrpx::test_http2_get_unique_header) ||
!CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) ||
!CU_add_test(pSuite, "http2_copy_norm_headers_to_nva",
shrpx::test_http2_copy_norm_headers_to_nva) ||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",
shrpx::test_http2_build_http1_headers_from_norm_headers) ||
!CU_add_test(pSuite, "http2_copy_headers_to_nva",
shrpx::test_http2_copy_headers_to_nva) ||
!CU_add_test(pSuite, "http2_build_http1_headers_from_headers",
shrpx::test_http2_build_http1_headers_from_headers) ||
!CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
!CU_add_test(pSuite, "http2_rewrite_location_uri",
shrpx::test_http2_rewrite_location_uri) ||
@ -88,21 +84,28 @@ int main(int argc, char *argv[]) {
shrpx::test_http2_parse_http_status_code) ||
!CU_add_test(pSuite, "http2_index_header",
shrpx::test_http2_index_header) ||
!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, "http2_lookup_token",
shrpx::test_http2_lookup_token) ||
!CU_add_test(pSuite, "http2_check_http2_pseudo_header",
shrpx::test_http2_check_http2_pseudo_header) ||
!CU_add_test(pSuite, "http2_http2_header_allowed",
shrpx::test_http2_http2_header_allowed) ||
!CU_add_test(pSuite, "http2_mandatory_request_headers_presence",
shrpx::test_http2_mandatory_request_headers_presence) ||
!CU_add_test(pSuite, "downstream_index_request_headers",
shrpx::test_downstream_index_request_headers) ||
!CU_add_test(pSuite, "downstream_index_response_headers",
shrpx::test_downstream_index_response_headers) ||
!CU_add_test(pSuite, "downstream_get_request_header",
shrpx::test_downstream_get_request_header) ||
!CU_add_test(pSuite, "downstream_get_response_header",
shrpx::test_downstream_get_response_header) ||
!CU_add_test(pSuite, "downstream_crumble_request_cookie",
shrpx::test_downstream_crumble_request_cookie) ||
!CU_add_test(pSuite, "downstream_assemble_request_cookie",
shrpx::test_downstream_assemble_request_cookie) ||
!CU_add_test(
pSuite, "downstream_rewrite_norm_location_response_header",
shrpx::test_downstream_rewrite_norm_location_response_header) ||
!CU_add_test(pSuite, "downstream_rewrite_location_response_header",
shrpx::test_downstream_rewrite_location_response_header) ||
!CU_add_test(pSuite, "config_parse_config_str_list",
shrpx::test_shrpx_config_parse_config_str_list) ||
!CU_add_test(pSuite, "config_parse_header",

View File

@ -116,12 +116,11 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
request_state_(INITIAL), request_major_(1), request_minor_(1),
response_state_(INITIAL), response_http_status_(0), response_major_(1),
response_minor_(1), upgrade_request_(false), upgraded_(false),
http2_upgrade_seen_(false), http2_settings_seen_(false),
chunked_request_(false), request_connection_close_(false),
request_header_key_prev_(false), request_http2_expect_body_(false),
chunked_response_(false), response_connection_close_(false),
response_header_key_prev_(false), expect_final_response_(false),
request_headers_normalized_(false) {
http2_upgrade_seen_(false), chunked_request_(false),
request_connection_close_(false), request_header_key_prev_(false),
request_http2_expect_body_(false), chunked_response_(false),
response_connection_close_(false), response_header_key_prev_(false),
expect_final_response_(false) {
ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
get_config()->stream_read_timeout);
@ -136,6 +135,9 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
upstream_wtimer_.data = this;
downstream_rtimer_.data = this;
downstream_wtimer_.data = this;
http2::init_hdidx(request_hdidx_);
http2::init_hdidx(response_hdidx_);
}
Downstream::~Downstream() {
@ -213,35 +215,15 @@ void Downstream::force_resume_read() {
}
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),
Header(name, ""), http2::name_less);
if (i != std::end(headers) && (*i).name == name) {
return i;
const Headers::value_type *get_header_linear(const Headers &headers,
const std::string &name) {
const Headers::value_type *res = nullptr;
for (auto &kv : headers) {
if (kv.name == name) {
res = &kv;
}
}
return std::end(headers);
}
} // namespace
namespace {
Headers::iterator get_norm_header(Headers &headers, const std::string &name) {
auto i = std::lower_bound(std::begin(headers), std::end(headers),
Header(name, ""), http2::name_less);
if (i != std::end(headers) && (*i).name == name) {
return i;
}
return std::end(headers);
}
} // namespace
namespace {
Headers::const_iterator get_header_linear(const Headers &headers,
const std::string &name) {
auto i = std::find_if(
std::begin(headers), std::end(headers),
[&name](const Header &header) { return header.name == name; });
return i;
return res;
}
} // namespace
@ -253,90 +235,70 @@ void Downstream::assemble_request_cookie() {
std::string &cookie = assembled_request_cookie_;
cookie = "";
for (auto &kv : request_headers_) {
if (util::strieq("cookie", kv.name.c_str())) {
auto end = kv.value.find_last_not_of(" ;");
if (end == std::string::npos) {
cookie += kv.value;
} else {
cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1);
}
cookie += "; ";
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq("cooki", kv.name.c_str(), 5)) {
continue;
}
auto end = kv.value.find_last_not_of(" ;");
if (end == std::string::npos) {
cookie += kv.value;
} else {
cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1);
}
cookie += "; ";
}
if (cookie.size() >= 2) {
cookie.erase(cookie.size() - 2);
}
}
void Downstream::crumble_request_cookie() {
Headers Downstream::crumble_request_cookie() {
Headers cookie_hdrs;
for (auto &kv : request_headers_) {
if (util::strieq("cookie", kv.name.c_str())) {
size_t last = kv.value.size();
size_t num = 0;
std::string rep_cookie;
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq("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;
}
auto first = j;
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
}
if (num == 0) {
if (first == 0 && j == last) {
break;
}
rep_cookie = kv.value.substr(first, j - first);
} else {
cookie_hdrs.push_back(
Header("cookie", kv.value.substr(first, j - first), kv.no_index));
}
++num;
for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j);
if (j == std::string::npos) {
break;
}
if (num > 0) {
kv.value = std::move(rep_cookie);
auto first = j;
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
}
cookie_hdrs.push_back(
Header("cookie", kv.value.substr(first, j - first), kv.no_index));
}
}
request_headers_.insert(std::end(request_headers_),
std::make_move_iterator(std::begin(cookie_hdrs)),
std::make_move_iterator(std::end(cookie_hdrs)));
if (request_headers_normalized_) {
normalize_request_headers();
}
return cookie_hdrs;
}
const std::string &Downstream::get_assembled_request_cookie() const {
return assembled_request_cookie_;
}
void Downstream::normalize_request_headers() {
http2::normalize_headers(request_headers_);
request_headers_normalized_ = true;
}
Headers::const_iterator
Downstream::get_norm_request_header(const std::string &name) const {
return get_norm_header(request_headers_, name);
}
Headers::const_iterator
Downstream::get_request_header(const std::string &name) const {
if (request_headers_normalized_) {
return get_norm_request_header(name);
void Downstream::index_request_headers() {
for (auto &kv : request_headers_) {
util::inp_strlower(kv.name);
}
return get_header_linear(request_headers_, name);
http2::index_headers(request_hdidx_, request_headers_);
}
bool Downstream::get_request_headers_normalized() const {
return request_headers_normalized_;
const Headers::value_type *Downstream::get_request_header(int token) const {
return http2::get_header(request_hdidx_, token, request_headers_);
}
const Headers::value_type *
Downstream::get_request_header(const std::string &name) const {
return get_header_linear(request_headers_, name);
}
void Downstream::add_request_header(std::string name, std::string value) {
@ -352,9 +314,10 @@ void Downstream::set_last_request_header_value(std::string value) {
item.value = std::move(value);
}
void Downstream::split_add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index) {
void Downstream::add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int token) {
http2::index_header(request_hdidx_, token, request_headers_.size());
request_headers_sum_ += namelen + valuelen;
http2::add_header(request_headers_, name, namelen, value, valuelen, no_index);
}
@ -378,7 +341,10 @@ void Downstream::append_last_request_header_value(const char *data,
item.value.append(data, len);
}
void Downstream::clear_request_headers() { Headers().swap(request_headers_); }
void Downstream::clear_request_headers() {
Headers().swap(request_headers_);
http2::init_hdidx(request_hdidx_);
}
size_t Downstream::get_request_headers_sum() const {
return request_headers_sum_;
@ -524,19 +490,23 @@ const Headers &Downstream::get_response_headers() const {
return response_headers_;
}
void Downstream::normalize_response_headers() {
http2::normalize_headers(response_headers_);
void Downstream::index_response_headers() {
for (auto &kv : response_headers_) {
util::inp_strlower(kv.name);
}
http2::index_headers(response_hdidx_, response_headers_);
}
Headers::const_iterator
Downstream::get_norm_response_header(const std::string &name) const {
return get_norm_header(response_headers_, name);
const Headers::value_type *Downstream::get_response_header(int token) const {
return http2::get_header(response_hdidx_, token, response_headers_);
}
void Downstream::rewrite_norm_location_response_header(
const std::string &upstream_scheme, uint16_t upstream_port) {
auto hd = get_norm_header(response_headers_, "location");
if (hd == std::end(response_headers_)) {
void
Downstream::rewrite_location_response_header(const std::string &upstream_scheme,
uint16_t upstream_port) {
auto hd =
http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_);
if (!hd) {
return;
}
http_parser_url u;
@ -553,15 +523,16 @@ void Downstream::rewrite_norm_location_response_header(
upstream_scheme, upstream_port);
}
if (new_uri.empty()) {
auto host = get_norm_request_header("host");
if (host == std::end(request_headers_)) {
auto host = get_request_header(http2::HD_HOST);
if (!host) {
return;
}
new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value,
upstream_scheme, upstream_port);
}
if (!new_uri.empty()) {
(*hd).value = std::move(new_uri);
auto idx = response_hdidx_[http2::HD_LOCATION];
response_headers_[idx].value = std::move(new_uri);
}
}
@ -578,9 +549,10 @@ void Downstream::set_last_response_header_value(std::string value) {
item.value = std::move(value);
}
void Downstream::split_add_response_header(const uint8_t *name, size_t namelen,
const uint8_t *value,
size_t valuelen, bool no_index) {
void Downstream::add_response_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int token) {
http2::index_header(response_hdidx_, token, response_headers_.size());
response_headers_sum_ += namelen + valuelen;
http2::add_header(response_headers_, name, namelen, value, valuelen,
no_index);
@ -605,7 +577,10 @@ void Downstream::append_last_response_header_value(const char *data,
item.value.append(data, len);
}
void Downstream::clear_response_headers() { Headers().swap(response_headers_); }
void Downstream::clear_response_headers() {
Headers().swap(response_headers_);
http2::init_hdidx(response_hdidx_);
}
size_t Downstream::get_response_headers_sum() const {
return response_headers_sum_;
@ -717,37 +692,31 @@ void Downstream::inspect_http1_request() {
upgrade_request_ = true;
}
for (auto &hd : request_headers_) {
if (!upgrade_request_ && util::strieq("upgrade", hd.name.c_str())) {
// TODO Perform more strict checking for upgrade headers
if (!upgrade_request_) {
auto idx = request_hdidx_[http2::HD_UPGRADE];
if (idx != -1) {
upgrade_request_ = true;
if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, hd.value.c_str(),
hd.value.size())) {
auto &val = request_headers_[idx].value;
// TODO Perform more strict checking for upgrade headers
if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(),
val.size())) {
http2_upgrade_seen_ = true;
}
} else if (!http2_settings_seen_ &&
util::strieq(hd.name.c_str(), "http2-settings")) {
http2_settings_seen_ = true;
http2_settings_ = hd.value;
} else if (!chunked_request_ &&
util::strieq(hd.name.c_str(), "transfer-encoding")) {
if (util::strifind(hd.value.c_str(), "chunked")) {
chunked_request_ = true;
}
}
}
auto idx = request_hdidx_[http2::HD_TRANSFER_ENCODING];
if (idx != -1 &&
util::strifind(request_headers_[idx].value.c_str(), "chunked")) {
chunked_request_ = true;
}
}
void Downstream::inspect_http1_response() {
for (auto &hd : response_headers_) {
if (!chunked_response_ &&
util::strieq(hd.name.c_str(), "transfer-encoding")) {
if (util::strifind(hd.value.c_str(), "chunked")) {
chunked_response_ = true;
}
}
auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING];
if (idx != -1 &&
util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
chunked_response_ = true;
}
}
@ -766,11 +735,20 @@ bool Downstream::get_upgraded() const { return upgraded_; }
bool Downstream::get_upgrade_request() const { return upgrade_request_; }
bool Downstream::get_http2_upgrade_request() const {
return request_bodylen_ == 0 && http2_upgrade_seen_ && http2_settings_seen_;
return request_bodylen_ == 0 && http2_upgrade_seen_ &&
request_hdidx_[http2::HD_HTTP2_SETTINGS] != -1;
}
namespace {
const std::string EMPTY;
} // namespace
const std::string &Downstream::get_http2_settings() const {
return http2_settings_;
auto idx = request_hdidx_[http2::HD_HTTP2_SETTINGS];
if (idx == -1) {
return EMPTY;
}
return request_headers_[idx].value;
}
void Downstream::set_downstream_stream_id(int32_t stream_id) {
@ -832,12 +810,18 @@ bool pseudo_header_allowed(const Headers &headers) {
}
} // namespace
bool Downstream::request_pseudo_header_allowed() const {
return pseudo_header_allowed(request_headers_);
bool Downstream::request_pseudo_header_allowed(int token) const {
if (!pseudo_header_allowed(request_headers_)) {
return false;
}
return http2::check_http2_request_pseudo_header(request_hdidx_, token);
}
bool Downstream::response_pseudo_header_allowed() const {
return pseudo_header_allowed(response_headers_);
bool Downstream::response_pseudo_header_allowed(int token) const {
if (!pseudo_header_allowed(response_headers_)) {
return false;
}
return http2::check_http2_response_pseudo_header(response_hdidx_, token);
}
void Downstream::init_upstream_timer() {

View File

@ -95,30 +95,27 @@ public:
const std::string &get_http2_settings() const;
// downstream request API
const Headers &get_request_headers() const;
void crumble_request_cookie();
// Crumbles (split cookie by ";") in request_headers_ and returns
// them. Headers::no_index is inherited.
Headers crumble_request_cookie();
void assemble_request_cookie();
const std::string &get_assembled_request_cookie() 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;
// Returns iterator pointing to the request header with the name
// |name|. This function acts like get_norm_request_header(), but
// if request_headers_ was not normalized, use linear search to find
// the header. Otherwise, get_norm_request_header() is used.
Headers::const_iterator get_request_header(const std::string &name) const;
bool get_request_headers_normalized() const;
// Lower the request header field names and indexes request headers
void index_request_headers();
// Returns pointer to the request header with the name |name|. If
// multiple header have |name| as name, return last occurrence from
// the beginning. If no such header is found, returns nullptr.
// This function must be called after headers are indexed
const Headers::value_type *get_request_header(int token) const;
// Returns pointer to the request header with the name |name|. If
// no such header is found, returns nullptr.
const Headers::value_type *get_request_header(const std::string &name) const;
void add_request_header(std::string name, std::string value);
void set_last_request_header_value(std::string value);
void split_add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
void add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
int token);
bool get_request_header_key_prev() const;
void append_last_request_header_key(const char *data, size_t len);
@ -161,7 +158,7 @@ public:
size_t get_request_datalen() const;
void dec_request_datalen(size_t len);
void reset_request_datalen();
bool request_pseudo_header_allowed() const;
bool request_pseudo_header_allowed(int token) const;
bool expect_response_body() const;
enum {
INITIAL,
@ -177,26 +174,22 @@ public:
Memchunks4K *get_request_buf();
// 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;
// Rewrites the location response header field. This function must
// be called after calling normalize_response_headers() and
// normalize_request_headers().
void rewrite_norm_location_response_header(const std::string &upstream_scheme,
uint16_t upstream_port);
// Lower the response header field names and indexes response headers
void index_response_headers();
// Returns pointer to the response header with the name |name|. If
// multiple header have |name| as name, return last occurrence from
// the beginning. If no such header is found, returns nullptr.
// This function must be called after response headers are indexed.
const Headers::value_type *get_response_header(int token) const;
// Rewrites the location response header field.
void rewrite_location_response_header(const std::string &upstream_scheme,
uint16_t upstream_port);
void add_response_header(std::string name, std::string value);
void set_last_response_header_value(std::string value);
void split_add_response_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
void add_response_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
int token);
bool get_response_header_key_prev() const;
void append_last_response_header_key(const char *data, size_t len);
@ -238,7 +231,7 @@ public:
void dec_response_datalen(size_t len);
size_t get_response_datalen() const;
void reset_response_datalen();
bool response_pseudo_header_allowed() const;
bool response_pseudo_header_allowed(int token) const;
// Call this method when there is incoming data in downstream
// connection.
@ -298,7 +291,6 @@ private:
std::string request_http2_authority_;
std::chrono::high_resolution_clock::time_point request_start_time_;
std::string assembled_request_cookie_;
std::string http2_settings_;
Memchunks4K request_buf_;
Memchunks4K response_buf_;
@ -343,6 +335,9 @@ private:
int response_major_;
int response_minor_;
int request_hdidx_[http2::HD_MAXIDX];
int response_hdidx_[http2::HD_MAXIDX];
// true if the request contains upgrade token (HTTP Upgrade or
// CONNECT)
bool upgrade_request_;
@ -350,7 +345,6 @@ private:
bool upgraded_;
bool http2_upgrade_seen_;
bool http2_settings_seen_;
bool chunked_request_;
bool request_connection_close_;
@ -362,9 +356,6 @@ private:
bool response_header_key_prev_;
bool expect_final_response_;
// true if request_headers_ is normalized
bool request_headers_normalized_;
bool use_timer_;
};

View File

@ -32,7 +32,7 @@
namespace shrpx {
void test_downstream_normalize_request_headers(void) {
void test_downstream_index_request_headers(void) {
Downstream d(nullptr, 0, 0);
d.add_request_header("1", "0");
d.add_request_header("2", "1");
@ -42,88 +42,83 @@ void test_downstream_normalize_request_headers(void) {
d.add_request_header("BravO", "5");
d.add_request_header(":method", "6");
d.add_request_header(":authority", "7");
d.normalize_request_headers();
d.index_request_headers();
auto ans = Headers{{":authority", "7"},
{":method", "6"},
{"1", "0"},
auto ans = Headers{{"1", "0"},
{"2", "1"},
{"alpha", "3"},
{"bravo", "5"},
{"charlie", "2"},
{"delta", "4"}};
{"alpha", "3"},
{"delta", "4"},
{"bravo", "5"},
{":method", "6"},
{":authority", "7"}};
CU_ASSERT(ans == d.get_request_headers());
}
void test_downstream_normalize_response_headers(void) {
void test_downstream_index_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();
d.index_response_headers();
auto ans =
Headers{{"alpha", "1"}, {"bravo", "3"}, {"charlie", "0"}, {"delta", "2"}};
Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}};
CU_ASSERT(ans == d.get_response_headers());
}
void test_downstream_get_norm_request_header(void) {
void test_downstream_get_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(Header("alpha", "0") == *i);
i = d.get_norm_request_header("bravo");
CU_ASSERT(Header("bravo", "1") == *i);
i = d.get_norm_request_header("delta");
CU_ASSERT(Header("delta", "4") == *i);
i = d.get_norm_request_header("echo");
CU_ASSERT(Header("echo", "5") == *i);
i = d.get_norm_request_header("foxtrot");
CU_ASSERT(i == std::end(d.get_request_headers()));
d.add_request_header(":authority", "1");
d.add_request_header("content-length", "2");
d.index_request_headers();
// By token
CU_ASSERT(Header(":authority", "1") ==
*d.get_request_header(http2::HD__AUTHORITY));
CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
// By name
CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
CU_ASSERT(nullptr == d.get_request_header("bravo"));
}
void test_downstream_get_norm_response_header(void) {
void test_downstream_get_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(Header("alpha", "0") == *i);
i = d.get_norm_response_header("bravo");
CU_ASSERT(Header("bravo", "1") == *i);
i = d.get_norm_response_header("delta");
CU_ASSERT(Header("delta", "4") == *i);
i = d.get_norm_response_header("echo");
CU_ASSERT(Header("echo", "5") == *i);
i = d.get_norm_response_header("foxtrot");
CU_ASSERT(i == std::end(d.get_response_headers()));
d.add_response_header(":status", "1");
d.add_response_header("content-length", "2");
d.index_response_headers();
// By token
CU_ASSERT(Header(":status", "1") ==
*d.get_response_header(http2::HD__STATUS));
CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
}
void test_downstream_crumble_request_cookie(void) {
Downstream d(nullptr, 0, 0);
d.add_request_header(":method", "get");
d.add_request_header(":path", "/");
d.add_request_header("cookie", "alpha; bravo; ; ;; charlie;;");
auto val = "alpha; bravo; ; ;; charlie;;";
d.add_request_header(
reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
d.add_request_header("cookie", ";delta");
d.add_request_header("cookie", "echo");
d.crumble_request_cookie();
Headers ans = {{":method", "get"},
{":path", "/"},
{"cookie", "alpha"},
{"cookie", "delta"},
{"cookie", "echo"},
auto cookies = d.crumble_request_cookie();
Headers ans = {{"cookie", "alpha"},
{"cookie", "bravo"},
{"cookie", "charlie"}};
CU_ASSERT(ans == d.get_request_headers());
{"cookie", "charlie"},
{"cookie", "delta"},
{"cookie", "echo"}};
CU_ASSERT(ans == cookies);
CU_ASSERT(cookies[0].no_index);
CU_ASSERT(cookies[1].no_index);
CU_ASSERT(cookies[2].no_index);
}
void test_downstream_assemble_request_cookie(void) {
@ -138,21 +133,24 @@ void test_downstream_assemble_request_cookie(void) {
CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
}
void test_downstream_rewrite_norm_location_response_header(void) {
void test_downstream_rewrite_location_response_header(void) {
{
Downstream d(nullptr, 0, 0);
d.add_request_header("host", "localhost:3000");
d.add_response_header("location", "http://localhost:3000/");
d.rewrite_norm_location_response_header("https", 443);
auto location = d.get_norm_response_header("location");
d.index_request_headers();
d.index_response_headers();
d.rewrite_location_response_header("https", 443);
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value);
}
{
Downstream d(nullptr, 0, 0);
d.set_request_http2_authority("localhost");
d.add_response_header("location", "http://localhost/");
d.rewrite_norm_location_response_header("https", 443);
auto location = d.get_norm_response_header("location");
d.index_response_headers();
d.rewrite_location_response_header("https", 443);
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value);
}
}

View File

@ -27,13 +27,13 @@
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);
void test_downstream_index_request_headers(void);
void test_downstream_index_response_headers(void);
void test_downstream_get_request_header(void);
void test_downstream_get_response_header(void);
void test_downstream_crumble_request_cookie(void);
void test_downstream_assemble_request_cookie(void);
void test_downstream_rewrite_norm_location_response_header(void);
void test_downstream_rewrite_location_response_header(void);
} // namespace shrpx

View File

@ -233,14 +233,12 @@ int Http2DownstreamConnection::push_request_headers() {
return 0;
}
size_t nheader = downstream_->get_request_headers().size();
Headers cookies;
if (!get_config()->http2_no_cookie_crumbling) {
downstream_->crumble_request_cookie();
cookies = downstream_->crumble_request_cookie();
}
assert(downstream_->get_request_headers_normalized());
auto end_headers = std::end(downstream_->get_request_headers());
// 7 means:
// 1. :method
// 2. :scheme
@ -250,10 +248,12 @@ int Http2DownstreamConnection::push_request_headers() {
// 6. x-forwarded-for (optional)
// 7. x-forwarded-proto (optional)
auto nva = std::vector<nghttp2_nv>();
nva.reserve(nheader + 7);
nva.reserve(nheader + 7 + cookies.size());
std::string via_value;
std::string xff_value;
std::string scheme, authority, path, query;
// To reconstruct HTTP/1 status line and headers, proxy should
// preserve host header field. See draft-09 section 8.1.3.1.
if (downstream_->get_request_method() == "CONNECT") {
@ -273,7 +273,7 @@ int Http2DownstreamConnection::push_request_headers() {
if (!downstream_->get_request_http2_authority().empty()) {
nva.push_back(http2::make_nv_ls(
":authority", downstream_->get_request_http2_authority()));
} else if (downstream_->get_norm_request_header("host") == end_headers) {
} else if (!downstream_->get_request_header(http2::HD_HOST)) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
@ -330,7 +330,7 @@ int Http2DownstreamConnection::push_request_headers() {
authority += util::utos(u.port);
}
nva.push_back(http2::make_nv_ls(":authority", authority));
} else if (downstream_->get_norm_request_header("host") == end_headers) {
} else if (!downstream_->get_request_header(http2::HD_HOST)) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
@ -341,27 +341,30 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(
http2::make_nv_ls(":method", downstream_->get_request_method()));
http2::copy_norm_headers_to_nva(nva, downstream_->get_request_headers());
http2::copy_headers_to_nva(nva, downstream_->get_request_headers());
bool chunked_encoding = false;
auto transfer_encoding =
downstream_->get_norm_request_header("transfer-encoding");
if (transfer_encoding != end_headers &&
downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
if (transfer_encoding &&
util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
chunked_encoding = true;
}
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
for (auto &nv : cookies) {
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
}
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
if (get_config()->add_x_forwarded_for) {
if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) {
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
xff_value = (*xff).value;
xff_value += ", ";
}
xff_value +=
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff != end_headers &&
!get_config()->strip_incoming_x_forwarded_for) {
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value));
}
@ -379,13 +382,13 @@ int Http2DownstreamConnection::push_request_headers() {
}
}
auto via = downstream_->get_norm_request_header("via");
auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via != end_headers) {
if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value));
}
} else {
if (via != end_headers) {
if (via) {
via_value = (*via).value;
via_value += ", ";
}
@ -407,7 +410,8 @@ int Http2DownstreamConnection::push_request_headers() {
}
auto content_length =
downstream_->get_norm_request_header("content-length") != end_headers;
downstream_->get_request_header(http2::HD_CONTENT_LENGTH);
// TODO check content-length: 0 case
if (downstream_->get_request_method() == "CONNECT" || chunked_encoding ||
content_length || downstream_->get_request_http2_expect_body()) {

View File

@ -709,18 +709,24 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (namelen > 0 && name[0] == ':') {
if (!downstream->response_pseudo_header_allowed() ||
!http2::check_http2_response_pseudo_header(name, namelen)) {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!downstream->response_pseudo_header_allowed(token)) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
downstream->split_add_response_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
if (!http2::http2_header_allowed(token)) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
downstream->add_response_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
} // namespace
@ -763,20 +769,11 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
auto upstream = downstream->get_upstream();
downstream->normalize_response_headers();
auto &nva = downstream->get_response_headers();
downstream->set_expect_final_response(false);
if (!http2::check_http2_response_headers(nva)) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
call_downstream_readcb(http2session, downstream);
return 0;
}
auto status = http2::get_unique_header(nva, ":status");
auto status = downstream->get_response_header(http2::HD__STATUS);
int status_code;
if (!http2::non_empty_value(status) ||
@ -824,7 +821,8 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
return 0;
}
auto content_length = http2::get_header(nva, "content-length");
auto content_length =
downstream->get_response_header(http2::HD_CONTENT_LENGTH);
if (!content_length && downstream->get_request_method() != "HEAD" &&
downstream->get_request_method() != "CONNECT") {
unsigned int status;

View File

@ -185,17 +185,22 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (namelen > 0 && name[0] == ':') {
if (!downstream->request_pseudo_header_allowed() ||
!http2::check_http2_request_pseudo_header(name, namelen)) {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!downstream->request_pseudo_header_allowed(token)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
downstream->split_add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
if (!http2::http2_header_allowed(token)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
downstream->add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
} // namespace
@ -238,7 +243,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
return 0;
}
downstream->normalize_request_headers();
auto &nva = downstream->get_request_headers();
if (LOG_ENABLED(INFO)) {
@ -254,17 +258,11 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva);
}
if (!http2::check_http2_request_headers(nva)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return 0;
}
auto host = http2::get_unique_header(nva, "host");
auto authority = http2::get_unique_header(nva, ":authority");
auto path = http2::get_unique_header(nva, ":path");
auto method = http2::get_unique_header(nva, ":method");
auto scheme = http2::get_unique_header(nva, ":scheme");
auto host = downstream->get_request_header(http2::HD_HOST);
auto authority = downstream->get_request_header(http2::HD__AUTHORITY);
auto path = downstream->get_request_header(http2::HD__PATH);
auto method = downstream->get_request_header(http2::HD__METHOD);
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
bool is_connect = method && "CONNECT" == method->value;
bool having_host = http2::non_empty_value(host);
@ -1101,14 +1099,12 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
}
}
downstream->normalize_response_headers();
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) {
downstream->rewrite_norm_location_response_header(
downstream->rewrite_location_response_header(
get_client_handler()->get_upstream_scheme(), get_config()->port);
}
auto end_headers = std::end(downstream->get_response_headers());
size_t nheader = downstream->get_response_headers().size();
auto nva = std::vector<nghttp2_nv>();
// 3 means :status and possible server and via header field.
@ -1117,7 +1113,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
auto response_status = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", response_status));
http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers());
http2::copy_headers_to_nva(nva, downstream->get_response_headers());
if (downstream->get_non_final_response()) {
if (LOG_ENABLED(INFO)) {
@ -1141,19 +1137,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));
} else {
auto server = downstream->get_norm_response_header("server");
if (server != end_headers) {
auto server = downstream->get_response_header(http2::HD_SERVER);
if (server) {
nva.push_back(http2::make_nv_ls("server", (*server).value));
}
}
auto via = downstream->get_norm_response_header("via");
auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via != end_headers) {
if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value));
}
} else {
if (via != end_headers) {
if (via) {
via_value = (*via).value;
via_value += ", ";
}

View File

@ -213,11 +213,8 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
}
int HttpDownstreamConnection::push_request_headers() {
assert(downstream_->get_request_headers_normalized());
downstream_->assemble_request_cookie();
auto end_headers = std::end(downstream_->get_request_headers());
// Assume that method and request path do not contain \r\n.
std::string hdrs = downstream_->get_request_method();
hdrs += " ";
@ -253,14 +250,14 @@ int HttpDownstreamConnection::push_request_headers() {
hdrs += downstream_->get_request_path();
}
hdrs += " HTTP/1.1\r\n";
if (downstream_->get_norm_request_header("host") == end_headers &&
if (!downstream_->get_request_header(http2::HD_HOST) &&
!downstream_->get_request_http2_authority().empty()) {
hdrs += "Host: ";
hdrs += downstream_->get_request_http2_authority();
hdrs += "\r\n";
}
http2::build_http1_headers_from_norm_headers(
hdrs, downstream_->get_request_headers());
http2::build_http1_headers_from_headers(hdrs,
downstream_->get_request_headers());
if (!downstream_->get_assembled_request_cookie().empty()) {
hdrs += "Cookie: ";
@ -270,7 +267,7 @@ int HttpDownstreamConnection::push_request_headers() {
if (downstream_->get_request_method() != "CONNECT" &&
downstream_->get_request_http2_expect_body() &&
downstream_->get_norm_request_header("content-length") == end_headers) {
!downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) {
downstream_->set_chunked_request(true);
hdrs += "Transfer-Encoding: chunked\r\n";
@ -279,18 +276,17 @@ int HttpDownstreamConnection::push_request_headers() {
if (downstream_->get_request_connection_close()) {
hdrs += "Connection: close\r\n";
}
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
if (get_config()->add_x_forwarded_for) {
hdrs += "X-Forwarded-For: ";
if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) {
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
hdrs += (*xff).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
hdrs += ", ";
}
hdrs += client_handler_->get_ipaddr();
hdrs += "\r\n";
} else if (xff != end_headers &&
!get_config()->strip_incoming_x_forwarded_for) {
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
hdrs += "X-Forwarded-For: ";
hdrs += (*xff).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
@ -308,17 +304,16 @@ int HttpDownstreamConnection::push_request_headers() {
hdrs += "http\r\n";
}
}
auto expect = downstream_->get_norm_request_header("expect");
if (expect != end_headers &&
!util::strifind((*expect).value.c_str(), "100-continue")) {
auto expect = downstream_->get_request_header(http2::HD_EXPECT);
if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) {
hdrs += "Expect: ";
hdrs += (*expect).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size());
hdrs += "\r\n";
}
auto via = downstream_->get_norm_request_header("via");
auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via != end_headers) {
if (via) {
hdrs += "Via: ";
hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
@ -326,7 +321,7 @@ int HttpDownstreamConnection::push_request_headers() {
}
} else {
hdrs += "Via: ";
if (via != end_headers) {
if (via) {
hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
hdrs += ", ";
@ -473,6 +468,8 @@ int htp_hdrs_completecb(http_parser *htp) {
downstream->set_response_major(htp->http_major);
downstream->set_response_minor(htp->http_minor);
downstream->index_response_headers();
if (downstream->get_non_final_response()) {
// For non-final response code, we just call
// on_downstream_header_complete() without changing response

View File

@ -149,7 +149,7 @@ int htp_hdrs_completecb(http_parser *htp) {
ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
}
downstream->normalize_request_headers();
downstream->index_request_headers();
downstream->inspect_http1_request();
@ -620,15 +620,15 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
hdrs += " ";
hdrs += http2::get_status_string(downstream->get_response_http_status());
hdrs += "\r\n";
downstream->normalize_response_headers();
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) {
downstream->rewrite_norm_location_response_header(
downstream->rewrite_location_response_header(
get_client_handler()->get_upstream_scheme(), get_config()->port);
}
auto end_headers = std::end(downstream->get_response_headers());
http2::build_http1_headers_from_norm_headers(
hdrs, downstream->get_response_headers());
http2::build_http1_headers_from_headers(hdrs,
downstream->get_response_headers());
auto output = downstream->get_response_buf();
@ -660,8 +660,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
hdrs += "Connection: close\r\n";
}
if (downstream->get_norm_response_header("alt-svc") == end_headers) {
// We won't change or alter alt-svc from backend at the moment.
if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
// We won't change or alter alt-svc from backend for now
if (!get_config()->altsvcs.empty()) {
hdrs += "Alt-Svc: ";
@ -684,17 +684,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
hdrs += get_config()->server_name;
hdrs += "\r\n";
} else {
auto server = downstream->get_norm_response_header("server");
if (server != end_headers) {
auto server = downstream->get_response_header(http2::HD_SERVER);
if (server) {
hdrs += "Server: ";
hdrs += (*server).value;
hdrs += "\r\n";
}
}
auto via = downstream->get_norm_response_header("via");
auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via != end_headers) {
if (via) {
hdrs += "Via: ";
hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
@ -702,7 +702,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
} else {
hdrs += "Via: ";
if (via != end_headers) {
if (via) {
hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
hdrs += ", ";

View File

@ -206,7 +206,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
case SHRPX_LOGF_HTTP:
if (downstream) {
auto hd = downstream->get_request_header(lf.value.get());
if (hd != std::end(downstream->get_request_headers())) {
if (hd) {
std::tie(p, avail) = copy((*hd).value.c_str(), avail, p);
break;
}

View File

@ -156,42 +156,33 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
downstream->reset_upstream_rtimer();
auto nv = frame->syn_stream.nv;
const char *path = nullptr;
const char *scheme = nullptr;
const char *host = nullptr;
const char *method = nullptr;
for (size_t i = 0; nv[i]; i += 2) {
if (strcmp(nv[i], ":path") == 0) {
path = nv[i + 1];
} else if (strcmp(nv[i], ":scheme") == 0) {
scheme = nv[i + 1];
} else if (strcmp(nv[i], ":method") == 0) {
method = nv[i + 1];
} else if (strcmp(nv[i], ":host") == 0) {
host = nv[i + 1];
} else if (nv[i][0] != ':') {
downstream->add_request_header(nv[i], nv[i + 1]);
}
downstream->add_request_header(nv[i], nv[i + 1]);
}
downstream->normalize_request_headers();
downstream->index_request_headers();
bool is_connect = method && strcmp("CONNECT", method) == 0;
if (!path || !host || !method || http2::lws(host) || http2::lws(path) ||
http2::lws(method) ||
(!is_connect && (!scheme || http2::lws(scheme)))) {
auto path = downstream->get_request_header(http2::HD__PATH);
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
auto host = downstream->get_request_header(http2::HD__HOST);
auto method = downstream->get_request_header(http2::HD__METHOD);
bool is_connect = method && "CONNECT" == method->value;
if (!path || !host || !method || !http2::non_empty_value(host) ||
!http2::non_empty_value(path) || !http2::non_empty_value(method) ||
(!is_connect && (!scheme || !http2::non_empty_value(scheme)))) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return;
}
downstream->set_request_method(method);
downstream->set_request_method(method->value);
if (is_connect) {
downstream->set_request_http2_authority(path);
downstream->set_request_http2_authority(path->value);
} else {
downstream->set_request_http2_scheme(scheme);
downstream->set_request_http2_authority(host);
downstream->set_request_path(path);
downstream->set_request_http2_scheme(scheme->value);
downstream->set_request_http2_authority(host->value);
downstream->set_request_path(path->value);
}
downstream->set_request_start_time(
@ -824,10 +815,10 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response header completed";
}
downstream->normalize_response_headers();
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) {
downstream->rewrite_norm_location_response_header(
downstream->rewrite_location_response_header(
get_client_handler()->get_upstream_scheme(), get_config()->port);
}
size_t nheader = downstream->get_response_headers().size();
@ -844,30 +835,39 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
nv[hdidx++] = ":version";
nv[hdidx++] = "HTTP/1.1";
for (auto &hd : downstream->get_response_headers()) {
if (hd.name.empty() || hd.name.c_str()[0] == ':' ||
util::strieq(hd.name.c_str(), "transfer-encoding") ||
util::strieq(hd.name.c_str(), "keep-alive") || // HTTP/1.0?
util::strieq(hd.name.c_str(), "connection") ||
util::strieq(hd.name.c_str(), "proxy-connection")) {
// These are ignored
} else if (!get_config()->no_via && util::strieq(hd.name.c_str(), "via")) {
via_value = hd.value;
} else if (!get_config()->http2_proxy && !get_config()->client_proxy &&
util::strieq(hd.name.c_str(), "server")) {
// Rewrite server header field later
} else {
nv[hdidx++] = hd.name.c_str();
nv[hdidx++] = hd.value.c_str();
if (hd.name.empty() || hd.name.c_str()[0] == ':') {
continue;
}
auto token = http2::lookup_token(hd.name);
switch (token) {
case http2::HD_CONNECTION:
case http2::HD_KEEP_ALIVE:
case http2::HD_PROXY_CONNECTION:
case http2::HD_TRANSFER_ENCODING:
case http2::HD_VIA:
case http2::HD_SERVER:
continue;
}
nv[hdidx++] = hd.name.c_str();
nv[hdidx++] = hd.value.c_str();
}
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
nv[hdidx++] = "server";
nv[hdidx++] = get_config()->server_name;
} else {
auto server = downstream->get_response_header(http2::HD_SERVER);
if (server) {
nv[hdidx++] = "server";
nv[hdidx++] = server->value.c_str();
}
}
if (!get_config()->no_via) {
if (!via_value.empty()) {
auto via = downstream->get_response_header(http2::HD_VIA);
if (via) {
via_value = via->value;
via_value += ", ";
}
via_value += http::create_via_header_value(