Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / Modules / websockets / WebSocketHandshake.cpp
1 /*
2  * Copyright (C) 2011 Google Inc.  All rights reserved.
3  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
4  * Copyright (C) 2018 Apple Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following disclaimer
14  * in the documentation and/or other materials provided with the
15  * distribution.
16  *     * Neither the name of Google Inc. nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "config.h"
34 #include "WebSocketHandshake.h"
35
36 #include "Cookie.h"
37 #include "CookieJar.h"
38 #include "Document.h"
39 #include "HTTPHeaderMap.h"
40 #include "HTTPHeaderNames.h"
41 #include "HTTPParsers.h"
42 #include "InspectorInstrumentation.h"
43 #include "Logging.h"
44 #include "ResourceRequest.h"
45 #include "ScriptExecutionContext.h"
46 #include "SecurityOrigin.h"
47 #include <wtf/URL.h>
48 #include "WebSocket.h"
49 #include <wtf/ASCIICType.h>
50 #include <wtf/CryptographicallyRandomNumber.h>
51 #include <wtf/MD5.h>
52 #include <wtf/SHA1.h>
53 #include <wtf/StdLibExtras.h>
54 #include <wtf/StringExtras.h>
55 #include <wtf/Vector.h>
56 #include <wtf/text/Base64.h>
57 #include <wtf/text/CString.h>
58 #include <wtf/text/StringBuilder.h>
59 #include <wtf/text/StringView.h>
60 #include <wtf/text/WTFString.h>
61 #include <wtf/unicode/CharacterNames.h>
62
63 namespace WebCore {
64
65 static String resourceName(const URL& url)
66 {
67     StringBuilder name;
68     name.append(url.path());
69     if (name.isEmpty())
70         name.append('/');
71     if (!url.query().isNull()) {
72         name.append('?');
73         name.append(url.query());
74     }
75     String result = name.toString();
76     ASSERT(!result.isEmpty());
77     ASSERT(!result.contains(' '));
78     return result;
79 }
80
81 static String hostName(const URL& url, bool secure)
82 {
83     ASSERT(url.protocolIs("wss") == secure);
84     StringBuilder builder;
85     builder.append(url.host().convertToASCIILowercase());
86     if (url.port() && ((!secure && url.port().value() != 80) || (secure && url.port().value() != 443))) {
87         builder.append(':');
88         builder.appendNumber(url.port().value());
89     }
90     return builder.toString();
91 }
92
93 static const size_t maxInputSampleSize = 128;
94 static String trimInputSample(const char* p, size_t len)
95 {
96     String s = String(p, std::min<size_t>(len, maxInputSampleSize));
97     if (len > maxInputSampleSize)
98         s.append(horizontalEllipsis);
99     return s;
100 }
101
102 static String generateSecWebSocketKey()
103 {
104     static const size_t nonceSize = 16;
105     unsigned char key[nonceSize];
106     cryptographicallyRandomValues(key, nonceSize);
107     return base64Encode(key, nonceSize);
108 }
109
110 String WebSocketHandshake::getExpectedWebSocketAccept(const String& secWebSocketKey)
111 {
112     static const char* const webSocketKeyGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
113     SHA1 sha1;
114     CString keyData = secWebSocketKey.ascii();
115     sha1.addBytes(reinterpret_cast<const uint8_t*>(keyData.data()), keyData.length());
116     sha1.addBytes(reinterpret_cast<const uint8_t*>(webSocketKeyGUID), strlen(webSocketKeyGUID));
117     SHA1::Digest hash;
118     sha1.computeHash(hash);
119     return base64Encode(hash.data(), SHA1::hashSize);
120 }
121
122 WebSocketHandshake::WebSocketHandshake(const URL& url, const String& protocol, Document* document, bool allowCookies)
123     : m_url(url)
124     , m_clientProtocol(protocol)
125     , m_secure(m_url.protocolIs("wss"))
126     , m_document(document)
127     , m_mode(Incomplete)
128     , m_allowCookies(allowCookies)
129 {
130     m_secWebSocketKey = generateSecWebSocketKey();
131     m_expectedAccept = getExpectedWebSocketAccept(m_secWebSocketKey);
132 }
133
134 WebSocketHandshake::~WebSocketHandshake() = default;
135
136 const URL& WebSocketHandshake::url() const
137 {
138     return m_url;
139 }
140
141 void WebSocketHandshake::setURL(const URL& url)
142 {
143     m_url = url.isolatedCopy();
144 }
145
146 // FIXME: Return type should just be String, not const String.
147 const String WebSocketHandshake::host() const
148 {
149     return m_url.host().convertToASCIILowercase();
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_document->securityOrigin().toString();
170 }
171
172 String WebSocketHandshake::clientLocation() const
173 {
174     StringBuilder builder;
175     builder.append(m_secure ? "wss" : "ws");
176     builder.appendLiteral("://");
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.appendLiteral("GET ");
188     builder.append(resourceName(m_url));
189     builder.appendLiteral(" 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     // Note: Cookies are not retrieved in the WebContent process. Instead, a proxy object is
200     // added in the handshake, and is exchanged for actual cookies in the Network process.
201
202     // Add no-cache headers to avoid compatibility issue.
203     // There are some proxies that rewrite "Connection: upgrade"
204     // to "Connection: close" in the response if a request doesn't contain
205     // these headers.
206     fields.append("Pragma: no-cache");
207     fields.append("Cache-Control: no-cache");
208
209     fields.append("Sec-WebSocket-Key: " + m_secWebSocketKey);
210     fields.append("Sec-WebSocket-Version: 13");
211     const String extensionValue = m_extensionDispatcher.createHeaderValue();
212     if (extensionValue.length())
213         fields.append("Sec-WebSocket-Extensions: " + extensionValue);
214
215     // Add a User-Agent header.
216     fields.append("User-Agent: " + m_document->userAgent(m_document->url()));
217
218     // Fields in the handshake are sent by the client in a random order; the
219     // order is not meaningful.  Thus, it's ok to send the order we constructed
220     // the fields.
221
222     for (auto& field : fields) {
223         builder.append(field);
224         builder.appendLiteral("\r\n");
225     }
226
227     builder.appendLiteral("\r\n");
228
229     return builder.toString().utf8();
230 }
231
232 ResourceRequest WebSocketHandshake::clientHandshakeRequest() const
233 {
234     // Keep the following consistent with clientHandshakeMessage().
235     ResourceRequest request(m_url);
236     request.setHTTPMethod("GET");
237
238     request.setHTTPHeaderField(HTTPHeaderName::Connection, "Upgrade");
239     request.setHTTPHeaderField(HTTPHeaderName::Host, hostName(m_url, m_secure));
240     request.setHTTPHeaderField(HTTPHeaderName::Origin, clientOrigin());
241     if (!m_clientProtocol.isEmpty())
242         request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketProtocol, m_clientProtocol);
243
244     URL url = httpURLForAuthenticationAndCookies();
245     if (m_allowCookies && m_document) {
246         RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(InspectorInstrumentation::hasFrontends());
247         String cookie = cookieRequestHeaderFieldValue(*m_document, url);
248         if (!cookie.isEmpty())
249             request.setHTTPHeaderField(HTTPHeaderName::Cookie, cookie);
250     }
251
252     request.setHTTPHeaderField(HTTPHeaderName::Pragma, "no-cache");
253     request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "no-cache");
254
255     request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketKey, m_secWebSocketKey);
256     request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketVersion, "13");
257     const String extensionValue = m_extensionDispatcher.createHeaderValue();
258     if (extensionValue.length())
259         request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketExtensions, extensionValue);
260
261     // Add a User-Agent header.
262     request.setHTTPUserAgent(m_document->userAgent(m_document->url()));
263
264     return request;
265 }
266
267 std::optional<CookieRequestHeaderFieldProxy> WebSocketHandshake::clientHandshakeCookieRequestHeaderFieldProxy() const
268 {
269     if (!m_document || !m_allowCookies)
270         return std::nullopt;
271     return cookieRequestHeaderFieldProxy(*m_document, httpURLForAuthenticationAndCookies());
272 }
273
274 void WebSocketHandshake::reset()
275 {
276     m_mode = Incomplete;
277     m_extensionDispatcher.reset();
278 }
279
280 void WebSocketHandshake::clearDocument()
281 {
282     m_document = nullptr;
283 }
284
285 int WebSocketHandshake::readServerHandshake(const char* header, size_t len)
286 {
287     m_mode = Incomplete;
288     int statusCode;
289     String statusText;
290     int lineLength = readStatusLine(header, len, statusCode, statusText);
291     if (lineLength == -1)
292         return -1;
293     if (statusCode == -1) {
294         m_mode = Failed; // m_failureReason is set inside readStatusLine().
295         return len;
296     }
297     LOG(Network, "WebSocketHandshake %p readServerHandshake() Status code is %d", this, statusCode);
298
299     m_serverHandshakeResponse = ResourceResponse();
300     m_serverHandshakeResponse.setHTTPStatusCode(statusCode);
301     m_serverHandshakeResponse.setHTTPStatusText(statusText);
302
303     if (statusCode != 101) {
304         m_mode = Failed;
305         m_failureReason = makeString("Unexpected response code: ", String::number(statusCode));
306         return len;
307     }
308     m_mode = Normal;
309     if (!strnstr(header, "\r\n\r\n", len)) {
310         // Just hasn't been received fully yet.
311         m_mode = Incomplete;
312         return -1;
313     }
314     const char* p = readHTTPHeaders(header + lineLength, header + len);
315     if (!p) {
316         LOG(Network, "WebSocketHandshake %p readServerHandshake() readHTTPHeaders() failed", this);
317         m_mode = Failed; // m_failureReason is set inside readHTTPHeaders().
318         return len;
319     }
320     if (!checkResponseHeaders()) {
321         LOG(Network, "WebSocketHandshake %p readServerHandshake() checkResponseHeaders() failed", this);
322         m_mode = Failed;
323         return p - header;
324     }
325
326     m_mode = Connected;
327     return p - header;
328 }
329
330 WebSocketHandshake::Mode WebSocketHandshake::mode() const
331 {
332     return m_mode;
333 }
334
335 String WebSocketHandshake::failureReason() const
336 {
337     return m_failureReason;
338 }
339
340 String WebSocketHandshake::serverWebSocketProtocol() const
341 {
342     return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SecWebSocketProtocol);
343 }
344
345 String WebSocketHandshake::serverSetCookie() const
346 {
347     return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SetCookie);
348 }
349
350 String WebSocketHandshake::serverUpgrade() const
351 {
352     return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::Upgrade);
353 }
354
355 String WebSocketHandshake::serverConnection() const
356 {
357     return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::Connection);
358 }
359
360 String WebSocketHandshake::serverWebSocketAccept() const
361 {
362     return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SecWebSocketAccept);
363 }
364
365 String WebSocketHandshake::acceptedExtensions() const
366 {
367     return m_extensionDispatcher.acceptedExtensions();
368 }
369
370 const ResourceResponse& WebSocketHandshake::serverHandshakeResponse() const
371 {
372     return m_serverHandshakeResponse;
373 }
374
375 void WebSocketHandshake::addExtensionProcessor(std::unique_ptr<WebSocketExtensionProcessor> processor)
376 {
377     m_extensionDispatcher.addProcessor(WTFMove(processor));
378 }
379
380 URL WebSocketHandshake::httpURLForAuthenticationAndCookies() const
381 {
382     URL url = m_url.isolatedCopy();
383     bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http");
384     ASSERT_UNUSED(couldSetProtocol, couldSetProtocol);
385     return url;
386 }
387
388 // https://tools.ietf.org/html/rfc6455#section-4.1
389 // "The HTTP version MUST be at least 1.1."
390 static inline bool headerHasValidHTTPVersion(StringView httpStatusLine)
391 {
392     const char* httpVersionStaticPreambleLiteral = "HTTP/";
393     StringView httpVersionStaticPreamble(reinterpret_cast<const LChar*>(httpVersionStaticPreambleLiteral), strlen(httpVersionStaticPreambleLiteral));
394     if (!httpStatusLine.startsWith(httpVersionStaticPreamble))
395         return false;
396
397     // Check that there is a version number which should be at least three characters after "HTTP/"
398     unsigned preambleLength = httpVersionStaticPreamble.length();
399     if (httpStatusLine.length() < preambleLength + 3)
400         return false;
401
402     auto dotPosition = httpStatusLine.find('.', preambleLength);
403     if (dotPosition == notFound)
404         return false;
405
406     StringView majorVersionView = httpStatusLine.substring(preambleLength, dotPosition - preambleLength);
407     bool isValid;
408     int majorVersion = majorVersionView.toIntStrict(isValid);
409     if (!isValid)
410         return false;
411
412     unsigned minorVersionLength;
413     unsigned charactersLeftAfterDotPosition = httpStatusLine.length() - dotPosition;
414     for (minorVersionLength = 1; minorVersionLength < charactersLeftAfterDotPosition; minorVersionLength++) {
415         if (!isASCIIDigit(httpStatusLine[dotPosition + minorVersionLength]))
416             break;
417     }
418     int minorVersion = (httpStatusLine.substring(dotPosition + 1, minorVersionLength)).toIntStrict(isValid);
419     if (!isValid)
420         return false;
421
422     return (majorVersion >= 1 && minorVersion >= 1) || majorVersion >= 2;
423 }
424
425 // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet.
426 // If the line is malformed or the status code is not a 3-digit number,
427 // statusCode and statusText will be set to -1 and a null string, respectively.
428 int WebSocketHandshake::readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText)
429 {
430     // Arbitrary size limit to prevent the server from sending an unbounded
431     // amount of data with no newlines and forcing us to buffer it all.
432     static const int maximumLength = 1024;
433
434     statusCode = -1;
435     statusText = String();
436
437     const char* space1 = nullptr;
438     const char* space2 = nullptr;
439     const char* p;
440     size_t consumedLength;
441
442     for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) {
443         if (*p == ' ') {
444             if (!space1)
445                 space1 = p;
446             else if (!space2)
447                 space2 = p;
448         } else if (*p == '\0') {
449             // The caller isn't prepared to deal with null bytes in status
450             // line. WebSockets specification doesn't prohibit this, but HTTP
451             // does, so we'll just treat this as an error.
452             m_failureReason = "Status line contains embedded null"_s;
453             return p + 1 - header;
454         } else if (!isASCII(*p)) {
455             m_failureReason = "Status line contains non-ASCII character"_s;
456             return p + 1 - header;
457         } else if (*p == '\n')
458             break;
459     }
460     if (consumedLength == headerLength)
461         return -1; // We have not received '\n' yet.
462
463     const char* end = p + 1;
464     int lineLength = end - header;
465     if (lineLength > maximumLength) {
466         m_failureReason = "Status line is too long"_s;
467         return maximumLength;
468     }
469
470     // The line must end with "\r\n".
471     if (lineLength < 2 || *(end - 2) != '\r') {
472         m_failureReason = "Status line does not end with CRLF"_s;
473         return lineLength;
474     }
475
476     if (!space1 || !space2) {
477         m_failureReason = makeString("No response code found: ", trimInputSample(header, lineLength - 2));
478         return lineLength;
479     }
480
481     StringView httpStatusLine(reinterpret_cast<const LChar*>(header), space1 - header);
482     if (!headerHasValidHTTPVersion(httpStatusLine)) {
483         m_failureReason = makeString("Invalid HTTP version string: ", httpStatusLine);
484         return lineLength;
485     }
486
487     StringView statusCodeString(reinterpret_cast<const LChar*>(space1 + 1), space2 - space1 - 1);
488     if (statusCodeString.length() != 3) // Status code must consist of three digits.
489         return lineLength;
490     for (int i = 0; i < 3; ++i)
491         if (!isASCIIDigit(statusCodeString[i])) {
492             m_failureReason = makeString("Invalid status code: ", statusCodeString);
493             return lineLength;
494         }
495
496     bool ok = false;
497     statusCode = statusCodeString.toIntStrict(ok);
498     ASSERT(ok);
499
500     statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n".
501     return lineLength;
502 }
503
504 const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end)
505 {
506     StringView name;
507     String value;
508     bool sawSecWebSocketExtensionsHeaderField = false;
509     bool sawSecWebSocketAcceptHeaderField = false;
510     bool sawSecWebSocketProtocolHeaderField = false;
511     const char* p = start;
512     for (; p < end; p++) {
513         size_t consumedLength = parseHTTPHeader(p, end - p, m_failureReason, name, value);
514         if (!consumedLength)
515             return nullptr;
516         p += consumedLength;
517
518         // Stop once we consumed an empty line.
519         if (name.isEmpty())
520             break;
521
522         HTTPHeaderName headerName;
523         if (!findHTTPHeaderName(name, headerName)) {
524             // Evidence in the wild shows that services make use of custom headers in the handshake
525             m_serverHandshakeResponse.addHTTPHeaderField(name.toString(), value);
526             continue;
527         }
528
529         // https://tools.ietf.org/html/rfc7230#section-3.2.4
530         // "Newly defined header fields SHOULD limit their field values to US-ASCII octets."
531         if ((headerName == HTTPHeaderName::SecWebSocketExtensions
532             || headerName == HTTPHeaderName::SecWebSocketAccept
533             || headerName == HTTPHeaderName::SecWebSocketProtocol)
534             && !value.isAllASCII()) {
535             m_failureReason = makeString(name, " header value should only contain ASCII characters");
536             return nullptr;
537         }
538         
539         if (headerName == HTTPHeaderName::SecWebSocketExtensions) {
540             if (sawSecWebSocketExtensionsHeaderField) {
541                 m_failureReason = "The Sec-WebSocket-Extensions header must not appear more than once in an HTTP response"_s;
542                 return nullptr;
543             }
544             if (!m_extensionDispatcher.processHeaderValue(value)) {
545                 m_failureReason = m_extensionDispatcher.failureReason();
546                 return nullptr;
547             }
548             sawSecWebSocketExtensionsHeaderField = true;
549         } else {
550             if (headerName == HTTPHeaderName::SecWebSocketAccept) {
551                 if (sawSecWebSocketAcceptHeaderField) {
552                     m_failureReason = "The Sec-WebSocket-Accept header must not appear more than once in an HTTP response"_s;
553                     return nullptr;
554                 }
555                 sawSecWebSocketAcceptHeaderField = true;
556             } else if (headerName == HTTPHeaderName::SecWebSocketProtocol) {
557                 if (sawSecWebSocketProtocolHeaderField) {
558                     m_failureReason = "The Sec-WebSocket-Protocol header must not appear more than once in an HTTP response"_s;
559                     return nullptr;
560                 }
561                 sawSecWebSocketProtocolHeaderField = true;
562             }
563
564             m_serverHandshakeResponse.addHTTPHeaderField(headerName, value);
565         }
566     }
567     return p;
568 }
569
570 bool WebSocketHandshake::checkResponseHeaders()
571 {
572     const String& serverWebSocketProtocol = this->serverWebSocketProtocol();
573     const String& serverUpgrade = this->serverUpgrade();
574     const String& serverConnection = this->serverConnection();
575     const String& serverWebSocketAccept = this->serverWebSocketAccept();
576
577     if (serverUpgrade.isNull()) {
578         m_failureReason = "Error during WebSocket handshake: 'Upgrade' header is missing"_s;
579         return false;
580     }
581     if (serverConnection.isNull()) {
582         m_failureReason = "Error during WebSocket handshake: 'Connection' header is missing"_s;
583         return false;
584     }
585     if (serverWebSocketAccept.isNull()) {
586         m_failureReason = "Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing"_s;
587         return false;
588     }
589
590     if (!equalLettersIgnoringASCIICase(serverUpgrade, "websocket")) {
591         m_failureReason = "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'"_s;
592         return false;
593     }
594     if (!equalLettersIgnoringASCIICase(serverConnection, "upgrade")) {
595         m_failureReason = "Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'"_s;
596         return false;
597     }
598
599     if (serverWebSocketAccept != m_expectedAccept) {
600         m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch"_s;
601         return false;
602     }
603     if (!serverWebSocketProtocol.isNull()) {
604         if (m_clientProtocol.isEmpty()) {
605             m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"_s;
606             return false;
607         }
608         Vector<String> result = m_clientProtocol.split(WebSocket::subprotocolSeparator());
609         if (!result.contains(serverWebSocketProtocol)) {
610             m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"_s;
611             return false;
612         }
613     }
614     return true;
615 }
616
617 } // namespace WebCore