b6bd8050b8794e579b540381f1c52e07a0534f1a
[WebKit-https.git] / Source / WebCore / platform / network / cf / CookieJarCFNet.cpp
1 /*
2  * Copyright (C) 2006-2017 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "PlatformCookieJar.h"
28
29 #if USE(CFURLCONNECTION)
30
31 #include "Cookie.h"
32 #include "CookieRequestHeaderFieldProxy.h"
33 #include "CookiesStrategy.h"
34 #include "NetworkStorageSession.h"
35 #include "NotImplemented.h"
36 #include "URL.h"
37 #include <CFNetwork/CFHTTPCookiesPriv.h>
38 #include <CoreFoundation/CoreFoundation.h>
39 #include <WebKitSystemInterface/WebKitSystemInterface.h>
40 #include <pal/spi/cf/CFNetworkSPI.h>
41 #include <windows.h>
42 #include <wtf/SoftLinking.h>
43 #include <wtf/cf/TypeCastsCF.h>
44 #include <wtf/text/WTFString.h>
45
46 enum {
47     CFHTTPCookieStorageAcceptPolicyExclusivelyFromMainDocumentDomain = 3
48 };
49
50 namespace WTF {
51
52 #define DECLARE_CF_TYPE_TRAIT(ClassName) \
53 template <> \
54 struct CFTypeTrait<ClassName##Ref> { \
55 static inline CFTypeID typeID() { return ClassName##GetTypeID(); } \
56 };
57
58 #if COMPILER(CLANG)
59 #pragma clang diagnostic push
60 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
61 #endif
62 DECLARE_CF_TYPE_TRAIT(CFHTTPCookie);
63 #if COMPILER(CLANG)
64 #pragma clang diagnostic pop
65 #endif
66
67 #undef DECLARE_CF_TYPE_TRAIT
68 } // namespace WTF
69
70 namespace WebCore {
71
72 static const CFStringRef s_setCookieKeyCF = CFSTR("Set-Cookie");
73 static const CFStringRef s_cookieCF = CFSTR("Cookie");
74 static const CFStringRef s_createdCF = CFSTR("Created");
75
76 static inline RetainPtr<CFStringRef> cookieDomain(CFHTTPCookieRef cookie)
77 {
78     return adoptCF(CFHTTPCookieCopyDomain(cookie));
79 }
80
81 static double canonicalCookieTime(double time)
82 {
83     if (!time)
84         return time;
85
86     return (time + kCFAbsoluteTimeIntervalSince1970) * 1000;
87 }
88
89 static double cookieCreatedTime(CFHTTPCookieRef cookie)
90 {
91     RetainPtr<CFDictionaryRef> props = adoptCF(CFHTTPCookieCopyProperties(cookie));
92     auto value = CFDictionaryGetValue(props.get(), s_createdCF);
93
94     auto asNumber = dynamic_cf_cast<CFNumberRef>(value);
95     if (asNumber) {
96         double asDouble;
97         if (CFNumberGetValue(asNumber, kCFNumberFloat64Type, &asDouble))
98             return canonicalCookieTime(asDouble);
99         return 0.0;
100     }
101
102     auto asString = dynamic_cf_cast<CFStringRef>(value);
103     if (asString)
104         return canonicalCookieTime(CFStringGetDoubleValue(asString));
105
106     return 0.0;
107 }
108
109 static inline CFAbsoluteTime cookieExpirationTime(CFHTTPCookieRef cookie)
110 {
111     return canonicalCookieTime(CFHTTPCookieGetExpirationTime(cookie));
112 }
113
114 static inline RetainPtr<CFStringRef> cookieName(CFHTTPCookieRef cookie)
115 {
116     return adoptCF(CFHTTPCookieCopyName(cookie));
117 }
118
119 static inline RetainPtr<CFStringRef> cookiePath(CFHTTPCookieRef cookie)
120 {
121     return adoptCF(CFHTTPCookieCopyPath(cookie));
122 }
123
124 static inline RetainPtr<CFStringRef> cookieValue(CFHTTPCookieRef cookie)
125 {
126     return adoptCF(CFHTTPCookieCopyValue(cookie));
127 }
128
129 static RetainPtr<CFArrayRef> filterCookies(CFArrayRef unfilteredCookies)
130 {
131     ASSERT(unfilteredCookies);
132     CFIndex count = CFArrayGetCount(unfilteredCookies);
133     RetainPtr<CFMutableArrayRef> filteredCookies = adoptCF(CFArrayCreateMutable(0, count, &kCFTypeArrayCallBacks));
134     for (CFIndex i = 0; i < count; ++i) {
135         CFHTTPCookieRef cookie = (CFHTTPCookieRef)CFArrayGetValueAtIndex(unfilteredCookies, i);
136
137         // <rdar://problem/5632883> CFHTTPCookieStorage would store an empty cookie,
138         // which would be sent as "Cookie: =". We have a workaround in setCookies() to prevent
139         // that, but we also need to avoid sending cookies that were previously stored, and
140         // there's no harm to doing this check because such a cookie is never valid.
141         if (!CFStringGetLength(cookieName(cookie).get()))
142             continue;
143
144         if (CFHTTPCookieIsHTTPOnly(cookie))
145             continue;
146
147         CFArrayAppendValue(filteredCookies.get(), cookie);
148     }
149     return filteredCookies;
150 }
151
152 static RetainPtr<CFArrayRef> copyCookiesForURLWithFirstPartyURL(const NetworkStorageSession& session, const URL& firstParty, const URL& url, IncludeSecureCookies includeSecureCookies)
153 {
154     bool secure = includeSecureCookies == IncludeSecureCookies::Yes;
155
156     ASSERT(!secure || (secure && url.protocolIs("https")));
157
158     UNUSED_PARAM(firstParty);
159     return adoptCF(CFHTTPCookieStorageCopyCookiesForURL(session.cookieStorage().get(), url.createCFURL().get(), secure));
160 }
161
162 static CFArrayRef createCookies(CFDictionaryRef headerFields, CFURLRef url)
163 {
164     CFArrayRef parsedCookies = CFHTTPCookieCreateWithResponseHeaderFields(kCFAllocatorDefault, headerFields, url);
165     if (!parsedCookies)
166         parsedCookies = CFArrayCreate(kCFAllocatorDefault, 0, 0, &kCFTypeArrayCallBacks);
167
168     return parsedCookies;
169 }
170
171 void setCookiesFromDOM(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo&, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, const String& value)
172 {
173     UNUSED_PARAM(frameID);
174     UNUSED_PARAM(pageID);
175     // <rdar://problem/5632883> CFHTTPCookieStorage stores an empty cookie, which would be sent as "Cookie: =".
176     if (value.isEmpty())
177         return;
178
179     RetainPtr<CFURLRef> urlCF = url.createCFURL();
180     RetainPtr<CFURLRef> firstPartyForCookiesCF = firstParty.createCFURL();
181
182     // <http://bugs.webkit.org/show_bug.cgi?id=6531>, <rdar://4409034>
183     // cookiesWithResponseHeaderFields doesn't parse cookies without a value
184     String cookieString = value.contains('=') ? value : value + "=";
185
186     RetainPtr<CFStringRef> cookieStringCF = cookieString.createCFString();
187     auto cookieStringCFPtr = cookieStringCF.get();
188     RetainPtr<CFDictionaryRef> headerFieldsCF = adoptCF(CFDictionaryCreate(kCFAllocatorDefault,
189         (const void**)&s_setCookieKeyCF, (const void**)&cookieStringCFPtr, 1,
190         &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
191
192     RetainPtr<CFArrayRef> unfilteredCookies = adoptCF(createCookies(headerFieldsCF.get(), urlCF.get()));
193     CFHTTPCookieStorageSetCookies(session.cookieStorage().get(), filterCookies(unfilteredCookies.get()).get(), urlCF.get(), firstPartyForCookiesCF.get());
194 }
195
196 static bool containsSecureCookies(CFArrayRef cookies)
197 {
198     CFIndex cookieCount = CFArrayGetCount(cookies);
199     while (cookieCount--) {
200         if (CFHTTPCookieIsSecure(checked_cf_cast<CFHTTPCookieRef>(CFArrayGetValueAtIndex(cookies, cookieCount))))
201             return true;
202     }
203
204     return false;
205 }
206
207 std::pair<String, bool> cookiesForDOM(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo&, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies includeSecureCookies)
208 {
209     UNUSED_PARAM(frameID);
210     UNUSED_PARAM(pageID);
211     RetainPtr<CFArrayRef> cookiesCF = copyCookiesForURLWithFirstPartyURL(session, firstParty, url, includeSecureCookies);
212
213     auto filteredCookies = filterCookies(cookiesCF.get());
214
215     bool didAccessSecureCookies = containsSecureCookies(filteredCookies.get());
216
217     RetainPtr<CFDictionaryRef> headerCF = adoptCF(CFHTTPCookieCopyRequestHeaderFields(kCFAllocatorDefault, filteredCookies.get()));
218     String cookieString = checked_cf_cast<CFStringRef>(CFDictionaryGetValue(headerCF.get(), s_cookieCF));
219     return { cookieString, didAccessSecureCookies };
220 }
221
222 std::pair<String, bool> cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo&, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies includeSecureCookies)
223 {
224     UNUSED_PARAM(frameID);
225     UNUSED_PARAM(pageID);
226     RetainPtr<CFArrayRef> cookiesCF = copyCookiesForURLWithFirstPartyURL(session, firstParty, url, includeSecureCookies);
227
228     bool didAccessSecureCookies = containsSecureCookies(cookiesCF.get());
229
230     RetainPtr<CFDictionaryRef> headerCF = adoptCF(CFHTTPCookieCopyRequestHeaderFields(kCFAllocatorDefault, cookiesCF.get()));
231     String cookieString = checked_cf_cast<CFStringRef>(CFDictionaryGetValue(headerCF.get(), s_cookieCF));
232     return { cookieString, didAccessSecureCookies };
233 }
234
235 std::pair<String, bool> cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const CookieRequestHeaderFieldProxy& headerFieldProxy)
236 {
237     return cookieRequestHeaderFieldValue(session, headerFieldProxy.firstParty, headerFieldProxy.url, headerFieldProxy.frameID, headerFieldProxy.pageID, headerFieldProxy.includeSecureCookies);
238 }
239
240 bool cookiesEnabled(const NetworkStorageSession& session)
241 {
242     CFHTTPCookieStorageAcceptPolicy policy = CFHTTPCookieStorageGetCookieAcceptPolicy(session.cookieStorage().get());
243     return policy == CFHTTPCookieStorageAcceptPolicyOnlyFromMainDocumentDomain || policy == CFHTTPCookieStorageAcceptPolicyExclusivelyFromMainDocumentDomain || policy == CFHTTPCookieStorageAcceptPolicyAlways;
244 }
245
246 bool getRawCookies(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo&, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, Vector<Cookie>& rawCookies)
247 {
248     UNUSED_PARAM(frameID);
249     UNUSED_PARAM(pageID);
250     rawCookies.clear();
251
252     auto includeSecureCookies = url.protocolIs("https") ? IncludeSecureCookies::Yes : IncludeSecureCookies::No;
253
254     RetainPtr<CFArrayRef> cookiesCF = copyCookiesForURLWithFirstPartyURL(session, firstParty, url, includeSecureCookies);
255
256     CFIndex count = CFArrayGetCount(cookiesCF.get());
257     rawCookies.reserveCapacity(count);
258
259     for (CFIndex i = 0; i < count; i++) {
260         CFHTTPCookieRef cookie = checked_cf_cast<CFHTTPCookieRef>(CFArrayGetValueAtIndex(cookiesCF.get(), i));
261         String name = cookieName(cookie).get();
262         String value = cookieValue(cookie).get();
263         String domain = cookieDomain(cookie).get();
264         String path = cookiePath(cookie).get();
265
266         double created = cookieCreatedTime(cookie);
267         double expires = cookieExpirationTime(cookie);
268
269         bool httpOnly = CFHTTPCookieIsHTTPOnly(cookie);
270         bool secure = CFHTTPCookieIsSecure(cookie);
271         bool session = false; // FIXME: Need API for if a cookie is a session cookie.
272
273         String comment;
274         URL commentURL;
275         Vector<uint16_t> ports;
276
277         rawCookies.uncheckedAppend(Cookie(name, value, domain, path, created, expires, httpOnly, secure, session, comment, commentURL, ports));
278     }
279
280     return true;
281 }
282
283 void deleteCookie(const NetworkStorageSession& session, const URL& url, const String& name)
284 {
285     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = session.cookieStorage();
286
287     RetainPtr<CFURLRef> urlCF = url.createCFURL();
288
289     bool sendSecureCookies = url.protocolIs("https");
290     RetainPtr<CFArrayRef> cookiesCF = adoptCF(CFHTTPCookieStorageCopyCookiesForURL(cookieStorage.get(), urlCF.get(), sendSecureCookies));
291
292     CFIndex count = CFArrayGetCount(cookiesCF.get());
293     for (CFIndex i = 0; i < count; i++) {
294         CFHTTPCookieRef cookie = checked_cf_cast<CFHTTPCookieRef>(CFArrayGetValueAtIndex(cookiesCF.get(), i));
295         if (String(cookieName(cookie).get()) == name) {
296             CFHTTPCookieStorageDeleteCookie(cookieStorage.get(), cookie);
297             break;
298         }
299     }
300 }
301
302 void getHostnamesWithCookies(const NetworkStorageSession& session, HashSet<String>& hostnames)
303 {
304     RetainPtr<CFArrayRef> cookiesCF = adoptCF(CFHTTPCookieStorageCopyCookies(session.cookieStorage().get()));
305     if (!cookiesCF)
306         return;
307
308     CFIndex count = CFArrayGetCount(cookiesCF.get());
309     for (CFIndex i = 0; i < count; ++i) {
310         CFHTTPCookieRef cookie = checked_cf_cast<CFHTTPCookieRef>(CFArrayGetValueAtIndex(cookiesCF.get(), i));
311         RetainPtr<CFStringRef> domain = cookieDomain(cookie);
312         hostnames.add(domain.get());
313     }
314 }
315
316 void deleteAllCookies(const NetworkStorageSession& session)
317 {
318     CFHTTPCookieStorageDeleteAllCookies(session.cookieStorage().get());
319 }
320
321 void deleteCookiesForHostnames(const NetworkStorageSession& session, const Vector<String>& hostnames)
322 {
323 }
324
325 void deleteAllCookiesModifiedSince(const NetworkStorageSession&, WallTime)
326 {
327 }
328
329 } // namespace WebCore
330
331 #endif // USE(CFURLCONNECTION)