diff --git a/genheaderfunc.py b/genheaderfunc.py index 98fa3f1c..05ca7d9f 100755 --- a/genheaderfunc.py +++ b/genheaderfunc.py @@ -5,14 +5,25 @@ HEADERS = [ ':method', ':path', ':scheme', - # disallowed h1 headers - 'connection', + ':status', + ':host', # for spdy 'expect', 'host', 'if-modified-since', + "te", + "cookie", + "http2-settings", + "server", + "via", + "x-forwarded-for", + "x-forwarded-proto", + "alt-svc", + "content-length", + "location", + # disallowed h1 headers + 'connection', 'keep-alive', 'proxy-connection', - 'te', 'transfer-encoding', 'upgrade' ] @@ -20,9 +31,7 @@ HEADERS = [ def to_enum_hd(k): res = 'HD_' for c in k.upper(): - if c == ':': - continue - if c == '-': + if c == ':' or c == '-': res += '_' continue res += c @@ -54,7 +63,7 @@ enum {''' def gen_index_header(): print '''\ -void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { +int lookup_token(const uint8_t *name, size_t namelen) { switch (namelen) {''' b = build_header(HEADERS) for size in sorted(b.keys()): @@ -70,8 +79,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { for k in headers: print '''\ if (util::streq("{}", name, {})) {{ - hdidx[{}] = idx; - return; + return {}; }}'''.format(k[:-1], size - 1, to_enum_hd(k)) print '''\ break;''' @@ -80,6 +88,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { break;''' print '''\ } + return -1; }''' if __name__ == '__main__': diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 514ce36d..0c2b393b 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -89,9 +89,12 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; } namespace { void append_nv(Stream *stream, const std::vector &nva) { - size_t idx = 0; - for (auto &nv : nva) { - http2::index_header(stream->hdidx, nv.name, nv.namelen, idx++); + for (size_t i = 0; i < nva.size(); ++i) { + auto &nv = nva[i]; + auto token = http2::lookup_token(nv.name, nv.namelen); + if (token != -1) { + http2::index_header(stream->hdidx, token, i); + } http2::add_header(stream->headers, nv.name, nv.namelen, nv.value, nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); } @@ -739,7 +742,7 @@ int Http2Handler::submit_non_final_response(const std::string &status, int Http2Handler::submit_push_promise(Stream *stream, const std::string &push_path) { auto authority = - http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers); + http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers); if (!authority) { authority = @@ -900,9 +903,9 @@ void prepare_redirect_response(Stream *stream, Http2Handler *hd, const std::string &path, const std::string &status) { auto scheme = - http2::get_header(stream->hdidx, http2::HD_SCHEME, stream->headers); + http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers); auto authority = - http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers); + http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers); if (!authority) { authority = http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers); @@ -924,7 +927,7 @@ void prepare_response(Stream *stream, Http2Handler *hd, bool allow_push = true) { int rv; auto reqpath = - http2::get_header(stream->hdidx, http2::HD_PATH, stream->headers)->value; + http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value; auto ims = get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers); @@ -1038,11 +1041,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (namelen > 0 && name[0] == ':') { + auto token = http2::lookup_token(name, namelen); + + if (name[0] == ':') { if ((!stream->headers.empty() && stream->headers.back().name.c_str()[0] != ':') || - !http2::check_http2_request_pseudo_header(stream->hdidx, name, - namelen)) { + !http2::check_http2_request_pseudo_header(stream->hdidx, token)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -1050,8 +1054,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, } } - http2::index_header(stream->hdidx, name, namelen, stream->headers.size()); + if (!http2::http2_header_allowed(token)) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + http2::index_header(stream->hdidx, token, stream->headers.size()); http2::add_header(stream->headers, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); return 0; @@ -1113,7 +1122,7 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - if (!http2::check_http2_request_headers(stream->hdidx)) { + if (!http2::http2_mandatory_request_headers_presence(stream->hdidx)) { hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); return 0; } diff --git a/src/http2.cc b/src/http2.cc index bf800a51..83596556 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -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(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 -bool check_pseudo_header(const uint8_t *name, size_t namelen, - InputIterator allowed_first, - InputIterator allowed_last) { - for (auto i = allowed_first; i != allowed_last; ++i) { - if (util::streq(*i, name, namelen)) { - return true; - } - } - - return false; -} -} // namespace - -bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen) { - return check_pseudo_header(name, namelen, REQUEST_PSEUDO_HD, - REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN); -} - -bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen) { - return check_pseudo_header(name, namelen, RESPONSE_PSEUDO_HD, - RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN); -} - -void normalize_headers(Headers &nva) { - for (auto &kv : nva) { - util::inp_strlower(kv.name); - } - std::stable_sort(std::begin(nva), std::end(nva), name_less); -} - Headers::value_type to_header(const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index) { @@ -328,26 +196,14 @@ void add_header(Headers &nva, const uint8_t *name, size_t namelen, nva.push_back(to_header(name, namelen, value, valuelen, no_index)); } -const Headers::value_type *get_unique_header(const Headers &nva, - const char *name) { - auto nv = Headers::value_type(name, ""); - auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less); - if (i != std::end(nva) && (*i).name == nv.name) { - auto j = i + 1; - if (j == std::end(nva) || (*j).name != nv.name) { - return &(*i); +const Headers::value_type *get_header(const Headers &nva, const char *name) { + const Headers::value_type *res = nullptr; + for (auto &nv : nva) { + if (nv.name == name) { + res = &nv; } } - return nullptr; -} - -const Headers::value_type *get_header(const Headers &nva, const char *name) { - auto nv = Headers::value_type(name, ""); - auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less); - if (i != std::end(nva) && (*i).name == nv.name) { - return &(*i); - } - return nullptr; + return res; } std::string value_to_str(const Headers::value_type *nv) { @@ -371,65 +227,54 @@ nghttp2_nv make_nv(const std::string &name, const std::string &value, value.size(), flags}; } -void copy_norm_headers_to_nva(std::vector &nva, - const Headers &headers) { - size_t i, j; - for (i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) { - auto &kv = headers[i]; - int rv = strcmp(kv.name.c_str(), IGN_HD[j]); - if (rv < 0) { - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); - } - ++i; - } else if (rv > 0) { - ++j; - } else { - ++i; +void copy_headers_to_nva(std::vector &nva, const Headers &headers) { + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; } - } - for (; i < headers.size(); ++i) { - auto &kv = headers[i]; - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); + switch (lookup_token(kv.name)) { + case HD_COOKIE: + case HD_CONNECTION: + case HD_HTTP2_SETTINGS: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_SERVER: + case HD_TRANSFER_ENCODING: + case HD_UPGRADE: + case HD_VIA: + case HD_X_FORWARDED_FOR: + case HD_X_FORWARDED_PROTO: + continue; } + nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); } } -void build_http1_headers_from_norm_headers(std::string &hdrs, - const Headers &headers) { - size_t i, j; - for (i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) { - auto &kv = headers[i]; - auto rv = strcmp(kv.name.c_str(), HTTP1_IGN_HD[j]); - - if (rv < 0) { - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - hdrs += kv.name; - capitalize(hdrs, hdrs.size() - kv.name.size()); - hdrs += ": "; - hdrs += kv.value; - sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); - hdrs += "\r\n"; - } - ++i; - } else if (rv > 0) { - ++j; - } else { - ++i; +void build_http1_headers_from_headers(std::string &hdrs, + const Headers &headers) { + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; } - } - for (; i < headers.size(); ++i) { - auto &kv = headers[i]; - - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - hdrs += kv.name; - capitalize(hdrs, hdrs.size() - kv.name.size()); - hdrs += ": "; - hdrs += kv.value; - sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); - hdrs += "\r\n"; + switch (lookup_token(kv.name)) { + case HD_CONNECTION: + case HD_COOKIE: + case HD_HTTP2_SETTINGS: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_SERVER: + case HD_UPGRADE: + case HD_VIA: + case HD_X_FORWARDED_FOR: + case HD_X_FORWARDED_PROTO: + continue; } + hdrs += kv.name; + capitalize(hdrs, hdrs.size() - kv.name.size()); + hdrs += ": "; + hdrs += kv.value; + sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); + hdrs += "\r\n"; } } @@ -572,18 +417,29 @@ int parse_http_status_code(const std::string &src) { return status; } -void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); } +int lookup_token(const std::string &name) { + return lookup_token(reinterpret_cast(name.c_str()), + name.size()); +} // This function was generated by genheaderfunc.py. Inspired by h2o // header lookup. https://github.com/h2o/h2o -void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { +int lookup_token(const uint8_t *name, size_t namelen) { switch (namelen) { case 2: switch (util::lowcase(name[namelen - 1])) { case 'e': if (util::streq("t", name, 1)) { - hdidx[HD_TE] = idx; - return; + return HD_TE; + } + break; + } + break; + case 3: + switch (util::lowcase(name[namelen - 1])) { + case 'a': + if (util::streq("vi", name, 2)) { + return HD_VIA; } break; } @@ -592,8 +448,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { switch (util::lowcase(name[namelen - 1])) { case 't': if (util::streq("hos", name, 3)) { - hdidx[HD_HOST] = idx; - return; + return HD_HOST; } break; } @@ -602,38 +457,67 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { switch (util::lowcase(name[namelen - 1])) { case 'h': if (util::streq(":pat", name, 4)) { - hdidx[HD_PATH] = idx; - return; + return HD__PATH; + } + break; + case 't': + if (util::streq(":hos", name, 4)) { + return HD__HOST; } break; } break; case 6: switch (util::lowcase(name[namelen - 1])) { + case 'e': + if (util::streq("cooki", name, 5)) { + return HD_COOKIE; + } + break; + case 'r': + if (util::streq("serve", name, 5)) { + return HD_SERVER; + } + break; case 't': if (util::streq("expec", name, 5)) { - hdidx[HD_EXPECT] = idx; - return; + return HD_EXPECT; } break; } break; case 7: switch (util::lowcase(name[namelen - 1])) { + case 'c': + if (util::streq("alt-sv", name, 6)) { + return HD_ALT_SVC; + } + break; case 'd': if (util::streq(":metho", name, 6)) { - hdidx[HD_METHOD] = idx; - return; + return HD__METHOD; } break; case 'e': if (util::streq(":schem", name, 6)) { - hdidx[HD_SCHEME] = idx; - return; + return HD__SCHEME; } if (util::streq("upgrad", name, 6)) { - hdidx[HD_UPGRADE] = idx; - return; + return HD_UPGRADE; + } + break; + case 's': + if (util::streq(":statu", name, 6)) { + return HD__STATUS; + } + break; + } + break; + case 8: + switch (util::lowcase(name[namelen - 1])) { + case 'n': + if (util::streq("locatio", name, 7)) { + return HD_LOCATION; } break; } @@ -642,20 +526,40 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { switch (util::lowcase(name[namelen - 1])) { case 'e': if (util::streq("keep-aliv", name, 9)) { - hdidx[HD_KEEP_ALIVE] = idx; - return; + return HD_KEEP_ALIVE; } break; case 'n': if (util::streq("connectio", name, 9)) { - hdidx[HD_CONNECTION] = idx; - return; + return HD_CONNECTION; } break; case 'y': if (util::streq(":authorit", name, 9)) { - hdidx[HD_AUTHORITY] = idx; - return; + return HD__AUTHORITY; + } + break; + } + break; + case 14: + switch (util::lowcase(name[namelen - 1])) { + case 'h': + if (util::streq("content-lengt", name, 13)) { + return HD_CONTENT_LENGTH; + } + break; + case 's': + if (util::streq("http2-setting", name, 13)) { + return HD_HTTP2_SETTINGS; + } + break; + } + break; + case 15: + switch (util::lowcase(name[namelen - 1])) { + case 'r': + if (util::streq("x-forwarded-fo", name, 14)) { + return HD_X_FORWARDED_FOR; } break; } @@ -664,8 +568,7 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { switch (util::lowcase(name[namelen - 1])) { case 'n': if (util::streq("proxy-connectio", name, 15)) { - hdidx[HD_PROXY_CONNECTION] = idx; - return; + return HD_PROXY_CONNECTION; } break; } @@ -674,96 +577,92 @@ void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx) { switch (util::lowcase(name[namelen - 1])) { case 'e': if (util::streq("if-modified-sinc", name, 16)) { - hdidx[HD_IF_MODIFIED_SINCE] = idx; - return; + return HD_IF_MODIFIED_SINCE; } break; case 'g': if (util::streq("transfer-encodin", name, 16)) { - hdidx[HD_TRANSFER_ENCODING] = idx; - return; + return HD_TRANSFER_ENCODING; + } + break; + case 'o': + if (util::streq("x-forwarded-prot", name, 16)) { + return HD_X_FORWARDED_PROTO; } break; } break; } + return -1; } -bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *s, - size_t len) { - switch (len) { - case 5: - switch (util::lowcase(s[len - 1])) { - case 'h': - if (util::streq(":pat", s, 4)) { - if (hdidx[HD_PATH] != -1) { - return false; - } - return true; - } - break; +void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); } + +void index_headers(int *hdidx, const Headers &headers) { + for (size_t i = 0; i < headers.size(); ++i) { + auto &kv = headers[i]; + auto token = lookup_token( + reinterpret_cast(kv.name.c_str()), kv.name.size()); + if (token >= 0) { + http2::index_header(hdidx, token, i); } - break; - case 7: - switch (util::lowcase(s[len - 1])) { - case 'd': - if (util::streq(":metho", s, 6)) { - if (hdidx[HD_METHOD] != -1) { - return false; - } - return true; - } - break; - case 'e': - if (util::streq(":schem", s, 6)) { - if (hdidx[HD_SCHEME] != -1) { - return false; - } - return true; - } - break; - } - break; - case 10: - switch (util::lowcase(s[len - 1])) { - case 'y': - if (util::streq(":authorit", s, 9)) { - if (hdidx[HD_AUTHORITY] != -1) { - return false; - } - return true; - } - break; - } - break; } - return false; } -bool check_http2_headers(int *hdidx) { - if (hdidx[HD_CONNECTION] != -1 || hdidx[HD_KEEP_ALIVE] != -1 || - hdidx[HD_PROXY_CONNECTION] != -1 || hdidx[HD_TE] != -1 || - hdidx[HD_TRANSFER_ENCODING] != -1 || hdidx[HD_UPGRADE] != -1) { +void index_header(int *hdidx, int token, size_t idx) { + if (token == -1) { + return; + } + assert(token < HD_MAXIDX); + hdidx[token] = idx; +} + +bool check_http2_request_pseudo_header(const int *hdidx, int token) { + switch (token) { + case HD__AUTHORITY: + case HD__METHOD: + case HD__PATH: + case HD__SCHEME: + return hdidx[token] == -1; + default: + return false; + } +} + +bool check_http2_response_pseudo_header(const int *hdidx, int token) { + switch (token) { + case HD__STATUS: + return hdidx[token] == -1; + default: + return false; + } +} + +bool http2_header_allowed(int token) { + switch (token) { + case HD_CONNECTION: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_TRANSFER_ENCODING: + case HD_UPGRADE: + return false; + default: + return true; + } +} + +bool http2_mandatory_request_headers_presence(const int *hdidx) { + if (hdidx[HD__METHOD] == -1 || hdidx[HD__PATH] == -1 || + hdidx[HD__SCHEME] == -1 || + (hdidx[HD__AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) { return false; } return true; } -bool check_http2_request_headers(int *hdidx) { - if (!check_http2_headers(hdidx)) { - return false; - } - if (hdidx[HD_METHOD] == -1 || hdidx[HD_PATH] == -1 || - hdidx[HD_SCHEME] == -1 || - (hdidx[HD_AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) { - return false; - } - return true; -} - -const Headers::value_type *get_header(int *hdidx, int hdkey, +const Headers::value_type *get_header(const int *hdidx, int token, const Headers &nva) { - auto i = hdidx[hdkey]; + auto i = hdidx[token]; if (i == -1) { return nullptr; } diff --git a/src/http2.h b/src/http2.h index 3020ae48..c21cfe7b 100644 --- a/src/http2.h +++ b/src/http2.h @@ -76,35 +76,6 @@ void sanitize_header_value(std::string &s, size_t offset); void copy_url_component(std::string &dest, const http_parser_url *u, int field, const char *url); -// Returns true if the header field |name| with length |namelen| bytes -// is valid for HTTP/2. -bool check_http2_allowed_header(const uint8_t *name, size_t namelen); - -// Calls check_http2_allowed_header with |name| and strlen(name), -// assuming |name| is null-terminated string. -bool check_http2_allowed_header(const char *name); - -// Checks that headers |nva| do not contain disallowed header fields -// in HTTP/2 spec. This function returns true if |nva| does not -// contains such headers. -bool check_http2_headers(const Headers &nva); - -// Calls check_http2_headers() -bool check_http2_request_headers(const Headers &nva); - -// Calls check_http2_headers() -bool check_http2_response_headers(const Headers &nva); - -// Returns true if |name| is allowed pusedo header for request. -bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen); - -// Returns true if |name| is allowed pusedo header for response. -bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen); - -bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs); - -void normalize_headers(Headers &nva); - Headers::value_type to_header(const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index); @@ -115,16 +86,9 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen, void add_header(Headers &nva, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index); -// Returns the iterator to the entry in |nva| which has name |name| -// and the |name| is uinque in the |nva|. If no such entry exist, -// returns nullptr. -const Headers::value_type *get_unique_header(const Headers &nva, - const char *name); - -// Returns the iterator to the entry in |nva| which has name -// |name|. If more than one entries which have the name |name|, first -// occurrence in |nva| is returned. If no such entry exist, returns -// nullptr. +// Returns pointer to the entry in |nva| which has name |name|. If +// more than one entries which have the name |name|, last occurrence +// in |nva| is returned. If no such entry exist, returns nullptr. const Headers::value_type *get_header(const Headers &nva, const char *name); // Returns nv->second if nv is not nullptr. Otherwise, returns "". @@ -165,14 +129,13 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) { // Appends headers in |headers| to |nv|. Certain headers, including // disallowed headers in HTTP/2 spec and headers which require // special handling (i.e. via), are not copied. -void copy_norm_headers_to_nva(std::vector &nva, - const Headers &headers); +void copy_headers_to_nva(std::vector &nva, const Headers &headers); // Appends HTTP/1.1 style header lines to |hdrs| from headers in // |headers|. Certain headers, which requires special handling // (i.e. via and cookie), are not appended. -void build_http1_headers_from_norm_headers(std::string &hdrs, - const Headers &headers); +void build_http1_headers_from_headers(std::string &hdrs, + const Headers &headers); // Return positive window_size_increment if WINDOW_UPDATE should be // sent for the stream |stream_id|. If |stream_id| == 0, this function @@ -218,43 +181,68 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, // Returns parsed HTTP status code. Returns -1 on failure. int parse_http_status_code(const std::string &src); +// Header fields to be indexed, except HD_MAXIDX which is convenient +// member to get maximum value. enum { - HD_AUTHORITY, - HD_METHOD, - HD_PATH, - HD_SCHEME, + HD__AUTHORITY, + HD__HOST, + HD__METHOD, + HD__PATH, + HD__SCHEME, + HD__STATUS, + HD_ALT_SVC, HD_CONNECTION, + HD_CONTENT_LENGTH, + HD_COOKIE, HD_EXPECT, HD_HOST, + HD_HTTP2_SETTINGS, HD_IF_MODIFIED_SINCE, HD_KEEP_ALIVE, + HD_LOCATION, HD_PROXY_CONNECTION, + HD_SERVER, HD_TE, HD_TRANSFER_ENCODING, HD_UPGRADE, + HD_VIA, + HD_X_FORWARDED_FOR, + HD_X_FORWARDED_PROTO, HD_MAXIDX, }; +// Looks up header token for header name |name| of length |namelen|. +// Only headers we are interested in are tokenized. If header name +// cannot be tokenized, returns -1. +int lookup_token(const uint8_t *name, size_t namelen); +int lookup_token(const std::string &name); + // Initializes |hdidx|, header index. The |hdidx| must point to the // array containing at least HD_MAXIDX elements. void init_hdidx(int *hdidx); -// Indexes header |name| of length |namelen| using index |idx|. -void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx); +// Indexes header |token| using index |idx|. +void index_header(int *hdidx, int token, size_t idx); +// Iterates |headers| and for each element, call index_header. +void index_headers(int *hdidx, const Headers &headers); -// Checks pseudo header |name| of length |namelen| are unique and -// allowed for HTTP/2 request. -bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *name, - size_t namelen); +// Returns true if HTTP/2 request pseudo header |token| is not indexed +// yet and not -1. +bool check_http2_request_pseudo_header(const int *hdidx, int token); -// Checks |hdidx| does not contain disallowed headers. -bool check_http2_headers(int *hdidx); -// Checks |hdidx| does not contain disallowed headers and contains -// mandatory headers. This funtions internally calls -// check_http2_headers(). -bool check_http2_request_headers(int *hdidx); +// Returns true if HTTP/2 response pseudo header |token| is not +// indexed yet and not -1. +bool check_http2_response_pseudo_header(const int *hdidx, int token); -// Returns header denoted by |hdkey| using index |hdidx|. -const Headers::value_type *get_header(int *hdidx, int hdkey, +// Returns true if header field denoted by |token| is allowed for +// HTTP/2. +bool http2_header_allowed(int token); + +// Returns true that |hdidx| contains mandatory HTTP/2 request +// headers. +bool http2_mandatory_request_headers_presence(const int *hdidx); + +// Returns header denoted by |token| using index |hdidx|. +const Headers::value_type *get_header(const int *hdidx, int token, const Headers &nva); } // namespace http2 diff --git a/src/http2_test.cc b/src/http2_test.cc index 13c927ef..9edc1bd1 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -100,55 +100,14 @@ void test_http2_add_header(void) { CU_ASSERT(Headers::value_type("a", "") == nva[0]); } -void test_http2_check_http2_headers(void) { - auto nva1 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"upgrade", "http2"}}; - CU_ASSERT(!http2::check_http2_headers(nva1)); - - auto nva2 = Headers{{"connection", "1"}, {"delta", "2"}, {"echo", "3"}}; - CU_ASSERT(!http2::check_http2_headers(nva2)); - - auto nva3 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"te2", "3"}}; - CU_ASSERT(http2::check_http2_headers(nva3)); - - auto n1 = ":authority"; - auto n1u8 = reinterpret_cast(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(n2); - - CU_ASSERT(!http2::check_http2_request_pseudo_header(n2u8, strlen(n2))); - CU_ASSERT(http2::check_http2_response_pseudo_header(n2u8, strlen(n2))); -} - -void test_http2_get_unique_header(void) { - auto nva = Headers{{"alpha", "1"}, - {"bravo", "2"}, - {"bravo", "3"}, - {"charlie", "4"}, - {"delta", "5"}, - {"echo", "6"}}; - const Headers::value_type *rv; - rv = http2::get_unique_header(nva, "delta"); - CU_ASSERT(rv != nullptr); - CU_ASSERT("delta" == rv->name); - - rv = http2::get_unique_header(nva, "bravo"); - CU_ASSERT(rv == nullptr); - - rv = http2::get_unique_header(nva, "foxtrot"); - CU_ASSERT(rv == nullptr); -} - void test_http2_get_header(void) { auto nva = Headers{{"alpha", "1"}, {"bravo", "2"}, {"bravo", "3"}, {"charlie", "4"}, {"delta", "5"}, - {"echo", "6"}}; + {"echo", "6"}, + {"content-length", "7"}}; const Headers::value_type *rv; rv = http2::get_header(nva, "delta"); CU_ASSERT(rv != nullptr); @@ -160,6 +119,12 @@ void test_http2_get_header(void) { rv = http2::get_header(nva, "foxtrot"); CU_ASSERT(rv == nullptr); + + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + hdidx[http2::HD_CONTENT_LENGTH] = 6; + rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva); + CU_ASSERT("content-length" == rv->name); } namespace { @@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true}, {"zulu", "12"}}; } // namespace -void test_http2_copy_norm_headers_to_nva(void) { +void test_http2_copy_headers_to_nva(void) { std::vector nva; - http2::copy_norm_headers_to_nva(nva, headers); - CU_ASSERT(7 == nva.size()); - auto ans = std::vector{0, 1, 4, 5, 6, 7, 12}; + http2::copy_headers_to_nva(nva, headers); + CU_ASSERT(9 == nva.size()); + auto ans = std::vector{0, 1, 4, 5, 6, 7, 8, 9, 12}; for (size_t i = 0; i < ans.size(); ++i) { check_nv(headers[ans[i]], &nva[i]); @@ -194,9 +159,9 @@ void test_http2_copy_norm_headers_to_nva(void) { } } -void test_http2_build_http1_headers_from_norm_headers(void) { +void test_http2_build_http1_headers_from_headers(void) { std::string hdrs; - http2::build_http1_headers_from_norm_headers(hdrs, headers); + http2::build_http1_headers_from_headers(hdrs, headers); CU_ASSERT(hdrs == "Alpha: 0\r\n" "Bravo: 1\r\n" "Delta: 4\r\n" @@ -206,15 +171,6 @@ void test_http2_build_http1_headers_from_norm_headers(void) { "Te: 8\r\n" "Te: 9\r\n" "Zulu: 12\r\n"); - - hdrs.clear(); - // Both nghttp2 and spdylay do not allow \r and \n in header value - // now. - - // auto hd2 = std::vector> - // {{"alpha", "bravo\r\ncharlie\r\n"}}; - // http2::build_http1_headers_from_norm_headers(hdrs, hd2); - // CU_ASSERT(hdrs == "Alpha: bravo charlie \r\n"); } void test_http2_lws(void) { @@ -274,12 +230,64 @@ void test_http2_index_header(void) { int hdidx[http2::HD_MAXIDX]; http2::init_hdidx(hdidx); - http2::index_header(hdidx, reinterpret_cast(":authority"), - 10, 0); - http2::index_header(hdidx, reinterpret_cast("hos"), 3, 1); + http2::index_header(hdidx, http2::HD__AUTHORITY, 0); + http2::index_header(hdidx, -1, 1); - CU_ASSERT(0 == hdidx[http2::HD_AUTHORITY]); - CU_ASSERT(-1 == hdidx[http2::HD_HOST]); + CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]); +} + +void test_http2_lookup_token(void) { + CU_ASSERT(http2::HD__AUTHORITY == http2::lookup_token(":authority")); + CU_ASSERT(-1 == http2::lookup_token(":authorit")); + CU_ASSERT(-1 == http2::lookup_token(":Authority")); + CU_ASSERT(http2::HD_EXPECT == http2::lookup_token("expect")); +} + +void test_http2_check_http2_pseudo_header(void) { + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + + CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD)); + hdidx[http2::HD__PATH] = 0; + CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD)); + hdidx[http2::HD__METHOD] = 1; + CU_ASSERT( + !http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD)); + CU_ASSERT(!http2::check_http2_request_pseudo_header(hdidx, http2::HD_VIA)); + + http2::init_hdidx(hdidx); + + CU_ASSERT( + http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS)); + hdidx[http2::HD__STATUS] = 0; + CU_ASSERT( + !http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS)); + CU_ASSERT(!http2::check_http2_response_pseudo_header(hdidx, http2::HD_VIA)); +} + +void test_http2_http2_header_allowed(void) { + CU_ASSERT(http2::http2_header_allowed(http2::HD__PATH)); + CU_ASSERT(http2::http2_header_allowed(http2::HD_CONTENT_LENGTH)); + CU_ASSERT(!http2::http2_header_allowed(http2::HD_CONNECTION)); +} + +void test_http2_mandatory_request_headers_presence(void) { + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__AUTHORITY] = 0; + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__METHOD] = 1; + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__PATH] = 2; + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__SCHEME] = 3; + CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx)); + + hdidx[http2::HD__AUTHORITY] = -1; + hdidx[http2::HD_HOST] = 0; + CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx)); } } // namespace shrpx diff --git a/src/http2_test.h b/src/http2_test.h index 2cb8e69b..f8127dc7 100644 --- a/src/http2_test.h +++ b/src/http2_test.h @@ -28,15 +28,17 @@ namespace shrpx { void test_http2_add_header(void); -void test_http2_check_http2_headers(void); -void test_http2_get_unique_header(void); void test_http2_get_header(void); -void test_http2_copy_norm_headers_to_nva(void); -void test_http2_build_http1_headers_from_norm_headers(void); +void test_http2_copy_headers_to_nva(void); +void test_http2_build_http1_headers_from_headers(void); void test_http2_lws(void); void test_http2_rewrite_location_uri(void); void test_http2_parse_http_status_code(void); void test_http2_index_header(void); +void test_http2_lookup_token(void); +void test_http2_check_http2_pseudo_header(void); +void test_http2_http2_header_allowed(void); +void test_http2_mandatory_request_headers_presence(void); } // namespace shrpx diff --git a/src/nghttp.cc b/src/nghttp.cc index 21fdbfec..0852d804 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -224,6 +224,9 @@ struct Request { int level; // RequestPriority value defined in HtmlParser.h int pri; + int res_hdidx[http2::HD_MAXIDX]; + // used for incoming PUSH_PROMISE + int req_hdidx[http2::HD_MAXIDX]; bool expect_final_response; // For pushed request, |uri| is empty and |u| is zero-cleared. @@ -235,7 +238,10 @@ struct Request { data_length(data_length), data_offset(0), response_len(0), inflater(nullptr), html_parser(nullptr), data_prd(data_prd), stream_id(-1), status(0), level(level), pri(pri), - expect_final_response(false) {} + expect_final_response(false) { + http2::init_hdidx(res_hdidx); + http2::init_hdidx(req_hdidx); + } ~Request() { nghttp2_gzip_inflate_del(inflater); @@ -354,12 +360,47 @@ struct Request { } } - bool response_pseudo_header_allowed() const { - return res_nva.empty() || res_nva.back().name.c_str()[0] == ':'; + bool response_pseudo_header_allowed(int token) const { + if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') { + return false; + } + switch (token) { + case http2::HD__STATUS: + return res_hdidx[token] == -1; + default: + return false; + } } - bool push_request_pseudo_header_allowed() const { - return res_nva.empty() || req_nva.back().name.c_str()[0] == ':'; + bool push_request_pseudo_header_allowed(int token) const { + if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') { + return false; + } + switch (token) { + case http2::HD__AUTHORITY: + case http2::HD__METHOD: + case http2::HD__PATH: + case http2::HD__SCHEME: + return req_hdidx[token] == -1; + default: + return false; + } + } + + Headers::value_type *get_res_header(int token) { + auto idx = res_hdidx[token]; + if (idx == -1) { + return nullptr; + } + return &res_nva[idx]; + } + + Headers::value_type *get_req_header(int token) { + auto idx = req_hdidx[token]; + if (idx == -1) { + return nullptr; + } + return &req_nva[idx]; } void record_request_time() { @@ -1537,8 +1578,6 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) { build_headers.emplace_back(kv.name, kv.value, kv.no_index); } - std::stable_sort(std::begin(build_headers), std::end(build_headers), - http2::name_less); auto nva = std::vector(); nva.reserve(build_headers.size()); @@ -1690,30 +1729,29 @@ void check_response_header(nghttp2_session *session, Request *req) { req->expect_final_response = false; + auto status_hd = req->get_res_header(http2::HD__STATUS); + + if (!status_hd) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, + NGHTTP2_PROTOCOL_ERROR); + return; + } + + auto status = http2::parse_http_status_code(status_hd->value); + if (status == -1) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, + NGHTTP2_PROTOCOL_ERROR); + return; + } + + req->status = status; + for (auto &nv : req->res_nva) { if ("content-encoding" == nv.name) { gzip = util::strieq("gzip", nv.value) || util::strieq("deflate", nv.value); continue; } - if (":status" == nv.name) { - int status; - if (req->status != 0 || - (status = http2::parse_http_status_code(nv.value)) == -1) { - - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, - NGHTTP2_PROTOCOL_ERROR); - return; - } - - req->status = status; - } - } - - if (req->status == 0) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, - NGHTTP2_PROTOCOL_ERROR); - return; } if (req->status / 100 == 1) { @@ -1797,15 +1835,17 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, break; } - if (namelen > 0 && name[0] == ':') { - if (!req->response_pseudo_header_allowed() || - !http2::check_http2_response_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + + if (name[0] == ':') { + if (!req->response_pseudo_header_allowed(token)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } } + http2::index_header(req->res_hdidx, token, req->res_nva.size()); http2::add_header(req->res_nva, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); break; @@ -1818,9 +1858,10 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, break; } - if (namelen > 0 && name[0] == ':') { - if (!req->push_request_pseudo_header_allowed() || - !http2::check_http2_request_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + + if (name[0] == ':') { + if (!req->push_request_pseudo_header_allowed(token)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -1828,6 +1869,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, } } + http2::index_header(req->req_hdidx, token, req->req_nva.size()); http2::add_header(req->req_nva, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); break; @@ -1895,36 +1937,27 @@ int on_frame_recv_callback2(nghttp2_session *session, if (!req) { break; } - std::string scheme, authority, method, path; - for (auto &nv : req->req_nva) { - if (nv.name == ":scheme") { - scheme = nv.value; - continue; - } - if (nv.name == ":authority" || nv.name == "host") { - authority = nv.value; - continue; - } - if (nv.name == ":method") { - method = nv.value; - continue; - } - if (nv.name == ":path") { - path = nv.value; - continue; - } + auto scheme = req->get_req_header(http2::HD__SCHEME); + auto authority = req->get_req_header(http2::HD__AUTHORITY); + auto method = req->get_req_header(http2::HD__METHOD); + auto path = req->get_req_header(http2::HD__PATH); + + if (!authority) { + authority = req->get_req_header(http2::HD_HOST); } - if (scheme.empty() || authority.empty() || method.empty() || path.empty() || - path[0] != '/') { + + if (!scheme || !authority || !method || !path || scheme->value.empty() || + authority->value.empty() || method->value.empty() || + path->value.empty() || path->value[0] != '/') { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_PROTOCOL_ERROR); break; } - std::string uri = scheme; + std::string uri = scheme->value; uri += "://"; - uri += authority; - uri += path; + uri += authority->value; + uri += path->value; http_parser_url u; memset(&u, 0, sizeof(u)); if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index f1eb2d83..6fe0456e 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -72,15 +72,11 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file", shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) || !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) || - !CU_add_test(pSuite, "http2_check_http2_headers", - shrpx::test_http2_check_http2_headers) || - !CU_add_test(pSuite, "http2_get_unique_header", - shrpx::test_http2_get_unique_header) || !CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) || - !CU_add_test(pSuite, "http2_copy_norm_headers_to_nva", - shrpx::test_http2_copy_norm_headers_to_nva) || - !CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers", - shrpx::test_http2_build_http1_headers_from_norm_headers) || + !CU_add_test(pSuite, "http2_copy_headers_to_nva", + shrpx::test_http2_copy_headers_to_nva) || + !CU_add_test(pSuite, "http2_build_http1_headers_from_headers", + shrpx::test_http2_build_http1_headers_from_headers) || !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) || !CU_add_test(pSuite, "http2_rewrite_location_uri", shrpx::test_http2_rewrite_location_uri) || @@ -88,21 +84,28 @@ int main(int argc, char *argv[]) { shrpx::test_http2_parse_http_status_code) || !CU_add_test(pSuite, "http2_index_header", shrpx::test_http2_index_header) || - !CU_add_test(pSuite, "downstream_normalize_request_headers", - shrpx::test_downstream_normalize_request_headers) || - !CU_add_test(pSuite, "downstream_normalize_response_headers", - shrpx::test_downstream_normalize_response_headers) || - !CU_add_test(pSuite, "downstream_get_norm_request_header", - shrpx::test_downstream_get_norm_request_header) || - !CU_add_test(pSuite, "downstream_get_norm_response_header", - shrpx::test_downstream_get_norm_response_header) || + !CU_add_test(pSuite, "http2_lookup_token", + shrpx::test_http2_lookup_token) || + !CU_add_test(pSuite, "http2_check_http2_pseudo_header", + shrpx::test_http2_check_http2_pseudo_header) || + !CU_add_test(pSuite, "http2_http2_header_allowed", + shrpx::test_http2_http2_header_allowed) || + !CU_add_test(pSuite, "http2_mandatory_request_headers_presence", + shrpx::test_http2_mandatory_request_headers_presence) || + !CU_add_test(pSuite, "downstream_index_request_headers", + shrpx::test_downstream_index_request_headers) || + !CU_add_test(pSuite, "downstream_index_response_headers", + shrpx::test_downstream_index_response_headers) || + !CU_add_test(pSuite, "downstream_get_request_header", + shrpx::test_downstream_get_request_header) || + !CU_add_test(pSuite, "downstream_get_response_header", + shrpx::test_downstream_get_response_header) || !CU_add_test(pSuite, "downstream_crumble_request_cookie", shrpx::test_downstream_crumble_request_cookie) || !CU_add_test(pSuite, "downstream_assemble_request_cookie", shrpx::test_downstream_assemble_request_cookie) || - !CU_add_test( - pSuite, "downstream_rewrite_norm_location_response_header", - shrpx::test_downstream_rewrite_norm_location_response_header) || + !CU_add_test(pSuite, "downstream_rewrite_location_response_header", + shrpx::test_downstream_rewrite_location_response_header) || !CU_add_test(pSuite, "config_parse_config_str_list", shrpx::test_shrpx_config_parse_config_str_list) || !CU_add_test(pSuite, "config_parse_header", diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index ea84efff..a107c8de 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -116,12 +116,11 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority) request_state_(INITIAL), request_major_(1), request_minor_(1), response_state_(INITIAL), response_http_status_(0), response_major_(1), response_minor_(1), upgrade_request_(false), upgraded_(false), - http2_upgrade_seen_(false), http2_settings_seen_(false), - chunked_request_(false), request_connection_close_(false), - request_header_key_prev_(false), request_http2_expect_body_(false), - chunked_response_(false), response_connection_close_(false), - response_header_key_prev_(false), expect_final_response_(false), - request_headers_normalized_(false) { + http2_upgrade_seen_(false), chunked_request_(false), + request_connection_close_(false), request_header_key_prev_(false), + request_http2_expect_body_(false), chunked_response_(false), + response_connection_close_(false), response_header_key_prev_(false), + expect_final_response_(false) { ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0., get_config()->stream_read_timeout); @@ -136,6 +135,9 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority) upstream_wtimer_.data = this; downstream_rtimer_.data = this; downstream_wtimer_.data = this; + + http2::init_hdidx(request_hdidx_); + http2::init_hdidx(response_hdidx_); } Downstream::~Downstream() { @@ -213,35 +215,15 @@ void Downstream::force_resume_read() { } namespace { -Headers::const_iterator get_norm_header(const Headers &headers, - const std::string &name) { - auto i = std::lower_bound(std::begin(headers), std::end(headers), - Header(name, ""), http2::name_less); - if (i != std::end(headers) && (*i).name == name) { - return i; +const Headers::value_type *get_header_linear(const Headers &headers, + const std::string &name) { + const Headers::value_type *res = nullptr; + for (auto &kv : headers) { + if (kv.name == name) { + res = &kv; + } } - return std::end(headers); -} -} // namespace - -namespace { -Headers::iterator get_norm_header(Headers &headers, const std::string &name) { - auto i = std::lower_bound(std::begin(headers), std::end(headers), - Header(name, ""), http2::name_less); - if (i != std::end(headers) && (*i).name == name) { - return i; - } - return std::end(headers); -} -} // namespace - -namespace { -Headers::const_iterator get_header_linear(const Headers &headers, - const std::string &name) { - auto i = std::find_if( - std::begin(headers), std::end(headers), - [&name](const Header &header) { return header.name == name; }); - return i; + return res; } } // namespace @@ -253,90 +235,70 @@ void Downstream::assemble_request_cookie() { std::string &cookie = assembled_request_cookie_; cookie = ""; for (auto &kv : request_headers_) { - if (util::strieq("cookie", kv.name.c_str())) { - auto end = kv.value.find_last_not_of(" ;"); - if (end == std::string::npos) { - cookie += kv.value; - } else { - cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1); - } - cookie += "; "; + if (kv.name.size() != 6 || kv.name[5] != 'e' || + !util::streq("cooki", kv.name.c_str(), 5)) { + continue; } + + auto end = kv.value.find_last_not_of(" ;"); + if (end == std::string::npos) { + cookie += kv.value; + } else { + cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1); + } + cookie += "; "; } if (cookie.size() >= 2) { cookie.erase(cookie.size() - 2); } } -void Downstream::crumble_request_cookie() { +Headers Downstream::crumble_request_cookie() { Headers cookie_hdrs; for (auto &kv : request_headers_) { - if (util::strieq("cookie", kv.name.c_str())) { - size_t last = kv.value.size(); - size_t num = 0; - std::string rep_cookie; + if (kv.name.size() != 6 || kv.name[5] != 'e' || + !util::streq("cooki", kv.name.c_str(), 5)) { + continue; + } + size_t last = kv.value.size(); - for (size_t j = 0; j < last;) { - j = kv.value.find_first_not_of("\t ;", j); - if (j == std::string::npos) { - break; - } - auto first = j; - - j = kv.value.find(';', j); - if (j == std::string::npos) { - j = last; - } - - if (num == 0) { - if (first == 0 && j == last) { - break; - } - rep_cookie = kv.value.substr(first, j - first); - } else { - cookie_hdrs.push_back( - Header("cookie", kv.value.substr(first, j - first), kv.no_index)); - } - ++num; + for (size_t j = 0; j < last;) { + j = kv.value.find_first_not_of("\t ;", j); + if (j == std::string::npos) { + break; } - if (num > 0) { - kv.value = std::move(rep_cookie); + auto first = j; + + j = kv.value.find(';', j); + if (j == std::string::npos) { + j = last; } + + cookie_hdrs.push_back( + Header("cookie", kv.value.substr(first, j - first), kv.no_index)); } } - request_headers_.insert(std::end(request_headers_), - std::make_move_iterator(std::begin(cookie_hdrs)), - std::make_move_iterator(std::end(cookie_hdrs))); - if (request_headers_normalized_) { - normalize_request_headers(); - } + return cookie_hdrs; } const std::string &Downstream::get_assembled_request_cookie() const { return assembled_request_cookie_; } -void Downstream::normalize_request_headers() { - http2::normalize_headers(request_headers_); - request_headers_normalized_ = true; -} - -Headers::const_iterator -Downstream::get_norm_request_header(const std::string &name) const { - return get_norm_header(request_headers_, name); -} - -Headers::const_iterator -Downstream::get_request_header(const std::string &name) const { - if (request_headers_normalized_) { - return get_norm_request_header(name); +void Downstream::index_request_headers() { + for (auto &kv : request_headers_) { + util::inp_strlower(kv.name); } - - return get_header_linear(request_headers_, name); + http2::index_headers(request_hdidx_, request_headers_); } -bool Downstream::get_request_headers_normalized() const { - return request_headers_normalized_; +const Headers::value_type *Downstream::get_request_header(int token) const { + return http2::get_header(request_hdidx_, token, request_headers_); +} + +const Headers::value_type * +Downstream::get_request_header(const std::string &name) const { + return get_header_linear(request_headers_, name); } void Downstream::add_request_header(std::string name, std::string value) { @@ -352,9 +314,10 @@ void Downstream::set_last_request_header_value(std::string value) { item.value = std::move(value); } -void Downstream::split_add_request_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index) { +void Downstream::add_request_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int token) { + http2::index_header(request_hdidx_, token, request_headers_.size()); request_headers_sum_ += namelen + valuelen; http2::add_header(request_headers_, name, namelen, value, valuelen, no_index); } @@ -378,7 +341,10 @@ void Downstream::append_last_request_header_value(const char *data, item.value.append(data, len); } -void Downstream::clear_request_headers() { Headers().swap(request_headers_); } +void Downstream::clear_request_headers() { + Headers().swap(request_headers_); + http2::init_hdidx(request_hdidx_); +} size_t Downstream::get_request_headers_sum() const { return request_headers_sum_; @@ -524,19 +490,23 @@ const Headers &Downstream::get_response_headers() const { return response_headers_; } -void Downstream::normalize_response_headers() { - http2::normalize_headers(response_headers_); +void Downstream::index_response_headers() { + for (auto &kv : response_headers_) { + util::inp_strlower(kv.name); + } + http2::index_headers(response_hdidx_, response_headers_); } -Headers::const_iterator -Downstream::get_norm_response_header(const std::string &name) const { - return get_norm_header(response_headers_, name); +const Headers::value_type *Downstream::get_response_header(int token) const { + return http2::get_header(response_hdidx_, token, response_headers_); } -void Downstream::rewrite_norm_location_response_header( - const std::string &upstream_scheme, uint16_t upstream_port) { - auto hd = get_norm_header(response_headers_, "location"); - if (hd == std::end(response_headers_)) { +void +Downstream::rewrite_location_response_header(const std::string &upstream_scheme, + uint16_t upstream_port) { + auto hd = + http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_); + if (!hd) { return; } http_parser_url u; @@ -553,15 +523,16 @@ void Downstream::rewrite_norm_location_response_header( upstream_scheme, upstream_port); } if (new_uri.empty()) { - auto host = get_norm_request_header("host"); - if (host == std::end(request_headers_)) { + auto host = get_request_header(http2::HD_HOST); + if (!host) { return; } new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value, upstream_scheme, upstream_port); } if (!new_uri.empty()) { - (*hd).value = std::move(new_uri); + auto idx = response_hdidx_[http2::HD_LOCATION]; + response_headers_[idx].value = std::move(new_uri); } } @@ -578,9 +549,10 @@ void Downstream::set_last_response_header_value(std::string value) { item.value = std::move(value); } -void Downstream::split_add_response_header(const uint8_t *name, size_t namelen, - const uint8_t *value, - size_t valuelen, bool no_index) { +void Downstream::add_response_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int token) { + http2::index_header(response_hdidx_, token, response_headers_.size()); response_headers_sum_ += namelen + valuelen; http2::add_header(response_headers_, name, namelen, value, valuelen, no_index); @@ -605,7 +577,10 @@ void Downstream::append_last_response_header_value(const char *data, item.value.append(data, len); } -void Downstream::clear_response_headers() { Headers().swap(response_headers_); } +void Downstream::clear_response_headers() { + Headers().swap(response_headers_); + http2::init_hdidx(response_hdidx_); +} size_t Downstream::get_response_headers_sum() const { return response_headers_sum_; @@ -717,37 +692,31 @@ void Downstream::inspect_http1_request() { upgrade_request_ = true; } - for (auto &hd : request_headers_) { - if (!upgrade_request_ && util::strieq("upgrade", hd.name.c_str())) { - // TODO Perform more strict checking for upgrade headers + if (!upgrade_request_) { + auto idx = request_hdidx_[http2::HD_UPGRADE]; + if (idx != -1) { upgrade_request_ = true; - if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, hd.value.c_str(), - hd.value.size())) { + auto &val = request_headers_[idx].value; + // TODO Perform more strict checking for upgrade headers + if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(), + val.size())) { http2_upgrade_seen_ = true; } - } else if (!http2_settings_seen_ && - util::strieq(hd.name.c_str(), "http2-settings")) { - - http2_settings_seen_ = true; - http2_settings_ = hd.value; - } else if (!chunked_request_ && - util::strieq(hd.name.c_str(), "transfer-encoding")) { - if (util::strifind(hd.value.c_str(), "chunked")) { - chunked_request_ = true; - } } } + auto idx = request_hdidx_[http2::HD_TRANSFER_ENCODING]; + if (idx != -1 && + util::strifind(request_headers_[idx].value.c_str(), "chunked")) { + chunked_request_ = true; + } } void Downstream::inspect_http1_response() { - for (auto &hd : response_headers_) { - if (!chunked_response_ && - util::strieq(hd.name.c_str(), "transfer-encoding")) { - if (util::strifind(hd.value.c_str(), "chunked")) { - chunked_response_ = true; - } - } + auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING]; + if (idx != -1 && + util::strifind(response_headers_[idx].value.c_str(), "chunked")) { + chunked_response_ = true; } } @@ -766,11 +735,20 @@ bool Downstream::get_upgraded() const { return upgraded_; } bool Downstream::get_upgrade_request() const { return upgrade_request_; } bool Downstream::get_http2_upgrade_request() const { - return request_bodylen_ == 0 && http2_upgrade_seen_ && http2_settings_seen_; + return request_bodylen_ == 0 && http2_upgrade_seen_ && + request_hdidx_[http2::HD_HTTP2_SETTINGS] != -1; } +namespace { +const std::string EMPTY; +} // namespace + const std::string &Downstream::get_http2_settings() const { - return http2_settings_; + auto idx = request_hdidx_[http2::HD_HTTP2_SETTINGS]; + if (idx == -1) { + return EMPTY; + } + return request_headers_[idx].value; } void Downstream::set_downstream_stream_id(int32_t stream_id) { @@ -832,12 +810,18 @@ bool pseudo_header_allowed(const Headers &headers) { } } // namespace -bool Downstream::request_pseudo_header_allowed() const { - return pseudo_header_allowed(request_headers_); +bool Downstream::request_pseudo_header_allowed(int token) const { + if (!pseudo_header_allowed(request_headers_)) { + return false; + } + return http2::check_http2_request_pseudo_header(request_hdidx_, token); } -bool Downstream::response_pseudo_header_allowed() const { - return pseudo_header_allowed(response_headers_); +bool Downstream::response_pseudo_header_allowed(int token) const { + if (!pseudo_header_allowed(response_headers_)) { + return false; + } + return http2::check_http2_response_pseudo_header(response_hdidx_, token); } void Downstream::init_upstream_timer() { diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 5c48ff6a..02c1b32d 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -95,30 +95,27 @@ public: const std::string &get_http2_settings() const; // downstream request API const Headers &get_request_headers() const; - void crumble_request_cookie(); + // Crumbles (split cookie by ";") in request_headers_ and returns + // them. Headers::no_index is inherited. + Headers crumble_request_cookie(); void assemble_request_cookie(); const std::string &get_assembled_request_cookie() const; - // Makes key lowercase and sort headers by name using < - void normalize_request_headers(); - // Returns iterator pointing to the request header with the name - // |name|. If multiple header have |name| as name, return first - // occurrence from the beginning. If no such header is found, - // returns std::end(get_request_headers()). This function must be - // called after calling normalize_request_headers(). - Headers::const_iterator - get_norm_request_header(const std::string &name) const; - // Returns iterator pointing to the request header with the name - // |name|. This function acts like get_norm_request_header(), but - // if request_headers_ was not normalized, use linear search to find - // the header. Otherwise, get_norm_request_header() is used. - Headers::const_iterator get_request_header(const std::string &name) const; - bool get_request_headers_normalized() const; + // Lower the request header field names and indexes request headers + void index_request_headers(); + // Returns pointer to the request header with the name |name|. If + // multiple header have |name| as name, return last occurrence from + // the beginning. If no such header is found, returns nullptr. + // This function must be called after headers are indexed + const Headers::value_type *get_request_header(int token) const; + // Returns pointer to the request header with the name |name|. If + // no such header is found, returns nullptr. + const Headers::value_type *get_request_header(const std::string &name) const; void add_request_header(std::string name, std::string value); void set_last_request_header_value(std::string value); - void split_add_request_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index); + void add_request_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int token); bool get_request_header_key_prev() const; void append_last_request_header_key(const char *data, size_t len); @@ -161,7 +158,7 @@ public: size_t get_request_datalen() const; void dec_request_datalen(size_t len); void reset_request_datalen(); - bool request_pseudo_header_allowed() const; + bool request_pseudo_header_allowed(int token) const; bool expect_response_body() const; enum { INITIAL, @@ -177,26 +174,22 @@ public: Memchunks4K *get_request_buf(); // downstream response API const Headers &get_response_headers() const; - // Makes key lowercase and sort headers by name using < - void normalize_response_headers(); - // Returns iterator pointing to the response header with the name - // |name|. If multiple header have |name| as name, return first - // occurrence from the beginning. If no such header is found, - // returns std::end(get_response_headers()). This function must be - // called after calling normalize_response_headers(). - Headers::const_iterator - get_norm_response_header(const std::string &name) const; - // Rewrites the location response header field. This function must - // be called after calling normalize_response_headers() and - // normalize_request_headers(). - void rewrite_norm_location_response_header(const std::string &upstream_scheme, - uint16_t upstream_port); + // Lower the response header field names and indexes response headers + void index_response_headers(); + // Returns pointer to the response header with the name |name|. If + // multiple header have |name| as name, return last occurrence from + // the beginning. If no such header is found, returns nullptr. + // This function must be called after response headers are indexed. + const Headers::value_type *get_response_header(int token) const; + // Rewrites the location response header field. + void rewrite_location_response_header(const std::string &upstream_scheme, + uint16_t upstream_port); void add_response_header(std::string name, std::string value); void set_last_response_header_value(std::string value); - void split_add_response_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index); + void add_response_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int token); bool get_response_header_key_prev() const; void append_last_response_header_key(const char *data, size_t len); @@ -238,7 +231,7 @@ public: void dec_response_datalen(size_t len); size_t get_response_datalen() const; void reset_response_datalen(); - bool response_pseudo_header_allowed() const; + bool response_pseudo_header_allowed(int token) const; // Call this method when there is incoming data in downstream // connection. @@ -298,7 +291,6 @@ private: std::string request_http2_authority_; std::chrono::high_resolution_clock::time_point request_start_time_; std::string assembled_request_cookie_; - std::string http2_settings_; Memchunks4K request_buf_; Memchunks4K response_buf_; @@ -343,6 +335,9 @@ private: int response_major_; int response_minor_; + int request_hdidx_[http2::HD_MAXIDX]; + int response_hdidx_[http2::HD_MAXIDX]; + // true if the request contains upgrade token (HTTP Upgrade or // CONNECT) bool upgrade_request_; @@ -350,7 +345,6 @@ private: bool upgraded_; bool http2_upgrade_seen_; - bool http2_settings_seen_; bool chunked_request_; bool request_connection_close_; @@ -362,9 +356,6 @@ private: bool response_header_key_prev_; bool expect_final_response_; - // true if request_headers_ is normalized - bool request_headers_normalized_; - bool use_timer_; }; diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc index b2924034..aab7ba85 100644 --- a/src/shrpx_downstream_test.cc +++ b/src/shrpx_downstream_test.cc @@ -32,7 +32,7 @@ namespace shrpx { -void test_downstream_normalize_request_headers(void) { +void test_downstream_index_request_headers(void) { Downstream d(nullptr, 0, 0); d.add_request_header("1", "0"); d.add_request_header("2", "1"); @@ -42,88 +42,83 @@ void test_downstream_normalize_request_headers(void) { d.add_request_header("BravO", "5"); d.add_request_header(":method", "6"); d.add_request_header(":authority", "7"); - d.normalize_request_headers(); + d.index_request_headers(); - auto ans = Headers{{":authority", "7"}, - {":method", "6"}, - {"1", "0"}, + auto ans = Headers{{"1", "0"}, {"2", "1"}, - {"alpha", "3"}, - {"bravo", "5"}, {"charlie", "2"}, - {"delta", "4"}}; + {"alpha", "3"}, + {"delta", "4"}, + {"bravo", "5"}, + {":method", "6"}, + {":authority", "7"}}; CU_ASSERT(ans == d.get_request_headers()); } -void test_downstream_normalize_response_headers(void) { +void test_downstream_index_response_headers(void) { Downstream d(nullptr, 0, 0); d.add_response_header("Charlie", "0"); d.add_response_header("Alpha", "1"); d.add_response_header("Delta", "2"); d.add_response_header("BravO", "3"); - d.normalize_response_headers(); + d.index_response_headers(); auto ans = - Headers{{"alpha", "1"}, {"bravo", "3"}, {"charlie", "0"}, {"delta", "2"}}; + Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}}; CU_ASSERT(ans == d.get_response_headers()); } -void test_downstream_get_norm_request_header(void) { +void test_downstream_get_request_header(void) { Downstream d(nullptr, 0, 0); d.add_request_header("alpha", "0"); - d.add_request_header("bravo", "1"); - d.add_request_header("bravo", "2"); - d.add_request_header("charlie", "3"); - d.add_request_header("delta", "4"); - d.add_request_header("echo", "5"); - auto i = d.get_norm_request_header("alpha"); - CU_ASSERT(Header("alpha", "0") == *i); - i = d.get_norm_request_header("bravo"); - CU_ASSERT(Header("bravo", "1") == *i); - i = d.get_norm_request_header("delta"); - CU_ASSERT(Header("delta", "4") == *i); - i = d.get_norm_request_header("echo"); - CU_ASSERT(Header("echo", "5") == *i); - i = d.get_norm_request_header("foxtrot"); - CU_ASSERT(i == std::end(d.get_request_headers())); + d.add_request_header(":authority", "1"); + d.add_request_header("content-length", "2"); + d.index_request_headers(); + + // By token + CU_ASSERT(Header(":authority", "1") == + *d.get_request_header(http2::HD__AUTHORITY)); + CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD)); + + // By name + CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha")); + CU_ASSERT(nullptr == d.get_request_header("bravo")); } -void test_downstream_get_norm_response_header(void) { +void test_downstream_get_response_header(void) { Downstream d(nullptr, 0, 0); d.add_response_header("alpha", "0"); - d.add_response_header("bravo", "1"); - d.add_response_header("bravo", "2"); - d.add_response_header("charlie", "3"); - d.add_response_header("delta", "4"); - d.add_response_header("echo", "5"); - auto i = d.get_norm_response_header("alpha"); - CU_ASSERT(Header("alpha", "0") == *i); - i = d.get_norm_response_header("bravo"); - CU_ASSERT(Header("bravo", "1") == *i); - i = d.get_norm_response_header("delta"); - CU_ASSERT(Header("delta", "4") == *i); - i = d.get_norm_response_header("echo"); - CU_ASSERT(Header("echo", "5") == *i); - i = d.get_norm_response_header("foxtrot"); - CU_ASSERT(i == std::end(d.get_response_headers())); + d.add_response_header(":status", "1"); + d.add_response_header("content-length", "2"); + d.index_response_headers(); + + // By token + CU_ASSERT(Header(":status", "1") == + *d.get_response_header(http2::HD__STATUS)); + CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD)); } void test_downstream_crumble_request_cookie(void) { Downstream d(nullptr, 0, 0); d.add_request_header(":method", "get"); d.add_request_header(":path", "/"); - d.add_request_header("cookie", "alpha; bravo; ; ;; charlie;;"); + auto val = "alpha; bravo; ; ;; charlie;;"; + d.add_request_header( + reinterpret_cast("cookie"), sizeof("cookie") - 1, + reinterpret_cast(val), strlen(val), true, -1); d.add_request_header("cookie", ";delta"); d.add_request_header("cookie", "echo"); - d.crumble_request_cookie(); - Headers ans = {{":method", "get"}, - {":path", "/"}, - {"cookie", "alpha"}, - {"cookie", "delta"}, - {"cookie", "echo"}, + auto cookies = d.crumble_request_cookie(); + + Headers ans = {{"cookie", "alpha"}, {"cookie", "bravo"}, - {"cookie", "charlie"}}; - CU_ASSERT(ans == d.get_request_headers()); + {"cookie", "charlie"}, + {"cookie", "delta"}, + {"cookie", "echo"}}; + CU_ASSERT(ans == cookies); + CU_ASSERT(cookies[0].no_index); + CU_ASSERT(cookies[1].no_index); + CU_ASSERT(cookies[2].no_index); } void test_downstream_assemble_request_cookie(void) { @@ -138,21 +133,24 @@ void test_downstream_assemble_request_cookie(void) { CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie()); } -void test_downstream_rewrite_norm_location_response_header(void) { +void test_downstream_rewrite_location_response_header(void) { { Downstream d(nullptr, 0, 0); d.add_request_header("host", "localhost:3000"); d.add_response_header("location", "http://localhost:3000/"); - d.rewrite_norm_location_response_header("https", 443); - auto location = d.get_norm_response_header("location"); + d.index_request_headers(); + d.index_response_headers(); + d.rewrite_location_response_header("https", 443); + auto location = d.get_response_header(http2::HD_LOCATION); CU_ASSERT("https://localhost/" == (*location).value); } { Downstream d(nullptr, 0, 0); d.set_request_http2_authority("localhost"); d.add_response_header("location", "http://localhost/"); - d.rewrite_norm_location_response_header("https", 443); - auto location = d.get_norm_response_header("location"); + d.index_response_headers(); + d.rewrite_location_response_header("https", 443); + auto location = d.get_response_header(http2::HD_LOCATION); CU_ASSERT("https://localhost/" == (*location).value); } } diff --git a/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h index 19effd91..b1e1aee5 100644 --- a/src/shrpx_downstream_test.h +++ b/src/shrpx_downstream_test.h @@ -27,13 +27,13 @@ namespace shrpx { -void test_downstream_normalize_request_headers(void); -void test_downstream_normalize_response_headers(void); -void test_downstream_get_norm_request_header(void); -void test_downstream_get_norm_response_header(void); +void test_downstream_index_request_headers(void); +void test_downstream_index_response_headers(void); +void test_downstream_get_request_header(void); +void test_downstream_get_response_header(void); void test_downstream_crumble_request_cookie(void); void test_downstream_assemble_request_cookie(void); -void test_downstream_rewrite_norm_location_response_header(void); +void test_downstream_rewrite_location_response_header(void); } // namespace shrpx diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index 65ba0f61..c143c3a1 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -233,14 +233,12 @@ int Http2DownstreamConnection::push_request_headers() { return 0; } size_t nheader = downstream_->get_request_headers().size(); + + Headers cookies; if (!get_config()->http2_no_cookie_crumbling) { - downstream_->crumble_request_cookie(); + cookies = downstream_->crumble_request_cookie(); } - assert(downstream_->get_request_headers_normalized()); - - auto end_headers = std::end(downstream_->get_request_headers()); - // 7 means: // 1. :method // 2. :scheme @@ -250,10 +248,12 @@ int Http2DownstreamConnection::push_request_headers() { // 6. x-forwarded-for (optional) // 7. x-forwarded-proto (optional) auto nva = std::vector(); - nva.reserve(nheader + 7); + nva.reserve(nheader + 7 + cookies.size()); + std::string via_value; std::string xff_value; std::string scheme, authority, path, query; + // To reconstruct HTTP/1 status line and headers, proxy should // preserve host header field. See draft-09 section 8.1.3.1. if (downstream_->get_request_method() == "CONNECT") { @@ -273,7 +273,7 @@ int Http2DownstreamConnection::push_request_headers() { if (!downstream_->get_request_http2_authority().empty()) { nva.push_back(http2::make_nv_ls( ":authority", downstream_->get_request_http2_authority())); - } else if (downstream_->get_norm_request_header("host") == end_headers) { + } else if (!downstream_->get_request_header(http2::HD_HOST)) { if (LOG_ENABLED(INFO)) { DCLOG(INFO, this) << "host header field missing"; } @@ -330,7 +330,7 @@ int Http2DownstreamConnection::push_request_headers() { authority += util::utos(u.port); } nva.push_back(http2::make_nv_ls(":authority", authority)); - } else if (downstream_->get_norm_request_header("host") == end_headers) { + } else if (!downstream_->get_request_header(http2::HD_HOST)) { if (LOG_ENABLED(INFO)) { DCLOG(INFO, this) << "host header field missing"; } @@ -341,27 +341,30 @@ int Http2DownstreamConnection::push_request_headers() { nva.push_back( http2::make_nv_ls(":method", downstream_->get_request_method())); - http2::copy_norm_headers_to_nva(nva, downstream_->get_request_headers()); + http2::copy_headers_to_nva(nva, downstream_->get_request_headers()); bool chunked_encoding = false; auto transfer_encoding = - downstream_->get_norm_request_header("transfer-encoding"); - if (transfer_encoding != end_headers && + downstream_->get_request_header(http2::HD_TRANSFER_ENCODING); + if (transfer_encoding && util::strieq((*transfer_encoding).value.c_str(), "chunked")) { chunked_encoding = true; } - auto xff = downstream_->get_norm_request_header("x-forwarded-for"); + for (auto &nv : cookies) { + nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index)); + } + + auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR); if (get_config()->add_x_forwarded_for) { - if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) { + if (xff && !get_config()->strip_incoming_x_forwarded_for) { xff_value = (*xff).value; xff_value += ", "; } xff_value += downstream_->get_upstream()->get_client_handler()->get_ipaddr(); nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value)); - } else if (xff != end_headers && - !get_config()->strip_incoming_x_forwarded_for) { + } else if (xff && !get_config()->strip_incoming_x_forwarded_for) { nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value)); } @@ -379,13 +382,13 @@ int Http2DownstreamConnection::push_request_headers() { } } - auto via = downstream_->get_norm_request_header("via"); + auto via = downstream_->get_request_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { nva.push_back(http2::make_nv_ls("via", (*via).value)); } } else { - if (via != end_headers) { + if (via) { via_value = (*via).value; via_value += ", "; } @@ -407,7 +410,8 @@ int Http2DownstreamConnection::push_request_headers() { } auto content_length = - downstream_->get_norm_request_header("content-length") != end_headers; + downstream_->get_request_header(http2::HD_CONTENT_LENGTH); + // TODO check content-length: 0 case if (downstream_->get_request_method() == "CONNECT" || chunked_encoding || content_length || downstream_->get_request_http2_expect_body()) { diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 855a36d6..d464a048 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -709,18 +709,24 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (namelen > 0 && name[0] == ':') { - if (!downstream->response_pseudo_header_allowed() || - !http2::check_http2_response_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + if (name[0] == ':') { + if (!downstream->response_pseudo_header_allowed(token)) { http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } } - downstream->split_add_response_header(name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + if (!http2::http2_header_allowed(token)) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + downstream->add_response_header(name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); return 0; } } // namespace @@ -763,20 +769,11 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream, auto upstream = downstream->get_upstream(); - downstream->normalize_response_headers(); auto &nva = downstream->get_response_headers(); downstream->set_expect_final_response(false); - if (!http2::check_http2_response_headers(nva)) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - downstream->set_response_state(Downstream::MSG_RESET); - call_downstream_readcb(http2session, downstream); - return 0; - } - - auto status = http2::get_unique_header(nva, ":status"); + auto status = downstream->get_response_header(http2::HD__STATUS); int status_code; if (!http2::non_empty_value(status) || @@ -824,7 +821,8 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream, return 0; } - auto content_length = http2::get_header(nva, "content-length"); + auto content_length = + downstream->get_response_header(http2::HD_CONTENT_LENGTH); if (!content_length && downstream->get_request_method() != "HEAD" && downstream->get_request_method() != "CONNECT") { unsigned int status; diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 1a8e0675..f84a757e 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -185,17 +185,22 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (namelen > 0 && name[0] == ':') { - if (!downstream->request_pseudo_header_allowed() || - !http2::check_http2_request_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + if (name[0] == ':') { + if (!downstream->request_pseudo_header_allowed(token)) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } } - downstream->split_add_request_header(name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + if (!http2::http2_header_allowed(token)) { + upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + downstream->add_request_header(name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); return 0; } } // namespace @@ -238,7 +243,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream, return 0; } - downstream->normalize_request_headers(); auto &nva = downstream->get_request_headers(); if (LOG_ENABLED(INFO)) { @@ -254,17 +258,11 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream, http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva); } - if (!http2::check_http2_request_headers(nva)) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - - return 0; - } - - auto host = http2::get_unique_header(nva, "host"); - auto authority = http2::get_unique_header(nva, ":authority"); - auto path = http2::get_unique_header(nva, ":path"); - auto method = http2::get_unique_header(nva, ":method"); - auto scheme = http2::get_unique_header(nva, ":scheme"); + auto host = downstream->get_request_header(http2::HD_HOST); + auto authority = downstream->get_request_header(http2::HD__AUTHORITY); + auto path = downstream->get_request_header(http2::HD__PATH); + auto method = downstream->get_request_header(http2::HD__METHOD); + auto scheme = downstream->get_request_header(http2::HD__SCHEME); bool is_connect = method && "CONNECT" == method->value; bool having_host = http2::non_empty_value(host); @@ -1101,14 +1099,12 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { } } - downstream->normalize_response_headers(); if (!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->no_location_rewrite) { - downstream->rewrite_norm_location_response_header( + downstream->rewrite_location_response_header( get_client_handler()->get_upstream_scheme(), get_config()->port); } - auto end_headers = std::end(downstream->get_response_headers()); size_t nheader = downstream->get_response_headers().size(); auto nva = std::vector(); // 3 means :status and possible server and via header field. @@ -1117,7 +1113,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { auto response_status = util::utos(downstream->get_response_http_status()); nva.push_back(http2::make_nv_ls(":status", response_status)); - http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers()); + http2::copy_headers_to_nva(nva, downstream->get_response_headers()); if (downstream->get_non_final_response()) { if (LOG_ENABLED(INFO)) { @@ -1141,19 +1137,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { if (!get_config()->http2_proxy && !get_config()->client_proxy) { nva.push_back(http2::make_nv_lc("server", get_config()->server_name)); } else { - auto server = downstream->get_norm_response_header("server"); - if (server != end_headers) { + auto server = downstream->get_response_header(http2::HD_SERVER); + if (server) { nva.push_back(http2::make_nv_ls("server", (*server).value)); } } - auto via = downstream->get_norm_response_header("via"); + auto via = downstream->get_response_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { nva.push_back(http2::make_nv_ls("via", (*via).value)); } } else { - if (via != end_headers) { + if (via) { via_value = (*via).value; via_value += ", "; } diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 0b18b3eb..cc6c2f78 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -213,11 +213,8 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { } int HttpDownstreamConnection::push_request_headers() { - assert(downstream_->get_request_headers_normalized()); - downstream_->assemble_request_cookie(); - auto end_headers = std::end(downstream_->get_request_headers()); // Assume that method and request path do not contain \r\n. std::string hdrs = downstream_->get_request_method(); hdrs += " "; @@ -253,14 +250,14 @@ int HttpDownstreamConnection::push_request_headers() { hdrs += downstream_->get_request_path(); } hdrs += " HTTP/1.1\r\n"; - if (downstream_->get_norm_request_header("host") == end_headers && + if (!downstream_->get_request_header(http2::HD_HOST) && !downstream_->get_request_http2_authority().empty()) { hdrs += "Host: "; hdrs += downstream_->get_request_http2_authority(); hdrs += "\r\n"; } - http2::build_http1_headers_from_norm_headers( - hdrs, downstream_->get_request_headers()); + http2::build_http1_headers_from_headers(hdrs, + downstream_->get_request_headers()); if (!downstream_->get_assembled_request_cookie().empty()) { hdrs += "Cookie: "; @@ -270,7 +267,7 @@ int HttpDownstreamConnection::push_request_headers() { if (downstream_->get_request_method() != "CONNECT" && downstream_->get_request_http2_expect_body() && - downstream_->get_norm_request_header("content-length") == end_headers) { + !downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) { downstream_->set_chunked_request(true); hdrs += "Transfer-Encoding: chunked\r\n"; @@ -279,18 +276,17 @@ int HttpDownstreamConnection::push_request_headers() { if (downstream_->get_request_connection_close()) { hdrs += "Connection: close\r\n"; } - auto xff = downstream_->get_norm_request_header("x-forwarded-for"); + auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR); if (get_config()->add_x_forwarded_for) { hdrs += "X-Forwarded-For: "; - if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) { + if (xff && !get_config()->strip_incoming_x_forwarded_for) { hdrs += (*xff).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size()); hdrs += ", "; } hdrs += client_handler_->get_ipaddr(); hdrs += "\r\n"; - } else if (xff != end_headers && - !get_config()->strip_incoming_x_forwarded_for) { + } else if (xff && !get_config()->strip_incoming_x_forwarded_for) { hdrs += "X-Forwarded-For: "; hdrs += (*xff).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size()); @@ -308,17 +304,16 @@ int HttpDownstreamConnection::push_request_headers() { hdrs += "http\r\n"; } } - auto expect = downstream_->get_norm_request_header("expect"); - if (expect != end_headers && - !util::strifind((*expect).value.c_str(), "100-continue")) { + auto expect = downstream_->get_request_header(http2::HD_EXPECT); + if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) { hdrs += "Expect: "; hdrs += (*expect).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size()); hdrs += "\r\n"; } - auto via = downstream_->get_norm_request_header("via"); + auto via = downstream_->get_request_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { hdrs += "Via: "; hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); @@ -326,7 +321,7 @@ int HttpDownstreamConnection::push_request_headers() { } } else { hdrs += "Via: "; - if (via != end_headers) { + if (via) { hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); hdrs += ", "; @@ -473,6 +468,8 @@ int htp_hdrs_completecb(http_parser *htp) { downstream->set_response_major(htp->http_major); downstream->set_response_minor(htp->http_minor); + downstream->index_response_headers(); + if (downstream->get_non_final_response()) { // For non-final response code, we just call // on_downstream_header_complete() without changing response diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 3b3a9ebc..0cd29b36 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -149,7 +149,7 @@ int htp_hdrs_completecb(http_parser *htp) { ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str(); } - downstream->normalize_request_headers(); + downstream->index_request_headers(); downstream->inspect_http1_request(); @@ -620,15 +620,15 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { hdrs += " "; hdrs += http2::get_status_string(downstream->get_response_http_status()); hdrs += "\r\n"; - downstream->normalize_response_headers(); + if (!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->no_location_rewrite) { - downstream->rewrite_norm_location_response_header( + downstream->rewrite_location_response_header( get_client_handler()->get_upstream_scheme(), get_config()->port); } - auto end_headers = std::end(downstream->get_response_headers()); - http2::build_http1_headers_from_norm_headers( - hdrs, downstream->get_response_headers()); + + http2::build_http1_headers_from_headers(hdrs, + downstream->get_response_headers()); auto output = downstream->get_response_buf(); @@ -660,8 +660,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { hdrs += "Connection: close\r\n"; } - if (downstream->get_norm_response_header("alt-svc") == end_headers) { - // We won't change or alter alt-svc from backend at the moment. + if (!downstream->get_response_header(http2::HD_ALT_SVC)) { + // We won't change or alter alt-svc from backend for now if (!get_config()->altsvcs.empty()) { hdrs += "Alt-Svc: "; @@ -684,17 +684,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { hdrs += get_config()->server_name; hdrs += "\r\n"; } else { - auto server = downstream->get_norm_response_header("server"); - if (server != end_headers) { + auto server = downstream->get_response_header(http2::HD_SERVER); + if (server) { hdrs += "Server: "; hdrs += (*server).value; hdrs += "\r\n"; } } - auto via = downstream->get_norm_response_header("via"); + auto via = downstream->get_response_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { hdrs += "Via: "; hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); @@ -702,7 +702,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { } } else { hdrs += "Via: "; - if (via != end_headers) { + if (via) { hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); hdrs += ", "; diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc index 9a74609b..8e5a0c26 100644 --- a/src/shrpx_log.cc +++ b/src/shrpx_log.cc @@ -206,7 +206,7 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { case SHRPX_LOGF_HTTP: if (downstream) { auto hd = downstream->get_request_header(lf.value.get()); - if (hd != std::end(downstream->get_request_headers())) { + if (hd) { std::tie(p, avail) = copy((*hd).value.c_str(), avail, p); break; } diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 14d971ff..9630bebb 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -156,42 +156,33 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, downstream->reset_upstream_rtimer(); auto nv = frame->syn_stream.nv; - const char *path = nullptr; - const char *scheme = nullptr; - const char *host = nullptr; - const char *method = nullptr; for (size_t i = 0; nv[i]; i += 2) { - if (strcmp(nv[i], ":path") == 0) { - path = nv[i + 1]; - } else if (strcmp(nv[i], ":scheme") == 0) { - scheme = nv[i + 1]; - } else if (strcmp(nv[i], ":method") == 0) { - method = nv[i + 1]; - } else if (strcmp(nv[i], ":host") == 0) { - host = nv[i + 1]; - } else if (nv[i][0] != ':') { - downstream->add_request_header(nv[i], nv[i + 1]); - } + downstream->add_request_header(nv[i], nv[i + 1]); } - downstream->normalize_request_headers(); + downstream->index_request_headers(); - bool is_connect = method && strcmp("CONNECT", method) == 0; - if (!path || !host || !method || http2::lws(host) || http2::lws(path) || - http2::lws(method) || - (!is_connect && (!scheme || http2::lws(scheme)))) { + auto path = downstream->get_request_header(http2::HD__PATH); + auto scheme = downstream->get_request_header(http2::HD__SCHEME); + auto host = downstream->get_request_header(http2::HD__HOST); + auto method = downstream->get_request_header(http2::HD__METHOD); + + bool is_connect = method && "CONNECT" == method->value; + if (!path || !host || !method || !http2::non_empty_value(host) || + !http2::non_empty_value(path) || !http2::non_empty_value(method) || + (!is_connect && (!scheme || !http2::non_empty_value(scheme)))) { upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); return; } - downstream->set_request_method(method); + downstream->set_request_method(method->value); if (is_connect) { - downstream->set_request_http2_authority(path); + downstream->set_request_http2_authority(path->value); } else { - downstream->set_request_http2_scheme(scheme); - downstream->set_request_http2_authority(host); - downstream->set_request_path(path); + downstream->set_request_http2_scheme(scheme->value); + downstream->set_request_http2_authority(host->value); + downstream->set_request_path(path->value); } downstream->set_request_start_time( @@ -824,10 +815,10 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { if (LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response header completed"; } - downstream->normalize_response_headers(); + if (!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->no_location_rewrite) { - downstream->rewrite_norm_location_response_header( + downstream->rewrite_location_response_header( get_client_handler()->get_upstream_scheme(), get_config()->port); } size_t nheader = downstream->get_response_headers().size(); @@ -844,30 +835,39 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { nv[hdidx++] = ":version"; nv[hdidx++] = "HTTP/1.1"; for (auto &hd : downstream->get_response_headers()) { - if (hd.name.empty() || hd.name.c_str()[0] == ':' || - util::strieq(hd.name.c_str(), "transfer-encoding") || - util::strieq(hd.name.c_str(), "keep-alive") || // HTTP/1.0? - util::strieq(hd.name.c_str(), "connection") || - util::strieq(hd.name.c_str(), "proxy-connection")) { - // These are ignored - } else if (!get_config()->no_via && util::strieq(hd.name.c_str(), "via")) { - via_value = hd.value; - } else if (!get_config()->http2_proxy && !get_config()->client_proxy && - util::strieq(hd.name.c_str(), "server")) { - // Rewrite server header field later - } else { - nv[hdidx++] = hd.name.c_str(); - nv[hdidx++] = hd.value.c_str(); + if (hd.name.empty() || hd.name.c_str()[0] == ':') { + continue; } + auto token = http2::lookup_token(hd.name); + switch (token) { + case http2::HD_CONNECTION: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_VIA: + case http2::HD_SERVER: + continue; + } + + nv[hdidx++] = hd.name.c_str(); + nv[hdidx++] = hd.value.c_str(); } if (!get_config()->http2_proxy && !get_config()->client_proxy) { nv[hdidx++] = "server"; nv[hdidx++] = get_config()->server_name; + } else { + auto server = downstream->get_response_header(http2::HD_SERVER); + if (server) { + nv[hdidx++] = "server"; + nv[hdidx++] = server->value.c_str(); + } } if (!get_config()->no_via) { - if (!via_value.empty()) { + auto via = downstream->get_response_header(http2::HD_VIA); + if (via) { + via_value = via->value; via_value += ", "; } via_value += http::create_via_header_value(