Index: net/websockets/websocket_basic_handshake_stream.cc |
diff --git a/net/websockets/websocket_basic_handshake_stream.cc b/net/websockets/websocket_basic_handshake_stream.cc |
index 1a8154b4b8b8b0863de003985825b593e756559b..40c48da4e29dd62187dc00a2386207b5520dc152 100644 |
--- a/net/websockets/websocket_basic_handshake_stream.cc |
+++ b/net/websockets/websocket_basic_handshake_stream.cc |
@@ -22,6 +22,7 @@ |
#include "net/http/http_stream_parser.h" |
#include "net/socket/client_socket_handle.h" |
#include "net/websockets/websocket_basic_stream.h" |
+#include "net/websockets/websocket_extension_parser.h" |
#include "net/websockets/websocket_handshake_constants.h" |
#include "net/websockets/websocket_handshake_handler.h" |
#include "net/websockets/websocket_stream.h" |
@@ -29,6 +30,12 @@ |
namespace net { |
namespace { |
+enum GetHeaderResult { |
+ GET_HEADER_OK, |
+ GET_HEADER_MISSING, |
+ GET_HEADER_MULTIPLE, |
+}; |
+ |
std::string GenerateHandshakeChallenge() { |
std::string raw_challenge(websockets::kRawChallengeLength, '\0'); |
crypto::RandBytes(string_as_array(&raw_challenge), raw_challenge.length()); |
@@ -46,57 +53,155 @@ void AddVectorHeaderIfNonEmpty(const char* name, |
headers->SetHeader(name, JoinString(value, ", ")); |
} |
-// If |case_sensitive| is false, then |value| must be in lower-case. |
-bool ValidateSingleTokenHeader( |
- const scoped_refptr<HttpResponseHeaders>& headers, |
- const base::StringPiece& name, |
- const std::string& value, |
- bool case_sensitive) { |
+GetHeaderResult GetSingleHeaderValue(const HttpResponseHeaders* headers, |
+ const base::StringPiece& name, |
+ std::string* value) { |
void* state = NULL; |
- std::string token; |
int tokens = 0; |
- bool has_value = false; |
+ std::string token; |
while (headers->EnumerateHeader(&state, name, &token)) { |
if (++tokens > 1) |
- return false; |
- has_value = case_sensitive ? value == token |
- : LowerCaseEqualsASCII(token, value.c_str()); |
+ return GET_HEADER_MULTIPLE; |
+ *value = token; |
+ } |
+ return tokens > 0 ? GET_HEADER_OK : GET_HEADER_MISSING; |
+} |
+ |
+bool ValidateUpgrade(const HttpResponseHeaders* headers, |
+ std::string* failure_message) { |
+ std::string value; |
+ GetHeaderResult result = |
+ GetSingleHeaderValue(headers, websockets::kUpgrade, &value); |
+ if (result == GET_HEADER_MISSING) { |
+ *failure_message = "'Upgrade' header is missing"; |
+ return false; |
+ } |
+ if (result == GET_HEADER_MULTIPLE) { |
+ *failure_message = |
+ "'Upgrade' header must not appear more than once in a response"; |
+ return false; |
+ } |
+ |
+ if (!LowerCaseEqualsASCII(value, websockets::kWebSocketLowercase)) { |
+ *failure_message = |
+ "'Upgrade' header value is not 'WebSocket': " + value; |
+ return false; |
} |
- return has_value; |
+ return true; |
+} |
+ |
+bool ValidateSecWebSocketAccept(const HttpResponseHeaders* headers, |
+ const std::string& expected, |
+ std::string* failure_message) { |
+ std::string actual; |
+ GetHeaderResult result = |
+ GetSingleHeaderValue(headers, websockets::kSecWebSocketAccept, &actual); |
+ if (result == GET_HEADER_MISSING) { |
+ *failure_message = "'Sec-WebSocket-Accept' header is missing"; |
Adam Rice
2013/12/06 07:02:31
In ValidateUpgrade() and ValidateSecWebSocketAccep
yhirano
2013/12/06 08:54:56
Done.
|
+ return false; |
+ } |
+ if (result == GET_HEADER_MULTIPLE) { |
+ *failure_message = |
+ "'Sec-WebSocket-Accept' header must not appear " |
+ "more than once in a response"; |
+ return false; |
+ } |
+ |
+ if (expected != actual) { |
+ *failure_message = "Incorrect 'Sec-WebSocket-Accept' header value"; |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool ValidateConnection(const HttpResponseHeaders* headers, |
+ std::string* failure_message) { |
Adam Rice
2013/12/06 07:02:31
Indentation.
yhirano
2013/12/06 08:54:56
Done.
|
+ // Connection header is permitted to contain other tokens. |
+ if (!headers->HasHeader(HttpRequestHeaders::kConnection)) { |
+ *failure_message = "'Connection' header is missing"; |
+ return false; |
+ } |
+ if (!headers->HasHeaderValue(HttpRequestHeaders::kConnection, |
+ websockets::kUpgrade)) { |
+ *failure_message = "'Connection' header value must contain 'Upgrade'"; |
+ return false; |
+ } |
+ return true; |
} |
bool ValidateSubProtocol( |
- const scoped_refptr<HttpResponseHeaders>& headers, |
+ const HttpResponseHeaders* headers, |
const std::vector<std::string>& requested_sub_protocols, |
- std::string* sub_protocol) { |
+ std::string* sub_protocol, |
+ std::string* failure_message) { |
void* state = NULL; |
std::string token; |
+ std::string last_token; |
base::hash_set<std::string> requested_set(requested_sub_protocols.begin(), |
requested_sub_protocols.end()); |
- int accepted = 0; |
+ int count = 0; |
+ bool has_multiple_protocols = false; |
+ bool has_invalid_protocol = false; |
+ |
while (headers->EnumerateHeader( |
- &state, websockets::kSecWebSocketProtocol, &token)) { |
+ &state, websockets::kSecWebSocketProtocol, &token) && |
Adam Rice
2013/12/06 07:02:31
The indentation looks weird here. I would expect i
yhirano
2013/12/06 08:54:56
Done.
|
+ !(has_multiple_protocols && has_invalid_protocol)) { |
if (requested_set.count(token) == 0) |
- return false; |
+ has_invalid_protocol = true; |
+ if (++count > 1) |
+ has_multiple_protocols = true; |
+ last_token = token; |
+ } |
- *sub_protocol = token; |
- // The server is only allowed to accept one protocol. |
- if (++accepted > 1) |
- return false; |
+ if (has_multiple_protocols) { |
+ *failure_message = |
+ "'Sec-WebSocket-Protocol' header must not " |
+ "appear more than once in a response"; |
+ return false; |
+ } else if (count > 0 && requested_sub_protocols.size() == 0) { |
+ *failure_message = |
+ std::string("Response must not include 'Sec-WebSocket-Protocol' " |
+ "header if not present in request: ") |
+ + last_token; |
+ return false; |
+ } else if (has_invalid_protocol) { |
+ *failure_message = |
+ "'Sec-WebSocket-Protocol' header value '" + |
+ last_token + |
+ "' in response does not match any of sent values"; |
+ return false; |
+ } else if (requested_sub_protocols.size() > 0 && count == 0) { |
+ *failure_message = |
+ "Sent non-empty 'Sec-WebSocket-Protocol' header " |
+ "but no response is received"; |
Adam Rice
2013/12/06 07:02:31
Grammatically, this should be "no response was rec
yhirano
2013/12/06 08:54:56
Done.
|
+ return false; |
} |
- // If the browser requested > 0 protocols, the server is required to accept |
- // one. |
- return requested_set.empty() || accepted == 1; |
+ *sub_protocol = last_token; |
+ return true; |
} |
-bool ValidateExtensions(const scoped_refptr<HttpResponseHeaders>& headers, |
+bool ValidateExtensions(const HttpResponseHeaders* headers, |
const std::vector<std::string>& requested_extensions, |
- std::string* extensions) { |
+ std::string* extensions, |
+ std::string* failure_message) { |
void* state = NULL; |
std::string token; |
while (headers->EnumerateHeader( |
&state, websockets::kSecWebSocketExtensions, &token)) { |
+ WebSocketExtensionParser parser; |
+ parser.Parse(token); |
+ if (parser.has_error()) { |
+ // TODO(yhirano) Set appropriate failure message. |
+ *failure_message = |
+ "'WebSocket-Extensions' header value is rejected by the parser: " + |
+ token; |
+ return false; |
+ } |
// TODO(ricea): Accept permessage-deflate with valid parameters. |
+ *failure_message = |
+ "Found an unsupported extension '" + |
+ parser.extension().name() + |
+ "' in 'Sec-WebSocket-Extensions' header"; |
return false; |
} |
return true; |
@@ -264,6 +369,10 @@ void WebSocketBasicHandshakeStream::SetWebSocketKeyForTesting( |
handshake_challenge_for_testing_.reset(new std::string(key)); |
} |
+std::string WebSocketBasicHandshakeStream::FailureMessage() const { |
+ return failure_message_; |
+} |
+ |
void WebSocketBasicHandshakeStream::ReadResponseHeadersCallback( |
const CompletionCallback& callback, |
int result) { |
@@ -295,20 +404,22 @@ int WebSocketBasicHandshakeStream::ValidateResponse() { |
int WebSocketBasicHandshakeStream::ValidateUpgradeResponse( |
const scoped_refptr<HttpResponseHeaders>& headers) { |
- if (ValidateSingleTokenHeader(headers, |
- websockets::kUpgrade, |
- websockets::kWebSocketLowercase, |
- false) && |
- ValidateSingleTokenHeader(headers, |
- websockets::kSecWebSocketAccept, |
- handshake_challenge_response_, |
- true) && |
- headers->HasHeaderValue(HttpRequestHeaders::kConnection, |
- websockets::kUpgrade) && |
- ValidateSubProtocol(headers, requested_sub_protocols_, &sub_protocol_) && |
- ValidateExtensions(headers, requested_extensions_, &extensions_)) { |
+ if (ValidateUpgrade(headers.get(), &failure_message_) && |
+ ValidateSecWebSocketAccept(headers.get(), |
+ handshake_challenge_response_, |
+ &failure_message_) && |
+ ValidateConnection(headers.get(), &failure_message_) && |
+ ValidateSubProtocol(headers.get(), |
+ requested_sub_protocols_, |
+ &sub_protocol_, |
+ &failure_message_) && |
+ ValidateExtensions(headers.get(), |
+ requested_extensions_, |
+ &extensions_, |
+ &failure_message_)) { |
return OK; |
} |
+ failure_message_ = "Error during WebSocket handshake: " + failure_message_; |
return ERR_INVALID_RESPONSE; |
} |