e3b728b83bf197d81bbc88bd55930ef7bff8f55e
[WebKit-https.git] / Source / WebCore / platform / network / curl / CookieJarCurl.cpp
1 /*
2  * Copyright (C) 2011 Apple Inc. All rights reserved.
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Lesser General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public
15  *  License along with this library; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "CookieJarCurl.h"
21
22 #if USE(CURL)
23 #include "Cookie.h"
24 #include "CurlContext.h"
25 #include "NotImplemented.h"
26 #include "URL.h"
27
28 #include <wtf/DateMath.h>
29 #include <wtf/HashMap.h>
30 #include <wtf/text/StringBuilder.h>
31 #include <wtf/text/StringHash.h>
32 #include <wtf/text/WTFString.h>
33
34 namespace WebCore {
35
36 static void readCurlCookieToken(const char*& cookie, String& token)
37 {
38     // Read the next token from a cookie with the Netscape cookie format.
39     // Curl separates each token in line with tab character.
40     const char* cookieStart = cookie;
41     while (cookie && cookie[0] && cookie[0] != '\t')
42         cookie++;
43     token = String(cookieStart, cookie - cookieStart);
44     if (cookie[0] == '\t')
45         cookie++;
46 }
47
48 static bool domainMatch(const String& cookieDomain, const String& host)
49 {
50     size_t index = host.find(cookieDomain);
51
52     bool tailMatch = (index != WTF::notFound && index + cookieDomain.length() == host.length());
53
54     // Check if host equals cookie domain.
55     if (tailMatch && !index)
56         return true;
57
58     // Check if host is a subdomain of the domain in the cookie.
59     // Curl uses a '.' in front of domains to indicate it's valid on subdomains.
60     if (tailMatch && index > 0 && host[index] == '.')
61         return true;
62
63     // Check the special case where host equals the cookie domain, except for a leading '.' in the cookie domain.
64     // E.g. cookie domain is .apple.com and host is apple.com. 
65     if (cookieDomain[0] == '.' && cookieDomain.find(host) == 1)
66         return true;
67
68     return false;
69 }
70
71 static void addMatchingCurlCookie(const char* cookie, const String& domain, const String& path, StringBuilder& cookies, bool httponly)
72 {
73     // Check if the cookie matches domain and path, and is not expired.
74     // If so, add it to the list of cookies.
75     //
76     // Description of the Netscape cookie file format which Curl uses:
77     //
78     // .netscape.com     TRUE   /  FALSE  946684799   NETSCAPE_ID  100103
79     //
80     // Each line represents a single piece of stored information. A tab is inserted between each of the fields.
81     //
82     // From left-to-right, here is what each field represents:
83     //
84     // domain - The domain that created AND that can read the variable.
85     // flag - A TRUE/FALSE value indicating if all machines within a given domain can access the variable. This value is set automatically by the browser, depending on the value you set for domain.
86     // path - The path within the domain that the variable is valid for.
87     // secure - A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.
88     // expiration - The UNIX time that the variable will expire on. UNIX time is defined as the number of seconds since Jan 1, 1970 00:00:00 GMT.
89     // name - The name of the variable.
90     // value - The value of the variable.
91
92     if (!cookie)
93         return;
94
95     String cookieDomain;
96     readCurlCookieToken(cookie, cookieDomain);
97
98     // HttpOnly cookie entries begin with "#HttpOnly_".
99     if (cookieDomain.startsWith("#HttpOnly_")) {
100         if (httponly)
101             cookieDomain.remove(0, 10);
102         else
103             return;
104     }
105
106     if (!domainMatch(cookieDomain, domain))
107         return;
108
109     String strBoolean;
110     readCurlCookieToken(cookie, strBoolean);
111
112     String strPath;
113     readCurlCookieToken(cookie, strPath);
114
115     // Check if path matches
116     int index = path.find(strPath);
117     if (index)
118         return;
119
120     String strSecure;
121     readCurlCookieToken(cookie, strSecure);
122
123     String strExpires;
124     readCurlCookieToken(cookie, strExpires);
125
126     int expires = strExpires.toInt();
127
128     time_t now = 0;
129     time(&now);
130
131     // Check if cookie has expired
132     if (expires && now > expires)
133         return;
134
135     String strName;
136     readCurlCookieToken(cookie, strName);
137
138     String strValue;
139     readCurlCookieToken(cookie, strValue);
140
141     // The cookie matches, add it to the cookie list.
142
143     if (cookies.length() > 0)
144         cookies.append("; ");
145
146     cookies.append(strName);
147     cookies.append("=");
148     cookies.append(strValue);
149
150 }
151
152 static String getNetscapeCookieFormat(const URL& url, const String& value)
153 {
154     // Constructs a cookie string in Netscape Cookie file format.
155
156     if (value.isEmpty())
157         return "";
158
159     String valueStr;
160     if (value.is8Bit())
161         valueStr = value;
162     else
163         valueStr = String::make8BitFrom16BitSource(value.characters16(), value.length());
164
165     Vector<String> attributes;
166     valueStr.split(';', false, attributes);
167
168     if (!attributes.size())
169         return "";
170
171     // First attribute should be <cookiename>=<cookievalue>
172     String cookieName, cookieValue;
173     Vector<String>::iterator attribute = attributes.begin();
174     if (attribute->contains('=')) {
175         Vector<String> nameValuePair;
176         attribute->split('=', true, nameValuePair);
177         cookieName = nameValuePair[0];
178         cookieValue = nameValuePair[1];
179     } else {
180         // According to RFC6265 we should ignore the entire
181         // set-cookie string now, but other browsers appear
182         // to treat this as <cookiename>=<empty>
183         cookieName = *attribute;
184     }
185     
186     int expires = 0;
187     String secure = "FALSE";
188     String path = url.baseAsString().substring(url.pathStart());
189     if (path.length() > 1 && path.endsWith('/'))
190         path.remove(path.length() - 1);
191     String domain = url.host();
192
193     // Iterate through remaining attributes
194     for (++attribute; attribute != attributes.end(); ++attribute) {
195         if (attribute->contains('=')) {
196             Vector<String> keyValuePair;
197             attribute->split('=', true, keyValuePair);
198             String key = keyValuePair[0].stripWhiteSpace();
199             String val = keyValuePair[1].stripWhiteSpace();
200             if (equalLettersIgnoringASCIICase(key, "expires")) {
201                 CString dateStr(reinterpret_cast<const char*>(val.characters8()), val.length());
202                 expires = WTF::parseDateFromNullTerminatedCharacters(dateStr.data()) / WTF::msPerSecond;
203             } else if (equalLettersIgnoringASCIICase(key, "max-age"))
204                 expires = time(0) + val.toInt();
205             else if (equalLettersIgnoringASCIICase(key, "domain"))
206                 domain = val;
207             else if (equalLettersIgnoringASCIICase(key, "path"))
208                 path = val;
209         } else {
210             String key = attribute->stripWhiteSpace();
211             if (equalLettersIgnoringASCIICase(key, "secure"))
212                 secure = "TRUE";
213         }
214     }
215     
216     String allowSubdomains = domain.startsWith('.') ? "TRUE" : "FALSE";
217     String expiresStr = String::number(expires);
218
219     int finalStringLength = domain.length() + path.length() + expiresStr.length() + cookieName.length();
220     finalStringLength += cookieValue.length() + secure.length() + allowSubdomains.length();
221     finalStringLength += 6; // Account for \t separators.
222     
223     StringBuilder cookieStr;
224     cookieStr.reserveCapacity(finalStringLength);
225     cookieStr.append(domain);
226     cookieStr.append("\t");
227     cookieStr.append(allowSubdomains);
228     cookieStr.append("\t");
229     cookieStr.append(path);
230     cookieStr.append("\t");
231     cookieStr.append(secure);
232     cookieStr.append("\t");
233     cookieStr.append(expiresStr);
234     cookieStr.append("\t");
235     cookieStr.append(cookieName);
236     cookieStr.append("\t");
237     cookieStr.append(cookieValue);
238
239     return cookieStr.toString();
240 }
241
242 void CookieJarCurlFileSystem::setCookiesFromDOM(const NetworkStorageSession&, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, const String& value)
243 {
244     UNUSED_PARAM(frameID);
245     UNUSED_PARAM(pageID);
246     CurlHandle curlHandle;
247
248     curlHandle.enableShareHandle();
249     curlHandle.enableCookieJarIfExists();
250
251     // CURL accepts cookies in either Set-Cookie or Netscape file format.
252     // However with Set-Cookie format, there is no way to specify that we
253     // should not allow cookies to be read from subdomains, which is the
254     // required behavior if the domain field is not explicity specified.
255     String cookie = getNetscapeCookieFormat(url, value);
256
257     if (!cookie.is8Bit())
258         cookie = String::make8BitFrom16BitSource(cookie.characters16(), cookie.length());
259
260     CString strCookie(reinterpret_cast<const char*>(cookie.characters8()), cookie.length());
261
262     curlHandle.setCookieList(strCookie.data());
263 }
264
265 static String cookiesForSession(const NetworkStorageSession&, const URL&, const URL& url, bool httponly)
266 {
267     String cookies;
268
269     CurlHandle curlHandle;
270     curlHandle.enableShareHandle();
271
272     CurlSList cookieList;
273     curlHandle.fetchCookieList(cookieList);
274     const struct curl_slist* list = cookieList.head();
275     if (list) {
276         String domain = url.host();
277         String path = url.path();
278         StringBuilder cookiesBuilder;
279
280         while (list) {
281             const char* cookie = list->data;
282             addMatchingCurlCookie(cookie, domain, path, cookiesBuilder, httponly);
283             list = list->next;
284         }
285
286         cookies = cookiesBuilder.toString();
287     }
288
289     return cookies;
290 }
291
292 std::pair<String, bool> CookieJarCurlFileSystem::cookiesForDOM(const NetworkStorageSession& session, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies)
293 {
294     UNUSED_PARAM(frameID);
295     UNUSED_PARAM(pageID);
296     // FIXME: This should filter secure cookies out if the caller requests it.
297     return { cookiesForSession(session, firstParty, url, false), false };
298 }
299
300 std::pair<String, bool> CookieJarCurlFileSystem::cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies)
301 {
302     UNUSED_PARAM(frameID);
303     UNUSED_PARAM(pageID);
304     // FIXME: This should filter secure cookies out if the caller requests it.
305     return { cookiesForSession(session, firstParty, url, true), false };
306 }
307
308 bool CookieJarCurlFileSystem::cookiesEnabled(const NetworkStorageSession&)
309 {
310     return true;
311 }
312
313 bool CookieJarCurlFileSystem::getRawCookies(const NetworkStorageSession&, const URL& firstParty, const URL&, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, Vector<Cookie>& rawCookies)
314 {
315     UNUSED_PARAM(frameID);
316     UNUSED_PARAM(pageID);
317     // FIXME: Not yet implemented
318     rawCookies.clear();
319     return false; // return true when implemented
320 }
321
322 void CookieJarCurlFileSystem::deleteCookie(const NetworkStorageSession&, const URL&, const String&)
323 {
324     // FIXME: Not yet implemented
325 }
326
327 void CookieJarCurlFileSystem::getHostnamesWithCookies(const NetworkStorageSession&, HashSet<String>& hostnames)
328 {
329     // FIXME: Not yet implemented
330 }
331
332 void CookieJarCurlFileSystem::deleteCookiesForHostnames(const NetworkStorageSession&, const Vector<String>& cookieHostNames)
333 {
334     // FIXME: Not yet implemented
335 }
336
337 void CookieJarCurlFileSystem::deleteAllCookies(const NetworkStorageSession&)
338 {
339     // FIXME: Not yet implemented
340 }
341
342 void CookieJarCurlFileSystem::deleteAllCookiesModifiedSince(const NetworkStorageSession&, WallTime)
343 {
344     // FIXME: Not yet implemented
345 }
346
347 // dispatcher functions
348
349 std::pair<String, bool> cookiesForDOM(const NetworkStorageSession& session, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies includeSecureCookies)
350 {
351     return CurlContext::singleton().cookieJar().cookiesForDOM(session, firstParty, url, frameID, pageID, includeSecureCookies);
352 }
353
354 void setCookiesFromDOM(const NetworkStorageSession& session, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, const String& value)
355 {
356     CurlContext::singleton().cookieJar().setCookiesFromDOM(session, firstParty, url, frameID, pageID, value);
357 }
358
359 std::pair<String, bool> cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies includeSecureCookies)
360 {
361     return CurlContext::singleton().cookieJar().cookieRequestHeaderFieldValue(session, firstParty, url, frameID, pageID, includeSecureCookies);
362 }
363
364 bool cookiesEnabled(const NetworkStorageSession& session)
365 {
366     return CurlContext::singleton().cookieJar().cookiesEnabled(session);
367 }
368
369 bool getRawCookies(const NetworkStorageSession& session, const URL& firstParty, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, Vector<Cookie>& rawCookies)
370 {
371     return CurlContext::singleton().cookieJar().getRawCookies(session, firstParty, url, frameID, pageID, rawCookies);
372 }
373
374 void deleteCookie(const NetworkStorageSession& session, const URL& url, const String& cookie)
375 {
376     CurlContext::singleton().cookieJar().deleteCookie(session, url, cookie);
377 }
378
379 void getHostnamesWithCookies(const NetworkStorageSession& session, HashSet<String>& hostnames)
380 {
381     CurlContext::singleton().cookieJar().getHostnamesWithCookies(session, hostnames);
382 }
383
384 void deleteCookiesForHostnames(const NetworkStorageSession& session, const Vector<String>& cookieHostNames)
385 {
386     CurlContext::singleton().cookieJar().deleteCookiesForHostnames(session, cookieHostNames);
387 }
388
389 void deleteAllCookies(const NetworkStorageSession& session)
390 {
391     CurlContext::singleton().cookieJar().deleteAllCookies(session);
392 }
393
394 void deleteAllCookiesModifiedSince(const NetworkStorageSession& session, WallTime since)
395 {
396     CurlContext::singleton().cookieJar().deleteAllCookiesModifiedSince(session, since);
397 }
398
399 }
400
401 #endif