nghttpx: Use :authority and host instead of :host
This commit is contained in:
parent
c4ae19e2a0
commit
67553d47e0
17
src/http2.cc
17
src/http2.cc
|
@ -144,7 +144,6 @@ bool check_http2_allowed_header(const uint8_t *name, size_t namelen)
|
|||
namespace {
|
||||
const char *DISALLOWED_HD[] = {
|
||||
"connection",
|
||||
"host",
|
||||
"keep-alive",
|
||||
"proxy-connection",
|
||||
"te",
|
||||
|
@ -161,7 +160,6 @@ namespace {
|
|||
const char *IGN_HD[] = {
|
||||
"connection",
|
||||
"expect",
|
||||
"host",
|
||||
"http2-settings",
|
||||
"keep-alive",
|
||||
"proxy-connection",
|
||||
|
@ -247,12 +245,18 @@ const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen,
|
|||
|
||||
std::string name_to_str(const nghttp2_nv *nv)
|
||||
{
|
||||
return std::string(reinterpret_cast<const char*>(nv->name), nv->namelen);
|
||||
if(nv) {
|
||||
return std::string(reinterpret_cast<const char*>(nv->name), nv->namelen);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string value_to_str(const nghttp2_nv *nv)
|
||||
{
|
||||
return std::string(reinterpret_cast<const char*>(nv->value), nv->valuelen);
|
||||
if(nv) {
|
||||
return std::string(reinterpret_cast<const char*>(nv->value), nv->valuelen);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool value_lws(const nghttp2_nv *nv)
|
||||
|
@ -269,6 +273,11 @@ bool value_lws(const nghttp2_nv *nv)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool non_empty_value(const nghttp2_nv* nv)
|
||||
{
|
||||
return nv && !http2::value_lws(nv) && http2::check_header_value(nv);
|
||||
}
|
||||
|
||||
void copy_norm_headers_to_nv
|
||||
(std::vector<const char*>& nv,
|
||||
const std::vector<std::pair<std::string, std::string>>& headers)
|
||||
|
|
|
@ -89,6 +89,10 @@ std::string value_to_str(const nghttp2_nv *nv);
|
|||
// Returns true if the value of |nv| includes only ' ' (0x20) or '\t'.
|
||||
bool value_lws(const nghttp2_nv *nv);
|
||||
|
||||
// Returns true if the value of |nv| is not empty value and not LWS
|
||||
// and not contain illegal characters.
|
||||
bool non_empty_value(const nghttp2_nv* nv);
|
||||
|
||||
// Appends headers in |headers| to |nv|. Certain headers, including
|
||||
// disallowed headers in HTTP/2.0 spec and headers which require
|
||||
// special handling (i.e. via), are not copied.
|
||||
|
|
|
@ -249,6 +249,26 @@ const std::string& Downstream::get_request_path() const
|
|||
return request_path_;
|
||||
}
|
||||
|
||||
const std::string& Downstream::get_request_http2_scheme() const
|
||||
{
|
||||
return request_http2_scheme_;
|
||||
}
|
||||
|
||||
void Downstream::set_request_http2_scheme(std::string scheme)
|
||||
{
|
||||
request_http2_scheme_ = std::move(scheme);
|
||||
}
|
||||
|
||||
const std::string& Downstream::get_request_http2_authority() const
|
||||
{
|
||||
return request_http2_authority_;
|
||||
}
|
||||
|
||||
void Downstream::set_request_http2_authority(std::string authority)
|
||||
{
|
||||
request_http2_authority_ = std::move(authority);
|
||||
}
|
||||
|
||||
void Downstream::set_request_major(int major)
|
||||
{
|
||||
request_major_ = major;
|
||||
|
|
|
@ -104,7 +104,15 @@ public:
|
|||
const std::string& get_request_method() const;
|
||||
void set_request_path(std::string path);
|
||||
void append_request_path(const char *data, size_t len);
|
||||
// Returns request path. For HTTP/1.1, this is request-target. For
|
||||
// HTTP/2, this is :path header field value.
|
||||
const std::string& get_request_path() const;
|
||||
// Returns HTTP/2 :scheme header field value.
|
||||
const std::string& get_request_http2_scheme() const;
|
||||
void set_request_http2_scheme(std::string scheme);
|
||||
// Returns HTTP/2 :authority header field value.
|
||||
const std::string& get_request_http2_authority() const;
|
||||
void set_request_http2_authority(std::string authority);
|
||||
void set_request_major(int major);
|
||||
void set_request_minor(int minor);
|
||||
int get_request_major() const;
|
||||
|
@ -184,6 +192,8 @@ private:
|
|||
int request_state_;
|
||||
std::string request_method_;
|
||||
std::string request_path_;
|
||||
std::string request_http2_scheme_;
|
||||
std::string request_http2_authority_;
|
||||
int request_major_;
|
||||
int request_minor_;
|
||||
bool chunked_request_;
|
||||
|
|
|
@ -222,22 +222,33 @@ int on_frame_recv_callback
|
|||
}
|
||||
}
|
||||
|
||||
auto host = http2::get_unique_header(nva, nvlen, ":host");
|
||||
auto host = http2::get_unique_header(nva, nvlen, "host");
|
||||
auto authority = http2::get_unique_header(nva, nvlen, ":authority");
|
||||
auto path = http2::get_unique_header(nva, nvlen, ":path");
|
||||
auto method = http2::get_unique_header(nva, nvlen, ":method");
|
||||
auto scheme = http2::get_unique_header(nva, nvlen, ":scheme");
|
||||
bool is_connect = method &&
|
||||
util::streq("CONNECT", method->value, method->valuelen);
|
||||
if(!host || !path || !method ||
|
||||
http2::value_lws(host) || http2::value_lws(path) ||
|
||||
http2::value_lws(method) ||
|
||||
(!is_connect && (!scheme || http2::value_lws(scheme))) ||
|
||||
!http2::check_header_value(host) ||
|
||||
!http2::check_header_value(path) ||
|
||||
!http2::check_header_value(method) ||
|
||||
(scheme && !http2::check_header_value(scheme))) {
|
||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||
return 0;
|
||||
bool having_host = http2::non_empty_value(host);
|
||||
bool having_authority = http2::non_empty_value(authority);
|
||||
|
||||
if(is_connect) {
|
||||
// Here we strictly require :authority header field.
|
||||
if(scheme || path || !having_authority) {
|
||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// For proxy, :authority is required. Otherwise, we can accept
|
||||
// :authority or host for methods.
|
||||
if(!http2::non_empty_value(method) ||
|
||||
!http2::non_empty_value(scheme) ||
|
||||
(get_config()->spdy_proxy && !having_authority) ||
|
||||
(!get_config()->spdy_proxy && !having_authority && !having_host) ||
|
||||
!http2::non_empty_value(path)) {
|
||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(!is_connect &&
|
||||
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
|
||||
|
@ -251,21 +262,10 @@ int on_frame_recv_callback
|
|||
}
|
||||
|
||||
downstream->set_request_method(http2::value_to_str(method));
|
||||
downstream->set_request_http2_scheme(http2::value_to_str(scheme));
|
||||
downstream->set_request_http2_authority(http2::value_to_str(authority));
|
||||
downstream->set_request_path(http2::value_to_str(path));
|
||||
|
||||
// SpdyDownstreamConnection examines request path to find
|
||||
// scheme. We construct abs URI for spdy_bridge mode as well as
|
||||
// spdy_proxy mode.
|
||||
if((get_config()->spdy_proxy || get_config()->spdy_bridge) &&
|
||||
scheme && path->value[0] == '/') {
|
||||
auto reqpath = http2::value_to_str(scheme);
|
||||
reqpath += "://";
|
||||
reqpath += http2::value_to_str(host);
|
||||
reqpath += http2::value_to_str(path);
|
||||
downstream->set_request_path(std::move(reqpath));
|
||||
} else {
|
||||
downstream->set_request_path(http2::value_to_str(path));
|
||||
}
|
||||
downstream->add_request_header("host", http2::value_to_str(host));
|
||||
downstream->check_upgrade_request();
|
||||
|
||||
auto dconn = upstream->get_client_handler()->get_downstream_connection();
|
||||
|
|
|
@ -116,13 +116,39 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream)
|
|||
|
||||
int HttpDownstreamConnection::push_request_headers()
|
||||
{
|
||||
downstream_->normalize_request_headers();
|
||||
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 += " ";
|
||||
hdrs += downstream_->get_request_path();
|
||||
if(downstream_->get_request_method() == "CONNECT") {
|
||||
if(!downstream_->get_request_http2_authority().empty()) {
|
||||
hdrs += downstream_->get_request_http2_authority();
|
||||
} else {
|
||||
hdrs += downstream_->get_request_path();
|
||||
}
|
||||
} else if(get_config()->spdy_proxy &&
|
||||
!downstream_->get_request_http2_scheme().empty() &&
|
||||
!downstream_->get_request_http2_authority().empty() &&
|
||||
downstream_->get_request_path().c_str()[0] == '/') {
|
||||
// Construct absolute-form request target because we are going to
|
||||
// send a request to a HTTP/1 proxy.
|
||||
hdrs += downstream_->get_request_http2_scheme();
|
||||
hdrs += "://";
|
||||
hdrs += downstream_->get_request_http2_authority();
|
||||
hdrs += downstream_->get_request_path();
|
||||
} else {
|
||||
// No proxy case. get_request_path() may be absolute-form but we
|
||||
// don't care.
|
||||
hdrs += downstream_->get_request_path();
|
||||
}
|
||||
hdrs += " HTTP/1.1\r\n";
|
||||
downstream_->normalize_request_headers();
|
||||
auto end_headers = std::end(downstream_->get_request_headers());
|
||||
if(downstream_->get_norm_request_header("host") == end_headers &&
|
||||
!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());
|
||||
|
||||
|
@ -147,10 +173,13 @@ int HttpDownstreamConnection::push_request_headers()
|
|||
}
|
||||
if(downstream_->get_request_method() != "CONNECT") {
|
||||
hdrs += "X-Forwarded-Proto: ";
|
||||
if(util::istartsWith(downstream_->get_request_path(), "http:")) {
|
||||
hdrs += "http\r\n";
|
||||
} else {
|
||||
if(!downstream_->get_request_http2_scheme().empty()) {
|
||||
hdrs += downstream_->get_request_http2_scheme();
|
||||
hdrs += "\r\n";
|
||||
} else if(util::istartsWith(downstream_->get_request_path(), "https:")) {
|
||||
hdrs += "https\r\n";
|
||||
} else {
|
||||
hdrs += "http\r\n";
|
||||
}
|
||||
}
|
||||
auto expect = downstream_->get_norm_request_header("expect");
|
||||
|
|
|
@ -232,19 +232,43 @@ int SpdyDownstreamConnection::push_request_headers()
|
|||
size_t nheader = downstream_->get_request_headers().size();
|
||||
downstream_->normalize_request_headers();
|
||||
auto end_headers = std::end(downstream_->get_request_headers());
|
||||
// 10 means :method, :scheme, :path and possible via and
|
||||
// x-forwarded-for header fields. We rename host header field as
|
||||
// :host.
|
||||
// 12 means:
|
||||
// 1. :method
|
||||
// 2. :scheme
|
||||
// 3. :path
|
||||
// 4. :authority (optional)
|
||||
// 5. via (optional)
|
||||
// 6. x-forwarded-for (optional)
|
||||
auto nv = std::vector<const char*>();
|
||||
nv.reserve(nheader * 2 + 10 + 1);
|
||||
std::string via_value;
|
||||
std::string xff_value;
|
||||
std::string scheme, path, query;
|
||||
std::string scheme, authority, path, query;
|
||||
if(downstream_->get_request_method() == "CONNECT") {
|
||||
// No :scheme header field for CONNECT method.
|
||||
// The upstream may be HTTP/2 or HTTP/1
|
||||
nv.push_back(":authority");
|
||||
if(!downstream_->get_request_http2_authority().empty()) {
|
||||
nv.push_back(downstream_->get_request_http2_authority().c_str());
|
||||
} else {
|
||||
nv.push_back(downstream_->get_request_path().c_str());
|
||||
}
|
||||
} else if(!downstream_->get_request_http2_scheme().empty()) {
|
||||
// Here the upstream is HTTP/2
|
||||
nv.push_back(":scheme");
|
||||
nv.push_back(downstream_->get_request_http2_scheme().c_str());
|
||||
nv.push_back(":path");
|
||||
nv.push_back(downstream_->get_request_path().c_str());
|
||||
if(!downstream_->get_request_http2_authority().empty()) {
|
||||
nv.push_back(":authority");
|
||||
nv.push_back(downstream_->get_request_http2_authority().c_str());
|
||||
} else if(downstream_->get_norm_request_header("host") == end_headers) {
|
||||
if(LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "host header field missing";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// The upstream is HTTP/1
|
||||
http_parser_url u;
|
||||
const char *url = downstream_->get_request_path().c_str();
|
||||
memset(&u, 0, sizeof(u));
|
||||
|
@ -253,6 +277,7 @@ int SpdyDownstreamConnection::push_request_headers()
|
|||
0, &u);
|
||||
if(rv == 0) {
|
||||
http2::copy_url_component(scheme, &u, UF_SCHEMA, url);
|
||||
http2::copy_url_component(authority, &u, UF_HOST, url);
|
||||
http2::copy_url_component(path, &u, UF_PATH, url);
|
||||
http2::copy_url_component(query, &u, UF_QUERY, url);
|
||||
if(path.empty()) {
|
||||
|
@ -277,6 +302,24 @@ int SpdyDownstreamConnection::push_request_headers()
|
|||
} else {
|
||||
nv.push_back(path.c_str());
|
||||
}
|
||||
if(!authority.empty()) {
|
||||
// TODO properly check IPv6 numeric address
|
||||
if(authority.find(":") != std::string::npos) {
|
||||
authority = "[" + authority;
|
||||
authority += "]";
|
||||
}
|
||||
if(u.field_set & (1 << UF_PORT)) {
|
||||
authority += ":";
|
||||
authority += util::utos(u.port);
|
||||
}
|
||||
nv.push_back(":authority");
|
||||
nv.push_back(authority.c_str());
|
||||
} else if(downstream_->get_norm_request_header("host") == end_headers) {
|
||||
if(LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "host header field missing";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
nv.push_back(":method");
|
||||
|
@ -284,16 +327,6 @@ int SpdyDownstreamConnection::push_request_headers()
|
|||
|
||||
http2::copy_norm_headers_to_nv(nv, downstream_->get_request_headers());
|
||||
|
||||
auto host = downstream_->get_norm_request_header("host");
|
||||
if(host == end_headers) {
|
||||
if(LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "host header field missing";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
nv.push_back(":host");
|
||||
nv.push_back((*host).second.c_str());
|
||||
|
||||
bool content_length = false;
|
||||
if(downstream_->get_norm_request_header("content-length") != end_headers) {
|
||||
content_length = true;
|
||||
|
|
|
@ -143,25 +143,24 @@ void on_ctrl_recv_callback
|
|||
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
SpdyUpstream *upstream = reinterpret_cast<SpdyUpstream*>(user_data);
|
||||
auto upstream = reinterpret_cast<SpdyUpstream*>(user_data);
|
||||
switch(type) {
|
||||
case SPDYLAY_SYN_STREAM: {
|
||||
if(LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Received upstream SYN_STREAM stream_id="
|
||||
<< frame->syn_stream.stream_id;
|
||||
}
|
||||
Downstream *downstream;
|
||||
downstream = new Downstream(upstream,
|
||||
frame->syn_stream.stream_id,
|
||||
frame->syn_stream.pri);
|
||||
auto downstream = new Downstream(upstream,
|
||||
frame->syn_stream.stream_id,
|
||||
frame->syn_stream.pri);
|
||||
upstream->add_downstream(downstream);
|
||||
downstream->init_response_body_buf();
|
||||
|
||||
char **nv = frame->syn_stream.nv;
|
||||
const char *path = 0;
|
||||
const char *scheme = 0;
|
||||
const char *host = 0;
|
||||
const char *method = 0;
|
||||
auto nv = frame->syn_stream.nv;
|
||||
const char *path = nullptr;
|
||||
const char *scheme = nullptr;
|
||||
const char *host = nullptr;
|
||||
const char *method = nullptr;
|
||||
const char *content_length = 0;
|
||||
for(size_t i = 0; nv[i]; i += 2) {
|
||||
if(strcmp(nv[i], ":path") == 0) {
|
||||
|
@ -170,7 +169,6 @@ void on_ctrl_recv_callback
|
|||
scheme = nv[i+1];
|
||||
} else if(strcmp(nv[i], ":method") == 0) {
|
||||
method = nv[i+1];
|
||||
downstream->set_request_method(nv[i+1]);
|
||||
} else if(strcmp(nv[i], ":host") == 0) {
|
||||
host = nv[i+1];
|
||||
} else if(nv[i][0] != ':') {
|
||||
|
@ -180,36 +178,31 @@ void on_ctrl_recv_callback
|
|||
downstream->add_request_header(nv[i], nv[i+1]);
|
||||
}
|
||||
}
|
||||
bool is_connect = method && strcmp("CONNECT", method) == 0;
|
||||
if(!path || !host || !method ||
|
||||
!http2::check_header_value(host) ||
|
||||
!http2::check_header_value(path) ||
|
||||
!http2::check_header_value(method) ||
|
||||
(scheme && !http2::check_header_value(scheme))) {
|
||||
(!is_connect && (!scheme || !http2::check_header_value(scheme)))) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
// Require content-length if FIN flag is not set.
|
||||
if(strcmp("CONNECT", method) != 0 &&
|
||||
(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) == 0 &&
|
||||
!content_length) {
|
||||
if(!is_connect && !content_length &&
|
||||
(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) == 0) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
// SpdyDownstreamConnection examines request path to find
|
||||
// scheme. We construct abs URI for spdy_bridge mode as well as
|
||||
// spdy_proxy mode.
|
||||
if((get_config()->spdy_proxy || get_config()->spdy_bridge) &&
|
||||
scheme && path[0] == '/') {
|
||||
std::string reqpath = scheme;
|
||||
reqpath += "://";
|
||||
reqpath += host;
|
||||
reqpath += path;
|
||||
downstream->set_request_path(std::move(reqpath));
|
||||
|
||||
downstream->set_request_method(method);
|
||||
if(is_connect) {
|
||||
downstream->set_request_http2_authority(path);
|
||||
} else {
|
||||
downstream->set_request_http2_scheme(scheme);
|
||||
downstream->set_request_http2_authority(host);
|
||||
downstream->set_request_path(path);
|
||||
}
|
||||
|
||||
downstream->add_request_header("host", host);
|
||||
downstream->check_upgrade_request();
|
||||
|
||||
if(LOG_ENABLED(INFO)) {
|
||||
|
@ -222,8 +215,7 @@ void on_ctrl_recv_callback
|
|||
<< "\n" << ss.str();
|
||||
}
|
||||
|
||||
DownstreamConnection *dconn;
|
||||
dconn = upstream->get_client_handler()->get_downstream_connection();
|
||||
auto dconn = upstream->get_client_handler()->get_downstream_connection();
|
||||
int rv = dconn->attach_downstream(downstream);
|
||||
if(rv != 0) {
|
||||
// If downstream connection fails, issue RST_STREAM.
|
||||
|
|
Loading…
Reference in New Issue