Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 72 additions & 61 deletions proxy/hdrs/VersionConverter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,93 +160,104 @@ 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<size_t>(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<size_t>(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)) {
int authority_len;
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;
Expand Down
21 changes: 19 additions & 2 deletions proxy/http2/HTTP2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 ||
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion proxy/http2/Http2ConnectionState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
15 changes: 15 additions & 0 deletions proxy/http2/Http2Stream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions proxy/http2/Http2Stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down