1/*
2 *  This library is free software; you can redistribute it and/or
3 *  modify it under the terms of the GNU Lesser General Public
4 *  License as published by the Free Software Foundation; either
5 *  version 2 of the License, or (at your option) any later version.
6 *
7 *  This library is distributed in the hope that it will be useful,
8 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10 *  Lesser General Public License for more details.
11 *
12 *  You should have received a copy of the GNU Lesser General Public
13 *  License along with this library; if not, write to the Free Software
14 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
15 */
16
17#include "config.h"
18#include "PlatformCookieJar.h"
19
20#include "Cookie.h"
21#include "KURL.h"
22#include "ResourceHandleManager.h"
23
24#include <wtf/DateMath.h>
25#include <wtf/HashMap.h>
26#include <wtf/text/StringBuilder.h>
27#include <wtf/text/StringHash.h>
28#include <wtf/text/WTFString.h>
29
30namespace WebCore {
31
32static void readCurlCookieToken(const char*& cookie, String& token)
33{
34    // Read the next token from a cookie with the Netscape cookie format.
35    // Curl separates each token in line with tab character.
36    while (cookie && cookie[0] && cookie[0] != '\t') {
37        token.append(cookie[0]);
38        cookie++;
39    }
40    if (cookie[0] == '\t')
41        cookie++;
42}
43
44static void addMatchingCurlCookie(const char* cookie, const String& domain, const String& path, StringBuilder& cookies, bool httponly)
45{
46    // Check if the cookie matches domain and path, and is not expired.
47    // If so, add it to the list of cookies.
48    //
49    // Description of the Netscape cookie file format which Curl uses:
50    //
51    // .netscape.com     TRUE   /  FALSE  946684799   NETSCAPE_ID  100103
52    //
53    // Each line represents a single piece of stored information. A tab is inserted between each of the fields.
54    //
55    // From left-to-right, here is what each field represents:
56    //
57    // domain - The domain that created AND that can read the variable.
58    // 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.
59    // path - The path within the domain that the variable is valid for.
60    // secure - A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.
61    // 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.
62    // name - The name of the variable.
63    // value - The value of the variable.
64
65    if (!cookie)
66        return;
67
68    String cookieDomain;
69    readCurlCookieToken(cookie, cookieDomain);
70
71    bool subDomain = false;
72
73    // HttpOnly cookie entries begin with "#HttpOnly_".
74    if (cookieDomain.startsWith("#HttpOnly_")) {
75        if (httponly)
76            cookieDomain.remove(0, 10);
77        else
78            return;
79    }
80
81
82    if (cookieDomain[0] == '.') {
83        // Check if domain is a subdomain of the domain in the cookie.
84        // Curl uses a '.' in front of domains to indicate its valid on subdomains.
85        cookieDomain.remove(0);
86        int lenDiff = domain.length() - cookieDomain.length();
87        int index = domain.find(cookieDomain);
88        if (index == lenDiff)
89            subDomain = true;
90    }
91
92    if (!subDomain && cookieDomain != domain)
93        return;
94
95    String strBoolean;
96    readCurlCookieToken(cookie, strBoolean);
97
98    String strPath;
99    readCurlCookieToken(cookie, strPath);
100
101    // Check if path matches
102    int index = path.find(strPath);
103    if (index)
104        return;
105
106    String strSecure;
107    readCurlCookieToken(cookie, strSecure);
108
109    String strExpires;
110    readCurlCookieToken(cookie, strExpires);
111
112    int expires = strExpires.toInt();
113
114    time_t now = 0;
115    time(&now);
116
117    // Check if cookie has expired
118    if (expires && now > expires)
119        return;
120
121    String strName;
122    readCurlCookieToken(cookie, strName);
123
124    String strValue;
125    readCurlCookieToken(cookie, strValue);
126
127    // The cookie matches, add it to the cookie list.
128
129    if (cookies.length() > 0)
130        cookies.append("; ");
131
132    cookies.append(strName);
133    cookies.append("=");
134    cookies.append(strValue);
135
136}
137
138static String getNetscapeCookieFormat(const KURL& url, const String& value)
139{
140    // Constructs a cookie string in Netscape Cookie file format.
141
142    if (value.isEmpty())
143        return "";
144
145    String valueStr;
146    if (value.is8Bit())
147        valueStr = value;
148    else
149        valueStr = String::make8BitFrom16BitSource(value.characters16(), value.length());
150
151    Vector<String> attributes;
152    valueStr.split(';', false, attributes);
153
154    if (!attributes.size())
155        return "";
156
157    // First attribute should be <cookiename>=<cookievalue>
158    String cookieName, cookieValue;
159    Vector<String>::iterator attribute = attributes.begin();
160    if (attribute->contains('=')) {
161        Vector<String> nameValuePair;
162        attribute->split('=', true, nameValuePair);
163        cookieName = nameValuePair[0];
164        cookieValue = nameValuePair[1];
165    } else {
166        // According to RFC6265 we should ignore the entire
167        // set-cookie string now, but other browsers appear
168        // to treat this as <cookiename>=<empty>
169        cookieName = *attribute;
170    }
171
172    int expires = 0;
173    String secure = "FALSE";
174    String path = url.baseAsString().substring(url.pathStart());
175    if (path.length() > 1 && path.endsWith('/'))
176        path.remove(path.length() - 1);
177    String domain = url.host();
178
179    // Iterate through remaining attributes
180    for (++attribute; attribute != attributes.end(); ++attribute) {
181        if (attribute->contains('=')) {
182            Vector<String> keyValuePair;
183            attribute->split('=', true, keyValuePair);
184            String key = keyValuePair[0].stripWhiteSpace().lower();
185            String val = keyValuePair[1].stripWhiteSpace();
186            if (key == "expires") {
187                CString dateStr(reinterpret_cast<const char*>(val.characters8()), val.length());
188                expires = WTF::parseDateFromNullTerminatedCharacters(dateStr.data()) / WTF::msPerSecond;
189            } else if (key == "max-age")
190                expires = time(0) + val.toInt();
191            else if (key == "domain")
192                domain = val;
193            else if (key == "path")
194                path = val;
195        } else {
196            String key = attribute->stripWhiteSpace().lower();
197            if (key == "secure")
198                secure = "TRUE";
199        }
200    }
201
202    String allowSubdomains = domain.startsWith('.') ? "TRUE" : "FALSE";
203    String expiresStr = String::number(expires);
204
205    int finalStringLength = domain.length() + path.length() + expiresStr.length() + cookieName.length();
206    finalStringLength += cookieValue.length() + secure.length() + allowSubdomains.length();
207    finalStringLength += 6; // Account for \t separators.
208
209    StringBuilder cookieStr;
210    cookieStr.reserveCapacity(finalStringLength);
211    cookieStr.append(domain + "\t");
212    cookieStr.append(allowSubdomains + "\t");
213    cookieStr.append(path + "\t");
214    cookieStr.append(secure + "\t");
215    cookieStr.append(expiresStr + "\t");
216    cookieStr.append(cookieName + "\t");
217    cookieStr.append(cookieValue);
218
219    return cookieStr.toString();
220}
221
222void setCookiesFromDOM(const NetworkStorageSession&, const KURL&, const KURL& url, const String& value)
223{
224    CURL* curl = curl_easy_init();
225
226    if (!curl)
227        return;
228
229    const char* cookieJarFileName = ResourceHandleManager::sharedInstance()->getCookieJarFileName();
230    CURLSH* curlsh = ResourceHandleManager::sharedInstance()->getCurlShareHandle();
231
232    curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieJarFileName);
233    curl_easy_setopt(curl, CURLOPT_SHARE, curlsh);
234
235    // CURL accepts cookies in either Set-Cookie or Netscape file format.
236    // However with Set-Cookie format, there is no way to specify that we
237    // should not allow cookies to be read from subdomains, which is the
238    // required behavior if the domain field is not explicity specified.
239    String cookie = getNetscapeCookieFormat(url, value);
240
241    CString strCookie(reinterpret_cast<const char*>(cookie.characters8()), cookie.length());
242
243    curl_easy_setopt(curl, CURLOPT_COOKIELIST, strCookie.data());
244
245    curl_easy_cleanup(curl);
246}
247
248static String cookiesForSession(const NetworkStorageSession&, const KURL&, const KURL& url, bool httponly)
249{
250    String cookies;
251    CURL* curl = curl_easy_init();
252
253    if (!curl)
254        return cookies;
255
256    CURLSH* curlsh = ResourceHandleManager::sharedInstance()->getCurlShareHandle();
257
258    curl_easy_setopt(curl, CURLOPT_SHARE, curlsh);
259
260    struct curl_slist* list = 0;
261    curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &list);
262
263    if (list) {
264        String domain = url.host();
265        String path = url.path();
266        StringBuilder cookiesBuilder;
267
268        struct curl_slist* item = list;
269        while (item) {
270            const char* cookie = item->data;
271            addMatchingCurlCookie(cookie, domain, path, cookiesBuilder, httponly);
272            item = item->next;
273        }
274
275        cookies = cookiesBuilder.toString();
276        curl_slist_free_all(list);
277    }
278
279    curl_easy_cleanup(curl);
280
281    return cookies;
282}
283
284String cookiesForDOM(const NetworkStorageSession& session, const KURL& firstParty, const KURL& url)
285{
286    return cookiesForSession(session, firstParty, url, false);
287}
288
289String cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const KURL& firstParty, const KURL& url)
290{
291    return cookiesForSession(session, firstParty, url, true);
292}
293
294bool cookiesEnabled(const NetworkStorageSession&, const KURL& /*firstParty*/, const KURL& /*url*/)
295{
296    return true;
297}
298
299bool getRawCookies(const NetworkStorageSession&, const KURL& /*firstParty*/, const KURL& /*url*/, Vector<Cookie>& rawCookies)
300{
301    // FIXME: Not yet implemented
302    rawCookies.clear();
303    return false; // return true when implemented
304}
305
306void deleteCookie(const NetworkStorageSession&, const KURL&, const String&)
307{
308    // FIXME: Not yet implemented
309}
310
311void getHostnamesWithCookies(const NetworkStorageSession&, HashSet<String>& hostnames)
312{
313    // FIXME: Not yet implemented
314}
315
316void deleteCookiesForHostname(const NetworkStorageSession&, const String& hostname)
317{
318    // FIXME: Not yet implemented
319}
320
321void deleteAllCookies(const NetworkStorageSession&)
322{
323    // FIXME: Not yet implemented
324}
325
326}
327