d1da443e169c347a61a364b8216739c08d133a1d
[WebKit-https.git] / WebCore / websockets / WebSocketHandshake.cpp
1 /*
2  * Copyright (C) 2009 Google Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32
33 #if ENABLE(WEB_SOCKETS)
34
35 #include "WebSocketHandshake.h"
36
37 #include "AtomicString.h"
38 #include "CString.h"
39 #include "CookieJar.h"
40 #include "Document.h"
41 #include "HTTPHeaderMap.h"
42 #include "KURL.h"
43 #include "Logging.h"
44 #include "ScriptExecutionContext.h"
45 #include "SecurityOrigin.h"
46 #include "StringBuilder.h"
47 #include <wtf/StringExtras.h>
48 #include <wtf/Vector.h>
49
50 namespace WebCore {
51
52 const char webSocketServerHandshakeHeader[] = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n";
53 const char webSocketUpgradeHeader[] = "Upgrade: WebSocket\r\n";
54 const char webSocketConnectionHeader[] = "Connection: Upgrade\r\n";
55
56 static String extractResponseCode(const char* header, int len)
57 {
58     const char* space1 = 0;
59     const char* space2 = 0;
60     const char* p;
61     for (p = header; p - header < len; p++) {
62         if (*p == ' ') {
63             if (!space1)
64                 space1 = p;
65             else if (!space2)
66                 space2 = p;
67         } else if (*p == '\n')
68             break;
69     }
70     if (p - header == len)
71         return String();
72     if (!space1 || !space2)
73         return "";
74     return String(space1 + 1, space2 - space1 - 1);
75 }
76
77 static String resourceName(const KURL& url)
78 {
79     if (url.query().isNull())
80         return url.path();
81     return url.path() + "?" + url.query();
82 }
83
84 WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, ScriptExecutionContext* context)
85     : m_url(url)
86     , m_clientProtocol(protocol)
87     , m_secure(m_url.protocolIs("wss"))
88     , m_context(context)
89     , m_mode(Incomplete)
90 {
91 }
92
93 WebSocketHandshake::~WebSocketHandshake()
94 {
95 }
96
97 const KURL& WebSocketHandshake::url() const
98 {
99     return m_url;
100 }
101
102 void WebSocketHandshake::setURL(const KURL& url)
103 {
104     m_url = url.copy();
105 }
106
107 const String WebSocketHandshake::host() const
108 {
109     return m_url.host().lower();
110 }
111
112 const String& WebSocketHandshake::clientProtocol() const
113 {
114     return m_clientProtocol;
115 }
116
117 void WebSocketHandshake::setClientProtocol(const String& protocol)
118 {
119     m_clientProtocol = protocol;
120 }
121
122 bool WebSocketHandshake::secure() const
123 {
124     return m_secure;
125 }
126
127 void WebSocketHandshake::setSecure(bool secure)
128 {
129     m_secure = secure;
130 }
131
132 String WebSocketHandshake::clientOrigin() const
133 {
134     return m_context->securityOrigin()->toString();
135 }
136
137 String WebSocketHandshake::clientLocation() const
138 {
139     StringBuilder builder;
140     builder.append(m_secure ? "wss" : "ws");
141     builder.append("://");
142     builder.append(m_url.host().lower());
143     if (m_url.port()) {
144         if ((!m_secure && m_url.port() != 80) || (m_secure && m_url.port() != 443)) {
145             builder.append(":");
146             builder.append(String::number(m_url.port()));
147         }
148     }
149     builder.append(resourceName(m_url));
150     return builder.toString();
151 }
152
153 CString WebSocketHandshake::clientHandshakeMessage() const
154 {
155     StringBuilder builder;
156
157     builder.append("GET ");
158     builder.append(resourceName(m_url));
159     builder.append(" HTTP/1.1\r\n");
160     builder.append("Upgrade: WebSocket\r\n");
161     builder.append("Connection: Upgrade\r\n");
162     builder.append("Host: ");
163     builder.append(m_url.host().lower());
164     if (m_url.port()) {
165         if ((!m_secure && m_url.port() != 80) || (m_secure && m_url.port() != 443)) {
166             builder.append(":");
167             builder.append(String::number(m_url.port()));
168         }
169     }
170     builder.append("\r\n");
171     builder.append("Origin: ");
172     builder.append(clientOrigin());
173     builder.append("\r\n");
174     if (!m_clientProtocol.isEmpty()) {
175         builder.append("WebSocket-Protocol: ");
176         builder.append(m_clientProtocol);
177         builder.append("\r\n");
178     }
179     KURL url = httpURLForAuthenticationAndCookies();
180     // FIXME: set authentication information or cookies for url.
181     // Set "Authorization: <credentials>" if authentication information exists for url.
182     if (m_context->isDocument()) {
183         Document* document = static_cast<Document*>(m_context);
184         String cookie = cookies(document, url);
185         if (!cookie.isEmpty()) {
186             builder.append("Cookie: ");
187             builder.append(cookie);
188             builder.append("\r\n");
189         }
190         // Set "Cookie2: <cookie>" if cookies 2 exists for url?
191     }
192     builder.append("\r\n");
193     return builder.toString().utf8();
194 }
195
196 void WebSocketHandshake::reset()
197 {
198     m_mode = Incomplete;
199
200     m_wsOrigin = String();
201     m_wsLocation = String();
202     m_wsProtocol = String();
203     m_setCookie = String();
204     m_setCookie2 = String();
205 }
206
207 int WebSocketHandshake::readServerHandshake(const char* header, size_t len)
208 {
209     m_mode = Incomplete;
210     if (len < sizeof(webSocketServerHandshakeHeader) - 1) {
211         // Just hasn't been received fully yet.
212         return -1;
213     }
214     if (!memcmp(header, webSocketServerHandshakeHeader, sizeof(webSocketServerHandshakeHeader) - 1))
215         m_mode = Normal;
216     else {
217         const String& code = extractResponseCode(header, len);
218         if (code.isNull()) {
219             LOG(Network, "short server handshake: %s", header);
220             return -1;
221         }
222         if (code.isEmpty()) {
223             LOG(Network, "no response code found: %s", header);
224             return len;
225         }
226         LOG(Network, "response code: %s", code.utf8().data());
227         if (code == "401") {
228             LOG(Network, "Authentication required");
229             return len;
230         } else {
231             LOG(Network, "Mismatch server handshake: %s", header);
232             return len;
233         }
234     }
235     const char* p = header + sizeof(webSocketServerHandshakeHeader) - 1;
236     const char* end = header + len + 1;
237
238     if (m_mode == Normal) {
239         size_t headerSize = end - p;
240         if (headerSize < sizeof(webSocketUpgradeHeader) - 1)
241             return 0;
242         if (memcmp(p, webSocketUpgradeHeader, sizeof(webSocketUpgradeHeader) - 1)) {
243             LOG(Network, "Bad upgrade header: %s", p);
244             return p - header + sizeof(webSocketUpgradeHeader) - 1;
245         }
246         p += sizeof(webSocketUpgradeHeader) - 1;
247
248         headerSize = end - p;
249         if (headerSize < sizeof(webSocketConnectionHeader) - 1)
250             return -1;
251         if (memcmp(p, webSocketConnectionHeader, sizeof(webSocketConnectionHeader) - 1)) {
252             LOG(Network, "Bad connection header: %s", p);
253             return p - header + sizeof(webSocketConnectionHeader) - 1;
254         }
255         p += sizeof(webSocketConnectionHeader) - 1;
256     }
257
258     if (!strnstr(p, "\r\n\r\n", end - p)) {
259         // Just hasn't been received fully yet.
260         return -1;
261     }
262     HTTPHeaderMap headers;
263     p = readHTTPHeaders(p, end, &headers);
264     if (!p) {
265         LOG(Network, "readHTTPHeaders failed");
266         m_mode = Failed;
267         return len;
268     }
269     if (!processHeaders(headers)) {
270         LOG(Network, "header process failed");
271         m_mode = Failed;
272         return p - header;
273     }
274     switch (m_mode) {
275     case Normal:
276         checkResponseHeaders();
277         break;
278     default:
279         m_mode = Failed;
280         break;
281     }
282     return p - header;
283 }
284
285 WebSocketHandshake::Mode WebSocketHandshake::mode() const
286 {
287     return m_mode;
288 }
289
290 const String& WebSocketHandshake::serverWebSocketOrigin() const
291 {
292     return m_wsOrigin;
293 }
294
295 void WebSocketHandshake::setServerWebSocketOrigin(const String& webSocketOrigin)
296 {
297     m_wsOrigin = webSocketOrigin;
298 }
299
300 const String& WebSocketHandshake::serverWebSocketLocation() const
301 {
302     return m_wsLocation;
303 }
304
305 void WebSocketHandshake::setServerWebSocketLocation(const String& webSocketLocation)
306 {
307     m_wsLocation = webSocketLocation;
308 }
309
310 const String& WebSocketHandshake::serverWebSocketProtocol() const
311 {
312     return m_wsProtocol;
313 }
314
315 void WebSocketHandshake::setServerWebSocketProtocol(const String& webSocketProtocol)
316 {
317     m_wsProtocol = webSocketProtocol;
318 }
319
320 const String& WebSocketHandshake::serverSetCookie() const
321 {
322     return m_setCookie;
323 }
324
325 void WebSocketHandshake::setServerSetCookie(const String& setCookie)
326 {
327     m_setCookie = setCookie;
328 }
329
330 const String& WebSocketHandshake::serverSetCookie2() const
331 {
332     return m_setCookie2;
333 }
334
335 void WebSocketHandshake::setServerSetCookie2(const String& setCookie2)
336 {
337     m_setCookie2 = setCookie2;
338 }
339
340 KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const
341 {
342     KURL url = m_url.copy();
343     url.setProtocol(m_secure ? "https" : "http");
344     return url;
345 }
346
347 const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end, HTTPHeaderMap* headers)
348 {
349     Vector<char> name;
350     Vector<char> value;
351     for (const char* p = start; p < end; p++) {
352         name.clear();
353         value.clear();
354
355         for (; p < end; p++) {
356             switch (*p) {
357             case '\r':
358                 if (name.isEmpty()) {
359                     if (p + 1 < end && *(p + 1) == '\n')
360                         return p + 2;
361                     LOG(Network, "CR doesn't follow LF p=%p end=%p", p, end);
362                     return 0;
363                 }
364                 LOG(Network, "Unexpected CR in name");
365                 return 0;
366             case '\n':
367                 LOG(Network, "Unexpected LF in name");
368                 return 0;
369             case ':':
370                 break;
371             default:
372                 if (*p >= 0x41 && *p <= 0x5a)
373                     name.append(*p + 0x20);
374                 else
375                     name.append(*p);
376                 continue;
377             }
378             if (*p == ':') {
379                 ++p;
380                 break;
381             }
382         }
383
384         for (; p < end && *p == 0x20; p++) { }
385
386         for (; p < end; p++) {
387             switch (*p) {
388             case '\r':
389                 break;
390             case '\n':
391                 LOG(Network, "Unexpected LF in value");
392                 return 0;
393             default:
394                 value.append(*p);
395             }
396             if (*p == '\r') {
397                 ++p;
398                 break;
399             }
400         }
401         if (p >= end || *p != '\n') {
402             LOG(Network, "CR doesn't follow LF after value p=%p end=%p", p, end);
403             return 0;
404         }
405         AtomicString nameStr(String::fromUTF8(name.data(), name.size()));
406         String valueStr = String::fromUTF8(value.data(), value.size());
407         LOG(Network, "name=%s value=%s", nameStr.string().utf8().data(), valueStr.utf8().data());
408         headers->add(nameStr, valueStr);
409     }
410     ASSERT_NOT_REACHED();
411     return 0;
412 }
413
414 bool WebSocketHandshake::processHeaders(const HTTPHeaderMap& headers)
415 {
416     for (HTTPHeaderMap::const_iterator it = headers.begin(); it != headers.end(); ++it) {
417         switch (m_mode) {
418         case Normal:
419             if (it->first == "websocket-origin")
420                 m_wsOrigin = it->second;
421             else if (it->first == "websocket-location")
422                 m_wsLocation = it->second;
423             else if (it->first == "websocket-protocol")
424                 m_wsProtocol = it->second;
425             else if (it->first == "set-cookie")
426                 m_setCookie = it->second;
427             else if (it->first == "set-cookie2")
428                 m_setCookie2 = it->second;
429             continue;
430         case Incomplete:
431         case Failed:
432         case Connected:
433             ASSERT_NOT_REACHED();
434         }
435         ASSERT_NOT_REACHED();
436     }
437     return true;
438 }
439
440 void WebSocketHandshake::checkResponseHeaders()
441 {
442     ASSERT(m_mode == Normal);
443     m_mode = Failed;
444     if (m_wsOrigin.isNull() || m_wsLocation.isNull())
445         return;
446
447     if (clientOrigin() != m_wsOrigin) {
448         LOG(Network, "Mismatch origin: %s != %s", clientOrigin().utf8().data(), m_wsOrigin.utf8().data());
449         return;
450     }
451     if (clientLocation() != m_wsLocation) {
452         LOG(Network, "Mismatch location: %s != %s", clientLocation().utf8().data(), m_wsLocation.utf8().data());
453         return;
454     }
455     if (!m_clientProtocol.isEmpty() && m_clientProtocol != m_wsProtocol) {
456         LOG(Network, "Mismatch protocol: %s != %s", m_clientProtocol.utf8().data(), m_wsProtocol.utf8().data());
457         return;
458     }
459     m_mode = Connected;
460     return;
461 }
462
463 }  // namespace WebCore
464
465 #endif  // ENABLE(WEB_SOCKETS)