diff --git a/proxy/hdrs/VersionConverter.cc b/proxy/hdrs/VersionConverter.cc index b239f9aa346..91032c20f5d 100644 --- a/proxy/hdrs/VersionConverter.cc +++ b/proxy/hdrs/VersionConverter.cc @@ -160,32 +160,51 @@ VersionConverter::_convert_req_from_1_to_2(HTTPHdr &header) const int VersionConverter::_convert_req_from_2_to_1(HTTPHdr &header) const { + bool is_connect_method = false; + // HTTP Version header.version_set(HTTPVersion(1, 1)); - // :scheme - if (MIMEField *field = header.field_find(PSEUDO_HEADER_SCHEME.data(), PSEUDO_HEADER_SCHEME.size()); + // :method + if (MIMEField *field = header.field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()); field != nullptr && field->value_is_valid(is_control_BIT | is_ws_BIT)) { - int scheme_len; - const char *scheme = field->value_get(&scheme_len); - const char *scheme_wks; - - int scheme_wks_idx = hdrtoken_tokenize(scheme, scheme_len, &scheme_wks); - - if (!(scheme_wks_idx > 0 && hdrtoken_wks_to_token_type(scheme_wks) == HDRTOKEN_TYPE_SCHEME)) { - // unknown scheme, validate the scheme - if (!validate_scheme({scheme, static_cast(scheme_len)})) { - return PARSE_RESULT_ERROR; - } + int method_len; + const char *method = field->value_get(&method_len); + if (method_len == HTTP_LEN_CONNECT && strncmp(HTTP_METHOD_CONNECT, method, HTTP_LEN_CONNECT) == 0) { + is_connect_method = true; } - header.m_http->u.req.m_url_impl->set_scheme(header.m_heap, scheme, scheme_wks_idx, scheme_len, true); - + header.method_set(method, method_len); header.field_delete(field); } else { return PARSE_RESULT_ERROR; } + if (!is_connect_method) { + // :scheme + if (MIMEField *field = header.field_find(PSEUDO_HEADER_SCHEME.data(), PSEUDO_HEADER_SCHEME.size()); + field != nullptr && field->value_is_valid(is_control_BIT | is_ws_BIT)) { + int scheme_len; + const char *scheme = field->value_get(&scheme_len); + const char *scheme_wks; + + int scheme_wks_idx = hdrtoken_tokenize(scheme, scheme_len, &scheme_wks); + + if (!(scheme_wks_idx > 0 && hdrtoken_wks_to_token_type(scheme_wks) == HDRTOKEN_TYPE_SCHEME)) { + // unknown scheme, validate the scheme + if (!validate_scheme({scheme, static_cast(scheme_len)})) { + return PARSE_RESULT_ERROR; + } + } + + header.m_http->u.req.m_url_impl->set_scheme(header.m_heap, scheme, scheme_wks_idx, scheme_len, true); + + header.field_delete(field); + } else { + return PARSE_RESULT_ERROR; + } + } + // :authority if (MIMEField *field = header.field_find(PSEUDO_HEADER_AUTHORITY.data(), PSEUDO_HEADER_AUTHORITY.size()); field != nullptr && field->value_is_valid(is_control_BIT | is_ws_BIT)) { @@ -193,60 +212,52 @@ VersionConverter::_convert_req_from_2_to_1(HTTPHdr &header) const const char *authority = field->value_get(&authority_len); header.m_http->u.req.m_url_impl->set_host(header.m_heap, authority, authority_len, true); - MIMEField *host = header.field_find(MIME_FIELD_HOST, MIME_LEN_HOST); - if (host == nullptr) { - // Add a Host header field. [RFC 7230] 5.4 says that if a client sends a - // Host header field, it SHOULD be the first header in the header section - // of a request. We accomplish that by simply renaming the :authority - // header as Host. - header.field_detach(field); - field->name_set(header.m_heap, header.m_mime, MIME_FIELD_HOST, MIME_LEN_HOST); - header.field_attach(field); - } else { - // There already is a Host header field. Simply set the value of the Host - // field to the current value of :authority and delete the :authority - // field. - host->value_set(header.m_heap, header.m_mime, authority, authority_len); - header.field_delete(field); + if (!is_connect_method) { + MIMEField *host = header.field_find(MIME_FIELD_HOST, MIME_LEN_HOST); + if (host == nullptr) { + // Add a Host header field. [RFC 7230] 5.4 says that if a client sends a + // Host header field, it SHOULD be the first header in the header section + // of a request. We accomplish that by simply renaming the :authority + // header as Host. + header.field_detach(field); + field->name_set(header.m_heap, header.m_mime, MIME_FIELD_HOST, MIME_LEN_HOST); + header.field_attach(field); + } else { + // There already is a Host header field. Simply set the value of the Host + // field to the current value of :authority and delete the :authority + // field. + host->value_set(header.m_heap, header.m_mime, authority, authority_len); + header.field_delete(field); + } } } else { return PARSE_RESULT_ERROR; } - // :path - if (MIMEField *field = header.field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size()); - field != nullptr && field->value_is_valid(is_control_BIT | is_ws_BIT)) { - int path_len; - const char *path = field->value_get(&path_len); - - // cut first '/' if there, because `url_print()` add '/' before printing path - if (path_len >= 1 && path[0] == '/') { - ++path; - --path_len; - } - - header.m_http->u.req.m_url_impl->set_path(header.m_heap, path, path_len, true); + if (!is_connect_method) { + // :path + if (MIMEField *field = header.field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size()); + field != nullptr && field->value_is_valid(is_control_BIT | is_ws_BIT)) { + int path_len; + const char *path = field->value_get(&path_len); + + // cut first '/' if there, because `url_print()` add '/' before printing path + if (path_len >= 1 && path[0] == '/') { + ++path; + --path_len; + } - header.field_delete(field); - } else { - return PARSE_RESULT_ERROR; - } + header.m_http->u.req.m_url_impl->set_path(header.m_heap, path, path_len, true); - // :method - if (MIMEField *field = header.field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()); - field != nullptr && field->value_is_valid(is_control_BIT | is_ws_BIT)) { - int method_len; - const char *method = field->value_get(&method_len); - - header.method_set(method, method_len); - header.field_delete(field); - } else { - return PARSE_RESULT_ERROR; - } + header.field_delete(field); + } else { + return PARSE_RESULT_ERROR; + } - // Combine Cookie header.([RFC 7540] 8.1.2.5.) - if (MIMEField *field = header.field_find(MIME_FIELD_COOKIE, MIME_LEN_COOKIE); field != nullptr) { - header.field_combine_dups(field, true, ';'); + // Combine Cookie header.([RFC 7540] 8.1.2.5.) + if (MIMEField *field = header.field_find(MIME_FIELD_COOKIE, MIME_LEN_COOKIE); field != nullptr) { + header.field_combine_dups(field, true, ';'); + } } return 0; diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index e3a5a1b806d..df8c836d4ea 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -491,7 +491,14 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ } MIMEFieldIter iter; - unsigned int expected_pseudo_header_count = is_outbound ? 1 : 4; + auto method_field = hdr->field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()); + bool has_connect_method = false; + if (method_field) { + int method_len; + const char *method_value = method_field->value_get(&method_len); + has_connect_method = method_len == HTTP_LEN_CONNECT && strncmp(HTTP_METHOD_CONNECT, method_value, HTTP_LEN_CONNECT) == 0; + } + unsigned int expected_pseudo_header_count = is_outbound ? 1 : has_connect_method ? 2 : 4; unsigned int pseudo_header_count = 0; if (is_trailing_header) { @@ -554,7 +561,7 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; } } else { - if (hdr->fields_count() >= 4) { + if (!has_connect_method && hdr->fields_count() >= 4) { if (hdr->field_find(PSEUDO_HEADER_SCHEME.data(), PSEUDO_HEADER_SCHEME.size()) == nullptr || hdr->field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()) == nullptr || hdr->field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size()) == nullptr || @@ -563,6 +570,16 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ // Decoded header field is invalid return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; } + } else if (has_connect_method && hdr->fields_count() >= 2) { + if (hdr->field_find(PSEUDO_HEADER_SCHEME.data(), PSEUDO_HEADER_SCHEME.size()) != nullptr || + hdr->field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()) == nullptr || + hdr->field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size()) != nullptr || + hdr->field_find(PSEUDO_HEADER_AUTHORITY.data(), PSEUDO_HEADER_AUTHORITY.size()) == nullptr || + hdr->field_find(PSEUDO_HEADER_STATUS.data(), PSEUDO_HEADER_STATUS.size()) != nullptr) { + // Decoded header field is invalid + return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + } + } else { // Pseudo headers is insufficient return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index a8bf54002ce..c897395c8d0 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -2092,7 +2092,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len _peer_rwnd, stream->get_peer_rwnd(), payload_length, flags); Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length); - this->session->xmit(data, flags & HTTP2_FLAGS_DATA_END_STREAM); + this->session->xmit(data, stream->is_tunneling() || flags & HTTP2_FLAGS_DATA_END_STREAM); if (flags & HTTP2_FLAGS_DATA_END_STREAM) { Http2StreamDebug(session, stream->get_id(), "END_STREAM"); diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index 54ac07c50ce..b4899203d49 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -276,6 +276,15 @@ Http2Stream::send_request(Http2ConnectionState &cstate) } } + if (_receive_header.type_get() == HTTP_TYPE_REQUEST) { + // Check whether the request uses CONNECT method + int method_len; + const char *method = _receive_header.method_get(&method_len); + if (method_len == HTTP_LEN_CONNECT && strncmp(method, HTTP_METHOD_CONNECT, HTTP_LEN_CONNECT) == 0) { + this->_is_tunneling = true; + } + } + if (this->expect_send_trailer()) { // Send read complete to terminate previous data tunnel this->read_vio.nbytes = this->read_vio.ndone; @@ -618,6 +627,12 @@ Http2Stream::is_outbound_connection() const return _is_outbound; } +bool +Http2Stream::is_tunneling() const +{ + return _is_tunneling; +} + /* Replace existing event only if the new event is different than the inprogress event */ Event * Http2Stream::send_tracked_event(Event *event, int send_event, VIO *vio) diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h index 01b93210f4d..3751e8ec966 100644 --- a/proxy/http2/Http2Stream.h +++ b/proxy/http2/Http2Stream.h @@ -85,6 +85,7 @@ class Http2Stream : public ProxyTransaction void send_request(Http2ConnectionState &cstate); void initiating_close(); bool is_outbound_connection() const; + bool is_tunneling() const; void terminate_if_possible(); void update_read_request(bool send_update); void update_write_request(bool send_update); @@ -230,6 +231,14 @@ class Http2Stream : public ProxyTransaction */ bool _is_outbound = false; + /** Whether CONNECT method is used. + * + * We cannot buffer outgoing data if this stream is used for tunneling (CONNECT method), because we don't know the + * protocol used in the tunnel and we cannot expect additional data from (following read event) from the server side + * without sending the data ATS currently has. + */ + bool _is_tunneling = false; + /** Whether the stream has been registered with the connection state. */ bool _registered_stream = true;