/* * Copyright (C) 2009 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #if ENABLE(WEB_SOCKETS) #include "WebSocketHandshake.h" #include "AtomicString.h" #include "CString.h" #include "Cookie.h" #include "CookieJar.h" #include "Document.h" #include "HTTPHeaderMap.h" #include "KURL.h" #include "Logging.h" #include "ScriptExecutionContext.h" #include "SecurityOrigin.h" #include "StringBuilder.h" #include #include namespace WebCore { const char webSocketServerHandshakeHeader[] = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"; const char webSocketUpgradeHeader[] = "Upgrade: WebSocket\r\n"; const char webSocketConnectionHeader[] = "Connection: Upgrade\r\n"; static String extractResponseCode(const char* header, int len) { const char* space1 = 0; const char* space2 = 0; const char* p; for (p = header; p - header < len; p++) { if (*p == ' ') { if (!space1) space1 = p; else if (!space2) space2 = p; } else if (*p == '\n') break; } if (p - header == len) return String(); if (!space1 || !space2) return ""; return String(space1 + 1, space2 - space1 - 1); } static String resourceName(const KURL& url) { String name = url.path(); if (name.isEmpty()) name = "/"; if (!url.query().isNull()) name += "?" + url.query(); ASSERT(!name.isEmpty()); ASSERT(!name.contains(' ')); return name; } WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, ScriptExecutionContext* context) : m_url(url) , m_clientProtocol(protocol) , m_secure(m_url.protocolIs("wss")) , m_context(context) , m_mode(Incomplete) { } WebSocketHandshake::~WebSocketHandshake() { } 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; } void WebSocketHandshake::setSecure(bool secure) { m_secure = secure; } String WebSocketHandshake::clientOrigin() const { return m_context->securityOrigin()->toString(); } String WebSocketHandshake::clientLocation() const { StringBuilder builder; builder.append(m_secure ? "wss" : "ws"); builder.append("://"); builder.append(m_url.host().lower()); if (m_url.port()) { if ((!m_secure && m_url.port() != 80) || (m_secure && m_url.port() != 443)) { builder.append(":"); builder.append(String::number(m_url.port())); } } builder.append(resourceName(m_url)); return builder.toString(); } CString WebSocketHandshake::clientHandshakeMessage() const { StringBuilder builder; builder.append("GET "); builder.append(resourceName(m_url)); builder.append(" HTTP/1.1\r\n"); builder.append("Upgrade: WebSocket\r\n"); builder.append("Connection: Upgrade\r\n"); builder.append("Host: "); builder.append(m_url.host().lower()); if (m_url.port()) { if ((!m_secure && m_url.port() != 80) || (m_secure && m_url.port() != 443)) { builder.append(":"); builder.append(String::number(m_url.port())); } } builder.append("\r\n"); builder.append("Origin: "); builder.append(clientOrigin()); builder.append("\r\n"); if (!m_clientProtocol.isEmpty()) { builder.append("WebSocket-Protocol: "); builder.append(m_clientProtocol); builder.append("\r\n"); } KURL url = httpURLForAuthenticationAndCookies(); // FIXME: set authentication information or cookies for url. // Set "Authorization: " if authentication information exists for url. if (m_context->isDocument()) { Document* document = static_cast(m_context); String cookie = cookieRequestHeaderFieldValue(document, url); if (!cookie.isEmpty()) { builder.append("Cookie: "); builder.append(cookie); builder.append("\r\n"); } // Set "Cookie2: " if cookies 2 exists for url? } builder.append("\r\n"); return builder.toString().utf8(); } void WebSocketHandshake::reset() { m_mode = Incomplete; m_wsOrigin = String(); m_wsLocation = String(); m_wsProtocol = String(); m_setCookie = String(); m_setCookie2 = String(); } int WebSocketHandshake::readServerHandshake(const char* header, size_t len) { m_mode = Incomplete; if (len < sizeof(webSocketServerHandshakeHeader) - 1) { // Just hasn't been received fully yet. return -1; } if (!memcmp(header, webSocketServerHandshakeHeader, sizeof(webSocketServerHandshakeHeader) - 1)) m_mode = Normal; else { const String& code = extractResponseCode(header, len); if (code.isNull()) { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Short server handshake: " + String(header, len), 0, clientOrigin()); return -1; } if (code.isEmpty()) { m_mode = Failed; m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "No response code found: " + String(header, len), 0, clientOrigin()); return len; } LOG(Network, "response code: %s", code.utf8().data()); if (code == "401") { m_mode = Failed; m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Authentication required, but not implemented yet.", 0, clientOrigin()); return len; } else { m_mode = Failed; m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected response code:" + code, 0, clientOrigin()); return len; } } const char* p = header + sizeof(webSocketServerHandshakeHeader) - 1; const char* end = header + len + 1; if (m_mode == Normal) { size_t headerSize = end - p; if (headerSize < sizeof(webSocketUpgradeHeader) - 1) { m_mode = Incomplete; return 0; } if (memcmp(p, webSocketUpgradeHeader, sizeof(webSocketUpgradeHeader) - 1)) { m_mode = Failed; m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Bad Upgrade header: " + String(p, end - p), 0, clientOrigin()); return p - header + sizeof(webSocketUpgradeHeader) - 1; } p += sizeof(webSocketUpgradeHeader) - 1; headerSize = end - p; if (headerSize < sizeof(webSocketConnectionHeader) - 1) { m_mode = Incomplete; return -1; } if (memcmp(p, webSocketConnectionHeader, sizeof(webSocketConnectionHeader) - 1)) { m_mode = Failed; m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Bad Connection header: " + String(p, end - p), 0, clientOrigin()); return p - header + sizeof(webSocketConnectionHeader) - 1; } p += sizeof(webSocketConnectionHeader) - 1; } if (!strnstr(p, "\r\n\r\n", end - p)) { // Just hasn't been received fully yet. m_mode = Incomplete; return -1; } HTTPHeaderMap headers; p = readHTTPHeaders(p, end, &headers); if (!p) { LOG(Network, "readHTTPHeaders failed"); m_mode = Failed; return len; } if (!processHeaders(headers)) { LOG(Network, "header process failed"); m_mode = Failed; return p - header; } switch (m_mode) { case Normal: checkResponseHeaders(); break; default: m_mode = Failed; break; } return p - header; } WebSocketHandshake::Mode WebSocketHandshake::mode() const { return m_mode; } const String& WebSocketHandshake::serverWebSocketOrigin() const { return m_wsOrigin; } void WebSocketHandshake::setServerWebSocketOrigin(const String& webSocketOrigin) { m_wsOrigin = webSocketOrigin; } const String& WebSocketHandshake::serverWebSocketLocation() const { return m_wsLocation; } void WebSocketHandshake::setServerWebSocketLocation(const String& webSocketLocation) { m_wsLocation = webSocketLocation; } const String& WebSocketHandshake::serverWebSocketProtocol() const { return m_wsProtocol; } void WebSocketHandshake::setServerWebSocketProtocol(const String& webSocketProtocol) { m_wsProtocol = webSocketProtocol; } const String& WebSocketHandshake::serverSetCookie() const { return m_setCookie; } void WebSocketHandshake::setServerSetCookie(const String& setCookie) { m_setCookie = setCookie; } const String& WebSocketHandshake::serverSetCookie2() const { return m_setCookie2; } void WebSocketHandshake::setServerSetCookie2(const String& setCookie2) { m_setCookie2 = setCookie2; } KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const { KURL url = m_url.copy(); bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http"); ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); return url; } const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end, HTTPHeaderMap* headers) { Vector name; Vector value; for (const char* p = start; p < end; p++) { name.clear(); value.clear(); for (; p < end; p++) { switch (*p) { case '\r': if (name.isEmpty()) { if (p + 1 < end && *(p + 1) == '\n') return p + 2; m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "CR doesn't follow LF at " + String(p, end - p), 0, clientOrigin()); return 0; } m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected CR in name at " + String(p, end - p), 0, clientOrigin()); return 0; case '\n': m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected LF in name at " + String(p, end - p), 0, clientOrigin()); return 0; case ':': break; default: if (*p >= 0x41 && *p <= 0x5a) name.append(*p + 0x20); else name.append(*p); continue; } if (*p == ':') { ++p; break; } } for (; p < end && *p == 0x20; p++) { } for (; p < end; p++) { switch (*p) { case '\r': break; case '\n': m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected LF in value at " + String(p, end - p), 0, clientOrigin()); return 0; default: value.append(*p); } if (*p == '\r') { ++p; break; } } if (p >= end || *p != '\n') { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "CR doesn't follow LF after value at " + String(p, end - p), 0, clientOrigin()); return 0; } AtomicString nameStr(String::fromUTF8(name.data(), name.size())); String valueStr = String::fromUTF8(value.data(), value.size()); LOG(Network, "name=%s value=%s", nameStr.string().utf8().data(), valueStr.utf8().data()); headers->add(nameStr, valueStr); } ASSERT_NOT_REACHED(); return 0; } bool WebSocketHandshake::processHeaders(const HTTPHeaderMap& headers) { for (HTTPHeaderMap::const_iterator it = headers.begin(); it != headers.end(); ++it) { switch (m_mode) { case Normal: if (it->first == "websocket-origin") m_wsOrigin = it->second; else if (it->first == "websocket-location") m_wsLocation = it->second; else if (it->first == "websocket-protocol") m_wsProtocol = it->second; else if (it->first == "set-cookie") m_setCookie = it->second; else if (it->first == "set-cookie2") m_setCookie2 = it->second; continue; case Incomplete: case Failed: case Connected: ASSERT_NOT_REACHED(); } ASSERT_NOT_REACHED(); } return true; } void WebSocketHandshake::checkResponseHeaders() { ASSERT(m_mode == Normal); m_mode = Failed; if (m_wsOrigin.isNull()) { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'websocket-origin' header is missing", 0, clientOrigin()); return; } if (m_wsLocation.isNull()) { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'websocket-location' header is missing", 0, clientOrigin()); return; } if (clientOrigin() != m_wsOrigin) { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: origin mismatch: " + clientOrigin() + " != " + m_wsOrigin, 0, clientOrigin()); return; } if (clientLocation() != m_wsLocation) { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: location mismatch: " + clientLocation() + " != " + m_wsLocation, 0, clientOrigin()); return; } if (!m_clientProtocol.isEmpty() && m_clientProtocol != m_wsProtocol) { m_context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: protocol mismatch: " + m_clientProtocol + " != " + m_wsProtocol, 0, clientOrigin()); return; } m_mode = Connected; return; } } // namespace WebCore #endif // ENABLE(WEB_SOCKETS)