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', ':method',
':path', ':path',
':scheme', ':scheme',
# disallowed h1 headers ':status',
'connection', ':host', # for spdy
'expect', 'expect',
'host', 'host',
'if-modified-since', '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', 'keep-alive',
'proxy-connection', 'proxy-connection',
'te',
'transfer-encoding', 'transfer-encoding',
'upgrade' 'upgrade'
] ]
@ -20,9 +31,7 @@ HEADERS = [
def to_enum_hd(k): def to_enum_hd(k):
res = 'HD_' res = 'HD_'
for c in k.upper(): for c in k.upper():
if c == ':': if c == ':' or c == '-':
continue
if c == '-':
res += '_' res += '_'
continue continue
res += c res += c
@ -54,7 +63,7 @@ enum {'''
def gen_index_header(): def gen_index_header():
print '''\ 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) {''' switch (namelen) {'''
b = build_header(HEADERS) b = build_header(HEADERS)
for size in sorted(b.keys()): 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: for k in headers:
print '''\ print '''\
if (util::streq("{}", name, {})) {{ if (util::streq("{}", name, {})) {{
hdidx[{}] = idx; return {};
return;
}}'''.format(k[:-1], size - 1, to_enum_hd(k)) }}'''.format(k[:-1], size - 1, to_enum_hd(k))
print '''\ print '''\
break;''' break;'''
@ -80,6 +88,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) {
break;''' break;'''
print '''\ print '''\
} }
return -1;
}''' }'''
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -89,9 +89,12 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
namespace { namespace {
void append_nv(Stream *stream, const std::vector<nghttp2_nv> &nva) { void append_nv(Stream *stream, const std::vector<nghttp2_nv> &nva) {
size_t idx = 0; for (size_t i = 0; i < nva.size(); ++i) {
for (auto &nv : nva) { auto &nv = nva[i];
http2::index_header(stream->hdidx, nv.name, nv.namelen, idx++); 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, http2::add_header(stream->headers, nv.name, nv.namelen, nv.value,
nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); 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, int Http2Handler::submit_push_promise(Stream *stream,
const std::string &push_path) { const std::string &push_path) {
auto authority = auto authority =
http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers); http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
if (!authority) { if (!authority) {
authority = authority =
@ -900,9 +903,9 @@ void prepare_redirect_response(Stream *stream, Http2Handler *hd,
const std::string &path, const std::string &path,
const std::string &status) { const std::string &status) {
auto scheme = auto scheme =
http2::get_header(stream->hdidx, http2::HD_SCHEME, stream->headers); http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers);
auto authority = auto authority =
http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers); http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
if (!authority) { if (!authority) {
authority = authority =
http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers); 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) { bool allow_push = true) {
int rv; int rv;
auto reqpath = 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 = auto ims =
get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers); 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; return 0;
} }
if (namelen > 0 && name[0] == ':') { auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if ((!stream->headers.empty() && if ((!stream->headers.empty() &&
stream->headers.back().name.c_str()[0] != ':') || stream->headers.back().name.c_str()[0] != ':') ||
!http2::check_http2_request_pseudo_header(stream->hdidx, name, !http2::check_http2_request_pseudo_header(stream->hdidx, token)) {
namelen)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR); 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, http2::add_header(stream->headers, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX); flags & NGHTTP2_NV_FLAG_NO_INDEX);
return 0; return 0;
@ -1113,7 +1122,7 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { 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); hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
return 0; 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, Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
bool no_index) { 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)); 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);
}
}
return nullptr;
}
const Headers::value_type *get_header(const Headers &nva, const char *name) { const Headers::value_type *get_header(const Headers &nva, const char *name) {
auto nv = Headers::value_type(name, ""); const Headers::value_type *res = nullptr;
auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less); for (auto &nv : nva) {
if (i != std::end(nva) && (*i).name == nv.name) { if (nv.name == name) {
return &(*i); res = &nv;
} }
return nullptr; }
return res;
} }
std::string value_to_str(const Headers::value_type *nv) { std::string value_to_str(const Headers::value_type *nv) {
@ -371,40 +227,48 @@ nghttp2_nv make_nv(const std::string &name, const std::string &value,
value.size(), flags}; value.size(), flags};
} }
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva, void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
const Headers &headers) { for (auto &kv : headers) {
size_t i, j; if (kv.name.empty() || kv.name[0] == ':') {
for (i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) { continue;
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; switch (lookup_token(kv.name)) {
} else if (rv > 0) { case HD_COOKIE:
++j; case HD_CONNECTION:
} else { case HD_HTTP2_SETTINGS:
++i; 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;
} }
}
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)); nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
} }
} }
}
void build_http1_headers_from_norm_headers(std::string &hdrs, void build_http1_headers_from_headers(std::string &hdrs,
const Headers &headers) { const Headers &headers) {
size_t i, j; for (auto &kv : headers) {
for (i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) { if (kv.name.empty() || kv.name[0] == ':') {
auto &kv = headers[i]; continue;
auto rv = strcmp(kv.name.c_str(), HTTP1_IGN_HD[j]); }
switch (lookup_token(kv.name)) {
if (rv < 0) { case HD_CONNECTION:
if (!kv.name.empty() && kv.name.c_str()[0] != ':') { 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; hdrs += kv.name;
capitalize(hdrs, hdrs.size() - kv.name.size()); capitalize(hdrs, hdrs.size() - kv.name.size());
hdrs += ": "; hdrs += ": ";
@ -412,25 +276,6 @@ void build_http1_headers_from_norm_headers(std::string &hdrs,
sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
hdrs += "\r\n"; hdrs += "\r\n";
} }
++i;
} else if (rv > 0) {
++j;
} else {
++i;
}
}
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";
}
}
} }
int32_t determine_window_update_transmission(nghttp2_session *session, int32_t determine_window_update_transmission(nghttp2_session *session,
@ -572,18 +417,29 @@ int parse_http_status_code(const std::string &src) {
return status; 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 // This function was generated by genheaderfunc.py. Inspired by h2o
// header lookup. https://github.com/h2o/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) { switch (namelen) {
case 2: case 2:
switch (util::lowcase(name[namelen - 1])) { switch (util::lowcase(name[namelen - 1])) {
case 'e': case 'e':
if (util::streq("t", name, 1)) { if (util::streq("t", name, 1)) {
hdidx[HD_TE] = idx; return HD_TE;
return; }
break;
}
break;
case 3:
switch (util::lowcase(name[namelen - 1])) {
case 'a':
if (util::streq("vi", name, 2)) {
return HD_VIA;
} }
break; 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])) { switch (util::lowcase(name[namelen - 1])) {
case 't': case 't':
if (util::streq("hos", name, 3)) { if (util::streq("hos", name, 3)) {
hdidx[HD_HOST] = idx; return HD_HOST;
return;
} }
break; 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])) { switch (util::lowcase(name[namelen - 1])) {
case 'h': case 'h':
if (util::streq(":pat", name, 4)) { if (util::streq(":pat", name, 4)) {
hdidx[HD_PATH] = idx; return HD__PATH;
return; }
break;
case 't':
if (util::streq(":hos", name, 4)) {
return HD__HOST;
} }
break; break;
} }
break; break;
case 6: case 6:
switch (util::lowcase(name[namelen - 1])) { 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': case 't':
if (util::streq("expec", name, 5)) { if (util::streq("expec", name, 5)) {
hdidx[HD_EXPECT] = idx; return HD_EXPECT;
return;
} }
break; break;
} }
break; break;
case 7: case 7:
switch (util::lowcase(name[namelen - 1])) { switch (util::lowcase(name[namelen - 1])) {
case 'c':
if (util::streq("alt-sv", name, 6)) {
return HD_ALT_SVC;
}
break;
case 'd': case 'd':
if (util::streq(":metho", name, 6)) { if (util::streq(":metho", name, 6)) {
hdidx[HD_METHOD] = idx; return HD__METHOD;
return;
} }
break; break;
case 'e': case 'e':
if (util::streq(":schem", name, 6)) { if (util::streq(":schem", name, 6)) {
hdidx[HD_SCHEME] = idx; return HD__SCHEME;
return;
} }
if (util::streq("upgrad", name, 6)) { if (util::streq("upgrad", name, 6)) {
hdidx[HD_UPGRADE] = idx; return HD_UPGRADE;
return; }
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; 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])) { switch (util::lowcase(name[namelen - 1])) {
case 'e': case 'e':
if (util::streq("keep-aliv", name, 9)) { if (util::streq("keep-aliv", name, 9)) {
hdidx[HD_KEEP_ALIVE] = idx; return HD_KEEP_ALIVE;
return;
} }
break; break;
case 'n': case 'n':
if (util::streq("connectio", name, 9)) { if (util::streq("connectio", name, 9)) {
hdidx[HD_CONNECTION] = idx; return HD_CONNECTION;
return;
} }
break; break;
case 'y': case 'y':
if (util::streq(":authorit", name, 9)) { if (util::streq(":authorit", name, 9)) {
hdidx[HD_AUTHORITY] = idx; return HD__AUTHORITY;
return; }
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; 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])) { switch (util::lowcase(name[namelen - 1])) {
case 'n': case 'n':
if (util::streq("proxy-connectio", name, 15)) { if (util::streq("proxy-connectio", name, 15)) {
hdidx[HD_PROXY_CONNECTION] = idx; return HD_PROXY_CONNECTION;
return;
} }
break; 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])) { switch (util::lowcase(name[namelen - 1])) {
case 'e': case 'e':
if (util::streq("if-modified-sinc", name, 16)) { if (util::streq("if-modified-sinc", name, 16)) {
hdidx[HD_IF_MODIFIED_SINCE] = idx; return HD_IF_MODIFIED_SINCE;
return;
} }
break; break;
case 'g': case 'g':
if (util::streq("transfer-encodin", name, 16)) { if (util::streq("transfer-encodin", name, 16)) {
hdidx[HD_TRANSFER_ENCODING] = idx; return HD_TRANSFER_ENCODING;
}
break;
case 'o':
if (util::streq("x-forwarded-prot", name, 16)) {
return HD_X_FORWARDED_PROTO;
}
break;
}
break;
}
return -1;
}
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);
}
}
}
void index_header(int *hdidx, int token, size_t idx) {
if (token == -1) {
return; return;
} }
break; assert(token < HD_MAXIDX);
hdidx[token] = idx;
} }
break;
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_request_pseudo_header(int *hdidx, const uint8_t *s, bool check_http2_response_pseudo_header(const int *hdidx, int token) {
size_t len) { switch (token) {
switch (len) { case HD__STATUS:
case 5: return hdidx[token] == -1;
switch (util::lowcase(s[len - 1])) { default:
case 'h':
if (util::streq(":pat", s, 4)) {
if (hdidx[HD_PATH] != -1) {
return false; return false;
} }
return true;
}
break;
}
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) { bool http2_header_allowed(int token) {
if (hdidx[HD_CONNECTION] != -1 || hdidx[HD_KEEP_ALIVE] != -1 || switch (token) {
hdidx[HD_PROXY_CONNECTION] != -1 || hdidx[HD_TE] != -1 || case HD_CONNECTION:
hdidx[HD_TRANSFER_ENCODING] != -1 || hdidx[HD_UPGRADE] != -1) { 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 false;
} }
return true; return true;
} }
bool check_http2_request_headers(int *hdidx) { const Headers::value_type *get_header(const int *hdidx, int token,
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 &nva) { const Headers &nva) {
auto i = hdidx[hdkey]; auto i = hdidx[token];
if (i == -1) { if (i == -1) {
return nullptr; 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, void copy_url_component(std::string &dest, const http_parser_url *u, int field,
const char *url); 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, Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
bool no_index); 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, void add_header(Headers &nva, const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index); const uint8_t *value, size_t valuelen, bool no_index);
// Returns the iterator to the entry in |nva| which has name |name| // Returns pointer to the entry in |nva| which has name |name|. If
// and the |name| is uinque in the |nva|. If no such entry exist, // more than one entries which have the name |name|, last occurrence
// returns nullptr. // in |nva| is returned. 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.
const Headers::value_type *get_header(const Headers &nva, const char *name); const Headers::value_type *get_header(const Headers &nva, const char *name);
// Returns nv->second if nv is not nullptr. Otherwise, returns "". // Returns nv->second if nv is not nullptr. Otherwise, returns "".
@ -165,13 +129,12 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
// Appends headers in |headers| to |nv|. Certain headers, including // Appends headers in |headers| to |nv|. Certain headers, including
// disallowed headers in HTTP/2 spec and headers which require // disallowed headers in HTTP/2 spec and headers which require
// special handling (i.e. via), are not copied. // special handling (i.e. via), are not copied.
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva, void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
const Headers &headers);
// Appends HTTP/1.1 style header lines to |hdrs| from headers in // Appends HTTP/1.1 style header lines to |hdrs| from headers in
// |headers|. Certain headers, which requires special handling // |headers|. Certain headers, which requires special handling
// (i.e. via and cookie), are not appended. // (i.e. via and cookie), are not appended.
void build_http1_headers_from_norm_headers(std::string &hdrs, void build_http1_headers_from_headers(std::string &hdrs,
const Headers &headers); const Headers &headers);
// Return positive window_size_increment if WINDOW_UPDATE should be // Return positive window_size_increment if WINDOW_UPDATE should be
@ -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. // Returns parsed HTTP status code. Returns -1 on failure.
int parse_http_status_code(const std::string &src); 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 { enum {
HD_AUTHORITY, HD__AUTHORITY,
HD_METHOD, HD__HOST,
HD_PATH, HD__METHOD,
HD_SCHEME, HD__PATH,
HD__SCHEME,
HD__STATUS,
HD_ALT_SVC,
HD_CONNECTION, HD_CONNECTION,
HD_CONTENT_LENGTH,
HD_COOKIE,
HD_EXPECT, HD_EXPECT,
HD_HOST, HD_HOST,
HD_HTTP2_SETTINGS,
HD_IF_MODIFIED_SINCE, HD_IF_MODIFIED_SINCE,
HD_KEEP_ALIVE, HD_KEEP_ALIVE,
HD_LOCATION,
HD_PROXY_CONNECTION, HD_PROXY_CONNECTION,
HD_SERVER,
HD_TE, HD_TE,
HD_TRANSFER_ENCODING, HD_TRANSFER_ENCODING,
HD_UPGRADE, HD_UPGRADE,
HD_VIA,
HD_X_FORWARDED_FOR,
HD_X_FORWARDED_PROTO,
HD_MAXIDX, 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 // Initializes |hdidx|, header index. The |hdidx| must point to the
// array containing at least HD_MAXIDX elements. // array containing at least HD_MAXIDX elements.
void init_hdidx(int *hdidx); void init_hdidx(int *hdidx);
// Indexes header |name| of length |namelen| using index |idx|. // Indexes header |token| using index |idx|.
void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t 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 // Returns true if HTTP/2 request pseudo header |token| is not indexed
// allowed for HTTP/2 request. // yet and not -1.
bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *name, bool check_http2_request_pseudo_header(const int *hdidx, int token);
size_t namelen);
// Checks |hdidx| does not contain disallowed headers. // Returns true if HTTP/2 response pseudo header |token| is not
bool check_http2_headers(int *hdidx); // indexed yet and not -1.
// Checks |hdidx| does not contain disallowed headers and contains bool check_http2_response_pseudo_header(const int *hdidx, int token);
// mandatory headers. This funtions internally calls
// check_http2_headers().
bool check_http2_request_headers(int *hdidx);
// Returns header denoted by |hdkey| using index |hdidx|. // Returns true if header field denoted by |token| is allowed for
const Headers::value_type *get_header(int *hdidx, int hdkey, // 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); const Headers &nva);
} // namespace http2 } // namespace http2

View File

@ -100,55 +100,14 @@ void test_http2_add_header(void) {
CU_ASSERT(Headers::value_type("a", "") == nva[0]); 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) { void test_http2_get_header(void) {
auto nva = Headers{{"alpha", "1"}, auto nva = Headers{{"alpha", "1"},
{"bravo", "2"}, {"bravo", "2"},
{"bravo", "3"}, {"bravo", "3"},
{"charlie", "4"}, {"charlie", "4"},
{"delta", "5"}, {"delta", "5"},
{"echo", "6"}}; {"echo", "6"},
{"content-length", "7"}};
const Headers::value_type *rv; const Headers::value_type *rv;
rv = http2::get_header(nva, "delta"); rv = http2::get_header(nva, "delta");
CU_ASSERT(rv != nullptr); CU_ASSERT(rv != nullptr);
@ -160,6 +119,12 @@ void test_http2_get_header(void) {
rv = http2::get_header(nva, "foxtrot"); rv = http2::get_header(nva, "foxtrot");
CU_ASSERT(rv == nullptr); 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 { namespace {
@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true},
{"zulu", "12"}}; {"zulu", "12"}};
} // namespace } // namespace
void test_http2_copy_norm_headers_to_nva(void) { void test_http2_copy_headers_to_nva(void) {
std::vector<nghttp2_nv> nva; std::vector<nghttp2_nv> nva;
http2::copy_norm_headers_to_nva(nva, headers); http2::copy_headers_to_nva(nva, headers);
CU_ASSERT(7 == nva.size()); CU_ASSERT(9 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12}; auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 8, 9, 12};
for (size_t i = 0; i < ans.size(); ++i) { for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[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; 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" CU_ASSERT(hdrs == "Alpha: 0\r\n"
"Bravo: 1\r\n" "Bravo: 1\r\n"
"Delta: 4\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: 8\r\n"
"Te: 9\r\n" "Te: 9\r\n"
"Zulu: 12\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) { void test_http2_lws(void) {
@ -274,12 +230,64 @@ void test_http2_index_header(void) {
int hdidx[http2::HD_MAXIDX]; int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx); http2::init_hdidx(hdidx);
http2::index_header(hdidx, reinterpret_cast<const uint8_t *>(":authority"), http2::index_header(hdidx, http2::HD__AUTHORITY, 0);
10, 0); http2::index_header(hdidx, -1, 1);
http2::index_header(hdidx, reinterpret_cast<const uint8_t *>("hos"), 3, 1);
CU_ASSERT(0 == hdidx[http2::HD_AUTHORITY]); CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]);
CU_ASSERT(-1 == hdidx[http2::HD_HOST]); }
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 } // namespace shrpx

View File

@ -28,15 +28,17 @@
namespace shrpx { namespace shrpx {
void test_http2_add_header(void); 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_get_header(void);
void test_http2_copy_norm_headers_to_nva(void); void test_http2_copy_headers_to_nva(void);
void test_http2_build_http1_headers_from_norm_headers(void); void test_http2_build_http1_headers_from_headers(void);
void test_http2_lws(void); void test_http2_lws(void);
void test_http2_rewrite_location_uri(void); void test_http2_rewrite_location_uri(void);
void test_http2_parse_http_status_code(void); void test_http2_parse_http_status_code(void);
void test_http2_index_header(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 } // namespace shrpx

View File

@ -224,6 +224,9 @@ struct Request {
int level; int level;
// RequestPriority value defined in HtmlParser.h // RequestPriority value defined in HtmlParser.h
int pri; int pri;
int res_hdidx[http2::HD_MAXIDX];
// used for incoming PUSH_PROMISE
int req_hdidx[http2::HD_MAXIDX];
bool expect_final_response; bool expect_final_response;
// For pushed request, |uri| is empty and |u| is zero-cleared. // 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), data_length(data_length), data_offset(0), response_len(0),
inflater(nullptr), html_parser(nullptr), data_prd(data_prd), inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
stream_id(-1), status(0), level(level), pri(pri), 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() { ~Request() {
nghttp2_gzip_inflate_del(inflater); nghttp2_gzip_inflate_del(inflater);
@ -354,12 +360,47 @@ struct Request {
} }
} }
bool response_pseudo_header_allowed() const { bool response_pseudo_header_allowed(int token) const {
return res_nva.empty() || res_nva.back().name.c_str()[0] == ':'; 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 { bool push_request_pseudo_header_allowed(int token) const {
return res_nva.empty() || req_nva.back().name.c_str()[0] == ':'; 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() { 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); 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>(); auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size()); nva.reserve(build_headers.size());
@ -1690,30 +1729,29 @@ void check_response_header(nghttp2_session *session, Request *req) {
req->expect_final_response = false; req->expect_final_response = false;
for (auto &nv : req->res_nva) { auto status_hd = req->get_res_header(http2::HD__STATUS);
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) {
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_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
return; return;
} }
req->status = status; req->status = status;
}
}
if (req->status == 0) { for (auto &nv : req->res_nva) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, if ("content-encoding" == nv.name) {
NGHTTP2_PROTOCOL_ERROR); gzip =
return; util::strieq("gzip", nv.value) || util::strieq("deflate", nv.value);
continue;
}
} }
if (req->status / 100 == 1) { if (req->status / 100 == 1) {
@ -1797,15 +1835,17 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
break; break;
} }
if (namelen > 0 && name[0] == ':') { auto token = http2::lookup_token(name, namelen);
if (!req->response_pseudo_header_allowed() ||
!http2::check_http2_response_pseudo_header(name, namelen)) { if (name[0] == ':') {
if (!req->response_pseudo_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 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, http2::add_header(req->res_nva, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX); flags & NGHTTP2_NV_FLAG_NO_INDEX);
break; break;
@ -1818,9 +1858,10 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
break; break;
} }
if (namelen > 0 && name[0] == ':') { auto token = http2::lookup_token(name, namelen);
if (!req->push_request_pseudo_header_allowed() ||
!http2::check_http2_request_pseudo_header(name, namelen)) { if (name[0] == ':') {
if (!req->push_request_pseudo_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id, frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR); 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, http2::add_header(req->req_nva, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX); flags & NGHTTP2_NV_FLAG_NO_INDEX);
break; break;
@ -1895,36 +1937,27 @@ int on_frame_recv_callback2(nghttp2_session *session,
if (!req) { if (!req) {
break; break;
} }
std::string scheme, authority, method, path; auto scheme = req->get_req_header(http2::HD__SCHEME);
for (auto &nv : req->req_nva) { auto authority = req->get_req_header(http2::HD__AUTHORITY);
if (nv.name == ":scheme") { auto method = req->get_req_header(http2::HD__METHOD);
scheme = nv.value; auto path = req->get_req_header(http2::HD__PATH);
continue;
if (!authority) {
authority = req->get_req_header(http2::HD_HOST);
} }
if (nv.name == ":authority" || nv.name == "host") {
authority = nv.value; if (!scheme || !authority || !method || !path || scheme->value.empty() ||
continue; authority->value.empty() || method->value.empty() ||
} path->value.empty() || path->value[0] != '/') {
if (nv.name == ":method") {
method = nv.value;
continue;
}
if (nv.name == ":path") {
path = nv.value;
continue;
}
}
if (scheme.empty() || authority.empty() || method.empty() || path.empty() ||
path[0] != '/') {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id, frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
break; break;
} }
std::string uri = scheme; std::string uri = scheme->value;
uri += "://"; uri += "://";
uri += authority; uri += authority->value;
uri += path; uri += path->value;
http_parser_url u; http_parser_url u;
memset(&u, 0, sizeof(u)); memset(&u, 0, sizeof(u));
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { 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", !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) || shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) || !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_get_header", shrpx::test_http2_get_header) ||
!CU_add_test(pSuite, "http2_copy_norm_headers_to_nva", !CU_add_test(pSuite, "http2_copy_headers_to_nva",
shrpx::test_http2_copy_norm_headers_to_nva) || shrpx::test_http2_copy_headers_to_nva) ||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers", !CU_add_test(pSuite, "http2_build_http1_headers_from_headers",
shrpx::test_http2_build_http1_headers_from_norm_headers) || shrpx::test_http2_build_http1_headers_from_headers) ||
!CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) || !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
!CU_add_test(pSuite, "http2_rewrite_location_uri", !CU_add_test(pSuite, "http2_rewrite_location_uri",
shrpx::test_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) || shrpx::test_http2_parse_http_status_code) ||
!CU_add_test(pSuite, "http2_index_header", !CU_add_test(pSuite, "http2_index_header",
shrpx::test_http2_index_header) || shrpx::test_http2_index_header) ||
!CU_add_test(pSuite, "downstream_normalize_request_headers", !CU_add_test(pSuite, "http2_lookup_token",
shrpx::test_downstream_normalize_request_headers) || shrpx::test_http2_lookup_token) ||
!CU_add_test(pSuite, "downstream_normalize_response_headers", !CU_add_test(pSuite, "http2_check_http2_pseudo_header",
shrpx::test_downstream_normalize_response_headers) || shrpx::test_http2_check_http2_pseudo_header) ||
!CU_add_test(pSuite, "downstream_get_norm_request_header", !CU_add_test(pSuite, "http2_http2_header_allowed",
shrpx::test_downstream_get_norm_request_header) || shrpx::test_http2_http2_header_allowed) ||
!CU_add_test(pSuite, "downstream_get_norm_response_header", !CU_add_test(pSuite, "http2_mandatory_request_headers_presence",
shrpx::test_downstream_get_norm_response_header) || 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", !CU_add_test(pSuite, "downstream_crumble_request_cookie",
shrpx::test_downstream_crumble_request_cookie) || shrpx::test_downstream_crumble_request_cookie) ||
!CU_add_test(pSuite, "downstream_assemble_request_cookie", !CU_add_test(pSuite, "downstream_assemble_request_cookie",
shrpx::test_downstream_assemble_request_cookie) || shrpx::test_downstream_assemble_request_cookie) ||
!CU_add_test( !CU_add_test(pSuite, "downstream_rewrite_location_response_header",
pSuite, "downstream_rewrite_norm_location_response_header", shrpx::test_downstream_rewrite_location_response_header) ||
shrpx::test_downstream_rewrite_norm_location_response_header) ||
!CU_add_test(pSuite, "config_parse_config_str_list", !CU_add_test(pSuite, "config_parse_config_str_list",
shrpx::test_shrpx_config_parse_config_str_list) || shrpx::test_shrpx_config_parse_config_str_list) ||
!CU_add_test(pSuite, "config_parse_header", !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), request_state_(INITIAL), request_major_(1), request_minor_(1),
response_state_(INITIAL), response_http_status_(0), response_major_(1), response_state_(INITIAL), response_http_status_(0), response_major_(1),
response_minor_(1), upgrade_request_(false), upgraded_(false), response_minor_(1), upgrade_request_(false), upgraded_(false),
http2_upgrade_seen_(false), http2_settings_seen_(false), http2_upgrade_seen_(false), chunked_request_(false),
chunked_request_(false), request_connection_close_(false), request_connection_close_(false), request_header_key_prev_(false),
request_header_key_prev_(false), request_http2_expect_body_(false), request_http2_expect_body_(false), chunked_response_(false),
chunked_response_(false), response_connection_close_(false), response_connection_close_(false), response_header_key_prev_(false),
response_header_key_prev_(false), expect_final_response_(false), expect_final_response_(false) {
request_headers_normalized_(false) {
ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0., ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
get_config()->stream_read_timeout); get_config()->stream_read_timeout);
@ -136,6 +135,9 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
upstream_wtimer_.data = this; upstream_wtimer_.data = this;
downstream_rtimer_.data = this; downstream_rtimer_.data = this;
downstream_wtimer_.data = this; downstream_wtimer_.data = this;
http2::init_hdidx(request_hdidx_);
http2::init_hdidx(response_hdidx_);
} }
Downstream::~Downstream() { Downstream::~Downstream() {
@ -213,35 +215,15 @@ void Downstream::force_resume_read() {
} }
namespace { namespace {
Headers::const_iterator get_norm_header(const Headers &headers, const Headers::value_type *get_header_linear(const Headers &headers,
const std::string &name) { const std::string &name) {
auto i = std::lower_bound(std::begin(headers), std::end(headers), const Headers::value_type *res = nullptr;
Header(name, ""), http2::name_less); for (auto &kv : headers) {
if (i != std::end(headers) && (*i).name == name) { if (kv.name == name) {
return i; res = &kv;
} }
return std::end(headers);
} }
} // namespace return res;
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;
} }
} // namespace } // namespace
@ -253,7 +235,11 @@ void Downstream::assemble_request_cookie() {
std::string &cookie = assembled_request_cookie_; std::string &cookie = assembled_request_cookie_;
cookie = ""; cookie = "";
for (auto &kv : request_headers_) { for (auto &kv : request_headers_) {
if (util::strieq("cookie", kv.name.c_str())) { 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(" ;"); auto end = kv.value.find_last_not_of(" ;");
if (end == std::string::npos) { if (end == std::string::npos) {
cookie += kv.value; cookie += kv.value;
@ -262,19 +248,19 @@ void Downstream::assemble_request_cookie() {
} }
cookie += "; "; cookie += "; ";
} }
}
if (cookie.size() >= 2) { if (cookie.size() >= 2) {
cookie.erase(cookie.size() - 2); cookie.erase(cookie.size() - 2);
} }
} }
void Downstream::crumble_request_cookie() { Headers Downstream::crumble_request_cookie() {
Headers cookie_hdrs; Headers cookie_hdrs;
for (auto &kv : request_headers_) { for (auto &kv : request_headers_) {
if (util::strieq("cookie", kv.name.c_str())) { if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq("cooki", kv.name.c_str(), 5)) {
continue;
}
size_t last = kv.value.size(); size_t last = kv.value.size();
size_t num = 0;
std::string rep_cookie;
for (size_t j = 0; j < last;) { for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j); j = kv.value.find_first_not_of("\t ;", j);
@ -288,57 +274,33 @@ void Downstream::crumble_request_cookie() {
j = last; j = last;
} }
if (num == 0) {
if (first == 0 && j == last) {
break;
}
rep_cookie = kv.value.substr(first, j - first);
} else {
cookie_hdrs.push_back( cookie_hdrs.push_back(
Header("cookie", kv.value.substr(first, j - first), kv.no_index)); Header("cookie", kv.value.substr(first, j - first), kv.no_index));
} }
++num;
}
if (num > 0) {
kv.value = std::move(rep_cookie);
}
}
}
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 { const std::string &Downstream::get_assembled_request_cookie() const {
return assembled_request_cookie_; return assembled_request_cookie_;
} }
void Downstream::normalize_request_headers() { void Downstream::index_request_headers() {
http2::normalize_headers(request_headers_); for (auto &kv : request_headers_) {
request_headers_normalized_ = true; util::inp_strlower(kv.name);
}
http2::index_headers(request_hdidx_, request_headers_);
} }
Headers::const_iterator const Headers::value_type *Downstream::get_request_header(int token) const {
Downstream::get_norm_request_header(const std::string &name) const { return http2::get_header(request_hdidx_, token, request_headers_);
return get_norm_header(request_headers_, name);
} }
Headers::const_iterator const Headers::value_type *
Downstream::get_request_header(const std::string &name) const { Downstream::get_request_header(const std::string &name) const {
if (request_headers_normalized_) {
return get_norm_request_header(name);
}
return get_header_linear(request_headers_, name); return get_header_linear(request_headers_, name);
} }
bool Downstream::get_request_headers_normalized() const {
return request_headers_normalized_;
}
void Downstream::add_request_header(std::string name, std::string value) { void Downstream::add_request_header(std::string name, std::string value) {
request_header_key_prev_ = true; request_header_key_prev_ = true;
request_headers_sum_ += name.size() + value.size(); request_headers_sum_ += name.size() + value.size();
@ -352,9 +314,10 @@ void Downstream::set_last_request_header_value(std::string value) {
item.value = std::move(value); item.value = std::move(value);
} }
void Downstream::split_add_request_header(const uint8_t *name, size_t namelen, void Downstream::add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
bool no_index) { bool no_index, int token) {
http2::index_header(request_hdidx_, token, request_headers_.size());
request_headers_sum_ += namelen + valuelen; request_headers_sum_ += namelen + valuelen;
http2::add_header(request_headers_, name, namelen, value, valuelen, no_index); 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); 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 { size_t Downstream::get_request_headers_sum() const {
return request_headers_sum_; return request_headers_sum_;
@ -524,19 +490,23 @@ const Headers &Downstream::get_response_headers() const {
return response_headers_; return response_headers_;
} }
void Downstream::normalize_response_headers() { void Downstream::index_response_headers() {
http2::normalize_headers(response_headers_); for (auto &kv : response_headers_) {
util::inp_strlower(kv.name);
}
http2::index_headers(response_hdidx_, response_headers_);
} }
Headers::const_iterator const Headers::value_type *Downstream::get_response_header(int token) const {
Downstream::get_norm_response_header(const std::string &name) const { return http2::get_header(response_hdidx_, token, response_headers_);
return get_norm_header(response_headers_, name);
} }
void Downstream::rewrite_norm_location_response_header( void
const std::string &upstream_scheme, uint16_t upstream_port) { Downstream::rewrite_location_response_header(const std::string &upstream_scheme,
auto hd = get_norm_header(response_headers_, "location"); uint16_t upstream_port) {
if (hd == std::end(response_headers_)) { auto hd =
http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_);
if (!hd) {
return; return;
} }
http_parser_url u; http_parser_url u;
@ -553,15 +523,16 @@ void Downstream::rewrite_norm_location_response_header(
upstream_scheme, upstream_port); upstream_scheme, upstream_port);
} }
if (new_uri.empty()) { if (new_uri.empty()) {
auto host = get_norm_request_header("host"); auto host = get_request_header(http2::HD_HOST);
if (host == std::end(request_headers_)) { if (!host) {
return; return;
} }
new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value, new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value,
upstream_scheme, upstream_port); upstream_scheme, upstream_port);
} }
if (!new_uri.empty()) { 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); item.value = std::move(value);
} }
void Downstream::split_add_response_header(const uint8_t *name, size_t namelen, void Downstream::add_response_header(const uint8_t *name, size_t namelen,
const uint8_t *value, const uint8_t *value, size_t valuelen,
size_t valuelen, bool no_index) { bool no_index, int token) {
http2::index_header(response_hdidx_, token, response_headers_.size());
response_headers_sum_ += namelen + valuelen; response_headers_sum_ += namelen + valuelen;
http2::add_header(response_headers_, name, namelen, value, valuelen, http2::add_header(response_headers_, name, namelen, value, valuelen,
no_index); no_index);
@ -605,7 +577,10 @@ void Downstream::append_last_response_header_value(const char *data,
item.value.append(data, len); 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 { size_t Downstream::get_response_headers_sum() const {
return response_headers_sum_; return response_headers_sum_;
@ -717,39 +692,33 @@ void Downstream::inspect_http1_request() {
upgrade_request_ = true; upgrade_request_ = true;
} }
for (auto &hd : request_headers_) { if (!upgrade_request_) {
if (!upgrade_request_ && util::strieq("upgrade", hd.name.c_str())) { auto idx = request_hdidx_[http2::HD_UPGRADE];
// TODO Perform more strict checking for upgrade headers if (idx != -1) {
upgrade_request_ = true; upgrade_request_ = true;
if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, hd.value.c_str(), auto &val = request_headers_[idx].value;
hd.value.size())) { // 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; http2_upgrade_seen_ = true;
} }
} else if (!http2_settings_seen_ && }
util::strieq(hd.name.c_str(), "http2-settings")) { }
auto idx = request_hdidx_[http2::HD_TRANSFER_ENCODING];
http2_settings_seen_ = true; if (idx != -1 &&
http2_settings_ = hd.value; util::strifind(request_headers_[idx].value.c_str(), "chunked")) {
} else if (!chunked_request_ &&
util::strieq(hd.name.c_str(), "transfer-encoding")) {
if (util::strifind(hd.value.c_str(), "chunked")) {
chunked_request_ = true; chunked_request_ = true;
} }
} }
}
}
void Downstream::inspect_http1_response() { void Downstream::inspect_http1_response() {
for (auto &hd : response_headers_) { auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING];
if (!chunked_response_ && if (idx != -1 &&
util::strieq(hd.name.c_str(), "transfer-encoding")) { util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
if (util::strifind(hd.value.c_str(), "chunked")) {
chunked_response_ = true; chunked_response_ = true;
} }
} }
}
}
void Downstream::reset_response() { void Downstream::reset_response() {
response_http_status_ = 0; response_http_status_ = 0;
@ -766,11 +735,20 @@ bool Downstream::get_upgraded() const { return upgraded_; }
bool Downstream::get_upgrade_request() const { return upgrade_request_; } bool Downstream::get_upgrade_request() const { return upgrade_request_; }
bool Downstream::get_http2_upgrade_request() const { 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 { 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) { void Downstream::set_downstream_stream_id(int32_t stream_id) {
@ -832,12 +810,18 @@ bool pseudo_header_allowed(const Headers &headers) {
} }
} // namespace } // namespace
bool Downstream::request_pseudo_header_allowed() const { bool Downstream::request_pseudo_header_allowed(int token) const {
return pseudo_header_allowed(request_headers_); 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 { bool Downstream::response_pseudo_header_allowed(int token) const {
return pseudo_header_allowed(response_headers_); if (!pseudo_header_allowed(response_headers_)) {
return false;
}
return http2::check_http2_response_pseudo_header(response_hdidx_, token);
} }
void Downstream::init_upstream_timer() { void Downstream::init_upstream_timer() {

View File

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

View File

@ -32,7 +32,7 @@
namespace shrpx { namespace shrpx {
void test_downstream_normalize_request_headers(void) { void test_downstream_index_request_headers(void) {
Downstream d(nullptr, 0, 0); Downstream d(nullptr, 0, 0);
d.add_request_header("1", "0"); d.add_request_header("1", "0");
d.add_request_header("2", "1"); 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("BravO", "5");
d.add_request_header(":method", "6"); d.add_request_header(":method", "6");
d.add_request_header(":authority", "7"); d.add_request_header(":authority", "7");
d.normalize_request_headers(); d.index_request_headers();
auto ans = Headers{{":authority", "7"}, auto ans = Headers{{"1", "0"},
{":method", "6"},
{"1", "0"},
{"2", "1"}, {"2", "1"},
{"alpha", "3"},
{"bravo", "5"},
{"charlie", "2"}, {"charlie", "2"},
{"delta", "4"}}; {"alpha", "3"},
{"delta", "4"},
{"bravo", "5"},
{":method", "6"},
{":authority", "7"}};
CU_ASSERT(ans == d.get_request_headers()); 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); Downstream d(nullptr, 0, 0);
d.add_response_header("Charlie", "0"); d.add_response_header("Charlie", "0");
d.add_response_header("Alpha", "1"); d.add_response_header("Alpha", "1");
d.add_response_header("Delta", "2"); d.add_response_header("Delta", "2");
d.add_response_header("BravO", "3"); d.add_response_header("BravO", "3");
d.normalize_response_headers(); d.index_response_headers();
auto ans = 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()); 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); Downstream d(nullptr, 0, 0);
d.add_request_header("alpha", "0"); d.add_request_header("alpha", "0");
d.add_request_header("bravo", "1"); d.add_request_header(":authority", "1");
d.add_request_header("bravo", "2"); d.add_request_header("content-length", "2");
d.add_request_header("charlie", "3"); d.index_request_headers();
d.add_request_header("delta", "4");
d.add_request_header("echo", "5"); // By token
auto i = d.get_norm_request_header("alpha"); CU_ASSERT(Header(":authority", "1") ==
CU_ASSERT(Header("alpha", "0") == *i); *d.get_request_header(http2::HD__AUTHORITY));
i = d.get_norm_request_header("bravo"); CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
CU_ASSERT(Header("bravo", "1") == *i);
i = d.get_norm_request_header("delta"); // By name
CU_ASSERT(Header("delta", "4") == *i); CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
i = d.get_norm_request_header("echo"); CU_ASSERT(nullptr == d.get_request_header("bravo"));
CU_ASSERT(Header("echo", "5") == *i);
i = d.get_norm_request_header("foxtrot");
CU_ASSERT(i == std::end(d.get_request_headers()));
} }
void test_downstream_get_norm_response_header(void) { void test_downstream_get_response_header(void) {
Downstream d(nullptr, 0, 0); Downstream d(nullptr, 0, 0);
d.add_response_header("alpha", "0"); d.add_response_header("alpha", "0");
d.add_response_header("bravo", "1"); d.add_response_header(":status", "1");
d.add_response_header("bravo", "2"); d.add_response_header("content-length", "2");
d.add_response_header("charlie", "3"); d.index_response_headers();
d.add_response_header("delta", "4");
d.add_response_header("echo", "5"); // By token
auto i = d.get_norm_response_header("alpha"); CU_ASSERT(Header(":status", "1") ==
CU_ASSERT(Header("alpha", "0") == *i); *d.get_response_header(http2::HD__STATUS));
i = d.get_norm_response_header("bravo"); CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
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()));
} }
void test_downstream_crumble_request_cookie(void) { void test_downstream_crumble_request_cookie(void) {
Downstream d(nullptr, 0, 0); Downstream d(nullptr, 0, 0);
d.add_request_header(":method", "get"); d.add_request_header(":method", "get");
d.add_request_header(":path", "/"); 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", ";delta");
d.add_request_header("cookie", "echo"); d.add_request_header("cookie", "echo");
d.crumble_request_cookie(); auto cookies = d.crumble_request_cookie();
Headers ans = {{":method", "get"},
{":path", "/"}, Headers ans = {{"cookie", "alpha"},
{"cookie", "alpha"},
{"cookie", "delta"},
{"cookie", "echo"},
{"cookie", "bravo"}, {"cookie", "bravo"},
{"cookie", "charlie"}}; {"cookie", "charlie"},
CU_ASSERT(ans == d.get_request_headers()); {"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) { 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()); 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); Downstream d(nullptr, 0, 0);
d.add_request_header("host", "localhost:3000"); d.add_request_header("host", "localhost:3000");
d.add_response_header("location", "http://localhost:3000/"); d.add_response_header("location", "http://localhost:3000/");
d.rewrite_norm_location_response_header("https", 443); d.index_request_headers();
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); CU_ASSERT("https://localhost/" == (*location).value);
} }
{ {
Downstream d(nullptr, 0, 0); Downstream d(nullptr, 0, 0);
d.set_request_http2_authority("localhost"); d.set_request_http2_authority("localhost");
d.add_response_header("location", "http://localhost/"); d.add_response_header("location", "http://localhost/");
d.rewrite_norm_location_response_header("https", 443); d.index_response_headers();
auto location = d.get_norm_response_header("location"); d.rewrite_location_response_header("https", 443);
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value); CU_ASSERT("https://localhost/" == (*location).value);
} }
} }

View File

@ -27,13 +27,13 @@
namespace shrpx { namespace shrpx {
void test_downstream_normalize_request_headers(void); void test_downstream_index_request_headers(void);
void test_downstream_normalize_response_headers(void); void test_downstream_index_response_headers(void);
void test_downstream_get_norm_request_header(void); void test_downstream_get_request_header(void);
void test_downstream_get_norm_response_header(void); void test_downstream_get_response_header(void);
void test_downstream_crumble_request_cookie(void); void test_downstream_crumble_request_cookie(void);
void test_downstream_assemble_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 } // namespace shrpx

View File

@ -233,14 +233,12 @@ int Http2DownstreamConnection::push_request_headers() {
return 0; return 0;
} }
size_t nheader = downstream_->get_request_headers().size(); size_t nheader = downstream_->get_request_headers().size();
Headers cookies;
if (!get_config()->http2_no_cookie_crumbling) { 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: // 7 means:
// 1. :method // 1. :method
// 2. :scheme // 2. :scheme
@ -250,10 +248,12 @@ int Http2DownstreamConnection::push_request_headers() {
// 6. x-forwarded-for (optional) // 6. x-forwarded-for (optional)
// 7. x-forwarded-proto (optional) // 7. x-forwarded-proto (optional)
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
nva.reserve(nheader + 7); nva.reserve(nheader + 7 + cookies.size());
std::string via_value; std::string via_value;
std::string xff_value; std::string xff_value;
std::string scheme, authority, path, query; std::string scheme, authority, path, query;
// To reconstruct HTTP/1 status line and headers, proxy should // To reconstruct HTTP/1 status line and headers, proxy should
// preserve host header field. See draft-09 section 8.1.3.1. // preserve host header field. See draft-09 section 8.1.3.1.
if (downstream_->get_request_method() == "CONNECT") { if (downstream_->get_request_method() == "CONNECT") {
@ -273,7 +273,7 @@ int Http2DownstreamConnection::push_request_headers() {
if (!downstream_->get_request_http2_authority().empty()) { if (!downstream_->get_request_http2_authority().empty()) {
nva.push_back(http2::make_nv_ls( nva.push_back(http2::make_nv_ls(
":authority", downstream_->get_request_http2_authority())); ":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)) { if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing"; DCLOG(INFO, this) << "host header field missing";
} }
@ -330,7 +330,7 @@ int Http2DownstreamConnection::push_request_headers() {
authority += util::utos(u.port); authority += util::utos(u.port);
} }
nva.push_back(http2::make_nv_ls(":authority", authority)); 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)) { if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing"; DCLOG(INFO, this) << "host header field missing";
} }
@ -341,27 +341,30 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back( nva.push_back(
http2::make_nv_ls(":method", downstream_->get_request_method())); 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; bool chunked_encoding = false;
auto transfer_encoding = auto transfer_encoding =
downstream_->get_norm_request_header("transfer-encoding"); downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
if (transfer_encoding != end_headers && if (transfer_encoding &&
util::strieq((*transfer_encoding).value.c_str(), "chunked")) { util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
chunked_encoding = true; 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 (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;
xff_value += ", "; xff_value += ", ";
} }
xff_value += xff_value +=
downstream_->get_upstream()->get_client_handler()->get_ipaddr(); downstream_->get_upstream()->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value)); nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff != end_headers && } else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
!get_config()->strip_incoming_x_forwarded_for) {
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value)); 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 (get_config()->no_via) {
if (via != end_headers) { if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value)); nva.push_back(http2::make_nv_ls("via", (*via).value));
} }
} else { } else {
if (via != end_headers) { if (via) {
via_value = (*via).value; via_value = (*via).value;
via_value += ", "; via_value += ", ";
} }
@ -407,7 +410,8 @@ int Http2DownstreamConnection::push_request_headers() {
} }
auto content_length = 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 || if (downstream_->get_request_method() == "CONNECT" || chunked_encoding ||
content_length || downstream_->get_request_http2_expect_body()) { 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; return 0;
} }
if (namelen > 0 && name[0] == ':') { auto token = http2::lookup_token(name, namelen);
if (!downstream->response_pseudo_header_allowed() ||
!http2::check_http2_response_pseudo_header(name, namelen)) {
if (name[0] == ':') {
if (!downstream->response_pseudo_header_allowed(token)) {
http2session->submit_rst_stream(frame->hd.stream_id, http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
} }
downstream->split_add_response_header(name, namelen, value, valuelen, if (!http2::http2_header_allowed(token)) {
flags & NGHTTP2_NV_FLAG_NO_INDEX); 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; return 0;
} }
} // namespace } // namespace
@ -763,20 +769,11 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
auto upstream = downstream->get_upstream(); auto upstream = downstream->get_upstream();
downstream->normalize_response_headers();
auto &nva = downstream->get_response_headers(); auto &nva = downstream->get_response_headers();
downstream->set_expect_final_response(false); downstream->set_expect_final_response(false);
if (!http2::check_http2_response_headers(nva)) { auto status = downstream->get_response_header(http2::HD__STATUS);
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");
int status_code; int status_code;
if (!http2::non_empty_value(status) || if (!http2::non_empty_value(status) ||
@ -824,7 +821,8 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
return 0; 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" && if (!content_length && downstream->get_request_method() != "HEAD" &&
downstream->get_request_method() != "CONNECT") { downstream->get_request_method() != "CONNECT") {
unsigned int status; unsigned int status;

View File

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

View File

@ -213,11 +213,8 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
} }
int HttpDownstreamConnection::push_request_headers() { int HttpDownstreamConnection::push_request_headers() {
assert(downstream_->get_request_headers_normalized());
downstream_->assemble_request_cookie(); downstream_->assemble_request_cookie();
auto end_headers = std::end(downstream_->get_request_headers());
// Assume that method and request path do not contain \r\n. // Assume that method and request path do not contain \r\n.
std::string hdrs = downstream_->get_request_method(); std::string hdrs = downstream_->get_request_method();
hdrs += " "; hdrs += " ";
@ -253,14 +250,14 @@ int HttpDownstreamConnection::push_request_headers() {
hdrs += downstream_->get_request_path(); hdrs += downstream_->get_request_path();
} }
hdrs += " HTTP/1.1\r\n"; 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()) { !downstream_->get_request_http2_authority().empty()) {
hdrs += "Host: "; hdrs += "Host: ";
hdrs += downstream_->get_request_http2_authority(); hdrs += downstream_->get_request_http2_authority();
hdrs += "\r\n"; hdrs += "\r\n";
} }
http2::build_http1_headers_from_norm_headers( http2::build_http1_headers_from_headers(hdrs,
hdrs, downstream_->get_request_headers()); downstream_->get_request_headers());
if (!downstream_->get_assembled_request_cookie().empty()) { if (!downstream_->get_assembled_request_cookie().empty()) {
hdrs += "Cookie: "; hdrs += "Cookie: ";
@ -270,7 +267,7 @@ int HttpDownstreamConnection::push_request_headers() {
if (downstream_->get_request_method() != "CONNECT" && if (downstream_->get_request_method() != "CONNECT" &&
downstream_->get_request_http2_expect_body() && 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); downstream_->set_chunked_request(true);
hdrs += "Transfer-Encoding: chunked\r\n"; hdrs += "Transfer-Encoding: chunked\r\n";
@ -279,18 +276,17 @@ int HttpDownstreamConnection::push_request_headers() {
if (downstream_->get_request_connection_close()) { if (downstream_->get_request_connection_close()) {
hdrs += "Connection: close\r\n"; 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) { if (get_config()->add_x_forwarded_for) {
hdrs += "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; hdrs += (*xff).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
hdrs += ", "; hdrs += ", ";
} }
hdrs += client_handler_->get_ipaddr(); hdrs += client_handler_->get_ipaddr();
hdrs += "\r\n"; hdrs += "\r\n";
} else if (xff != end_headers && } else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
!get_config()->strip_incoming_x_forwarded_for) {
hdrs += "X-Forwarded-For: "; hdrs += "X-Forwarded-For: ";
hdrs += (*xff).value; hdrs += (*xff).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
@ -308,17 +304,16 @@ int HttpDownstreamConnection::push_request_headers() {
hdrs += "http\r\n"; hdrs += "http\r\n";
} }
} }
auto expect = downstream_->get_norm_request_header("expect"); auto expect = downstream_->get_request_header(http2::HD_EXPECT);
if (expect != end_headers && if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) {
!util::strifind((*expect).value.c_str(), "100-continue")) {
hdrs += "Expect: "; hdrs += "Expect: ";
hdrs += (*expect).value; hdrs += (*expect).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size());
hdrs += "\r\n"; 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 (get_config()->no_via) {
if (via != end_headers) { if (via) {
hdrs += "Via: "; hdrs += "Via: ";
hdrs += (*via).value; hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
@ -326,7 +321,7 @@ int HttpDownstreamConnection::push_request_headers() {
} }
} else { } else {
hdrs += "Via: "; hdrs += "Via: ";
if (via != end_headers) { if (via) {
hdrs += (*via).value; hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
hdrs += ", "; hdrs += ", ";
@ -473,6 +468,8 @@ int htp_hdrs_completecb(http_parser *htp) {
downstream->set_response_major(htp->http_major); downstream->set_response_major(htp->http_major);
downstream->set_response_minor(htp->http_minor); downstream->set_response_minor(htp->http_minor);
downstream->index_response_headers();
if (downstream->get_non_final_response()) { if (downstream->get_non_final_response()) {
// For non-final response code, we just call // For non-final response code, we just call
// on_downstream_header_complete() without changing response // 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(); ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
} }
downstream->normalize_request_headers(); downstream->index_request_headers();
downstream->inspect_http1_request(); downstream->inspect_http1_request();
@ -620,15 +620,15 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
hdrs += " "; hdrs += " ";
hdrs += http2::get_status_string(downstream->get_response_http_status()); hdrs += http2::get_status_string(downstream->get_response_http_status());
hdrs += "\r\n"; hdrs += "\r\n";
downstream->normalize_response_headers();
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) { !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); 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( http2::build_http1_headers_from_headers(hdrs,
hdrs, downstream->get_response_headers()); downstream->get_response_headers());
auto output = downstream->get_response_buf(); auto output = downstream->get_response_buf();
@ -660,8 +660,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
hdrs += "Connection: close\r\n"; hdrs += "Connection: close\r\n";
} }
if (downstream->get_norm_response_header("alt-svc") == end_headers) { if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
// We won't change or alter alt-svc from backend at the moment. // We won't change or alter alt-svc from backend for now
if (!get_config()->altsvcs.empty()) { if (!get_config()->altsvcs.empty()) {
hdrs += "Alt-Svc: "; hdrs += "Alt-Svc: ";
@ -684,17 +684,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
hdrs += get_config()->server_name; hdrs += get_config()->server_name;
hdrs += "\r\n"; hdrs += "\r\n";
} else { } else {
auto server = downstream->get_norm_response_header("server"); auto server = downstream->get_response_header(http2::HD_SERVER);
if (server != end_headers) { if (server) {
hdrs += "Server: "; hdrs += "Server: ";
hdrs += (*server).value; hdrs += (*server).value;
hdrs += "\r\n"; 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 (get_config()->no_via) {
if (via != end_headers) { if (via) {
hdrs += "Via: "; hdrs += "Via: ";
hdrs += (*via).value; hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
@ -702,7 +702,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
} }
} else { } else {
hdrs += "Via: "; hdrs += "Via: ";
if (via != end_headers) { if (via) {
hdrs += (*via).value; hdrs += (*via).value;
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
hdrs += ", "; hdrs += ", ";

View File

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

View File

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