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 "HTTPHeaderMap.h" |
39 | #include "HTTPHeaderNames.h" |
40 | #include "HTTPParsers.h" |
41 | #include "InspectorInstrumentation.h" |
42 | #include "Logging.h" |
43 | #include "ResourceRequest.h" |
44 | #include "ScriptExecutionContext.h" |
45 | #include "SecurityOrigin.h" |
46 | #include <wtf/URL.h> |
47 | #include "WebSocket.h" |
48 | #include <wtf/ASCIICType.h> |
49 | #include <wtf/CryptographicallyRandomNumber.h> |
50 | #include <wtf/MD5.h> |
51 | #include <wtf/SHA1.h> |
52 | #include <wtf/StdLibExtras.h> |
53 | #include <wtf/StringExtras.h> |
54 | #include <wtf/Vector.h> |
55 | #include <wtf/text/Base64.h> |
56 | #include <wtf/text/CString.h> |
57 | #include <wtf/text/StringBuilder.h> |
58 | #include <wtf/text/StringView.h> |
59 | #include <wtf/text/WTFString.h> |
60 | #include <wtf/unicode/CharacterNames.h> |
61 | |
62 | namespace WebCore { |
63 | |
64 | static String resourceName(const URL& url) |
65 | { |
66 | StringBuilder name; |
67 | name.append(url.path()); |
68 | if (name.isEmpty()) |
69 | name.append('/'); |
70 | if (!url.query().isNull()) { |
71 | name.append('?'); |
72 | name.append(url.query()); |
73 | } |
74 | String result = name.toString(); |
75 | ASSERT(!result.isEmpty()); |
76 | ASSERT(!result.contains(' ')); |
77 | return result; |
78 | } |
79 | |
80 | static String hostName(const URL& url, bool secure) |
81 | { |
82 | ASSERT(url.protocolIs("wss" ) == secure); |
83 | StringBuilder builder; |
84 | builder.append(url.host().convertToASCIILowercase()); |
85 | if (url.port() && ((!secure && url.port().value() != 80) || (secure && url.port().value() != 443))) { |
86 | builder.append(':'); |
87 | builder.appendNumber(url.port().value()); |
88 | } |
89 | return builder.toString(); |
90 | } |
91 | |
92 | static const size_t maxInputSampleSize = 128; |
93 | static String trimInputSample(const char* p, size_t len) |
94 | { |
95 | String s = String(p, std::min<size_t>(len, maxInputSampleSize)); |
96 | if (len > maxInputSampleSize) |
97 | s.append(horizontalEllipsis); |
98 | return s; |
99 | } |
100 | |
101 | static String generateSecWebSocketKey() |
102 | { |
103 | static const size_t nonceSize = 16; |
104 | unsigned char key[nonceSize]; |
105 | cryptographicallyRandomValues(key, nonceSize); |
106 | return base64Encode(key, nonceSize); |
107 | } |
108 | |
109 | String WebSocketHandshake::getExpectedWebSocketAccept(const String& secWebSocketKey) |
110 | { |
111 | static const char* const webSocketKeyGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ; |
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 | SHA1::Digest hash; |
117 | sha1.computeHash(hash); |
118 | return base64Encode(hash.data(), SHA1::hashSize); |
119 | } |
120 | |
121 | WebSocketHandshake::WebSocketHandshake(const URL& url, const String& protocol, const String& userAgent, const String& clientOrigin, bool allowCookies) |
122 | : m_url(url) |
123 | , m_clientProtocol(protocol) |
124 | , m_secure(m_url.protocolIs("wss" )) |
125 | , m_mode(Incomplete) |
126 | , m_userAgent(userAgent) |
127 | , m_clientOrigin(clientOrigin) |
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::clientLocation() const |
168 | { |
169 | StringBuilder builder; |
170 | builder.append(m_secure ? "wss" : "ws" ); |
171 | builder.appendLiteral("://" ); |
172 | builder.append(hostName(m_url, m_secure)); |
173 | builder.append(resourceName(m_url)); |
174 | return builder.toString(); |
175 | } |
176 | |
177 | CString WebSocketHandshake::clientHandshakeMessage() const |
178 | { |
179 | // Keep the following consistent with clientHandshakeRequest(). |
180 | StringBuilder builder; |
181 | |
182 | builder.appendLiteral("GET " ); |
183 | builder.append(resourceName(m_url)); |
184 | builder.appendLiteral(" HTTP/1.1\r\n" ); |
185 | |
186 | Vector<String> fields; |
187 | fields.append("Upgrade: websocket" ); |
188 | fields.append("Connection: Upgrade" ); |
189 | fields.append("Host: " + hostName(m_url, m_secure)); |
190 | fields.append("Origin: " + m_clientOrigin); |
191 | if (!m_clientProtocol.isEmpty()) |
192 | fields.append("Sec-WebSocket-Protocol: " + m_clientProtocol); |
193 | |
194 | // Note: Cookies are not retrieved in the WebContent process. Instead, a proxy object is |
195 | // added in the handshake, and is exchanged for actual cookies in the Network process. |
196 | |
197 | // Add no-cache headers to avoid compatibility issue. |
198 | // There are some proxies that rewrite "Connection: upgrade" |
199 | // to "Connection: close" in the response if a request doesn't contain |
200 | // these headers. |
201 | fields.append("Pragma: no-cache" ); |
202 | fields.append("Cache-Control: no-cache" ); |
203 | |
204 | fields.append("Sec-WebSocket-Key: " + m_secWebSocketKey); |
205 | fields.append("Sec-WebSocket-Version: 13" ); |
206 | const String extensionValue = m_extensionDispatcher.createHeaderValue(); |
207 | if (extensionValue.length()) |
208 | fields.append("Sec-WebSocket-Extensions: " + extensionValue); |
209 | |
210 | // Add a User-Agent header. |
211 | fields.append(makeString("User-Agent: " , m_userAgent)); |
212 | |
213 | // Fields in the handshake are sent by the client in a random order; the |
214 | // order is not meaningful. Thus, it's ok to send the order we constructed |
215 | // the fields. |
216 | |
217 | for (auto& field : fields) { |
218 | builder.append(field); |
219 | builder.appendLiteral("\r\n" ); |
220 | } |
221 | |
222 | builder.appendLiteral("\r\n" ); |
223 | |
224 | return builder.toString().utf8(); |
225 | } |
226 | |
227 | ResourceRequest WebSocketHandshake::clientHandshakeRequest(Function<String(const URL&)>&& ) const |
228 | { |
229 | // Keep the following consistent with clientHandshakeMessage(). |
230 | ResourceRequest request(m_url); |
231 | request.setHTTPMethod("GET" ); |
232 | |
233 | request.setHTTPHeaderField(HTTPHeaderName::Connection, "Upgrade" ); |
234 | request.setHTTPHeaderField(HTTPHeaderName::Host, hostName(m_url, m_secure)); |
235 | request.setHTTPHeaderField(HTTPHeaderName::Origin, m_clientOrigin); |
236 | if (!m_clientProtocol.isEmpty()) |
237 | request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketProtocol, m_clientProtocol); |
238 | |
239 | URL url = httpURLForAuthenticationAndCookies(); |
240 | if (m_allowCookies) { |
241 | String cookie = cookieRequestHeaderFieldValue(url); |
242 | if (!cookie.isEmpty()) |
243 | request.setHTTPHeaderField(HTTPHeaderName::Cookie, cookie); |
244 | } |
245 | |
246 | request.setHTTPHeaderField(HTTPHeaderName::Pragma, "no-cache" ); |
247 | request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "no-cache" ); |
248 | |
249 | request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketKey, m_secWebSocketKey); |
250 | request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketVersion, "13" ); |
251 | const String extensionValue = m_extensionDispatcher.createHeaderValue(); |
252 | if (extensionValue.length()) |
253 | request.setHTTPHeaderField(HTTPHeaderName::SecWebSocketExtensions, extensionValue); |
254 | |
255 | // Add a User-Agent header. |
256 | request.setHTTPUserAgent(m_userAgent); |
257 | |
258 | return request; |
259 | } |
260 | |
261 | void WebSocketHandshake::reset() |
262 | { |
263 | m_mode = Incomplete; |
264 | m_extensionDispatcher.reset(); |
265 | } |
266 | |
267 | int WebSocketHandshake::readServerHandshake(const char* , size_t len) |
268 | { |
269 | m_mode = Incomplete; |
270 | int statusCode; |
271 | String statusText; |
272 | int lineLength = readStatusLine(header, len, statusCode, statusText); |
273 | if (lineLength == -1) |
274 | return -1; |
275 | if (statusCode == -1) { |
276 | m_mode = Failed; // m_failureReason is set inside readStatusLine(). |
277 | return len; |
278 | } |
279 | LOG(Network, "WebSocketHandshake %p readServerHandshake() Status code is %d" , this, statusCode); |
280 | |
281 | m_serverHandshakeResponse = ResourceResponse(); |
282 | m_serverHandshakeResponse.setHTTPStatusCode(statusCode); |
283 | m_serverHandshakeResponse.setHTTPStatusText(statusText); |
284 | |
285 | if (statusCode != 101) { |
286 | m_mode = Failed; |
287 | m_failureReason = makeString("Unexpected response code: " , statusCode); |
288 | return len; |
289 | } |
290 | m_mode = Normal; |
291 | if (!strnstr(header, "\r\n\r\n" , len)) { |
292 | // Just hasn't been received fully yet. |
293 | m_mode = Incomplete; |
294 | return -1; |
295 | } |
296 | const char* p = readHTTPHeaders(header + lineLength, header + len); |
297 | if (!p) { |
298 | LOG(Network, "WebSocketHandshake %p readServerHandshake() readHTTPHeaders() failed" , this); |
299 | m_mode = Failed; // m_failureReason is set inside readHTTPHeaders(). |
300 | return len; |
301 | } |
302 | if (!checkResponseHeaders()) { |
303 | LOG(Network, "WebSocketHandshake %p readServerHandshake() checkResponseHeaders() failed" , this); |
304 | m_mode = Failed; |
305 | return p - header; |
306 | } |
307 | |
308 | m_mode = Connected; |
309 | return p - header; |
310 | } |
311 | |
312 | WebSocketHandshake::Mode WebSocketHandshake::mode() const |
313 | { |
314 | return m_mode; |
315 | } |
316 | |
317 | String WebSocketHandshake::failureReason() const |
318 | { |
319 | return m_failureReason; |
320 | } |
321 | |
322 | String WebSocketHandshake::serverWebSocketProtocol() const |
323 | { |
324 | return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SecWebSocketProtocol); |
325 | } |
326 | |
327 | String WebSocketHandshake::serverSetCookie() const |
328 | { |
329 | return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SetCookie); |
330 | } |
331 | |
332 | String WebSocketHandshake::serverUpgrade() const |
333 | { |
334 | return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::Upgrade); |
335 | } |
336 | |
337 | String WebSocketHandshake::serverConnection() const |
338 | { |
339 | return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::Connection); |
340 | } |
341 | |
342 | String WebSocketHandshake::serverWebSocketAccept() const |
343 | { |
344 | return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SecWebSocketAccept); |
345 | } |
346 | |
347 | String WebSocketHandshake::acceptedExtensions() const |
348 | { |
349 | return m_extensionDispatcher.acceptedExtensions(); |
350 | } |
351 | |
352 | const ResourceResponse& WebSocketHandshake::serverHandshakeResponse() const |
353 | { |
354 | return m_serverHandshakeResponse; |
355 | } |
356 | |
357 | void WebSocketHandshake::addExtensionProcessor(std::unique_ptr<WebSocketExtensionProcessor> processor) |
358 | { |
359 | m_extensionDispatcher.addProcessor(WTFMove(processor)); |
360 | } |
361 | |
362 | URL WebSocketHandshake::httpURLForAuthenticationAndCookies() const |
363 | { |
364 | URL url = m_url.isolatedCopy(); |
365 | bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http" ); |
366 | ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); |
367 | return url; |
368 | } |
369 | |
370 | // https://tools.ietf.org/html/rfc6455#section-4.1 |
371 | // "The HTTP version MUST be at least 1.1." |
372 | static inline bool (StringView httpStatusLine) |
373 | { |
374 | const char* httpVersionStaticPreambleLiteral = "HTTP/" ; |
375 | StringView httpVersionStaticPreamble(reinterpret_cast<const LChar*>(httpVersionStaticPreambleLiteral), strlen(httpVersionStaticPreambleLiteral)); |
376 | if (!httpStatusLine.startsWith(httpVersionStaticPreamble)) |
377 | return false; |
378 | |
379 | // Check that there is a version number which should be at least three characters after "HTTP/" |
380 | unsigned preambleLength = httpVersionStaticPreamble.length(); |
381 | if (httpStatusLine.length() < preambleLength + 3) |
382 | return false; |
383 | |
384 | auto dotPosition = httpStatusLine.find('.', preambleLength); |
385 | if (dotPosition == notFound) |
386 | return false; |
387 | |
388 | StringView majorVersionView = httpStatusLine.substring(preambleLength, dotPosition - preambleLength); |
389 | bool isValid; |
390 | int majorVersion = majorVersionView.toIntStrict(isValid); |
391 | if (!isValid) |
392 | return false; |
393 | |
394 | unsigned minorVersionLength; |
395 | unsigned charactersLeftAfterDotPosition = httpStatusLine.length() - dotPosition; |
396 | for (minorVersionLength = 1; minorVersionLength < charactersLeftAfterDotPosition; minorVersionLength++) { |
397 | if (!isASCIIDigit(httpStatusLine[dotPosition + minorVersionLength])) |
398 | break; |
399 | } |
400 | int minorVersion = (httpStatusLine.substring(dotPosition + 1, minorVersionLength)).toIntStrict(isValid); |
401 | if (!isValid) |
402 | return false; |
403 | |
404 | return (majorVersion >= 1 && minorVersion >= 1) || majorVersion >= 2; |
405 | } |
406 | |
407 | // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet. |
408 | // If the line is malformed or the status code is not a 3-digit number, |
409 | // statusCode and statusText will be set to -1 and a null string, respectively. |
410 | int WebSocketHandshake::readStatusLine(const char* , size_t , int& statusCode, String& statusText) |
411 | { |
412 | // Arbitrary size limit to prevent the server from sending an unbounded |
413 | // amount of data with no newlines and forcing us to buffer it all. |
414 | static const int maximumLength = 1024; |
415 | |
416 | statusCode = -1; |
417 | statusText = String(); |
418 | |
419 | const char* space1 = nullptr; |
420 | const char* space2 = nullptr; |
421 | const char* p; |
422 | size_t consumedLength; |
423 | |
424 | for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) { |
425 | if (*p == ' ') { |
426 | if (!space1) |
427 | space1 = p; |
428 | else if (!space2) |
429 | space2 = p; |
430 | } else if (*p == '\0') { |
431 | // The caller isn't prepared to deal with null bytes in status |
432 | // line. WebSockets specification doesn't prohibit this, but HTTP |
433 | // does, so we'll just treat this as an error. |
434 | m_failureReason = "Status line contains embedded null"_s ; |
435 | return p + 1 - header; |
436 | } else if (!isASCII(*p)) { |
437 | m_failureReason = "Status line contains non-ASCII character"_s ; |
438 | return p + 1 - header; |
439 | } else if (*p == '\n') |
440 | break; |
441 | } |
442 | if (consumedLength == headerLength) |
443 | return -1; // We have not received '\n' yet. |
444 | |
445 | const char* end = p + 1; |
446 | int lineLength = end - header; |
447 | if (lineLength > maximumLength) { |
448 | m_failureReason = "Status line is too long"_s ; |
449 | return maximumLength; |
450 | } |
451 | |
452 | // The line must end with "\r\n". |
453 | if (lineLength < 2 || *(end - 2) != '\r') { |
454 | m_failureReason = "Status line does not end with CRLF"_s ; |
455 | return lineLength; |
456 | } |
457 | |
458 | if (!space1 || !space2) { |
459 | m_failureReason = makeString("No response code found: " , trimInputSample(header, lineLength - 2)); |
460 | return lineLength; |
461 | } |
462 | |
463 | StringView httpStatusLine(reinterpret_cast<const LChar*>(header), space1 - header); |
464 | if (!headerHasValidHTTPVersion(httpStatusLine)) { |
465 | m_failureReason = makeString("Invalid HTTP version string: " , httpStatusLine); |
466 | return lineLength; |
467 | } |
468 | |
469 | StringView statusCodeString(reinterpret_cast<const LChar*>(space1 + 1), space2 - space1 - 1); |
470 | if (statusCodeString.length() != 3) // Status code must consist of three digits. |
471 | return lineLength; |
472 | for (int i = 0; i < 3; ++i) |
473 | if (!isASCIIDigit(statusCodeString[i])) { |
474 | m_failureReason = makeString("Invalid status code: " , statusCodeString); |
475 | return lineLength; |
476 | } |
477 | |
478 | bool ok = false; |
479 | statusCode = statusCodeString.toIntStrict(ok); |
480 | ASSERT(ok); |
481 | |
482 | statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n". |
483 | return lineLength; |
484 | } |
485 | |
486 | const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end) |
487 | { |
488 | StringView name; |
489 | String value; |
490 | bool = false; |
491 | bool = false; |
492 | bool = false; |
493 | const char* p = start; |
494 | for (; p < end; p++) { |
495 | size_t consumedLength = parseHTTPHeader(p, end - p, m_failureReason, name, value); |
496 | if (!consumedLength) |
497 | return nullptr; |
498 | p += consumedLength; |
499 | |
500 | // Stop once we consumed an empty line. |
501 | if (name.isEmpty()) |
502 | break; |
503 | |
504 | HTTPHeaderName ; |
505 | if (!findHTTPHeaderName(name, headerName)) { |
506 | // Evidence in the wild shows that services make use of custom headers in the handshake |
507 | m_serverHandshakeResponse.addHTTPHeaderField(name.toString(), value); |
508 | continue; |
509 | } |
510 | |
511 | // https://tools.ietf.org/html/rfc7230#section-3.2.4 |
512 | // "Newly defined header fields SHOULD limit their field values to US-ASCII octets." |
513 | if ((headerName == HTTPHeaderName::SecWebSocketExtensions |
514 | || headerName == HTTPHeaderName::SecWebSocketAccept |
515 | || headerName == HTTPHeaderName::SecWebSocketProtocol) |
516 | && !value.isAllASCII()) { |
517 | m_failureReason = makeString(name, " header value should only contain ASCII characters" ); |
518 | return nullptr; |
519 | } |
520 | |
521 | if (headerName == HTTPHeaderName::SecWebSocketExtensions) { |
522 | if (sawSecWebSocketExtensionsHeaderField) { |
523 | m_failureReason = "The Sec-WebSocket-Extensions header must not appear more than once in an HTTP response"_s ; |
524 | return nullptr; |
525 | } |
526 | if (!m_extensionDispatcher.processHeaderValue(value)) { |
527 | m_failureReason = m_extensionDispatcher.failureReason(); |
528 | return nullptr; |
529 | } |
530 | sawSecWebSocketExtensionsHeaderField = true; |
531 | } else { |
532 | if (headerName == HTTPHeaderName::SecWebSocketAccept) { |
533 | if (sawSecWebSocketAcceptHeaderField) { |
534 | m_failureReason = "The Sec-WebSocket-Accept header must not appear more than once in an HTTP response"_s ; |
535 | return nullptr; |
536 | } |
537 | sawSecWebSocketAcceptHeaderField = true; |
538 | } else if (headerName == HTTPHeaderName::SecWebSocketProtocol) { |
539 | if (sawSecWebSocketProtocolHeaderField) { |
540 | m_failureReason = "The Sec-WebSocket-Protocol header must not appear more than once in an HTTP response"_s ; |
541 | return nullptr; |
542 | } |
543 | sawSecWebSocketProtocolHeaderField = true; |
544 | } |
545 | |
546 | m_serverHandshakeResponse.addHTTPHeaderField(headerName, value); |
547 | } |
548 | } |
549 | return p; |
550 | } |
551 | |
552 | bool WebSocketHandshake::checkResponseHeaders() |
553 | { |
554 | const String& serverWebSocketProtocol = this->serverWebSocketProtocol(); |
555 | const String& serverUpgrade = this->serverUpgrade(); |
556 | const String& serverConnection = this->serverConnection(); |
557 | const String& serverWebSocketAccept = this->serverWebSocketAccept(); |
558 | |
559 | if (serverUpgrade.isNull()) { |
560 | m_failureReason = "Error during WebSocket handshake: 'Upgrade' header is missing"_s ; |
561 | return false; |
562 | } |
563 | if (serverConnection.isNull()) { |
564 | m_failureReason = "Error during WebSocket handshake: 'Connection' header is missing"_s ; |
565 | return false; |
566 | } |
567 | if (serverWebSocketAccept.isNull()) { |
568 | m_failureReason = "Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing"_s ; |
569 | return false; |
570 | } |
571 | |
572 | if (!equalLettersIgnoringASCIICase(serverUpgrade, "websocket" )) { |
573 | m_failureReason = "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'"_s ; |
574 | return false; |
575 | } |
576 | if (!equalLettersIgnoringASCIICase(serverConnection, "upgrade" )) { |
577 | m_failureReason = "Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'"_s ; |
578 | return false; |
579 | } |
580 | |
581 | if (serverWebSocketAccept != m_expectedAccept) { |
582 | m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch"_s ; |
583 | return false; |
584 | } |
585 | if (!serverWebSocketProtocol.isNull()) { |
586 | if (m_clientProtocol.isEmpty()) { |
587 | m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"_s ; |
588 | return false; |
589 | } |
590 | Vector<String> result = m_clientProtocol.split(WebSocket::subprotocolSeparator()); |
591 | if (!result.contains(serverWebSocketProtocol)) { |
592 | m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"_s ; |
593 | return false; |
594 | } |
595 | } |
596 | return true; |
597 | } |
598 | |
599 | } // namespace WebCore |
600 | |