1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * Copyright (C) Research In Motion Limited 2011. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 34 #include "modules/websockets/WebSocket.h" 35 #include "modules/websockets/WebSocketHandshake.h" 36 37 #include "core/dom/Document.h" 38 #include "core/dom/ScriptExecutionContext.h" 39 #include "core/inspector/ScriptCallStack.h" 40 #include "core/loader/CookieJar.h" 41 #include "core/platform/Cookie.h" 42 #include "core/platform/HistogramSupport.h" 43 #include "core/platform/Logging.h" 44 #include "core/platform/network/HTTPHeaderMap.h" 45 #include "core/platform/network/HTTPParsers.h" 46 #include "weborigin/KURL.h" 47 #include "weborigin/SecurityOrigin.h" 48 #include "wtf/CryptographicallyRandomNumber.h" 49 #include "wtf/SHA1.h" 50 #include "wtf/StdLibExtras.h" 51 #include "wtf/StringExtras.h" 52 #include "wtf/Vector.h" 53 #include "wtf/text/Base64.h" 54 #include "wtf/text/CString.h" 55 #include "wtf/text/StringBuilder.h" 56 #include "wtf/text/WTFString.h" 57 #include "wtf/unicode/CharacterNames.h" 58 59 namespace WebCore { 60 61 static const char randomCharacterInSecWebSocketKey[] = "!\"#$%&'()*+,-./:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; 62 63 static String resourceName(const KURL& url) 64 { 65 StringBuilder name; 66 name.append(url.path()); 67 if (name.isEmpty()) 68 name.append('/'); 69 if (!url.query().isNull()) { 70 name.append('?'); 71 name.append(url.query()); 72 } 73 String result = name.toString(); 74 ASSERT(!result.isEmpty()); 75 ASSERT(!result.contains(' ')); 76 return result; 77 } 78 79 static String hostName(const KURL& url, bool secure) 80 { 81 ASSERT(url.protocolIs("wss") == secure); 82 StringBuilder builder; 83 builder.append(url.host().lower()); 84 if (url.port() && ((!secure && url.port() != 80) || (secure && url.port() != 443))) { 85 builder.append(':'); 86 builder.appendNumber(url.port()); 87 } 88 return builder.toString(); 89 } 90 91 static const size_t maxInputSampleSize = 128; 92 static String trimInputSample(const char* p, size_t len) 93 { 94 String s = String(p, std::min<size_t>(len, maxInputSampleSize)); 95 if (len > maxInputSampleSize) 96 s.append(horizontalEllipsis); 97 return s; 98 } 99 100 static String generateSecWebSocketKey() 101 { 102 static const size_t nonceSize = 16; 103 unsigned char key[nonceSize]; 104 cryptographicallyRandomValues(key, nonceSize); 105 return base64Encode(reinterpret_cast<char*>(key), nonceSize); 106 } 107 108 String WebSocketHandshake::getExpectedWebSocketAccept(const String& secWebSocketKey) 109 { 110 static const char* const webSocketKeyGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 111 static const size_t sha1HashSize = 20; // FIXME: This should be defined in SHA1.h. 112 SHA1 sha1; 113 CString keyData = secWebSocketKey.ascii(); 114 sha1.addBytes(reinterpret_cast<const uint8_t*>(keyData.data()), keyData.length()); 115 sha1.addBytes(reinterpret_cast<const uint8_t*>(webSocketKeyGUID), strlen(webSocketKeyGUID)); 116 Vector<uint8_t, sha1HashSize> hash; 117 sha1.computeHash(hash); 118 return base64Encode(reinterpret_cast<const char*>(hash.data()), sha1HashSize); 119 } 120 121 WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, ScriptExecutionContext* context) 122 : m_url(url) 123 , m_clientProtocol(protocol) 124 , m_secure(m_url.protocolIs("wss")) 125 , m_context(context) 126 , m_mode(Incomplete) 127 { 128 m_secWebSocketKey = generateSecWebSocketKey(); 129 m_expectedAccept = getExpectedWebSocketAccept(m_secWebSocketKey); 130 } 131 132 WebSocketHandshake::~WebSocketHandshake() 133 { 134 HistogramSupport::histogramEnumeration("WebCore.WebSocket.HandshakeResult", m_mode, WebSocketHandshake::ModeMax); 135 } 136 137 const KURL& WebSocketHandshake::url() const 138 { 139 return m_url; 140 } 141 142 void WebSocketHandshake::setURL(const KURL& url) 143 { 144 m_url = url.copy(); 145 } 146 147 const String WebSocketHandshake::host() const 148 { 149 return m_url.host().lower(); 150 } 151 152 const String& WebSocketHandshake::clientProtocol() const 153 { 154 return m_clientProtocol; 155 } 156 157 void WebSocketHandshake::setClientProtocol(const String& protocol) 158 { 159 m_clientProtocol = protocol; 160 } 161 162 bool WebSocketHandshake::secure() const 163 { 164 return m_secure; 165 } 166 167 String WebSocketHandshake::clientOrigin() const 168 { 169 return m_context->securityOrigin()->toString(); 170 } 171 172 String WebSocketHandshake::clientLocation() const 173 { 174 StringBuilder builder; 175 builder.append(m_secure ? "wss" : "ws"); 176 builder.append("://"); 177 builder.append(hostName(m_url, m_secure)); 178 builder.append(resourceName(m_url)); 179 return builder.toString(); 180 } 181 182 CString WebSocketHandshake::clientHandshakeMessage() const 183 { 184 // Keep the following consistent with clientHandshakeRequest(). 185 StringBuilder builder; 186 187 builder.append("GET "); 188 builder.append(resourceName(m_url)); 189 builder.append(" HTTP/1.1\r\n"); 190 191 Vector<String> fields; 192 fields.append("Upgrade: websocket"); 193 fields.append("Connection: Upgrade"); 194 fields.append("Host: " + hostName(m_url, m_secure)); 195 fields.append("Origin: " + clientOrigin()); 196 if (!m_clientProtocol.isEmpty()) 197 fields.append("Sec-WebSocket-Protocol: " + m_clientProtocol); 198 199 KURL url = httpURLForAuthenticationAndCookies(); 200 if (m_context->isDocument()) { 201 Document* document = toDocument(m_context); 202 String cookie = cookieRequestHeaderFieldValue(document, url); 203 if (!cookie.isEmpty()) 204 fields.append("Cookie: " + cookie); 205 // Set "Cookie2: <cookie>" if cookies 2 exists for url? 206 } 207 208 // Add no-cache headers to avoid compatibility issue. 209 // There are some proxies that rewrite "Connection: upgrade" 210 // to "Connection: close" in the response if a request doesn't contain 211 // these headers. 212 fields.append("Pragma: no-cache"); 213 fields.append("Cache-Control: no-cache"); 214 215 fields.append("Sec-WebSocket-Key: " + m_secWebSocketKey); 216 fields.append("Sec-WebSocket-Version: 13"); 217 const String extensionValue = m_extensionDispatcher.createHeaderValue(); 218 if (extensionValue.length()) 219 fields.append("Sec-WebSocket-Extensions: " + extensionValue); 220 221 // Add a User-Agent header. 222 fields.append("User-Agent: " + m_context->userAgent(m_context->url())); 223 224 // Fields in the handshake are sent by the client in a random order; the 225 // order is not meaningful. Thus, it's ok to send the order we constructed 226 // the fields. 227 228 for (size_t i = 0; i < fields.size(); i++) { 229 builder.append(fields[i]); 230 builder.append("\r\n"); 231 } 232 233 builder.append("\r\n"); 234 235 return builder.toString().utf8(); 236 } 237 238 PassRefPtr<WebSocketHandshakeRequest> WebSocketHandshake::clientHandshakeRequest() const 239 { 240 // Keep the following consistent with clientHandshakeMessage(). 241 // FIXME: do we need to store m_secWebSocketKey1, m_secWebSocketKey2 and 242 // m_key3 in WebSocketHandshakeRequest? 243 RefPtr<WebSocketHandshakeRequest> request = WebSocketHandshakeRequest::create("GET", m_url); 244 request->addHeaderField("Upgrade", "websocket"); 245 request->addHeaderField("Connection", "Upgrade"); 246 request->addHeaderField("Host", hostName(m_url, m_secure)); 247 request->addHeaderField("Origin", clientOrigin()); 248 if (!m_clientProtocol.isEmpty()) 249 request->addHeaderField("Sec-WebSocket-Protocol", m_clientProtocol); 250 251 KURL url = httpURLForAuthenticationAndCookies(); 252 if (m_context->isDocument()) { 253 Document* document = toDocument(m_context); 254 String cookie = cookieRequestHeaderFieldValue(document, url); 255 if (!cookie.isEmpty()) 256 request->addHeaderField("Cookie", cookie); 257 // Set "Cookie2: <cookie>" if cookies 2 exists for url? 258 } 259 260 request->addHeaderField("Pragma", "no-cache"); 261 request->addHeaderField("Cache-Control", "no-cache"); 262 263 request->addHeaderField("Sec-WebSocket-Key", m_secWebSocketKey); 264 request->addHeaderField("Sec-WebSocket-Version", "13"); 265 const String extensionValue = m_extensionDispatcher.createHeaderValue(); 266 if (extensionValue.length()) 267 request->addHeaderField("Sec-WebSocket-Extensions", extensionValue); 268 269 // Add a User-Agent header. 270 request->addHeaderField("User-Agent", m_context->userAgent(m_context->url())); 271 272 return request.release(); 273 } 274 275 void WebSocketHandshake::reset() 276 { 277 m_mode = Incomplete; 278 m_extensionDispatcher.reset(); 279 } 280 281 void WebSocketHandshake::clearScriptExecutionContext() 282 { 283 m_context = 0; 284 } 285 286 int WebSocketHandshake::readServerHandshake(const char* header, size_t len) 287 { 288 m_mode = Incomplete; 289 int statusCode; 290 String statusText; 291 int lineLength = readStatusLine(header, len, statusCode, statusText); 292 if (lineLength == -1) 293 return -1; 294 if (statusCode == -1) { 295 m_mode = Failed; // m_failureReason is set inside readStatusLine(). 296 return len; 297 } 298 LOG(Network, "WebSocketHandshake %p readServerHandshake() Status code is %d", this, statusCode); 299 m_response.setStatusCode(statusCode); 300 m_response.setStatusText(statusText); 301 if (statusCode != 101) { 302 m_mode = Failed; 303 m_failureReason = "Unexpected response code: " + String::number(statusCode); 304 return len; 305 } 306 m_mode = Normal; 307 if (!strnstr(header, "\r\n\r\n", len)) { 308 // Just hasn't been received fully yet. 309 m_mode = Incomplete; 310 return -1; 311 } 312 const char* p = readHTTPHeaders(header + lineLength, header + len); 313 if (!p) { 314 LOG(Network, "WebSocketHandshake %p readServerHandshake() readHTTPHeaders() failed", this); 315 m_mode = Failed; // m_failureReason is set inside readHTTPHeaders(). 316 return len; 317 } 318 if (!checkResponseHeaders()) { 319 LOG(Network, "WebSocketHandshake %p readServerHandshake() checkResponseHeaders() failed", this); 320 m_mode = Failed; 321 return p - header; 322 } 323 324 m_mode = Connected; 325 return p - header; 326 } 327 328 WebSocketHandshake::Mode WebSocketHandshake::mode() const 329 { 330 return m_mode; 331 } 332 333 String WebSocketHandshake::failureReason() const 334 { 335 return m_failureReason; 336 } 337 338 String WebSocketHandshake::serverWebSocketProtocol() const 339 { 340 return m_response.headerFields().get("sec-websocket-protocol"); 341 } 342 343 String WebSocketHandshake::serverSetCookie() const 344 { 345 return m_response.headerFields().get("set-cookie"); 346 } 347 348 String WebSocketHandshake::serverSetCookie2() const 349 { 350 return m_response.headerFields().get("set-cookie2"); 351 } 352 353 String WebSocketHandshake::serverUpgrade() const 354 { 355 return m_response.headerFields().get("upgrade"); 356 } 357 358 String WebSocketHandshake::serverConnection() const 359 { 360 return m_response.headerFields().get("connection"); 361 } 362 363 String WebSocketHandshake::serverWebSocketAccept() const 364 { 365 return m_response.headerFields().get("sec-websocket-accept"); 366 } 367 368 String WebSocketHandshake::acceptedExtensions() const 369 { 370 return m_extensionDispatcher.acceptedExtensions(); 371 } 372 373 const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const 374 { 375 return m_response; 376 } 377 378 void WebSocketHandshake::addExtensionProcessor(PassOwnPtr<WebSocketExtensionProcessor> processor) 379 { 380 m_extensionDispatcher.addProcessor(processor); 381 } 382 383 KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const 384 { 385 KURL url = m_url.copy(); 386 bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http"); 387 ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); 388 return url; 389 } 390 391 // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet. 392 // If the line is malformed or the status code is not a 3-digit number, 393 // statusCode and statusText will be set to -1 and a null string, respectively. 394 int WebSocketHandshake::readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText) 395 { 396 // Arbitrary size limit to prevent the server from sending an unbounded 397 // amount of data with no newlines and forcing us to buffer it all. 398 static const int maximumLength = 1024; 399 400 statusCode = -1; 401 statusText = String(); 402 403 const char* space1 = 0; 404 const char* space2 = 0; 405 const char* p; 406 size_t consumedLength; 407 408 for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) { 409 if (*p == ' ') { 410 if (!space1) 411 space1 = p; 412 else if (!space2) 413 space2 = p; 414 } else if (*p == '\0') { 415 // The caller isn't prepared to deal with null bytes in status 416 // line. WebSockets specification doesn't prohibit this, but HTTP 417 // does, so we'll just treat this as an error. 418 m_failureReason = "Status line contains embedded null"; 419 return p + 1 - header; 420 } else if (*p == '\n') 421 break; 422 } 423 if (consumedLength == headerLength) 424 return -1; // We have not received '\n' yet. 425 426 const char* end = p + 1; 427 int lineLength = end - header; 428 if (lineLength > maximumLength) { 429 m_failureReason = "Status line is too long"; 430 return maximumLength; 431 } 432 433 // The line must end with "\r\n". 434 if (lineLength < 2 || *(end - 2) != '\r') { 435 m_failureReason = "Status line does not end with CRLF"; 436 return lineLength; 437 } 438 439 if (!space1 || !space2) { 440 m_failureReason = "No response code found: " + trimInputSample(header, lineLength - 2); 441 return lineLength; 442 } 443 444 String statusCodeString(space1 + 1, space2 - space1 - 1); 445 if (statusCodeString.length() != 3) // Status code must consist of three digits. 446 return lineLength; 447 for (int i = 0; i < 3; ++i) 448 if (statusCodeString[i] < '0' || statusCodeString[i] > '9') { 449 m_failureReason = "Invalid status code: " + statusCodeString; 450 return lineLength; 451 } 452 453 bool ok = false; 454 statusCode = statusCodeString.toInt(&ok); 455 ASSERT(ok); 456 457 statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n". 458 return lineLength; 459 } 460 461 const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end) 462 { 463 m_response.clearHeaderFields(); 464 465 AtomicString name; 466 String value; 467 bool sawSecWebSocketAcceptHeaderField = false; 468 bool sawSecWebSocketProtocolHeaderField = false; 469 const char* p = start; 470 for (; p < end; p++) { 471 size_t consumedLength = parseHTTPHeader(p, end - p, m_failureReason, name, value); 472 if (!consumedLength) 473 return 0; 474 p += consumedLength; 475 476 // Stop once we consumed an empty line. 477 if (name.isEmpty()) 478 break; 479 480 // Sec-WebSocket-Extensions may be split. We parse and check the 481 // header value every time the header appears. 482 if (equalIgnoringCase("sec-websocket-extensions", name)) { 483 if (!m_extensionDispatcher.processHeaderValue(value)) { 484 m_failureReason = m_extensionDispatcher.failureReason(); 485 return 0; 486 } 487 } else if (equalIgnoringCase("Sec-WebSocket-Accept", name)) { 488 if (sawSecWebSocketAcceptHeaderField) { 489 m_failureReason = "The Sec-WebSocket-Accept header MUST NOT appear more than once in an HTTP response"; 490 return 0; 491 } 492 m_response.addHeaderField(name, value); 493 sawSecWebSocketAcceptHeaderField = true; 494 } else if (equalIgnoringCase("Sec-WebSocket-Protocol", name)) { 495 if (sawSecWebSocketProtocolHeaderField) { 496 m_failureReason = "The Sec-WebSocket-Protocol header MUST NOT appear more than once in an HTTP response"; 497 return 0; 498 } 499 m_response.addHeaderField(name, value); 500 sawSecWebSocketProtocolHeaderField = true; 501 } else 502 m_response.addHeaderField(name, value); 503 } 504 return p; 505 } 506 507 bool WebSocketHandshake::checkResponseHeaders() 508 { 509 const String& serverWebSocketProtocol = this->serverWebSocketProtocol(); 510 const String& serverUpgrade = this->serverUpgrade(); 511 const String& serverConnection = this->serverConnection(); 512 const String& serverWebSocketAccept = this->serverWebSocketAccept(); 513 514 if (serverUpgrade.isNull()) { 515 m_failureReason = "Error during WebSocket handshake: 'Upgrade' header is missing"; 516 return false; 517 } 518 if (serverConnection.isNull()) { 519 m_failureReason = "Error during WebSocket handshake: 'Connection' header is missing"; 520 return false; 521 } 522 if (serverWebSocketAccept.isNull()) { 523 m_failureReason = "Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing"; 524 return false; 525 } 526 527 if (!equalIgnoringCase(serverUpgrade, "websocket")) { 528 m_failureReason = "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'"; 529 return false; 530 } 531 if (!equalIgnoringCase(serverConnection, "upgrade")) { 532 m_failureReason = "Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'"; 533 return false; 534 } 535 536 if (serverWebSocketAccept != m_expectedAccept) { 537 m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch"; 538 return false; 539 } 540 if (!serverWebSocketProtocol.isNull()) { 541 if (m_clientProtocol.isEmpty()) { 542 m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"; 543 return false; 544 } 545 Vector<String> result; 546 m_clientProtocol.split(String(WebSocket::subProtocolSeperator()), result); 547 if (!result.contains(serverWebSocketProtocol)) { 548 m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"; 549 return false; 550 } 551 } else if (!m_clientProtocol.isEmpty()) { 552 m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"; 553 return false; 554 } 555 return true; 556 } 557 558 } // namespace WebCore 559