-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
websocket: http request header Connection may contains multiple values #2070
Changes from 4 commits
c45b1ad
d11de77
4ffbe41
4913e34
8d1b113
f922cb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,6 +102,31 @@ class HeaderString { | |
*/ | ||
bool find(const char* str) const { return strstr(c_str(), str); } | ||
|
||
/** | ||
* HeaderString is in token list form, each token separated by commas or whitespace, | ||
* see https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1 for more information, | ||
* header field value's case sensitivity depends on each header. | ||
* @return whether contains token in case insensitive manner. | ||
*/ | ||
bool caseInsensitiveContains(const char* token) const { | ||
// Avoid dead loop if token argument is empty. | ||
const int n = strlen(token); | ||
if (n == 0) { | ||
return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this should return false? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you are right. |
||
} | ||
|
||
// Find token substring, skip if it's partial of other token. | ||
const char* tokens = c_str(); | ||
for (const char* p = tokens; (p = strcasestr(p, token)); p += n) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this logic is really hard to quickly read. Can you add a small comment on what it is doing? |
||
if ((p == tokens || *(p - 1) == ' ' || *(p - 1) == ',') && | ||
(*(p + n) == '\0' || *(p + n) == ' ' || *(p + n) == ',')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't you need to check here that the length of the remaining header is >= n before you do this? I think this code can overrun the header. Please make sure you add more test cases to cover small headers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it advance the pointer p only if found the token, there is no overrun. I will add more test cases cover small headers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see. Sorry got it. Yup you are right. Please just add some small header tests and we are good. |
||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Set the value of the string by copying data into it. This overwrites any existing string. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,18 +22,30 @@ void ConnectionManagerUtility::mutateRequestHeaders( | |
Runtime::RandomGenerator& random, Runtime::Loader& runtime, | ||
const LocalInfo::LocalInfo& local_info) { | ||
// Clean proxy headers. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops sorry, one more nit: Can you please move this comment down with the rest of header removes? |
||
request_headers.removeEnvoyInternalRequest(); | ||
request_headers.removeKeepAlive(); | ||
request_headers.removeProxyConnection(); | ||
request_headers.removeTransferEncoding(); | ||
|
||
// If this is a WebSocket Upgrade request, do not remove the Connection and Upgrade headers, | ||
// as we forward them verbatim to the upstream hosts. | ||
if (protocol != Protocol::Http11 || !Utility::isWebSocketUpgradeRequest(request_headers)) { | ||
if (protocol == Protocol::Http11 && Utility::isWebSocketUpgradeRequest(request_headers)) { | ||
// The current WebSocket implementation re-uses the HTTP1 codec to send upgrade headers to | ||
// the upstream host. This adds the "transfer-encoding: chunked" request header if the stream | ||
// has not ended and content-length does not exist. In HTTP1.1, if transfer-encoding and | ||
// content-length both do not exist this means there is no request body. After transfer-encoding | ||
// is stripped here, the upstream request becomes invalid. We can fix it by explicitly adding a | ||
// "content-length: 0" request header here. | ||
const bool no_body = (!request_headers.TransferEncoding() && !request_headers.ContentLength()); | ||
if (no_body) { | ||
request_headers.insertContentLength().value(uint64_t(0)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test case where we check for the existence of content-length: 0? We should be able to look for this in one of the conn_manager_impl websocket tests. |
||
} | ||
} else { | ||
request_headers.removeConnection(); | ||
request_headers.removeUpgrade(); | ||
} | ||
|
||
request_headers.removeEnvoyInternalRequest(); | ||
request_headers.removeKeepAlive(); | ||
request_headers.removeProxyConnection(); | ||
request_headers.removeTransferEncoding(); | ||
|
||
// If we are "using remote address" this means that we create/append to XFF with our immediate | ||
// peer. Cases where we don't "use remote address" include trusted double proxy where we expect | ||
// our peer to have already properly set XFF, etc. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -193,31 +193,35 @@ TEST_P(IntegrationTest, WebSocketConnectionDownstreamDisconnect) { | |
IntegrationTcpClientPtr tcp_client; | ||
FakeRawConnectionPtr fake_upstream_connection; | ||
const std::string upgrade_req_str = "GET /websocket/test HTTP/1.1\r\nHost: host\r\nConnection: " | ||
"Upgrade\r\nUpgrade: websocket\r\n\r\n"; | ||
"keep-alive, Upgrade\r\nUpgrade: websocket\r\n\r\n"; | ||
const std::string upgrade_resp_str = | ||
"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"; | ||
|
||
tcp_client = makeTcpConnection(lookupPort("http")); | ||
// Send websocket upgrade request | ||
// The request path gets rewritten from /websocket/test to /websocket. | ||
// The size of headers received by the destination is 225 bytes. | ||
// The size of headers received by the destination is 228 bytes. | ||
tcp_client->write(upgrade_req_str); | ||
fake_upstream_connection = fake_upstreams_[0]->waitForRawConnection(); | ||
fake_upstream_connection->waitForData(225); | ||
const std::string data = fake_upstream_connection->waitForData(228); | ||
// The chunked transfer-encoding for no body upstream request must valid | ||
if (std::string::npos != data.find("transfer-encoding: chunked")) { | ||
EXPECT_NE(std::string::npos, data.find("\r\n\r\n0\r\n")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not really clear what this is testing for, can you search for the entire "content-length" etc. header? Same below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
} | ||
// Accept websocket upgrade request | ||
fake_upstream_connection->write(upgrade_resp_str); | ||
tcp_client->waitForData(upgrade_resp_str); | ||
// Standard TCP proxy semantics post upgrade | ||
tcp_client->write("hello"); | ||
// datalen = 225 + strlen(hello) | ||
fake_upstream_connection->waitForData(230); | ||
// datalen = 228 + strlen(hello) | ||
fake_upstream_connection->waitForData(233); | ||
fake_upstream_connection->write("world"); | ||
tcp_client->waitForData(upgrade_resp_str + "world"); | ||
tcp_client->write("bye!"); | ||
// downstream disconnect | ||
tcp_client->close(); | ||
// datalen = 225 + strlen(hello) + strlen(bye!) | ||
fake_upstream_connection->waitForData(234); | ||
// datalen = 228 + strlen(hello) + strlen(bye!) | ||
fake_upstream_connection->waitForData(237); | ||
fake_upstream_connection->waitForDisconnect(); | ||
} | ||
|
||
|
@@ -230,23 +234,27 @@ TEST_P(IntegrationTest, WebSocketConnectionUpstreamDisconnect) { | |
IntegrationTcpClientPtr tcp_client; | ||
FakeRawConnectionPtr fake_upstream_connection; | ||
const std::string upgrade_req_str = "GET /websocket/test HTTP/1.1\r\nHost: host\r\nConnection: " | ||
"Upgrade\r\nUpgrade: websocket\r\n\r\n"; | ||
"keep-alive, Upgrade\r\nUpgrade: websocket\r\n\r\n"; | ||
const std::string upgrade_resp_str = | ||
"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"; | ||
tcp_client = makeTcpConnection(lookupPort("http")); | ||
// Send websocket upgrade request | ||
tcp_client->write(upgrade_req_str); | ||
fake_upstream_connection = fake_upstreams_[0]->waitForRawConnection(); | ||
// The request path gets rewritten from /websocket/test to /websocket. | ||
// The size of headers received by the destination is 225 bytes. | ||
fake_upstream_connection->waitForData(225); | ||
// The size of headers received by the destination is 228 bytes. | ||
const std::string data = fake_upstream_connection->waitForData(228); | ||
// The chunked transfer-encoding for no body upstream request must valid | ||
if (std::string::npos != data.find("transfer-encoding: chunked")) { | ||
EXPECT_NE(std::string::npos, data.find("\r\n\r\n0\r\n")); | ||
} | ||
// Accept websocket upgrade request | ||
fake_upstream_connection->write(upgrade_resp_str); | ||
tcp_client->waitForData(upgrade_resp_str); | ||
// Standard TCP proxy semantics post upgrade | ||
tcp_client->write("hello"); | ||
// datalen = 225 + strlen(hello) | ||
fake_upstream_connection->waitForData(230); | ||
// datalen = 228 + strlen(hello) | ||
fake_upstream_connection->waitForData(233); | ||
fake_upstream_connection->write("world"); | ||
// upstream disconnect | ||
fake_upstream_connection->close(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test case for this branch?