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