Commit d63b4c10 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

nghttpx: Forward multiple via, xff, and xfp header fields

Previously, for Via, X-Forwarded-For, and X-Forwarded-Proto header
field, nghttpx only forwarded the last header field of each.  With
this commit, nghttpx forwards all of them if it is configured to do
so.
parent 963e220a
......@@ -358,15 +358,21 @@ nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
namespace {
void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers, uint8_t nv_flags) {
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
const HeaderRefs &headers, uint8_t nv_flags,
uint32_t flags) {
auto it_forwarded = std::end(headers);
auto it_xff = std::end(headers);
auto it_xfp = std::end(headers);
auto it_via = std::end(headers);
for (auto it = std::begin(headers); it != std::end(headers); ++it) {
auto kv = &(*it);
if (kv->name.empty() || kv->name[0] == ':') {
continue;
}
switch (kv.token) {
switch (kv->token) {
case HD_COOKIE:
case HD_CONNECTION:
case HD_FORWARDED:
case HD_HOST:
case HD_HTTP2_SETTINGS:
case HD_KEEP_ALIVE:
......@@ -375,51 +381,157 @@ void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
case HD_TE:
case HD_TRANSFER_ENCODING:
case HD_UPGRADE:
case HD_VIA:
continue;
case HD_FORWARDED:
if (flags & HDOP_STRIP_FORWARDED) {
continue;
}
if (it_forwarded == std::end(headers)) {
it_forwarded = it;
continue;
}
kv = &(*it_forwarded);
it_forwarded = it;
break;
case HD_X_FORWARDED_FOR:
if (flags & HDOP_STRIP_X_FORWARDED_FOR) {
continue;
}
if (it_xff == std::end(headers)) {
it_xff = it;
continue;
}
kv = &(*it_xff);
it_xff = it;
break;
case HD_X_FORWARDED_PROTO:
continue;
if (flags & HDOP_STRIP_X_FORWARDED_PROTO) {
continue;
}
if (it_xfp == std::end(headers)) {
it_xfp = it;
continue;
}
kv = &(*it_xfp);
it_xfp = it;
break;
case HD_VIA:
if (flags & HDOP_STRIP_VIA) {
continue;
}
if (it_via == std::end(headers)) {
it_via = it;
continue;
}
kv = &(*it_via);
it_via = it;
break;
}
nva.push_back(make_nv_internal(kv.name, kv.value, kv.no_index, nv_flags));
nva.push_back(
make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
}
}
} // namespace
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE);
const HeaderRefs &headers, uint32_t flags) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE, flags);
}
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers) {
const HeaderRefs &headers, uint32_t flags) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE);
NGHTTP2_NV_FLAG_NO_COPY_VALUE,
flags);
}
void build_http1_headers_from_headers(DefaultMemchunks *buf,
const HeaderRefs &headers) {
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
const HeaderRefs &headers,
uint32_t flags) {
auto it_forwarded = std::end(headers);
auto it_xff = std::end(headers);
auto it_xfp = std::end(headers);
auto it_via = std::end(headers);
for (auto it = std::begin(headers); it != std::end(headers); ++it) {
auto kv = &(*it);
if (kv->name.empty() || kv->name[0] == ':') {
continue;
}
switch (kv.token) {
switch (kv->token) {
case HD_CONNECTION:
case HD_COOKIE:
case HD_FORWARDED:
case HD_HOST:
case HD_HTTP2_SETTINGS:
case HD_KEEP_ALIVE:
case HD_PROXY_CONNECTION:
case HD_SERVER:
case HD_UPGRADE:
case HD_VIA:
continue;
case HD_FORWARDED:
if (flags & HDOP_STRIP_FORWARDED) {
continue;
}
if (it_forwarded == std::end(headers)) {
it_forwarded = it;
continue;
}
kv = &(*it_forwarded);
it_forwarded = it;
break;
case HD_X_FORWARDED_FOR:
if (flags & HDOP_STRIP_X_FORWARDED_FOR) {
continue;
}
if (it_xff == std::end(headers)) {
it_xff = it;
continue;
}
kv = &(*it_xff);
it_xff = it;
break;
case HD_X_FORWARDED_PROTO:
continue;
if (flags & HDOP_STRIP_X_FORWARDED_PROTO) {
continue;
}
if (it_xfp == std::end(headers)) {
it_xfp = it;
continue;
}
kv = &(*it_xfp);
it_xfp = it;
break;
case HD_VIA:
if (flags & HDOP_STRIP_VIA) {
continue;
}
if (it_via == std::end(headers)) {
it_via = it;
continue;
}
kv = &(*it_via);
it_via = it;
break;
}
capitalize(buf, kv.name);
capitalize(buf, kv->name);
buf->append(": ");
buf->append(kv.value);
buf->append(kv->value);
buf->append("\r\n");
}
}
......
......@@ -187,24 +187,50 @@ nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
}
enum HeaderBuildOp {
HDOP_NONE,
// Forwarded header fields must be stripped. If this flag is not
// set, all Forwarded header fields other than last one are added.
HDOP_STRIP_FORWARDED = 1,
// X-Forwarded-For header fields must be stripped. If this flag is
// not set, all X-Forwarded-For header fields other than last one
// are added.
HDOP_STRIP_X_FORWARDED_FOR = 1 << 1,
// X-Forwarded-Proto header fields must be stripped. If this flag
// is not set, all X-Forwarded-Proto header fields other than last
// one are added.
HDOP_STRIP_X_FORWARDED_PROTO = 1 << 2,
// Via header fields must be stripped. If this flag is not set, all
// Via header fields other than last one are added.
HDOP_STRIP_VIA = 1 << 3,
// Strip above all header fields.
HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR |
HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA,
};
// Appends headers in |headers| to |nv|. |headers| must be indexed
// before this call (its element's token field is assigned). Certain
// headers, including disallowed headers in HTTP/2 spec and headers
// which require special handling (i.e. via), are not copied.
// which require special handling (i.e. via), are not copied. |flags|
// is one or more of HeaderBuildOp flags. They tell function that
// certain header fields should not be added.
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers);
const HeaderRefs &headers, uint32_t flags);
// Just like copy_headers_to_nva(), but this adds
// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers);
const HeaderRefs &headers, uint32_t flags);
// Appends HTTP/1.1 style header lines to |buf| from headers in
// |headers|. |headers| must be indexed before this call (its
// element's token field is assigned). Certain headers, which
// requires special handling (i.e. via and cookie), are not appended.
// |flags| is one or more of HeaderBuildOp flags. They tell function
// that certain header fields should not be added.
void build_http1_headers_from_headers(DefaultMemchunks *buf,
const HeaderRefs &headers);
const HeaderRefs &headers,
uint32_t flags);
// Return positive window_size_increment if WINDOW_UPDATE should be
// sent for the stream |stream_id|. If |stream_id| == 0, this function
......
......@@ -150,11 +150,33 @@ auto headers = HeaderRefs{
{StringRef::from_lit("zulu"), StringRef::from_lit("12")}};
} // namespace
namespace {
auto headers2 = HeaderRefs{
{StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff1"), false,
http2::HD_X_FORWARDED_FOR},
{StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff2"), false,
http2::HD_X_FORWARDED_FOR},
{StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp1"),
false, http2::HD_X_FORWARDED_PROTO},
{StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp2"),
false, http2::HD_X_FORWARDED_PROTO},
{StringRef::from_lit("forwarded"), StringRef::from_lit("fwd1"), false,
http2::HD_FORWARDED},
{StringRef::from_lit("forwarded"), StringRef::from_lit("fwd2"), false,
http2::HD_FORWARDED},
{StringRef::from_lit("via"), StringRef::from_lit("via1"), false,
http2::HD_VIA},
{StringRef::from_lit("via"), StringRef::from_lit("via2"), false,
http2::HD_VIA},
};
} // namespace
void test_http2_copy_headers_to_nva(void) {
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
std::vector<nghttp2_nv> nva;
http2::copy_headers_to_nva_nocopy(nva, headers);
http2::copy_headers_to_nva_nocopy(nva, headers,
http2::HDOP_STRIP_X_FORWARDED_FOR);
CU_ASSERT(7 == nva.size());
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
......@@ -169,7 +191,7 @@ void test_http2_copy_headers_to_nva(void) {
}
nva.clear();
http2::copy_headers_to_nva(nva, headers);
http2::copy_headers_to_nva(nva, headers, http2::HDOP_STRIP_X_FORWARDED_FOR);
CU_ASSERT(7 == nva.size());
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
......@@ -180,12 +202,27 @@ void test_http2_copy_headers_to_nva(void) {
CU_ASSERT(NGHTTP2_NV_FLAG_NONE == nva[i].flags);
}
}
nva.clear();
auto ans2 = std::vector<int>{0, 2, 4, 6};
http2::copy_headers_to_nva(nva, headers2, http2::HDOP_NONE);
CU_ASSERT(ans2.size() == nva.size());
for (size_t i = 0; i < ans2.size(); ++i) {
check_nv(headers2[ans2[i]], &nva[i]);
}
nva.clear();
http2::copy_headers_to_nva(nva, headers2, http2::HDOP_STRIP_ALL);
CU_ASSERT(nva.empty());
}
void test_http2_build_http1_headers_from_headers(void) {
MemchunkPool pool;
DefaultMemchunks buf(&pool);
http2::build_http1_headers_from_headers(&buf, headers);
http2::build_http1_headers_from_headers(&buf, headers,
http2::HDOP_STRIP_X_FORWARDED_FOR);
auto hdrs = std::string(buf.head->pos, buf.head->last);
CU_ASSERT("Alpha: 0\r\n"
"Bravo: 1\r\n"
......@@ -196,6 +233,21 @@ void test_http2_build_http1_headers_from_headers(void) {
"Te: 8\r\n"
"Te: 9\r\n"
"Zulu: 12\r\n" == hdrs);
buf.reset();
http2::build_http1_headers_from_headers(&buf, headers2, http2::HDOP_NONE);
hdrs = std::string(buf.head->pos, buf.head->last);
CU_ASSERT("X-Forwarded-For: xff1\r\n"
"X-Forwarded-Proto: xfp1\r\n"
"Forwarded: fwd1\r\n"
"Via: via1\r\n" == hdrs);
buf.reset();
http2::build_http1_headers_from_headers(&buf, headers2,
http2::HDOP_STRIP_ALL);
CU_ASSERT(0 == buf.rleft());
}
void test_http2_lws(void) {
......
......@@ -203,7 +203,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
nva.reserve(trailers.size());
// We cannot use nocopy version, since nva may be touched after
// Downstream object is deleted.
http2::copy_headers_to_nva(nva, trailers);
http2::copy_headers_to_nva(nva, trailers, http2::HDOP_STRIP_ALL);
if (!nva.empty()) {
rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
if (rv != 0) {
......@@ -310,7 +310,16 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
}
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers());
auto &fwdconf = httpconf.forwarded;
auto &xffconf = httpconf.xff;
auto &xfpconf = httpconf.xfp;
uint32_t build_flags =
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0);
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags);
if (!http2conf.no_cookie_crumbling) {
downstream_->crumble_request_cookie(nva);
......@@ -319,8 +328,6 @@ int Http2DownstreamConnection::push_request_headers() {
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
auto &fwdconf = httpconf.forwarded;
auto fwd =
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
......@@ -351,8 +358,6 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
}
auto &xffconf = httpconf.xff;
auto xff = xffconf.strip_incoming ? nullptr
: req.fs.header(http2::HD_X_FORWARDED_FOR);
......@@ -371,7 +376,6 @@ int Http2DownstreamConnection::push_request_headers() {
}
if (!config->http2_proxy && req.method != HTTP_CONNECT) {
auto &xfpconf = httpconf.xfp;
auto xfp = xfpconf.strip_incoming
? nullptr
: req.fs.header(http2::HD_X_FORWARDED_PROTO);
......
......@@ -1418,7 +1418,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
if (!trailers.empty()) {
std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size());
http2::copy_headers_to_nva_nocopy(nva, trailers);
http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
if (!nva.empty()) {
rv = nghttp2_submit_trailer(session, stream_id, nva.data(),
nva.size());
......@@ -1667,7 +1667,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
if (downstream->get_non_final_response()) {
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers());
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
http2::HDOP_STRIP_ALL);
if (LOG_ENABLED(INFO)) {
log_response_headers(downstream, nva);
......@@ -1687,7 +1688,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
return 0;
}
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers());
http2::copy_headers_to_nva_nocopy(
nva, resp.fs.headers(), http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA);
if (!config->http2_proxy && !httpconf.no_server_rewrite) {
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
......
......@@ -538,7 +538,16 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append(authority);
buf->append("\r\n");
http2::build_http1_headers_from_headers(buf, req.fs.headers());
auto &fwdconf = httpconf.forwarded;
auto &xffconf = httpconf.xff;
auto &xfpconf = httpconf.xfp;
uint32_t build_flags =
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0);
http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags);
auto cookie = downstream_->assemble_request_cookie();
if (!cookie.empty()) {
......@@ -577,8 +586,6 @@ int HttpDownstreamConnection::push_request_headers() {
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
auto &fwdconf = httpconf.forwarded;
auto fwd =
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
......@@ -611,8 +618,6 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append("\r\n");
}
auto &xffconf = httpconf.xff;
auto xff = xffconf.strip_incoming ? nullptr
: req.fs.header(http2::HD_X_FORWARDED_FOR);
......@@ -630,7 +635,6 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append("\r\n");
}
if (!config->http2_proxy && !connect_method) {
auto &xfpconf = httpconf.xfp;
auto xfp = xfpconf.strip_incoming
? nullptr
: req.fs.header(http2::HD_X_FORWARDED_PROTO);
......@@ -740,7 +744,8 @@ int HttpDownstreamConnection::end_upload_data() {
output->append("0\r\n\r\n");
} else {
output->append("0\r\n");
http2::build_http1_headers_from_headers(output, trailers);
http2::build_http1_headers_from_headers(output, trailers,
http2::HDOP_STRIP_ALL);
output->append("\r\n");
}
......
......@@ -1059,9 +1059,10 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
get_client_handler()->get_upstream_scheme());
}
http2::build_http1_headers_from_headers(buf, resp.fs.headers());
if (downstream->get_non_final_response()) {
http2::build_http1_headers_from_headers(buf, resp.fs.headers(),
http2::HDOP_STRIP_ALL);
buf->append("\r\n");
if (LOG_ENABLED(INFO)) {
......@@ -1073,6 +1074,9 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
return 0;
}
http2::build_http1_headers_from_headers(
buf, resp.fs.headers(), http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA);
auto worker = handler_->get_worker();
// after graceful shutdown commenced, add connection: close header
......@@ -1205,7 +1209,8 @@ int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
output->append("0\r\n\r\n");
} else {
output->append("0\r\n");
http2::build_http1_headers_from_headers(output, trailers);
http2::build_http1_headers_from_headers(output, trailers,
http2::HDOP_STRIP_ALL);
output->append("\r\n");
}
}
......
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