Commit 3ae44ef2 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

nghttpd, nghttpx: Rework incoming header handling

parent 730d47f7
......@@ -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__':
......
......@@ -89,9 +89,12 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
namespace {
void append_nv(Stream *stream, const std::vector<nghttp2_nv> &nva) {
size_t idx = 0;
for (auto &nv : nva) {
http2::index_header(stream->hdidx, nv.name, nv.namelen, idx++);
for (size_t i = 0; i < nva.size(); ++i) {
auto &nv = nva[i];
auto token = http2::lookup_token(nv.name, nv.namelen);
if (token != -1) {
http2::index_header(stream->hdidx, token, i);
}
http2::add_header(stream->headers, nv.name, nv.namelen, nv.value,
nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
}
......@@ -739,7 +742,7 @@ int Http2Handler::submit_non_final_response(const std::string &status,
int Http2Handler::submit_push_promise(Stream *stream,
const std::string &push_path) {
auto authority =
http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers);
http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
if (!authority) {
authority =
......@@ -900,9 +903,9 @@ void prepare_redirect_response(Stream *stream, Http2Handler *hd,
const std::string &path,
const std::string &status) {
auto scheme =
http2::get_header(stream->hdidx, http2::HD_SCHEME, stream->headers);
http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers);
auto authority =
http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers);
http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
if (!authority) {
authority =
http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers);
......@@ -924,7 +927,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
bool allow_push = true) {
int rv;
auto reqpath =
http2::get_header(stream->hdidx, http2::HD_PATH, stream->headers)->value;
http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value;
auto ims =
get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers);
......@@ -1038,11 +1041,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (namelen > 0 && name[0] == ':') {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if ((!stream->headers.empty() &&
stream->headers.back().name.c_str()[0] != ':') ||
!http2::check_http2_request_pseudo_header(stream->hdidx, name,
namelen)) {
!http2::check_http2_request_pseudo_header(stream->hdidx, token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
......@@ -1050,8 +1054,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
}
}
http2::index_header(stream->hdidx, name, namelen, stream->headers.size());
if (!http2::http2_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
http2::index_header(stream->hdidx, token, stream->headers.size());
http2::add_header(stream->headers, name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
return 0;
......@@ -1113,7 +1122,7 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
if (!http2::check_http2_request_headers(stream->hdidx)) {
if (!http2::http2_mandatory_request_headers_presence(stream->hdidx)) {
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
return 0;
}
......
This diff is collapsed.
......@@ -76,35 +76,6 @@ void sanitize_header_value(std::string &s, size_t offset);
void copy_url_component(std::string &dest, const http_parser_url *u, int field,
const char *url);
// Returns true if the header field |name| with length |namelen| bytes
// is valid for HTTP/2.
bool check_http2_allowed_header(const uint8_t *name, size_t namelen);
// Calls check_http2_allowed_header with |name| and strlen(name),
// assuming |name| is null-terminated string.
bool check_http2_allowed_header(const char *name);
// Checks that headers |nva| do not contain disallowed header fields
// in HTTP/2 spec. This function returns true if |nva| does not
// contains such headers.
bool check_http2_headers(const Headers &nva);
// Calls check_http2_headers()
bool check_http2_request_headers(const Headers &nva);
// Calls check_http2_headers()
bool check_http2_response_headers(const Headers &nva);
// Returns true if |name| is allowed pusedo header for request.
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen);
// Returns true if |name| is allowed pusedo header for response.
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen);
bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs);
void normalize_headers(Headers &nva);
Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index);
......@@ -115,16 +86,9 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
void add_header(Headers &nva, const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index);
// Returns the iterator to the entry in |nva| which has name |name|
// and the |name| is uinque in the |nva|. If no such entry exist,
// returns nullptr.
const Headers::value_type *get_unique_header(const Headers &nva,
const char *name);
// Returns the iterator to the entry in |nva| which has name
// |name|. If more than one entries which have the name |name|, first
// occurrence in |nva| is returned. If no such entry exist, returns
// nullptr.
// Returns pointer to the entry in |nva| which has name |name|. If
// more than one entries which have the name |name|, last occurrence
// in |nva| is returned. If no such entry exist, returns nullptr.
const Headers::value_type *get_header(const Headers &nva, const char *name);
// Returns nv->second if nv is not nullptr. Otherwise, returns "".
......@@ -165,14 +129,13 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
// Appends headers in |headers| to |nv|. Certain headers, including
// disallowed headers in HTTP/2 spec and headers which require
// special handling (i.e. via), are not copied.
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
const Headers &headers);
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
// |headers|. Certain headers, which requires special handling
// (i.e. via and cookie), are not appended.
void build_http1_headers_from_norm_headers(std::string &hdrs,
const Headers &headers);
void build_http1_headers_from_headers(std::string &hdrs,
const Headers &headers);
// Return positive window_size_increment if WINDOW_UPDATE should be
// sent for the stream |stream_id|. If |stream_id| == 0, this function
......@@ -218,43 +181,68 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
// Returns parsed HTTP status code. Returns -1 on failure.
int parse_http_status_code(const std::string &src);
// Header fields to be indexed, except HD_MAXIDX which is convenient
// member to get maximum value.
enum {
HD_AUTHORITY,
HD_METHOD,
HD_PATH,
HD_SCHEME,
HD__AUTHORITY,
HD__HOST,
HD__METHOD,
HD__PATH,
HD__SCHEME,
HD__STATUS,
HD_ALT_SVC,
HD_CONNECTION,
HD_CONTENT_LENGTH,
HD_COOKIE,
HD_EXPECT,
HD_HOST,
HD_HTTP2_SETTINGS,
HD_IF_MODIFIED_SINCE,
HD_KEEP_ALIVE,
HD_LOCATION,
HD_PROXY_CONNECTION,
HD_SERVER,
HD_TE,
HD_TRANSFER_ENCODING,
HD_UPGRADE,
HD_VIA,
HD_X_FORWARDED_FOR,
HD_X_FORWARDED_PROTO,
HD_MAXIDX,
};
// Looks up header token for header name |name| of length |namelen|.
// Only headers we are interested in are tokenized. If header name
// cannot be tokenized, returns -1.
int lookup_token(const uint8_t *name, size_t namelen);
int lookup_token(const std::string &name);
// Initializes |hdidx|, header index. The |hdidx| must point to the
// array containing at least HD_MAXIDX elements.
void init_hdidx(int *hdidx);
// Indexes header |name| of length |namelen| using index |idx|.
void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx);
// 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);
// 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 header denoted by |hdkey| using index |hdidx|.
const Headers::value_type *get_header(int *hdidx, int hdkey,
// 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);
// 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);
// 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 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
......
......@@ -100,55 +100,14 @@ void test_http2_add_header(void) {
CU_ASSERT(Headers::value_type("a", "") == nva[0]);
}
void test_http2_check_http2_headers(void) {
auto nva1 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"upgrade", "http2"}};
CU_ASSERT(!http2::check_http2_headers(nva1));
auto nva2 = Headers{{"connection", "1"}, {"delta", "2"}, {"echo", "3"}};
CU_ASSERT(!http2::check_http2_headers(nva2));
auto nva3 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"te2", "3"}};
CU_ASSERT(http2::check_http2_headers(nva3));
auto n1 = ":authority";
auto n1u8 = reinterpret_cast<const uint8_t *>(n1);
CU_ASSERT(http2::check_http2_request_pseudo_header(n1u8, strlen(n1)));
CU_ASSERT(!http2::check_http2_response_pseudo_header(n1u8, strlen(n1)));
auto n2 = ":status";
auto n2u8 = reinterpret_cast<const uint8_t *>(n2);
CU_ASSERT(!http2::check_http2_request_pseudo_header(n2u8, strlen(n2)));
CU_ASSERT(http2::check_http2_response_pseudo_header(n2u8, strlen(n2)));
}
void test_http2_get_unique_header(void) {
auto nva = Headers{{"alpha", "1"},
{"bravo", "2"},
{"bravo", "3"},
{"charlie", "4"},
{"delta", "5"},
{"echo", "6"}};
const Headers::value_type *rv;
rv = http2::get_unique_header(nva, "delta");
CU_ASSERT(rv != nullptr);
CU_ASSERT("delta" == rv->name);
rv = http2::get_unique_header(nva, "bravo");
CU_ASSERT(rv == nullptr);
rv = http2::get_unique_header(nva, "foxtrot");
CU_ASSERT(rv == nullptr);
}
void test_http2_get_header(void) {
auto nva = Headers{{"alpha", "1"},
{"bravo", "2"},
{"bravo", "3"},
{"charlie", "4"},
{"delta", "5"},
{"echo", "6"}};
{"echo", "6"},
{"content-length", "7"}};
const Headers::value_type *rv;
rv = http2::get_header(nva, "delta");
CU_ASSERT(rv != nullptr);
......@@ -160,6 +119,12 @@ void test_http2_get_header(void) {
rv = http2::get_header(nva, "foxtrot");
CU_ASSERT(rv == nullptr);
int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx);
hdidx[http2::HD_CONTENT_LENGTH] = 6;
rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva);
CU_ASSERT("content-length" == rv->name);
}
namespace {
......@@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true},
{"zulu", "12"}};
} // namespace
void test_http2_copy_norm_headers_to_nva(void) {
void test_http2_copy_headers_to_nva(void) {
std::vector<nghttp2_nv> nva;
http2::copy_norm_headers_to_nva(nva, headers);
CU_ASSERT(7 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
http2::copy_headers_to_nva(nva, headers);
CU_ASSERT(9 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 8, 9, 12};
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
......@@ -194,9 +159,9 @@ void test_http2_copy_norm_headers_to_nva(void) {
}
}
void test_http2_build_http1_headers_from_norm_headers(void) {
void test_http2_build_http1_headers_from_headers(void) {
std::string hdrs;
http2::build_http1_headers_from_norm_headers(hdrs, headers);
http2::build_http1_headers_from_headers(hdrs, headers);
CU_ASSERT(hdrs == "Alpha: 0\r\n"
"Bravo: 1\r\n"
"Delta: 4\r\n"
......@@ -206,15 +171,6 @@ void test_http2_build_http1_headers_from_norm_headers(void) {
"Te: 8\r\n"
"Te: 9\r\n"
"Zulu: 12\r\n");
hdrs.clear();
// Both nghttp2 and spdylay do not allow \r and \n in header value
// now.
// auto hd2 = std::vector<std::pair<std::string, std::string>>
// {{"alpha", "bravo\r\ncharlie\r\n"}};
// http2::build_http1_headers_from_norm_headers(hdrs, hd2);
// CU_ASSERT(hdrs == "Alpha: bravo charlie \r\n");
}
void test_http2_lws(void) {
......@@ -274,12 +230,64 @@ void test_http2_index_header(void) {
int hdidx[http2::HD_MAXIDX];
http2::init_hdidx(hdidx);
http2::index_header(hdidx, reinterpret_cast<const uint8_t *>(":authority"),
10, 0);
http2::index_header(hdidx, reinterpret_cast<const uint8_t *>("hos"), 3, 1);
http2::index_header(hdidx, http2::HD__AUTHORITY, 0);
http2::index_header(hdidx, -1, 1);
CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]);
}
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(0 == hdidx[http2::HD_AUTHORITY]);
CU_ASSERT(-1 == hdidx[http2::HD_HOST]);
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
......@@ -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
......
......@@ -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(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];
}
bool push_request_pseudo_header_allowed() const {
return res_nva.empty() || req_nva.back().name.c_str()[0] == ':';
Headers::value_type *get_req_header(int token) {
auto idx = req_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &req_nva[idx];
}
void record_request_time() {
......@@ -1537,8 +1578,6 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
build_headers.emplace_back(kv.name, kv.value, kv.no_index);
}
std::stable_sort(std::begin(build_headers), std::end(build_headers),
http2::name_less);
auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size());
......@@ -1690,32 +1729,31 @@ void check_response_header(nghttp2_session *session, Request *req) {
req->expect_final_response = false;
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;
}
auto status_hd = req->get_res_header(http2::HD__STATUS);
req->status = status;
}
if (!status_hd) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR);
return;
}
if (req->status == 0) {
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 (req->status / 100 == 1) {
req->expect_final_response = true;
req->status = 0;
......@@ -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) {
......
......@@ -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",
......
This diff is collapsed.
......@@ -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_;
};
......
......@@ -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"},
{"charlie", "2"},
{"alpha", "3"},
{"delta", "4"},
{"bravo", "5"},
{"charlie", "2"},
{"delta", "4"}};
{":method", "6"},
{":authority", "7"}};
CU_ASSERT(ans == d.get_request_headers());
}
void test_downstream_normalize_response_headers(void) {
void test_downstream_index_response_headers(void) {
Downstream d(nullptr, 0, 0);
d.add_response_header("Charlie", "0");
d.add_response_header("Alpha", "1");
d.add_response_header("Delta", "2");
d.add_response_header("BravO", "3");
d.normalize_response_headers();
d.index_response_headers();
auto ans =
Headers{{"alpha", "1"}, {"bravo", "3"}, {"charlie", "0"}, {"delta", "2"}};
Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}};
CU_ASSERT(ans == d.get_response_headers());
}
void test_downstream_get_norm_request_header(void) {
void test_downstream_get_request_header(void) {
Downstream d(nullptr, 0, 0);
d.add_request_header("alpha", "0");
d.add_request_header("bravo", "1");
d.add_request_header("bravo", "2");
d.add_request_header("charlie", "3");
d.add_request_header("delta", "4");
d.add_request_header("echo", "5");
auto i = d.get_norm_request_header("alpha");
CU_ASSERT(Header("alpha", "0") == *i);
i = d.get_norm_request_header("bravo");
CU_ASSERT(Header("bravo", "1") == *i);
i = d.get_norm_request_header("delta");
CU_ASSERT(Header("delta", "4") == *i);
i = d.get_norm_request_header("echo");
CU_ASSERT(Header("echo", "5") == *i);
i = d.get_norm_request_header("foxtrot");
CU_ASSERT(i == std::end(d.get_request_headers()));
d.add_request_header(":authority", "1");
d.add_request_header("content-length", "2");
d.index_request_headers();
// By token
CU_ASSERT(Header(":authority", "1") ==
*d.get_request_header(http2::HD__AUTHORITY));
CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
// By name
CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
CU_ASSERT(nullptr == d.get_request_header("bravo"));
}
void test_downstream_get_norm_response_header(void) {
void test_downstream_get_response_header(void) {
Downstream d(nullptr, 0, 0);
d.add_response_header("alpha", "0");
d.add_response_header("bravo", "1");
d.add_response_header("bravo", "2");
d.add_response_header("charlie", "3");
d.add_response_header("delta", "4");
d.add_response_header("echo", "5");
auto i = d.get_norm_response_header("alpha");
CU_ASSERT(Header("alpha", "0") == *i);
i = d.get_norm_response_header("bravo");
CU_ASSERT(Header("bravo", "1") == *i);
i = d.get_norm_response_header("delta");
CU_ASSERT(Header("delta", "4") == *i);
i = d.get_norm_response_header("echo");
CU_ASSERT(Header("echo", "5") == *i);
i = d.get_norm_response_header("foxtrot");
CU_ASSERT(i == std::end(d.get_response_headers()));
d.add_response_header(":status", "1");
d.add_response_header("content-length", "2");
d.index_response_headers();
// By token
CU_ASSERT(Header(":status", "1") ==
*d.get_response_header(http2::HD__STATUS));
CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
}
void test_downstream_crumble_request_cookie(void) {
Downstream d(nullptr, 0, 0);
d.add_request_header(":method", "get");
d.add_request_header(":path", "/");
d.add_request_header("cookie", "alpha; bravo; ; ;; charlie;;");
auto val = "alpha; bravo; ; ;; charlie;;";
d.add_request_header(
reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
d.add_request_header("cookie", ";delta");
d.add_request_header("cookie", "echo");
d.crumble_request_cookie();
Headers ans = {{":method", "get"},
{":path", "/"},
{"cookie", "alpha"},
{"cookie", "delta"},
{"cookie", "echo"},
auto cookies = d.crumble_request_cookie();
Headers ans = {{"cookie", "alpha"},
{"cookie", "bravo"},
{"cookie", "charlie"}};
CU_ASSERT(ans == d.get_request_headers());
{"cookie", "charlie"},
{"cookie", "delta"},
{"cookie", "echo"}};
CU_ASSERT(ans == cookies);
CU_ASSERT(cookies[0].no_index);
CU_ASSERT(cookies[1].no_index);
CU_ASSERT(cookies[2].no_index);
}
void test_downstream_assemble_request_cookie(void) {
......@@ -138,21 +133,24 @@ void test_downstream_assemble_request_cookie(void) {
CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
}
void test_downstream_rewrite_norm_location_response_header(void) {
void test_downstream_rewrite_location_response_header(void) {
{
Downstream d(nullptr, 0, 0);
d.add_request_header("host", "localhost:3000");
d.add_response_header("location", "http://localhost:3000/");
d.rewrite_norm_location_response_header("https", 443);
auto location = d.get_norm_response_header("location");
d.index_request_headers();
d.index_response_headers();
d.rewrite_location_response_header("https", 443);
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value);
}
{
Downstream d(nullptr, 0, 0);
d.set_request_http2_authority("localhost");
d.add_response_header("location", "http://localhost/");
d.rewrite_norm_location_response_header("https", 443);
auto location = d.get_norm_response_header("location");
d.index_response_headers();
d.rewrite_location_response_header("https", 443);
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value);
}
}
......
......@@ -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
......
......@@ -233,14 +233,12 @@ int Http2DownstreamConnection::push_request_headers() {
return 0;
}
size_t nheader = downstream_->get_request_headers().size();
Headers cookies;
if (!get_config()->http2_no_cookie_crumbling) {
downstream_->crumble_request_cookie();
cookies = downstream_->crumble_request_cookie();
}
assert(downstream_->get_request_headers_normalized());
auto end_headers = std::end(downstream_->get_request_headers());
// 7 means:
// 1. :method
// 2. :scheme
......@@ -250,10 +248,12 @@ int Http2DownstreamConnection::push_request_headers() {
// 6. x-forwarded-for (optional)
// 7. x-forwarded-proto (optional)
auto nva = std::vector<nghttp2_nv>();
nva.reserve(nheader + 7);
nva.reserve(nheader + 7 + cookies.size());
std::string via_value;
std::string xff_value;
std::string scheme, authority, path, query;
// To reconstruct HTTP/1 status line and headers, proxy should
// preserve host header field. See draft-09 section 8.1.3.1.
if (downstream_->get_request_method() == "CONNECT") {
......@@ -273,7 +273,7 @@ int Http2DownstreamConnection::push_request_headers() {
if (!downstream_->get_request_http2_authority().empty()) {
nva.push_back(http2::make_nv_ls(
":authority", downstream_->get_request_http2_authority()));
} else if (downstream_->get_norm_request_header("host") == end_headers) {
} else if (!downstream_->get_request_header(http2::HD_HOST)) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
......@@ -330,7 +330,7 @@ int Http2DownstreamConnection::push_request_headers() {
authority += util::utos(u.port);
}
nva.push_back(http2::make_nv_ls(":authority", authority));
} else if (downstream_->get_norm_request_header("host") == end_headers) {
} else if (!downstream_->get_request_header(http2::HD_HOST)) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
......@@ -341,27 +341,30 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(
http2::make_nv_ls(":method", downstream_->get_request_method()));
http2::copy_norm_headers_to_nva(nva, downstream_->get_request_headers());
http2::copy_headers_to_nva(nva, downstream_->get_request_headers());
bool chunked_encoding = false;
auto transfer_encoding =
downstream_->get_norm_request_header("transfer-encoding");
if (transfer_encoding != end_headers &&
downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
if (transfer_encoding &&
util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
chunked_encoding = true;
}
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
for (auto &nv : cookies) {
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
}
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
if (get_config()->add_x_forwarded_for) {
if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) {
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
xff_value = (*xff).value;
xff_value += ", ";
}
xff_value +=
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff != end_headers &&
!get_config()->strip_incoming_x_forwarded_for) {
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value));
}
......@@ -379,13 +382,13 @@ int Http2DownstreamConnection::push_request_headers() {
}
}
auto via = downstream_->get_norm_request_header("via");
auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via != end_headers) {
if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value));
}
} else {
if (via != end_headers) {
if (via) {
via_value = (*via).value;
via_value += ", ";
}
......@@ -407,7 +410,8 @@ int Http2DownstreamConnection::push_request_headers() {
}
auto content_length =
downstream_->get_norm_request_header("content-length") != end_headers;
downstream_->get_request_header(http2::HD_CONTENT_LENGTH);
// TODO check content-length: 0 case
if (downstream_->get_request_method() == "CONNECT" || chunked_encoding ||
content_length || downstream_->get_request_http2_expect_body()) {
......
......@@ -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;
......
......@@ -185,17 +185,22 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (namelen > 0 && name[0] == ':') {
if (!downstream->request_pseudo_header_allowed() ||
!http2::check_http2_request_pseudo_header(name, namelen)) {
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!downstream->request_pseudo_header_allowed(token)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
downstream->split_add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX);
if (!http2::http2_header_allowed(token)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
downstream->add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
} // namespace
......@@ -238,7 +243,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
return 0;
}
downstream->normalize_request_headers();
auto &nva = downstream->get_request_headers();
if (LOG_ENABLED(INFO)) {
......@@ -254,17 +258,11 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva);
}
if (!http2::check_http2_request_headers(nva)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return 0;
}
auto host = http2::get_unique_header(nva, "host");
auto authority = http2::get_unique_header(nva, ":authority");
auto path = http2::get_unique_header(nva, ":path");
auto method = http2::get_unique_header(nva, ":method");
auto scheme = http2::get_unique_header(nva, ":scheme");
auto host = downstream->get_request_header(http2::HD_HOST);
auto authority = downstream->get_request_header(http2::HD__AUTHORITY);
auto path = downstream->get_request_header(http2::HD__PATH);
auto method = downstream->get_request_header(http2::HD__METHOD);
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
bool is_connect = method && "CONNECT" == method->value;
bool having_host = http2::non_empty_value(host);
......@@ -1101,14 +1099,12 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
}
}
downstream->normalize_response_headers();
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) {
downstream->rewrite_norm_location_response_header(
downstream->rewrite_location_response_header(
get_client_handler()->get_upstream_scheme(), get_config()->port);
}
auto end_headers = std::end(downstream->get_response_headers());
size_t nheader = downstream->get_response_headers().size();
auto nva = std::vector<nghttp2_nv>();
// 3 means :status and possible server and via header field.
......@@ -1117,7 +1113,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
auto response_status = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", response_status));
http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers());
http2::copy_headers_to_nva(nva, downstream->get_response_headers());
if (downstream->get_non_final_response()) {
if (LOG_ENABLED(INFO)) {
......@@ -1141,19 +1137,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
} else {
auto server = downstream->get_norm_response_header("server");
if (server != end_headers) {
auto server = downstream->get_response_header(http2::HD_SERVER);
if (server) {
nva.push_back(http2::make_nv_ls("server", (*server).value));
}
}
auto via = downstream->get_norm_response_header("via");
auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via != end_headers) {
if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value));
}
} else {
if (via != end_headers) {
if (via) {
via_value = (*via).value;
via_value += ", ";
}
......
......@@ -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
......
......@@ -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 += ", ";
......
......@@ -206,7 +206,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
case SHRPX_LOGF_HTTP:
if (downstream) {
auto hd = downstream->get_request_header(lf.value.get());
if (hd != std::end(downstream->get_request_headers())) {
if (hd) {
std::tie(p, avail) = copy((*hd).value.c_str(), avail, p);
break;
}
......
......@@ -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();
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 && strcmp("CONNECT", method) == 0;
if (!path || !host || !method || http2::lws(host) || http2::lws(path) ||
http2::lws(method) ||
(!is_connect && (!scheme || http2::lws(scheme)))) {
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(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment