This source file includes following definitions.
- formatHandshakeFailureReason
- resourceName
- hostName
- trimInputSample
- generateSecWebSocketKey
- getExpectedWebSocketAccept
- m_mode
- url
- setURL
- host
- clientProtocol
- setClientProtocol
- secure
- clientOrigin
- clientLocation
- clientHandshakeMessage
- clientHandshakeRequest
- reset
- clearDocument
- readServerHandshake
- mode
- failureReason
- serverWebSocketProtocol
- serverUpgrade
- serverConnection
- serverWebSocketAccept
- acceptedExtensions
- serverHandshakeResponse
- addExtensionProcessor
- httpURLForAuthenticationAndCookies
- readStatusLine
- readHTTPHeaders
- checkResponseHeaders
#include "config.h"
#include "modules/websockets/WebSocketHandshake.h"
#include "core/dom/Document.h"
#include "core/inspector/ScriptCallStack.h"
#include "core/loader/CookieJar.h"
#include "modules/websockets/WebSocket.h"
#include "platform/Cookie.h"
#include "platform/Logging.h"
#include "platform/network/HTTPHeaderMap.h"
#include "platform/network/HTTPParsers.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/Platform.h"
#include "wtf/CryptographicallyRandomNumber.h"
#include "wtf/SHA1.h"
#include "wtf/StdLibExtras.h"
#include "wtf/StringExtras.h"
#include "wtf/Vector.h"
#include "wtf/text/Base64.h"
#include "wtf/text/CString.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/unicode/CharacterNames.h"
namespace WebCore {
namespace {
const char* const missingProtocolWhiteList[] = {
"ica.citrix.com",
};
String formatHandshakeFailureReason(const String& detail)
{
return "Error during WebSocket handshake: " + detail;
}
}
static String resourceName(const KURL& url)
{
StringBuilder name;
name.append(url.path());
if (name.isEmpty())
name.append('/');
if (!url.query().isNull()) {
name.append('?');
name.append(url.query());
}
String result = name.toString();
ASSERT(!result.isEmpty());
ASSERT(!result.contains(' '));
return result;
}
static String hostName(const KURL& url, bool secure)
{
ASSERT(url.protocolIs("wss") == secure);
StringBuilder builder;
builder.append(url.host().lower());
if (url.port() && ((!secure && url.port() != 80) || (secure && url.port() != 443))) {
builder.append(':');
builder.appendNumber(url.port());
}
return builder.toString();
}
static const size_t maxInputSampleSize = 128;
static String trimInputSample(const char* p, size_t len)
{
if (len > maxInputSampleSize)
return String(p, maxInputSampleSize) + horizontalEllipsis;
return String(p, len);
}
static String generateSecWebSocketKey()
{
static const size_t nonceSize = 16;
unsigned char key[nonceSize];
cryptographicallyRandomValues(key, nonceSize);
return base64Encode(reinterpret_cast<char*>(key), nonceSize);
}
String WebSocketHandshake::getExpectedWebSocketAccept(const String& secWebSocketKey)
{
static const char webSocketKeyGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
SHA1 sha1;
CString keyData = secWebSocketKey.ascii();
sha1.addBytes(reinterpret_cast<const uint8_t*>(keyData.data()), keyData.length());
sha1.addBytes(reinterpret_cast<const uint8_t*>(webSocketKeyGUID), strlen(webSocketKeyGUID));
Vector<uint8_t, SHA1::outputSizeBytes> hash;
sha1.computeHash(hash);
return base64Encode(reinterpret_cast<const char*>(hash.data()),
SHA1::outputSizeBytes);
}
WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, Document* document)
: m_url(url)
, m_clientProtocol(protocol)
, m_secure(m_url.protocolIs("wss"))
, m_document(document)
, m_mode(Incomplete)
{
m_secWebSocketKey = generateSecWebSocketKey();
m_expectedAccept = getExpectedWebSocketAccept(m_secWebSocketKey);
}
WebSocketHandshake::~WebSocketHandshake()
{
blink::Platform::current()->histogramEnumeration("WebCore.WebSocket.HandshakeResult", m_mode, WebSocketHandshake::ModeMax);
}
const KURL& WebSocketHandshake::url() const
{
return m_url;
}
void WebSocketHandshake::setURL(const KURL& url)
{
m_url = url.copy();
}
const String WebSocketHandshake::host() const
{
return m_url.host().lower();
}
const String& WebSocketHandshake::clientProtocol() const
{
return m_clientProtocol;
}
void WebSocketHandshake::setClientProtocol(const String& protocol)
{
m_clientProtocol = protocol;
}
bool WebSocketHandshake::secure() const
{
return m_secure;
}
String WebSocketHandshake::clientOrigin() const
{
return m_document->securityOrigin()->toString();
}
String WebSocketHandshake::clientLocation() const
{
StringBuilder builder;
builder.append(m_secure ? "wss" : "ws");
builder.append("://");
builder.append(hostName(m_url, m_secure));
builder.append(resourceName(m_url));
return builder.toString();
}
CString WebSocketHandshake::clientHandshakeMessage() const
{
ASSERT(m_document);
StringBuilder builder;
builder.append("GET ");
builder.append(resourceName(m_url));
builder.append(" HTTP/1.1\r\n");
Vector<String> fields;
fields.append("Upgrade: websocket");
fields.append("Connection: Upgrade");
fields.append("Host: " + hostName(m_url, m_secure));
fields.append("Origin: " + clientOrigin());
if (!m_clientProtocol.isEmpty())
fields.append("Sec-WebSocket-Protocol: " + m_clientProtocol);
fields.append("Pragma: no-cache");
fields.append("Cache-Control: no-cache");
fields.append("Sec-WebSocket-Key: " + m_secWebSocketKey);
fields.append("Sec-WebSocket-Version: 13");
const String extensionValue = m_extensionDispatcher.createHeaderValue();
if (extensionValue.length())
fields.append("Sec-WebSocket-Extensions: " + extensionValue);
fields.append("User-Agent: " + m_document->userAgent(m_document->url()));
for (size_t i = 0; i < fields.size(); i++) {
builder.append(fields[i]);
builder.append("\r\n");
}
builder.append("\r\n");
return builder.toString().utf8();
}
PassRefPtr<WebSocketHandshakeRequest> WebSocketHandshake::clientHandshakeRequest() const
{
ASSERT(m_document);
RefPtr<WebSocketHandshakeRequest> request = WebSocketHandshakeRequest::create(m_url);
request->addHeaderField("Upgrade", "websocket");
request->addHeaderField("Connection", "Upgrade");
request->addHeaderField("Host", AtomicString(hostName(m_url, m_secure)));
request->addHeaderField("Origin", AtomicString(clientOrigin()));
if (!m_clientProtocol.isEmpty())
request->addHeaderField("Sec-WebSocket-Protocol", AtomicString(m_clientProtocol));
KURL url = httpURLForAuthenticationAndCookies();
String cookie = cookieRequestHeaderFieldValue(m_document, url);
if (!cookie.isEmpty())
request->addHeaderField("Cookie", AtomicString(cookie));
request->addHeaderField("Pragma", "no-cache");
request->addHeaderField("Cache-Control", "no-cache");
request->addHeaderField("Sec-WebSocket-Key", AtomicString(m_secWebSocketKey));
request->addHeaderField("Sec-WebSocket-Version", "13");
const String extensionValue = m_extensionDispatcher.createHeaderValue();
if (extensionValue.length())
request->addHeaderField("Sec-WebSocket-Extensions", AtomicString(extensionValue));
request->addHeaderField("User-Agent", AtomicString(m_document->userAgent(m_document->url())));
return request.release();
}
void WebSocketHandshake::reset()
{
m_mode = Incomplete;
m_extensionDispatcher.reset();
}
void WebSocketHandshake::clearDocument()
{
m_document = 0;
}
int WebSocketHandshake::readServerHandshake(const char* header, size_t len)
{
m_mode = Incomplete;
int statusCode;
String statusText;
int lineLength = readStatusLine(header, len, statusCode, statusText);
if (lineLength == -1)
return -1;
if (statusCode == -1) {
m_mode = Failed;
return len;
}
WTF_LOG(Network, "WebSocketHandshake %p readServerHandshake() Status code is %d", this, statusCode);
m_response.setStatusCode(statusCode);
m_response.setStatusText(statusText);
if (statusCode != 101) {
m_mode = Failed;
m_failureReason = formatHandshakeFailureReason("Unexpected response code: " + String::number(statusCode));
return len;
}
m_mode = Normal;
if (!strnstr(header, "\r\n\r\n", len)) {
m_mode = Incomplete;
return -1;
}
const char* p = readHTTPHeaders(header + lineLength, header + len);
if (!p) {
WTF_LOG(Network, "WebSocketHandshake %p readServerHandshake() readHTTPHeaders() failed", this);
m_mode = Failed;
return len;
}
if (!checkResponseHeaders()) {
WTF_LOG(Network, "WebSocketHandshake %p readServerHandshake() checkResponseHeaders() failed", this);
m_mode = Failed;
return p - header;
}
m_mode = Connected;
return p - header;
}
WebSocketHandshake::Mode WebSocketHandshake::mode() const
{
return m_mode;
}
String WebSocketHandshake::failureReason() const
{
return m_failureReason;
}
const AtomicString& WebSocketHandshake::serverWebSocketProtocol() const
{
return m_response.headerFields().get("sec-websocket-protocol");
}
const AtomicString& WebSocketHandshake::serverUpgrade() const
{
return m_response.headerFields().get("upgrade");
}
const AtomicString& WebSocketHandshake::serverConnection() const
{
return m_response.headerFields().get("connection");
}
const AtomicString& WebSocketHandshake::serverWebSocketAccept() const
{
return m_response.headerFields().get("sec-websocket-accept");
}
String WebSocketHandshake::acceptedExtensions() const
{
return m_extensionDispatcher.acceptedExtensions();
}
const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const
{
return m_response;
}
void WebSocketHandshake::addExtensionProcessor(PassOwnPtr<WebSocketExtensionProcessor> processor)
{
m_extensionDispatcher.addProcessor(processor);
}
KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const
{
KURL url = m_url.copy();
bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http");
ASSERT_UNUSED(couldSetProtocol, couldSetProtocol);
return url;
}
int WebSocketHandshake::readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText)
{
static const int maximumLength = 1024;
statusCode = -1;
statusText = String();
const char* space1 = 0;
const char* space2 = 0;
const char* p;
size_t consumedLength;
for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) {
if (*p == ' ') {
if (!space1)
space1 = p;
else if (!space2)
space2 = p;
} else if (*p == '\0') {
m_failureReason = formatHandshakeFailureReason("Status line contains embedded null");
return p + 1 - header;
} else if (*p == '\n') {
break;
}
}
if (consumedLength == headerLength)
return -1;
const char* end = p + 1;
int lineLength = end - header;
if (lineLength > maximumLength) {
m_failureReason = formatHandshakeFailureReason("Status line is too long");
return maximumLength;
}
if (lineLength < 2 || *(end - 2) != '\r') {
m_failureReason = formatHandshakeFailureReason("Status line does not end with CRLF");
return lineLength;
}
if (!space1 || !space2) {
m_failureReason = formatHandshakeFailureReason("No response code found in status line: " + trimInputSample(header, lineLength - 2));
return lineLength;
}
String statusCodeString(space1 + 1, space2 - space1 - 1);
if (statusCodeString.length() != 3)
return lineLength;
for (int i = 0; i < 3; ++i) {
if (statusCodeString[i] < '0' || statusCodeString[i] > '9') {
m_failureReason = formatHandshakeFailureReason("Invalid status code: " + statusCodeString);
return lineLength;
}
}
bool ok = false;
statusCode = statusCodeString.toInt(&ok);
ASSERT(ok);
statusText = String(space2 + 1, end - space2 - 3);
return lineLength;
}
const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end)
{
m_response.clearHeaderFields();
AtomicString name;
AtomicString value;
bool sawSecWebSocketAcceptHeaderField = false;
bool sawSecWebSocketProtocolHeaderField = false;
const char* p = start;
for (; p < end; p++) {
size_t consumedLength = parseHTTPHeader(p, end - p, m_failureReason, name, value);
if (!consumedLength)
return 0;
p += consumedLength;
if (name.isEmpty())
break;
if (equalIgnoringCase("Sec-WebSocket-Extensions", name)) {
if (!m_extensionDispatcher.processHeaderValue(value)) {
m_failureReason = formatHandshakeFailureReason(m_extensionDispatcher.failureReason());
return 0;
}
} else if (equalIgnoringCase("Sec-WebSocket-Accept", name)) {
if (sawSecWebSocketAcceptHeaderField) {
m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Accept' header must not appear more than once in a response");
return 0;
}
m_response.addHeaderField(name, value);
sawSecWebSocketAcceptHeaderField = true;
} else if (equalIgnoringCase("Sec-WebSocket-Protocol", name)) {
if (sawSecWebSocketProtocolHeaderField) {
m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Protocol' header must not appear more than once in a response");
return 0;
}
m_response.addHeaderField(name, value);
sawSecWebSocketProtocolHeaderField = true;
} else {
m_response.addHeaderField(name, value);
}
}
String extensions = m_extensionDispatcher.acceptedExtensions();
if (!extensions.isEmpty())
m_response.addHeaderField("Sec-WebSocket-Extensions", AtomicString(extensions));
return p;
}
bool WebSocketHandshake::checkResponseHeaders()
{
const AtomicString& serverWebSocketProtocol = this->serverWebSocketProtocol();
const AtomicString& serverUpgrade = this->serverUpgrade();
const AtomicString& serverConnection = this->serverConnection();
const AtomicString& serverWebSocketAccept = this->serverWebSocketAccept();
if (serverUpgrade.isNull()) {
m_failureReason = formatHandshakeFailureReason("'Upgrade' header is missing");
return false;
}
if (serverConnection.isNull()) {
m_failureReason = formatHandshakeFailureReason("'Connection' header is missing");
return false;
}
if (serverWebSocketAccept.isNull()) {
m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Accept' header is missing");
return false;
}
if (!equalIgnoringCase(serverUpgrade, "websocket")) {
m_failureReason = formatHandshakeFailureReason("'Upgrade' header value is not 'WebSocket': " + serverUpgrade);
return false;
}
if (!equalIgnoringCase(serverConnection, "upgrade")) {
m_failureReason = formatHandshakeFailureReason("'Connection' header value is not 'Upgrade': " + serverConnection);
return false;
}
if (serverWebSocketAccept != m_expectedAccept) {
m_failureReason = formatHandshakeFailureReason("Incorrect 'Sec-WebSocket-Accept' header value");
return false;
}
if (!serverWebSocketProtocol.isNull()) {
if (m_clientProtocol.isEmpty()) {
m_failureReason = formatHandshakeFailureReason("Response must not include 'Sec-WebSocket-Protocol' header if not present in request: " + serverWebSocketProtocol);
return false;
}
Vector<String> result;
m_clientProtocol.split(String(WebSocket::subProtocolSeperator()), result);
if (!result.contains(serverWebSocketProtocol)) {
m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Protocol' header value '" + serverWebSocketProtocol + "' in response does not match any of sent values");
return false;
}
} else if (!m_clientProtocol.isEmpty()) {
Vector<String> protocols;
m_clientProtocol.split(String(WebSocket::subProtocolSeperator()), protocols);
bool match = false;
for (size_t i = 0; i < protocols.size() && !match; ++i) {
for (size_t j = 0; j < WTF_ARRAY_LENGTH(missingProtocolWhiteList) && !match; ++j) {
if (protocols[i] == missingProtocolWhiteList[j])
match = true;
}
}
if (!match) {
m_failureReason = formatHandshakeFailureReason("Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received");
return false;
}
}
return true;
}
}