diff --git a/src/http2.cc b/src/http2.cc index 68e39de8..da2069e6 100644 --- a/src/http2.cc +++ b/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(nv->name), nv->namelen); + if(nv) { + return std::string(reinterpret_cast(nv->name), nv->namelen); + } + return ""; } std::string value_to_str(const nghttp2_nv *nv) { - return std::string(reinterpret_cast(nv->value), nv->valuelen); + if(nv) { + return std::string(reinterpret_cast(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& nv, const std::vector>& headers) diff --git a/src/http2.h b/src/http2.h index 0270e7c7..4ba7eae2 100644 --- a/src/http2.h +++ b/src/http2.h @@ -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. diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index 08f4239c..c9f6059d 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -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; diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index f3662d30..b074dfd8 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -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_; diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index a9b3af20..eb96b073 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -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(); diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 8264efb6..9eb362ce 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -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"); diff --git a/src/shrpx_spdy_downstream_connection.cc b/src/shrpx_spdy_downstream_connection.cc index 1c37d089..f79e8724 100644 --- a/src/shrpx_spdy_downstream_connection.cc +++ b/src/shrpx_spdy_downstream_connection.cc @@ -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(); 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; diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index a9a749d8..26cbfbc0 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -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(user_data); + auto upstream = reinterpret_cast(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.