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/WebSocketHandshake.h" 35 36 #include "core/dom/Document.h" 37 #include "core/dom/ExecutionContext.h" 38 #include "core/inspector/ScriptCallStack.h" 39 #include "core/loader/CookieJar.h" 40 #include "modules/websockets/WebSocket.h" 41 #include "platform/Cookie.h" 42 #include "platform/Logging.h" 43 #include "platform/network/HTTPHeaderMap.h" 44 #include "platform/network/HTTPParsers.h" 45 #include "platform/weborigin/SecurityOrigin.h" 46 #include "public/platform/Platform.h" 47 #include "wtf/CryptographicallyRandomNumber.h" 48 #include "wtf/SHA1.h" 49 #include "wtf/StdLibExtras.h" 50 #include "wtf/StringExtras.h" 51 #include "wtf/Vector.h" 52 #include "wtf/text/Base64.h" 53 #include "wtf/text/CString.h" 54 #include "wtf/text/StringBuilder.h" 55 #include "wtf/unicode/CharacterNames.h" 56 57 namespace WebCore { 58 59 namespace { 60 61 // FIXME: The spec says that the Sec-WebSocket-Protocol header in a handshake 62 // response can't be null if the header in a request is not null. 63 // Some servers are not accustomed to the shutdown, 64 // so we provide an adhoc white-list for it tentatively. 65 const char* const missingProtocolWhiteList[] = { 66 "ica.citrix.com", 67 }; 68 69 String formatHandshakeFailureReason(const String& detail) 70 { 71 return "Error during WebSocket handshake: " + detail; 72 } 73 74 } // namespace 75 76 static String resourceName(const KURL& url) 77 { 78 StringBuilder name; 79 name.append(url.path()); 80 if (name.isEmpty()) 81 name.append('/'); 82 if (!url.query().isNull()) { 83 name.append('?'); 84 name.append(url.query()); 85 } 86 String result = name.toString(); 87 ASSERT(!result.isEmpty()); 88 ASSERT(!result.contains(' ')); 89 return result; 90 } 91 92 static String hostName(const KURL& url, bool secure) 93 { 94 ASSERT(url.protocolIs("wss") == secure); 95 StringBuilder builder; 96 builder.append(url.host().lower()); 97 if (url.port() && ((!secure && url.port() != 80) || (secure && url.port() != 443))) { 98 builder.append(':'); 99 builder.appendNumber(url.port()); 100 } 101 return builder.toString(); 102 } 103 104 static const size_t maxInputSampleSize = 128; 105 static String trimInputSample(const char* p, size_t len) 106 { 107 String s = String(p, std::min<size_t>(len, maxInputSampleSize)); 108 if (len > maxInputSampleSize) 109 s.append(horizontalEllipsis); 110 return s; 111 } 112 113 static String generateSecWebSocketKey() 114 { 115 static const size_t nonceSize = 16; 116 unsigned char key[nonceSize]; 117 cryptographicallyRandomValues(key, nonceSize); 118 return base64Encode(reinterpret_cast<char*>(key), nonceSize); 119 } 120 121 String WebSocketHandshake::getExpectedWebSocketAccept(const String& secWebSocketKey) 122 { 123 static const char webSocketKeyGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 124 static const size_t sha1HashSize = 20; // FIXME: This should be defined in SHA1.h. 125 SHA1 sha1; 126 CString keyData = secWebSocketKey.ascii(); 127 sha1.addBytes(reinterpret_cast<const uint8_t*>(keyData.data()), keyData.length()); 128 sha1.addBytes(reinterpret_cast<const uint8_t*>(webSocketKeyGUID), strlen(webSocketKeyGUID)); 129 Vector<uint8_t, sha1HashSize> hash; 130 sha1.computeHash(hash); 131 return base64Encode(reinterpret_cast<const char*>(hash.data()), sha1HashSize); 132 } 133 134 WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, ExecutionContext* context) 135 : m_url(url) 136 , m_clientProtocol(protocol) 137 , m_secure(m_url.protocolIs("wss")) 138 , m_context(context) 139 , m_mode(Incomplete) 140 { 141 m_secWebSocketKey = generateSecWebSocketKey(); 142 m_expectedAccept = getExpectedWebSocketAccept(m_secWebSocketKey); 143 } 144 145 WebSocketHandshake::~WebSocketHandshake() 146 { 147 blink::Platform::current()->histogramEnumeration("WebCore.WebSocket.HandshakeResult", m_mode, WebSocketHandshake::ModeMax); 148 } 149 150 const KURL& WebSocketHandshake::url() const 151 { 152 return m_url; 153 } 154 155 void WebSocketHandshake::setURL(const KURL& url) 156 { 157 m_url = url.copy(); 158 } 159 160 const String WebSocketHandshake::host() const 161 { 162 return m_url.host().lower(); 163 } 164 165 const String& WebSocketHandshake::clientProtocol() const 166 { 167 return m_clientProtocol; 168 } 169 170 void WebSocketHandshake::setClientProtocol(const String& protocol) 171 { 172 m_clientProtocol = protocol; 173 } 174 175 bool WebSocketHandshake::secure() const 176 { 177 return m_secure; 178 } 179 180 String WebSocketHandshake::clientOrigin() const 181 { 182 return m_context->securityOrigin()->toString(); 183 } 184 185 String WebSocketHandshake::clientLocation() const 186 { 187 StringBuilder builder; 188 builder.append(m_secure ? "wss" : "ws"); 189 builder.append("://"); 190 builder.append(hostName(m_url, m_secure)); 191 builder.append(resourceName(m_url)); 192 return builder.toString(); 193 } 194 195 CString WebSocketHandshake::clientHandshakeMessage() const 196 { 197 // Keep the following consistent with clientHandshakeRequest(). 198 StringBuilder builder; 199 200 builder.append("GET "); 201 builder.append(resourceName(m_url)); 202 builder.append(" HTTP/1.1\r\n"); 203 204 Vector<String> fields; 205 fields.append("Upgrade: websocket"); 206 fields.append("Connection: Upgrade"); 207 fields.append("Host: " + hostName(m_url, m_secure)); 208 fields.append("Origin: " + clientOrigin()); 209 if (!m_clientProtocol.isEmpty()) 210 fields.append("Sec-WebSocket-Protocol: " + m_clientProtocol); 211 212 KURL url = httpURLForAuthenticationAndCookies(); 213 if (m_context->isDocument()) { 214 Document* document = toDocument(m_context); 215 String cookie = cookieRequestHeaderFieldValue(document, url); 216 if (!cookie.isEmpty()) 217 fields.append("Cookie: " + cookie); 218 // Set "Cookie2: <cookie>" if cookies 2 exists for url? 219 } 220 221 // Add no-cache headers to avoid compatibility issue. 222 // There are some proxies that rewrite "Connection: upgrade" 223 // to "Connection: close" in the response if a request doesn't contain 224 // these headers. 225 fields.append("Pragma: no-cache"); 226 fields.append("Cache-Control: no-cache"); 227 228 fields.append("Sec-WebSocket-Key: " + m_secWebSocketKey); 229 fields.append("Sec-WebSocket-Version: 13"); 230 const String extensionValue = m_extensionDispatcher.createHeaderValue(); 231 if (extensionValue.length()) 232 fields.append("Sec-WebSocket-Extensions: " + extensionValue); 233 234 // Add a User-Agent header. 235 fields.append("User-Agent: " + m_context->userAgent(m_context->url())); 236 237 // Fields in the handshake are sent by the client in a random order; the 238 // order is not meaningful. Thus, it's ok to send the order we constructed 239 // the fields. 240 241 for (size_t i = 0; i < fields.size(); i++) { 242 builder.append(fields[i]); 243 builder.append("\r\n"); 244 } 245 246 builder.append("\r\n"); 247 248 return builder.toString().utf8(); 249 } 250 251 PassRefPtr<WebSocketHandshakeRequest> WebSocketHandshake::clientHandshakeRequest() const 252 { 253 // Keep the following consistent with clientHandshakeMessage(). 254 // FIXME: do we need to store m_secWebSocketKey1, m_secWebSocketKey2 and 255 // m_key3 in WebSocketHandshakeRequest? 256 RefPtr<WebSocketHandshakeRequest> request = WebSocketHandshakeRequest::create("GET", m_url); 257 request->addHeaderField("Upgrade", "websocket"); 258 request->addHeaderField("Connection", "Upgrade"); 259 request->addHeaderField("Host", hostName(m_url, m_secure)); 260 request->addHeaderField("Origin", clientOrigin()); 261 if (!m_clientProtocol.isEmpty()) 262 request->addHeaderField("Sec-WebSocket-Protocol", m_clientProtocol); 263 264 KURL url = httpURLForAuthenticationAndCookies(); 265 if (m_context->isDocument()) { 266 Document* document = toDocument(m_context); 267 String cookie = cookieRequestHeaderFieldValue(document, url); 268 if (!cookie.isEmpty()) 269 request->addHeaderField("Cookie", cookie); 270 // Set "Cookie2: <cookie>" if cookies 2 exists for url? 271 } 272 273 request->addHeaderField("Pragma", "no-cache"); 274 request->addHeaderField("Cache-Control", "no-cache"); 275 276 request->addHeaderField("Sec-WebSocket-Key", m_secWebSocketKey); 277 request->addHeaderField("Sec-WebSocket-Version", "13"); 278 const String extensionValue = m_extensionDispatcher.createHeaderValue(); 279 if (extensionValue.length()) 280 request->addHeaderField("Sec-WebSocket-Extensions", extensionValue); 281 282 // Add a User-Agent header. 283 request->addHeaderField("User-Agent", m_context->userAgent(m_context->url())); 284 285 return request.release(); 286 } 287 288 void WebSocketHandshake::reset() 289 { 290 m_mode = Incomplete; 291 m_extensionDispatcher.reset(); 292 } 293 294 void WebSocketHandshake::clearExecutionContext() 295 { 296 m_context = 0; 297 } 298 299 int WebSocketHandshake::readServerHandshake(const char* header, size_t len) 300 { 301 m_mode = Incomplete; 302 int statusCode; 303 String statusText; 304 int lineLength = readStatusLine(header, len, statusCode, statusText); 305 if (lineLength == -1) 306 return -1; 307 if (statusCode == -1) { 308 m_mode = Failed; // m_failureReason is set inside readStatusLine(). 309 return len; 310 } 311 WTF_LOG(Network, "WebSocketHandshake %p readServerHandshake() Status code is %d", this, statusCode); 312 m_response.setStatusCode(statusCode); 313 m_response.setStatusText(statusText); 314 if (statusCode != 101) { 315 m_mode = Failed; 316 m_failureReason = formatHandshakeFailureReason("Unexpected response code: " + String::number(statusCode)); 317 return len; 318 } 319 m_mode = Normal; 320 if (!strnstr(header, "\r\n\r\n", len)) { 321 // Just hasn't been received fully yet. 322 m_mode = Incomplete; 323 return -1; 324 } 325 const char* p = readHTTPHeaders(header + lineLength, header + len); 326 if (!p) { 327 WTF_LOG(Network, "WebSocketHandshake %p readServerHandshake() readHTTPHeaders() failed", this); 328 m_mode = Failed; // m_failureReason is set inside readHTTPHeaders(). 329 return len; 330 } 331 if (!checkResponseHeaders()) { 332 WTF_LOG(Network, "WebSocketHandshake %p readServerHandshake() checkResponseHeaders() failed", this); 333 m_mode = Failed; 334 return p - header; 335 } 336 337 m_mode = Connected; 338 return p - header; 339 } 340 341 WebSocketHandshake::Mode WebSocketHandshake::mode() const 342 { 343 return m_mode; 344 } 345 346 String WebSocketHandshake::failureReason() const 347 { 348 return m_failureReason; 349 } 350 351 const AtomicString& WebSocketHandshake::serverWebSocketProtocol() const 352 { 353 return m_response.headerFields().get("sec-websocket-protocol"); 354 } 355 356 const AtomicString& WebSocketHandshake::serverSetCookie() const 357 { 358 return m_response.headerFields().get("set-cookie"); 359 } 360 361 const AtomicString& WebSocketHandshake::serverSetCookie2() const 362 { 363 return m_response.headerFields().get("set-cookie2"); 364 } 365 366 const AtomicString& WebSocketHandshake::serverUpgrade() const 367 { 368 return m_response.headerFields().get("upgrade"); 369 } 370 371 const AtomicString& WebSocketHandshake::serverConnection() const 372 { 373 return m_response.headerFields().get("connection"); 374 } 375 376 const AtomicString& WebSocketHandshake::serverWebSocketAccept() const 377 { 378 return m_response.headerFields().get("sec-websocket-accept"); 379 } 380 381 String WebSocketHandshake::acceptedExtensions() const 382 { 383 return m_extensionDispatcher.acceptedExtensions(); 384 } 385 386 const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const 387 { 388 return m_response; 389 } 390 391 void WebSocketHandshake::addExtensionProcessor(PassOwnPtr<WebSocketExtensionProcessor> processor) 392 { 393 m_extensionDispatcher.addProcessor(processor); 394 } 395 396 KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const 397 { 398 KURL url = m_url.copy(); 399 bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http"); 400 ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); 401 return url; 402 } 403 404 // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet. 405 // If the line is malformed or the status code is not a 3-digit number, 406 // statusCode and statusText will be set to -1 and a null string, respectively. 407 int WebSocketHandshake::readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText) 408 { 409 // Arbitrary size limit to prevent the server from sending an unbounded 410 // amount of data with no newlines and forcing us to buffer it all. 411 static const int maximumLength = 1024; 412 413 statusCode = -1; 414 statusText = String(); 415 416 const char* space1 = 0; 417 const char* space2 = 0; 418 const char* p; 419 size_t consumedLength; 420 421 for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) { 422 if (*p == ' ') { 423 if (!space1) 424 space1 = p; 425 else if (!space2) 426 space2 = p; 427 } else if (*p == '\0') { 428 // The caller isn't prepared to deal with null bytes in status 429 // line. WebSockets specification doesn't prohibit this, but HTTP 430 // does, so we'll just treat this as an error. 431 m_failureReason = formatHandshakeFailureReason("Status line contains embedded null"); 432 return p + 1 - header; 433 } else if (*p == '\n') { 434 break; 435 } 436 } 437 if (consumedLength == headerLength) 438 return -1; // We have not received '\n' yet. 439 440 const char* end = p + 1; 441 int lineLength = end - header; 442 if (lineLength > maximumLength) { 443 m_failureReason = formatHandshakeFailureReason("Status line is too long"); 444 return maximumLength; 445 } 446 447 // The line must end with "\r\n". 448 if (lineLength < 2 || *(end - 2) != '\r') { 449 m_failureReason = formatHandshakeFailureReason("Status line does not end with CRLF"); 450 return lineLength; 451 } 452 453 if (!space1 || !space2) { 454 m_failureReason = formatHandshakeFailureReason("No response code found in status line: " + trimInputSample(header, lineLength - 2)); 455 return lineLength; 456 } 457 458 String statusCodeString(space1 + 1, space2 - space1 - 1); 459 if (statusCodeString.length() != 3) // Status code must consist of three digits. 460 return lineLength; 461 for (int i = 0; i < 3; ++i) { 462 if (statusCodeString[i] < '0' || statusCodeString[i] > '9') { 463 m_failureReason = formatHandshakeFailureReason("Invalid status code: " + statusCodeString); 464 return lineLength; 465 } 466 } 467 468 bool ok = false; 469 statusCode = statusCodeString.toInt(&ok); 470 ASSERT(ok); 471 472 statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n". 473 return lineLength; 474 } 475 476 const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end) 477 { 478 m_response.clearHeaderFields(); 479 480 AtomicString name; 481 AtomicString value; 482 bool sawSecWebSocketAcceptHeaderField = false; 483 bool sawSecWebSocketProtocolHeaderField = false; 484 const char* p = start; 485 for (; p < end; p++) { 486 size_t consumedLength = parseHTTPHeader(p, end - p, m_failureReason, name, value); 487 if (!consumedLength) 488 return 0; 489 p += consumedLength; 490 491 // Stop once we consumed an empty line. 492 if (name.isEmpty()) 493 break; 494 495 // Sec-WebSocket-Extensions may be split. We parse and check the 496 // header value every time the header appears. 497 if (equalIgnoringCase("Sec-WebSocket-Extensions", name)) { 498 if (!m_extensionDispatcher.processHeaderValue(value)) { 499 m_failureReason = formatHandshakeFailureReason(m_extensionDispatcher.failureReason()); 500 return 0; 501 } 502 } else if (equalIgnoringCase("Sec-WebSocket-Accept", name)) { 503 if (sawSecWebSocketAcceptHeaderField) { 504 m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Accept' header must not appear more than once in a response"); 505 return 0; 506 } 507 m_response.addHeaderField(name, value); 508 sawSecWebSocketAcceptHeaderField = true; 509 } else if (equalIgnoringCase("Sec-WebSocket-Protocol", name)) { 510 if (sawSecWebSocketProtocolHeaderField) { 511 m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Protocol' header must not appear more than once in a response"); 512 return 0; 513 } 514 m_response.addHeaderField(name, value); 515 sawSecWebSocketProtocolHeaderField = true; 516 } else { 517 m_response.addHeaderField(name, value); 518 } 519 } 520 521 String extensions = m_extensionDispatcher.acceptedExtensions(); 522 if (!extensions.isEmpty()) 523 m_response.addHeaderField("Sec-WebSocket-Extensions", extensions); 524 return p; 525 } 526 527 bool WebSocketHandshake::checkResponseHeaders() 528 { 529 const AtomicString& serverWebSocketProtocol = this->serverWebSocketProtocol(); 530 const AtomicString& serverUpgrade = this->serverUpgrade(); 531 const AtomicString& serverConnection = this->serverConnection(); 532 const AtomicString& serverWebSocketAccept = this->serverWebSocketAccept(); 533 534 if (serverUpgrade.isNull()) { 535 m_failureReason = formatHandshakeFailureReason("'Upgrade' header is missing"); 536 return false; 537 } 538 if (serverConnection.isNull()) { 539 m_failureReason = formatHandshakeFailureReason("'Connection' header is missing"); 540 return false; 541 } 542 if (serverWebSocketAccept.isNull()) { 543 m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Accept' header is missing"); 544 return false; 545 } 546 547 if (!equalIgnoringCase(serverUpgrade, "websocket")) { 548 m_failureReason = formatHandshakeFailureReason("'Upgrade' header value is not 'WebSocket': " + serverUpgrade); 549 return false; 550 } 551 if (!equalIgnoringCase(serverConnection, "upgrade")) { 552 m_failureReason = formatHandshakeFailureReason("'Connection' header value is not 'Upgrade': " + serverConnection); 553 return false; 554 } 555 556 if (serverWebSocketAccept != m_expectedAccept) { 557 m_failureReason = formatHandshakeFailureReason("Incorrect 'Sec-WebSocket-Accept' header value"); 558 return false; 559 } 560 if (!serverWebSocketProtocol.isNull()) { 561 if (m_clientProtocol.isEmpty()) { 562 m_failureReason = formatHandshakeFailureReason("Response must not include 'Sec-WebSocket-Protocol' header if not present in request: " + serverWebSocketProtocol); 563 return false; 564 } 565 Vector<String> result; 566 m_clientProtocol.split(String(WebSocket::subProtocolSeperator()), result); 567 if (!result.contains(serverWebSocketProtocol)) { 568 m_failureReason = formatHandshakeFailureReason("'Sec-WebSocket-Protocol' header value '" + serverWebSocketProtocol + "' in response does not match any of sent values"); 569 return false; 570 } 571 } else if (!m_clientProtocol.isEmpty()) { 572 // FIXME: Some servers are not accustomed to this failure, so we provide an adhoc white-list for it tentatively. 573 Vector<String> protocols; 574 m_clientProtocol.split(String(WebSocket::subProtocolSeperator()), protocols); 575 bool match = false; 576 for (size_t i = 0; i < protocols.size() && !match; ++i) { 577 for (size_t j = 0; j < WTF_ARRAY_LENGTH(missingProtocolWhiteList) && !match; ++j) { 578 if (protocols[i] == missingProtocolWhiteList[j]) 579 match = true; 580 } 581 } 582 if (!match) { 583 m_failureReason = formatHandshakeFailureReason("Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received"); 584 return false; 585 } 586 } 587 return true; 588 } 589 590 } // namespace WebCore 591