1 /* 2 * Copyright (C) 2009 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 #if ENABLE(WEB_SOCKETS) 35 36 #include "WebSocketHandshake.h" 37 38 #include "Cookie.h" 39 #include "CookieJar.h" 40 #include "Document.h" 41 #include "HTTPHeaderMap.h" 42 #include "KURL.h" 43 #include "Logging.h" 44 #include "ScriptCallStack.h" 45 #include "ScriptExecutionContext.h" 46 #include "SecurityOrigin.h" 47 #include <wtf/CryptographicallyRandomNumber.h> 48 #include <wtf/MD5.h> 49 #include <wtf/StdLibExtras.h> 50 #include <wtf/StringExtras.h> 51 #include <wtf/Vector.h> 52 #include <wtf/text/AtomicString.h> 53 #include <wtf/text/CString.h> 54 #include <wtf/text/StringBuilder.h> 55 #include <wtf/text/StringConcatenate.h> 56 #include <wtf/unicode/CharacterNames.h> 57 58 namespace WebCore { 59 60 static const char randomCharacterInSecWebSocketKey[] = "!\"#$%&'()*+,-./:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; 61 62 static String resourceName(const KURL& url) 63 { 64 String name = url.path(); 65 if (name.isEmpty()) 66 name = "/"; 67 if (!url.query().isNull()) 68 name += "?" + url.query(); 69 ASSERT(!name.isEmpty()); 70 ASSERT(!name.contains(' ')); 71 return name; 72 } 73 74 static String hostName(const KURL& url, bool secure) 75 { 76 ASSERT(url.protocolIs("wss") == secure); 77 StringBuilder builder; 78 builder.append(url.host().lower()); 79 if (url.port() && ((!secure && url.port() != 80) || (secure && url.port() != 443))) { 80 builder.append(':'); 81 builder.append(String::number(url.port())); 82 } 83 return builder.toString(); 84 } 85 86 static const size_t maxConsoleMessageSize = 128; 87 static String trimConsoleMessage(const char* p, size_t len) 88 { 89 String s = String(p, std::min<size_t>(len, maxConsoleMessageSize)); 90 if (len > maxConsoleMessageSize) 91 s.append(horizontalEllipsis); 92 return s; 93 } 94 95 static uint32_t randomNumberLessThan(uint32_t n) 96 { 97 if (!n) 98 return 0; 99 if (n == std::numeric_limits<uint32_t>::max()) 100 return cryptographicallyRandomNumber(); 101 uint32_t max = std::numeric_limits<uint32_t>::max() - (std::numeric_limits<uint32_t>::max() % n); 102 ASSERT(!(max % n)); 103 uint32_t v; 104 do { 105 v = cryptographicallyRandomNumber(); 106 } while (v >= max); 107 return v % n; 108 } 109 110 static void generateSecWebSocketKey(uint32_t& number, String& key) 111 { 112 uint32_t space = randomNumberLessThan(12) + 1; 113 uint32_t max = 4294967295U / space; 114 number = randomNumberLessThan(max); 115 uint32_t product = number * space; 116 117 String s = String::number(product); 118 int n = randomNumberLessThan(12) + 1; 119 DEFINE_STATIC_LOCAL(String, randomChars, (randomCharacterInSecWebSocketKey)); 120 for (int i = 0; i < n; i++) { 121 int pos = randomNumberLessThan(s.length() + 1); 122 int chpos = randomNumberLessThan(randomChars.length()); 123 s.insert(randomChars.substring(chpos, 1), pos); 124 } 125 DEFINE_STATIC_LOCAL(String, spaceChar, (" ")); 126 for (uint32_t i = 0; i < space; i++) { 127 int pos = randomNumberLessThan(s.length() - 1) + 1; 128 s.insert(spaceChar, pos); 129 } 130 ASSERT(s[0] != ' '); 131 ASSERT(s[s.length() - 1] != ' '); 132 key = s; 133 } 134 135 static void generateKey3(unsigned char key3[8]) 136 { 137 cryptographicallyRandomValues(key3, 8); 138 } 139 140 static void setChallengeNumber(unsigned char* buf, uint32_t number) 141 { 142 unsigned char* p = buf + 3; 143 for (int i = 0; i < 4; i++) { 144 *p = number & 0xFF; 145 --p; 146 number >>= 8; 147 } 148 } 149 150 static void generateExpectedChallengeResponse(uint32_t number1, uint32_t number2, unsigned char key3[8], unsigned char expectedChallenge[16]) 151 { 152 unsigned char challenge[16]; 153 setChallengeNumber(&challenge[0], number1); 154 setChallengeNumber(&challenge[4], number2); 155 memcpy(&challenge[8], key3, 8); 156 MD5 md5; 157 md5.addBytes(challenge, sizeof(challenge)); 158 Vector<uint8_t, 16> digest; 159 md5.checksum(digest); 160 memcpy(expectedChallenge, digest.data(), 16); 161 } 162 163 WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, ScriptExecutionContext* context) 164 : m_url(url) 165 , m_clientProtocol(protocol) 166 , m_secure(m_url.protocolIs("wss")) 167 , m_context(context) 168 , m_mode(Incomplete) 169 { 170 uint32_t number1; 171 uint32_t number2; 172 generateSecWebSocketKey(number1, m_secWebSocketKey1); 173 generateSecWebSocketKey(number2, m_secWebSocketKey2); 174 generateKey3(m_key3); 175 generateExpectedChallengeResponse(number1, number2, m_key3, m_expectedChallengeResponse); 176 } 177 178 WebSocketHandshake::~WebSocketHandshake() 179 { 180 } 181 182 const KURL& WebSocketHandshake::url() const 183 { 184 return m_url; 185 } 186 187 void WebSocketHandshake::setURL(const KURL& url) 188 { 189 m_url = url.copy(); 190 } 191 192 const String WebSocketHandshake::host() const 193 { 194 return m_url.host().lower(); 195 } 196 197 const String& WebSocketHandshake::clientProtocol() const 198 { 199 return m_clientProtocol; 200 } 201 202 void WebSocketHandshake::setClientProtocol(const String& protocol) 203 { 204 m_clientProtocol = protocol; 205 } 206 207 bool WebSocketHandshake::secure() const 208 { 209 return m_secure; 210 } 211 212 String WebSocketHandshake::clientOrigin() const 213 { 214 return m_context->securityOrigin()->toString(); 215 } 216 217 String WebSocketHandshake::clientLocation() const 218 { 219 StringBuilder builder; 220 builder.append(m_secure ? "wss" : "ws"); 221 builder.append("://"); 222 builder.append(hostName(m_url, m_secure)); 223 builder.append(resourceName(m_url)); 224 return builder.toString(); 225 } 226 227 CString WebSocketHandshake::clientHandshakeMessage() const 228 { 229 // Keep the following consistent with clientHandshakeRequest(). 230 StringBuilder builder; 231 232 builder.append("GET "); 233 builder.append(resourceName(m_url)); 234 builder.append(" HTTP/1.1\r\n"); 235 236 Vector<String> fields; 237 fields.append("Upgrade: WebSocket"); 238 fields.append("Connection: Upgrade"); 239 fields.append("Host: " + hostName(m_url, m_secure)); 240 fields.append("Origin: " + clientOrigin()); 241 if (!m_clientProtocol.isEmpty()) 242 fields.append("Sec-WebSocket-Protocol: " + m_clientProtocol); 243 244 KURL url = httpURLForAuthenticationAndCookies(); 245 if (m_context->isDocument()) { 246 Document* document = static_cast<Document*>(m_context); 247 String cookie = cookieRequestHeaderFieldValue(document, url); 248 if (!cookie.isEmpty()) 249 fields.append("Cookie: " + cookie); 250 // Set "Cookie2: <cookie>" if cookies 2 exists for url? 251 } 252 253 fields.append("Sec-WebSocket-Key1: " + m_secWebSocketKey1); 254 fields.append("Sec-WebSocket-Key2: " + m_secWebSocketKey2); 255 256 // Fields in the handshake are sent by the client in a random order; the 257 // order is not meaningful. Thus, it's ok to send the order we constructed 258 // the fields. 259 260 for (size_t i = 0; i < fields.size(); i++) { 261 builder.append(fields[i]); 262 builder.append("\r\n"); 263 } 264 265 builder.append("\r\n"); 266 267 CString handshakeHeader = builder.toString().utf8(); 268 char* characterBuffer = 0; 269 CString msg = CString::newUninitialized(handshakeHeader.length() + sizeof(m_key3), characterBuffer); 270 memcpy(characterBuffer, handshakeHeader.data(), handshakeHeader.length()); 271 memcpy(characterBuffer + handshakeHeader.length(), m_key3, sizeof(m_key3)); 272 return msg; 273 } 274 275 WebSocketHandshakeRequest WebSocketHandshake::clientHandshakeRequest() const 276 { 277 // Keep the following consistent with clientHandshakeMessage(). 278 // FIXME: do we need to store m_secWebSocketKey1, m_secWebSocketKey2 and 279 // m_key3 in WebSocketHandshakeRequest? 280 WebSocketHandshakeRequest request("GET", m_url); 281 request.addHeaderField("Upgrade", "WebSocket"); 282 request.addHeaderField("Connection", "Upgrade"); 283 request.addHeaderField("Host", hostName(m_url, m_secure)); 284 request.addHeaderField("Origin", clientOrigin()); 285 if (!m_clientProtocol.isEmpty()) 286 request.addHeaderField("Sec-WebSocket-Protocol:", m_clientProtocol); 287 288 KURL url = httpURLForAuthenticationAndCookies(); 289 if (m_context->isDocument()) { 290 Document* document = static_cast<Document*>(m_context); 291 String cookie = cookieRequestHeaderFieldValue(document, url); 292 if (!cookie.isEmpty()) 293 request.addHeaderField("Cookie", cookie); 294 // Set "Cookie2: <cookie>" if cookies 2 exists for url? 295 } 296 297 request.addHeaderField("Sec-WebSocket-Key1", m_secWebSocketKey1); 298 request.addHeaderField("Sec-WebSocket-Key2", m_secWebSocketKey2); 299 request.setKey3(m_key3); 300 301 return request; 302 } 303 304 void WebSocketHandshake::reset() 305 { 306 m_mode = Incomplete; 307 } 308 309 void WebSocketHandshake::clearScriptExecutionContext() 310 { 311 m_context = 0; 312 } 313 314 int WebSocketHandshake::readServerHandshake(const char* header, size_t len) 315 { 316 m_mode = Incomplete; 317 int statusCode; 318 String statusText; 319 int lineLength = readStatusLine(header, len, statusCode, statusText); 320 if (lineLength == -1) 321 return -1; 322 if (statusCode == -1) { 323 m_mode = Failed; 324 return len; 325 } 326 LOG(Network, "response code: %d", statusCode); 327 m_response.setStatusCode(statusCode); 328 m_response.setStatusText(statusText); 329 if (statusCode != 101) { 330 m_mode = Failed; 331 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, makeString("Unexpected response code: ", String::number(statusCode)), 0, clientOrigin(), 0); 332 return len; 333 } 334 m_mode = Normal; 335 if (!strnstr(header, "\r\n\r\n", len)) { 336 // Just hasn't been received fully yet. 337 m_mode = Incomplete; 338 return -1; 339 } 340 const char* p = readHTTPHeaders(header + lineLength, header + len); 341 if (!p) { 342 LOG(Network, "readHTTPHeaders failed"); 343 m_mode = Failed; 344 return len; 345 } 346 if (!checkResponseHeaders()) { 347 LOG(Network, "header process failed"); 348 m_mode = Failed; 349 return p - header; 350 } 351 if (len < static_cast<size_t>(p - header + sizeof(m_expectedChallengeResponse))) { 352 // Just hasn't been received /expected/ yet. 353 m_mode = Incomplete; 354 return -1; 355 } 356 m_response.setChallengeResponse(static_cast<const unsigned char*>(static_cast<const void*>(p))); 357 if (memcmp(p, m_expectedChallengeResponse, sizeof(m_expectedChallengeResponse))) { 358 m_mode = Failed; 359 return (p - header) + sizeof(m_expectedChallengeResponse); 360 } 361 m_mode = Connected; 362 return (p - header) + sizeof(m_expectedChallengeResponse); 363 } 364 365 WebSocketHandshake::Mode WebSocketHandshake::mode() const 366 { 367 return m_mode; 368 } 369 370 String WebSocketHandshake::serverWebSocketOrigin() const 371 { 372 return m_response.headerFields().get("sec-websocket-origin"); 373 } 374 375 String WebSocketHandshake::serverWebSocketLocation() const 376 { 377 return m_response.headerFields().get("sec-websocket-location"); 378 } 379 380 String WebSocketHandshake::serverWebSocketProtocol() const 381 { 382 return m_response.headerFields().get("sec-websocket-protocol"); 383 } 384 385 String WebSocketHandshake::serverSetCookie() const 386 { 387 return m_response.headerFields().get("set-cookie"); 388 } 389 390 String WebSocketHandshake::serverSetCookie2() const 391 { 392 return m_response.headerFields().get("set-cookie2"); 393 } 394 395 String WebSocketHandshake::serverUpgrade() const 396 { 397 return m_response.headerFields().get("upgrade"); 398 } 399 400 String WebSocketHandshake::serverConnection() const 401 { 402 return m_response.headerFields().get("connection"); 403 } 404 405 const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const 406 { 407 return m_response; 408 } 409 410 KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const 411 { 412 KURL url = m_url.copy(); 413 bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http"); 414 ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); 415 return url; 416 } 417 418 // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet. 419 // If the line is malformed or the status code is not a 3-digit number, 420 // statusCode and statusText will be set to -1 and a null string, respectively. 421 int WebSocketHandshake::readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText) 422 { 423 // Arbitrary size limit to prevent the server from sending an unbounded 424 // amount of data with no newlines and forcing us to buffer it all. 425 static const int maximumLength = 1024; 426 427 statusCode = -1; 428 statusText = String(); 429 430 const char* space1 = 0; 431 const char* space2 = 0; 432 const char* p; 433 size_t consumedLength; 434 435 for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) { 436 if (*p == ' ') { 437 if (!space1) 438 space1 = p; 439 else if (!space2) 440 space2 = p; 441 } else if (*p == '\0') { 442 // The caller isn't prepared to deal with null bytes in status 443 // line. WebSockets specification doesn't prohibit this, but HTTP 444 // does, so we'll just treat this as an error. 445 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Status line contains embedded null", 0, clientOrigin(), 0); 446 return p + 1 - header; 447 } else if (*p == '\n') 448 break; 449 } 450 if (consumedLength == headerLength) 451 return -1; // We have not received '\n' yet. 452 453 const char* end = p + 1; 454 if (end - header > maximumLength) { 455 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Status line is too long", 0, clientOrigin(), 0); 456 return maximumLength; 457 } 458 int lineLength = end - header; 459 460 if (!space1 || !space2) { 461 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "No response code found: " + trimConsoleMessage(header, lineLength - 1), 0, clientOrigin(), 0); 462 return lineLength; 463 } 464 465 // The line must end with "\r\n". 466 if (*(end - 2) != '\r') { 467 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Status line does not end with CRLF", 0, clientOrigin(), 0); 468 return lineLength; 469 } 470 471 String statusCodeString(space1 + 1, space2 - space1 - 1); 472 if (statusCodeString.length() != 3) // Status code must consist of three digits. 473 return lineLength; 474 for (int i = 0; i < 3; ++i) 475 if (statusCodeString[i] < '0' || statusCodeString[i] > '9') { 476 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Invalid status code: " + statusCodeString, 0, clientOrigin(), 0); 477 return lineLength; 478 } 479 480 bool ok = false; 481 statusCode = statusCodeString.toInt(&ok); 482 ASSERT(ok); 483 484 statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n". 485 return lineLength; 486 } 487 488 const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end) 489 { 490 m_response.clearHeaderFields(); 491 492 Vector<char> name; 493 Vector<char> value; 494 for (const char* p = start; p < end; p++) { 495 name.clear(); 496 value.clear(); 497 498 for (; p < end; p++) { 499 switch (*p) { 500 case '\r': 501 if (name.isEmpty()) { 502 if (p + 1 < end && *(p + 1) == '\n') 503 return p + 2; 504 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "CR doesn't follow LF at " + trimConsoleMessage(p, end - p), 0, clientOrigin(), 0); 505 return 0; 506 } 507 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected CR in name at " + trimConsoleMessage(name.data(), name.size()), 0, clientOrigin(), 0); 508 return 0; 509 case '\n': 510 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected LF in name at " + trimConsoleMessage(name.data(), name.size()), 0, clientOrigin(), 0); 511 return 0; 512 case ':': 513 break; 514 default: 515 name.append(*p); 516 continue; 517 } 518 if (*p == ':') { 519 ++p; 520 break; 521 } 522 } 523 524 for (; p < end && *p == 0x20; p++) { } 525 526 for (; p < end; p++) { 527 switch (*p) { 528 case '\r': 529 break; 530 case '\n': 531 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected LF in value at " + trimConsoleMessage(value.data(), value.size()), 0, clientOrigin(), 0); 532 return 0; 533 default: 534 value.append(*p); 535 } 536 if (*p == '\r') { 537 ++p; 538 break; 539 } 540 } 541 if (p >= end || *p != '\n') { 542 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "CR doesn't follow LF after value at " + trimConsoleMessage(p, end - p), 0, clientOrigin(), 0); 543 return 0; 544 } 545 AtomicString nameStr = AtomicString::fromUTF8(name.data(), name.size()); 546 String valueStr = String::fromUTF8(value.data(), value.size()); 547 if (nameStr.isNull()) { 548 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "invalid UTF-8 sequence in header name", 0, clientOrigin(), 0); 549 return 0; 550 } 551 if (valueStr.isNull()) { 552 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "invalid UTF-8 sequence in header value", 0, clientOrigin(), 0); 553 return 0; 554 } 555 LOG(Network, "name=%s value=%s", nameStr.string().utf8().data(), valueStr.utf8().data()); 556 m_response.addHeaderField(nameStr, valueStr); 557 } 558 ASSERT_NOT_REACHED(); 559 return 0; 560 } 561 562 bool WebSocketHandshake::checkResponseHeaders() 563 { 564 const String& serverWebSocketLocation = this->serverWebSocketLocation(); 565 const String& serverWebSocketOrigin = this->serverWebSocketOrigin(); 566 const String& serverWebSocketProtocol = this->serverWebSocketProtocol(); 567 const String& serverUpgrade = this->serverUpgrade(); 568 const String& serverConnection = this->serverConnection(); 569 570 if (serverUpgrade.isNull()) { 571 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'Upgrade' header is missing", 0, clientOrigin(), 0); 572 return false; 573 } 574 if (serverConnection.isNull()) { 575 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'Connection' header is missing", 0, clientOrigin(), 0); 576 return false; 577 } 578 if (serverWebSocketOrigin.isNull()) { 579 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'Sec-WebSocket-Origin' header is missing", 0, clientOrigin(), 0); 580 return false; 581 } 582 if (serverWebSocketLocation.isNull()) { 583 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'Sec-WebSocket-Location' header is missing", 0, clientOrigin(), 0); 584 return false; 585 } 586 587 if (!equalIgnoringCase(serverUpgrade, "websocket")) { 588 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'", 0, clientOrigin(), 0); 589 return false; 590 } 591 if (!equalIgnoringCase(serverConnection, "upgrade")) { 592 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'", 0, clientOrigin(), 0); 593 return false; 594 } 595 596 if (clientOrigin() != serverWebSocketOrigin) { 597 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: origin mismatch: " + clientOrigin() + " != " + serverWebSocketOrigin, 0, clientOrigin(), 0); 598 return false; 599 } 600 if (clientLocation() != serverWebSocketLocation) { 601 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: location mismatch: " + clientLocation() + " != " + serverWebSocketLocation, 0, clientOrigin(), 0); 602 return false; 603 } 604 if (!m_clientProtocol.isEmpty() && m_clientProtocol != serverWebSocketProtocol) { 605 m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: protocol mismatch: " + m_clientProtocol + " != " + serverWebSocketProtocol, 0, clientOrigin(), 0); 606 return false; 607 } 608 return true; 609 } 610 611 } // namespace WebCore 612 613 #endif // ENABLE(WEB_SOCKETS) 614