3fa77e875027d60e8f5cf0eb51224a56e96d50a3
[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 "PlatformCookieJar.h"
21
22 #if USE(CURL)
23
24 #include "Cookie.h"
25 #include "CurlContext.h"
26 #include "NotImplemented.h"
27 #include "URL.h"
28
29 #include <wtf/DateMath.h>
30 #include <wtf/HashMap.h>
31 #include <wtf/text/StringBuilder.h>
32 #include <wtf/text/StringHash.h>
33 #include <wtf/text/WTFString.h>
34
35 namespace WebCore {
36
37 static void readCurlCookieToken(const char*& cookie, String& token)
38 {
39     // Read the next token from a cookie with the Netscape cookie format.
40     // Curl separates each token in line with tab character.
41     const char* cookieStart = cookie;
42     while (cookie && cookie[0] && cookie[0] != '\t')
43         cookie++;
44     token = String(cookieStart, cookie - cookieStart);
45     if (cookie[0] == '\t')
46         cookie++;
47 }
48
49 static bool domainMatch(const String& cookieDomain, const String& host)
50 {
51     size_t index = host.find(cookieDomain);
52
53     bool tailMatch = (index != WTF::notFound && index + cookieDomain.length() == host.length());
54
55     // Check if host equals cookie domain.
56     if (tailMatch && !index)
57         return true;
58
59     // Check if host is a subdomain of the domain in the cookie.
60     // Curl uses a '.' in front of domains to indicate it's valid on subdomains.
61     if (tailMatch && index > 0 && host[index] == '.')
62         return true;
63
64     // Check the special case where host equals the cookie domain, except for a leading '.' in the cookie domain.
65     // E.g. cookie domain is .apple.com and host is apple.com. 
66     if (cookieDomain[0] == '.' && cookieDomain.find(host) == 1)
67         return true;
68
69     return false;
70 }
71
72 static void addMatchingCurlCookie(const char* cookie, const String& domain, const String& path, StringBuilder& cookies, bool httponly)
73 {
74     // Check if the cookie matches domain and path, and is not expired.
75     // If so, add it to the list of cookies.
76     //
77     // Description of the Netscape cookie file format which Curl uses:
78     //
79     // .netscape.com     TRUE   /  FALSE  946684799   NETSCAPE_ID  100103
80     //
81     // Each line represents a single piece of stored information. A tab is inserted between each of the fields.
82     //
83     // From left-to-right, here is what each field represents:
84     //
85     // domain - The domain that created AND that can read the variable.
86     // 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.
87     // path - The path within the domain that the variable is valid for.
88     // secure - A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.
89     // 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.
90     // name - The name of the variable.
91     // value - The value of the variable.
92
93     if (!cookie)
94         return;
95
96     String cookieDomain;
97     readCurlCookieToken(cookie, cookieDomain);
98
99     // HttpOnly cookie entries begin with "#HttpOnly_".
100     if (cookieDomain.startsWith("#HttpOnly_")) {
101         if (httponly)
102             cookieDomain.remove(0, 10);
103         else
104             return;
105     }
106
107     if (!domainMatch(cookieDomain, domain))
108         return;
109
110     String strBoolean;
111     readCurlCookieToken(cookie, strBoolean);
112
113     String strPath;
114     readCurlCookieToken(cookie, strPath);
115
116     // Check if path matches
117     int index = path.find(strPath);
118     if (index)
119         return;
120
121     String strSecure;
122     readCurlCookieToken(cookie, strSecure);
123
124     String strExpires;
125     readCurlCookieToken(cookie, strExpires);
126
127     int expires = strExpires.toInt();
128
129     time_t now = 0;
130     time(&now);
131
132     // Check if cookie has expired
133     if (expires && now > expires)
134         return;
135
136     String strName;
137     readCurlCookieToken(cookie, strName);
138
139     String strValue;
140     readCurlCookieToken(cookie, strValue);
141
142     // The cookie matches, add it to the cookie list.
143
144     if (cookies.length() > 0)
145         cookies.append("; ");
146
147     cookies.append(strName);
148     cookies.append("=");
149     cookies.append(strValue);
150
151 }
152
153 static String getNetscapeCookieFormat(const URL& url, const String& value)
154 {
155     // Constructs a cookie string in Netscape Cookie file format.
156
157     if (value.isEmpty())
158         return "";
159
160     String valueStr;
161     if (value.is8Bit())
162         valueStr = value;
163     else
164         valueStr = String::make8BitFrom16BitSource(value.characters16(), value.length());
165
166     Vector<String> attributes;
167     valueStr.split(';', false, attributes);
168
169     if (!attributes.size())
170         return "";
171
172     // First attribute should be <cookiename>=<cookievalue>
173     String cookieName, cookieValue;
174     Vector<String>::iterator attribute = attributes.begin();
175     if (attribute->contains('=')) {
176         Vector<String> nameValuePair;
177         attribute->split('=', true, nameValuePair);
178         cookieName = nameValuePair[0];
179         cookieValue = nameValuePair[1];
180     } else {
181         // According to RFC6265 we should ignore the entire
182         // set-cookie string now, but other browsers appear
183         // to treat this as <cookiename>=<empty>
184         cookieName = *attribute;
185     }
186     
187     int expires = 0;
188     String secure = "FALSE";
189     String path = url.baseAsString().substring(url.pathStart());
190     if (path.length() > 1 && path.endsWith('/'))
191         path.remove(path.length() - 1);
192     String domain = url.host();
193
194     // Iterate through remaining attributes
195     for (++attribute; attribute != attributes.end(); ++attribute) {
196         if (attribute->contains('=')) {
197             Vector<String> keyValuePair;
198             attribute->split('=', true, keyValuePair);
199             String key = keyValuePair[0].stripWhiteSpace();
200             String val = keyValuePair[1].stripWhiteSpace();
201             if (equalLettersIgnoringASCIICase(key, "expires")) {
202                 CString dateStr(reinterpret_cast<const char*>(val.characters8()), val.length());
203                 expires = WTF::parseDateFromNullTerminatedCharacters(dateStr.data()) / WTF::msPerSecond;
204             } else if (equalLettersIgnoringASCIICase(key, "max-age"))
205                 expires = time(0) + val.toInt();
206             else if (equalLettersIgnoringASCIICase(key, "domain"))
207                 domain = val;
208             else if (equalLettersIgnoringASCIICase(key, "path"))
209                 path = val;
210         } else {
211             String key = attribute->stripWhiteSpace();
212             if (equalLettersIgnoringASCIICase(key, "secure"))
213                 secure = "TRUE";
214         }
215     }
216     
217     String allowSubdomains = domain.startsWith('.') ? "TRUE" : "FALSE";
218     String expiresStr = String::number(expires);
219
220     int finalStringLength = domain.length() + path.length() + expiresStr.length() + cookieName.length();
221     finalStringLength += cookieValue.length() + secure.length() + allowSubdomains.length();
222     finalStringLength += 6; // Account for \t separators.
223     
224     StringBuilder cookieStr;
225     cookieStr.reserveCapacity(finalStringLength);
226     cookieStr.append(domain);
227     cookieStr.append("\t");
228     cookieStr.append(allowSubdomains);
229     cookieStr.append("\t");
230     cookieStr.append(path);
231     cookieStr.append("\t");
232     cookieStr.append(secure);
233     cookieStr.append("\t");
234     cookieStr.append(expiresStr);
235     cookieStr.append("\t");
236     cookieStr.append(cookieName);
237     cookieStr.append("\t");
238     cookieStr.append(cookieValue);
239
240     return cookieStr.toString();
241 }
242
243 void setCookiesFromDOM(const NetworkStorageSession&, const URL&, const URL& url, const String& value)
244 {
245     CurlHandle curlHandle;
246
247     curlHandle.enableShareHandle();
248     curlHandle.enableCookieJarIfExists();
249
250     // CURL accepts cookies in either Set-Cookie or Netscape file format.
251     // However with Set-Cookie format, there is no way to specify that we
252     // should not allow cookies to be read from subdomains, which is the
253     // required behavior if the domain field is not explicity specified.
254     String cookie = getNetscapeCookieFormat(url, value);
255
256     if (!cookie.is8Bit())
257         cookie = String::make8BitFrom16BitSource(cookie.characters16(), cookie.length());
258
259     CString strCookie(reinterpret_cast<const char*>(cookie.characters8()), cookie.length());
260
261     curlHandle.setCookieList(strCookie.data());
262 }
263
264 static String cookiesForSession(const NetworkStorageSession&, const URL&, const URL& url, bool httponly)
265 {
266     String cookies;
267
268     CurlHandle curlHandle;
269     curlHandle.enableShareHandle();
270
271     CurlSList cookieList;
272     curlHandle.fetchCookieList(cookieList);
273     const struct curl_slist* list = cookieList.head();
274     if (list) {
275         String domain = url.host();
276         String path = url.path();
277         StringBuilder cookiesBuilder;
278
279         while (list) {
280             const char* cookie = list->data;
281             addMatchingCurlCookie(cookie, domain, path, cookiesBuilder, httponly);
282             list = list->next;
283         }
284
285         cookies = cookiesBuilder.toString();
286     }
287
288     return cookies;
289 }
290
291 String cookiesForDOM(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
292 {
293     return cookiesForSession(session, firstParty, url, false);
294 }
295
296 String cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
297 {
298     return cookiesForSession(session, firstParty, url, true);
299 }
300
301 bool cookiesEnabled(const NetworkStorageSession&, const URL& /*firstParty*/, const URL& /*url*/)
302 {
303     return true;
304 }
305
306 bool getRawCookies(const NetworkStorageSession&, const URL& /*firstParty*/, const URL& /*url*/, Vector<Cookie>& rawCookies)
307 {
308     // FIXME: Not yet implemented
309     rawCookies.clear();
310     return false; // return true when implemented
311 }
312
313 void deleteCookie(const NetworkStorageSession&, const URL&, const String&)
314 {
315     // FIXME: Not yet implemented
316 }
317
318 void getHostnamesWithCookies(const NetworkStorageSession&, HashSet<String>&)
319 {
320     // FIXME: Not yet implemented
321 }
322
323 void deleteCookiesForHostnames(const NetworkStorageSession&, const Vector<String>&)
324 {
325     // FIXME: Not yet implemented
326 }
327
328 void deleteAllCookies(const NetworkStorageSession&)
329 {
330     // FIXME: Not yet implemented
331 }
332
333 void deleteAllCookiesModifiedSince(const NetworkStorageSession&, std::chrono::system_clock::time_point)
334 {
335     // FIXME: Not yet implemented
336 }
337
338 }
339
340 #endif