Implement Same-Site cookies
[WebKit-https.git] / Source / WebCore / platform / network / mac / CookieJarMac.mm
1 /*
2  * Copyright (C) 2003-2018 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 #import "config.h"
27 #import "PlatformCookieJar.h"
28
29 #import "CookieRequestHeaderFieldProxy.h"
30 #import "CookiesStrategy.h"
31 #import "NetworkStorageSession.h"
32 #import <pal/spi/cf/CFNetworkSPI.h>
33 #import <wtf/BlockObjCExceptions.h>
34
35 #import "Cookie.h"
36 #import "CookieStorage.h"
37 #import "SameSiteInfo.h"
38 #import "URL.h"
39 #import <wtf/Optional.h>
40 #import <wtf/ProcessPrivilege.h>
41 #import <wtf/text/StringBuilder.h>
42
43 @interface NSURL ()
44 - (CFURLRef)_cfurl;
45 @end
46
47 namespace WebCore {
48
49 static NSArray *httpCookies(CFHTTPCookieStorageRef cookieStorage)
50 {
51     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
52     if (!cookieStorage)
53         return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
54     
55     auto cookies = adoptCF(CFHTTPCookieStorageCopyCookies(cookieStorage));
56     return [NSHTTPCookie _cf2nsCookies:cookies.get()];
57 }
58
59 static void deleteHTTPCookie(CFHTTPCookieStorageRef cookieStorage, NSHTTPCookie *cookie)
60 {
61     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
62     if (!cookieStorage) {
63         [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
64         return;
65     }
66     
67     CFHTTPCookieStorageDeleteCookie(cookieStorage, [cookie _GetInternalCFHTTPCookie]);
68 }
69
70 static NSArray *cookiesForURL(NSHTTPCookieStorage *storage, NSURL *url, NSURL *mainDocumentURL, const std::optional<SameSiteInfo>& sameSiteInfo, NSString *partition = nullptr)
71 {
72     // The _getCookiesForURL: method calls the completionHandler synchronously. We use std::optional<> to ensure this invariant.
73     std::optional<RetainPtr<NSArray *>> cookiesPtr;
74     auto completionHandler = [&cookiesPtr] (NSArray *cookies) { cookiesPtr = retainPtr(cookies); };
75     NSDictionary *policyProperties;
76     if (!sameSiteInfo)
77         policyProperties = nullptr;
78     else {
79         static NSURL *emptyURL = [[NSURL alloc] initWithString:@""];
80         policyProperties = @{
81             @"_kCFHTTPCookiePolicyPropertySiteForCookies": sameSiteInfo->isSameSite ? url : emptyURL,
82             @"_kCFHTTPCookiePolicyPropertyIsTopLevelNavigation": [NSNumber numberWithBool:sameSiteInfo->isTopSite],
83         };
84     }
85     if ([storage respondsToSelector:@selector(_getCookiesForURL:mainDocumentURL:partition:policyProperties:completionHandler:)])
86         [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition policyProperties:policyProperties completionHandler:completionHandler];
87     else
88         [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition completionHandler:completionHandler];
89     UNUSED_PARAM(sameSiteInfo);
90     ASSERT(!!cookiesPtr);
91     return cookiesPtr->autorelease();
92 }
93
94 static void setHTTPCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSArray *cookies, NSURL *url, NSURL *mainDocumentURL, const SameSiteInfo& sameSiteInfo)
95 {
96     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
97     // FIXME: We should not allocate the empty URL. Instead use CFSTR() and convert to NSString. Even better define this constant
98     // somewhere it can be shared.
99     static NSURL *emptyURL = [[NSURL alloc] initWithString:@""];
100     NSDictionary *policyProperties = @{
101         (NSString *)_kCFHTTPCookiePolicyPropertySiteForCookies: sameSiteInfo.isSameSite ? url : emptyURL,
102         (NSString *)_kCFHTTPCookiePolicyPropertyIsTopLevelNavigation: [NSNumber numberWithBool:sameSiteInfo.isTopSite],
103     };
104     if (!cookieStorage) {
105         if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)])
106             [[NSHTTPCookieStorage sharedHTTPCookieStorage] _setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL policyProperties:policyProperties];
107         else
108             [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL];
109         return;
110     }
111     if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)]) {
112         // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
113         // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
114         RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]);
115         [nsCookieStorage _setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL policyProperties:policyProperties];
116     } else {
117         auto cfCookies = adoptCF([NSHTTPCookie _ns2cfCookies:cookies]);
118         CFHTTPCookieStorageSetCookies(cookieStorage, cfCookies.get(), [url _cfurl], [mainDocumentURL _cfurl]);
119     }
120 }
121
122 static NSArray *httpCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSURL *firstParty, const std::optional<SameSiteInfo>& sameSiteInfo, NSURL *url)
123 {
124     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
125     if (!cookieStorage)
126         cookieStorage = _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault);
127
128     // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
129     // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
130     RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]);
131     return cookiesForURL(nsCookieStorage.get(), url, firstParty, sameSiteInfo);
132 }
133
134 static RetainPtr<NSArray> filterCookies(NSArray *unfilteredCookies)
135 {
136     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
137     NSUInteger count = [unfilteredCookies count];
138     RetainPtr<NSMutableArray> filteredCookies = adoptNS([[NSMutableArray alloc] initWithCapacity:count]);
139
140     for (NSUInteger i = 0; i < count; ++i) {
141         NSHTTPCookie *cookie = (NSHTTPCookie *)[unfilteredCookies objectAtIndex:i];
142
143         // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie,
144         // which would be sent as "Cookie: =". We have a workaround in setCookies() to prevent
145         // that, but we also need to avoid sending cookies that were previously stored, and
146         // there's no harm to doing this check because such a cookie is never valid.
147         if (![[cookie name] length])
148             continue;
149
150         if ([cookie isHTTPOnly])
151             continue;
152
153         [filteredCookies.get() addObject:cookie];
154     }
155
156     return filteredCookies;
157 }
158
159 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
160
161 static NSArray *applyPartitionToCookies(NSString *partition, NSArray *cookies)
162 {
163     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
164
165     // FIXME 24747739: CFNetwork should expose this key as SPI
166     static NSString * const partitionKey = @"StoragePartition";
167
168     NSMutableArray *partitionedCookies = [NSMutableArray arrayWithCapacity:cookies.count];
169     for (NSHTTPCookie *cookie in cookies) {
170         RetainPtr<NSMutableDictionary> properties = adoptNS([cookie.properties mutableCopy]);
171         [properties setObject:partition forKey:partitionKey];
172         [partitionedCookies addObject:[NSHTTPCookie cookieWithProperties:properties.get()]];
173     }
174
175     return partitionedCookies;
176 }
177
178 static bool cookiesAreBlockedForURL(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
179 {
180     return session.shouldBlockCookies(firstParty, url);
181 }
182
183 static NSArray *cookiesInPartitionForURL(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID)
184 {
185     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
186     String partition = session.cookieStoragePartition(firstParty, url, frameID, pageID);
187     if (partition.isEmpty())
188         return nil;
189
190     // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
191     // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
192     RetainPtr<NSHTTPCookieStorage> cookieStorage;
193     if (auto storage = session.cookieStorage())
194         cookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:storage.get()]);
195     else
196         cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
197     return cookiesForURL(cookieStorage.get(), url, firstParty, sameSiteInfo, partition);
198 }
199
200 #endif // HAVE(CFNETWORK_STORAGE_PARTITIONING)
201     
202 static NSArray *cookiesForURL(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID)
203 {
204 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
205     if (cookiesAreBlockedForURL(session, firstParty, url))
206         return nil;
207     
208     if (NSArray *cookies = cookiesInPartitionForURL(session, firstParty, sameSiteInfo, url, frameID, pageID))
209         return cookies;
210 #else
211     UNUSED_PARAM(frameID);
212     UNUSED_PARAM(pageID);
213 #endif
214     return httpCookiesForURL(session.cookieStorage().get(), firstParty, sameSiteInfo, url);
215 }
216
217 enum IncludeHTTPOnlyOrNot { DoNotIncludeHTTPOnly, IncludeHTTPOnly };
218 static std::pair<String, bool> cookiesForSession(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeHTTPOnlyOrNot includeHTTPOnly, IncludeSecureCookies includeSecureCookies)
219 {
220     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
221
222     BEGIN_BLOCK_OBJC_EXCEPTIONS;
223
224     NSArray *cookies = cookiesForURL(session, firstParty, sameSiteInfo, url, frameID, pageID);
225     if (![cookies count])
226         return { String(), false }; // Return a null string, not an empty one that StringBuilder would create below.
227
228     StringBuilder cookiesBuilder;
229     bool didAccessSecureCookies = false;
230     for (NSHTTPCookie *cookie in cookies) {
231         if (![[cookie name] length])
232             continue;
233
234         if (!includeHTTPOnly && [cookie isHTTPOnly])
235             continue;
236
237         if ([cookie isSecure]) {
238             didAccessSecureCookies = true;
239             if (includeSecureCookies == IncludeSecureCookies::No)
240                 continue;
241         }
242
243         if (!cookiesBuilder.isEmpty())
244             cookiesBuilder.appendLiteral("; ");
245
246         cookiesBuilder.append([cookie name]);
247         cookiesBuilder.append('=');
248         cookiesBuilder.append([cookie value]);
249     }
250     return { cookiesBuilder.toString(), didAccessSecureCookies };
251
252     END_BLOCK_OBJC_EXCEPTIONS;
253     return { String(), false };
254 }
255
256 static void deleteAllHTTPCookies(CFHTTPCookieStorageRef cookieStorage)
257 {
258     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
259
260     if (!cookieStorage) {
261         NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
262         NSArray *cookies = [cookieStorage cookies];
263         if (!cookies)
264             return;
265
266         for (NSHTTPCookie *cookie in cookies)
267             [cookieStorage deleteCookie:cookie];
268         return;
269     }
270
271     CFHTTPCookieStorageDeleteAllCookies(cookieStorage);
272 }
273
274 std::pair<String, bool> cookiesForDOM(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies includeSecureCookies)
275 {
276     return cookiesForSession(session, firstParty, sameSiteInfo, url, frameID, pageID, DoNotIncludeHTTPOnly, includeSecureCookies);
277 }
278
279 std::pair<String, bool> cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, IncludeSecureCookies includeSecureCookies)
280 {
281     return cookiesForSession(session, firstParty, sameSiteInfo, url, frameID, pageID, IncludeHTTPOnly, includeSecureCookies);
282 }
283
284 std::pair<String, bool> cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const CookieRequestHeaderFieldProxy& headerFieldProxy)
285 {
286     return cookiesForSession(session, headerFieldProxy.firstParty, headerFieldProxy.sameSiteInfo, headerFieldProxy.url, headerFieldProxy.frameID, headerFieldProxy.pageID, IncludeHTTPOnly, headerFieldProxy.includeSecureCookies);
287 }
288
289 void setCookiesFromDOM(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, const String& cookieStr)
290 {
291     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
292
293     BEGIN_BLOCK_OBJC_EXCEPTIONS;
294
295     // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie,
296     // which would be sent as "Cookie: =".
297     if (cookieStr.isEmpty())
298         return;
299
300     // <http://bugs.webkit.org/show_bug.cgi?id=6531>, <rdar://4409034>
301     // cookiesWithResponseHeaderFields doesn't parse cookies without a value
302     String cookieString = cookieStr.contains('=') ? cookieStr : cookieStr + "=";
303
304     NSURL *cookieURL = url;
305     NSDictionary *headerFields = [NSDictionary dictionaryWithObject:cookieString forKey:@"Set-Cookie"];
306
307 #if PLATFORM(MAC)
308     NSArray *unfilteredCookies = [NSHTTPCookie _parsedCookiesWithResponseHeaderFields:headerFields forURL:cookieURL];
309 #else
310     NSArray *unfilteredCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:cookieURL];
311 #endif
312
313     RetainPtr<NSArray> filteredCookies = filterCookies(unfilteredCookies);
314     ASSERT([filteredCookies.get() count] <= 1);
315
316 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
317     if (session.shouldBlockCookies(firstParty, url))
318         return;
319
320     String partition = session.cookieStoragePartition(firstParty, url, frameID, pageID);
321     if (!partition.isEmpty())
322         filteredCookies = applyPartitionToCookies(partition, filteredCookies.get());
323 #else
324     UNUSED_PARAM(frameID);
325     UNUSED_PARAM(pageID);
326 #endif
327
328     setHTTPCookiesForURL(session.cookieStorage().get(), filteredCookies.get(), cookieURL, firstParty, sameSiteInfo);
329
330     END_BLOCK_OBJC_EXCEPTIONS;
331 }
332
333 static NSHTTPCookieAcceptPolicy httpCookieAcceptPolicy(CFHTTPCookieStorageRef cookieStorage)
334 {
335     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
336
337     if (!cookieStorage)
338         return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy];
339
340     return static_cast<NSHTTPCookieAcceptPolicy>(CFHTTPCookieStorageGetCookieAcceptPolicy(cookieStorage));
341 }
342
343 bool cookiesEnabled(const NetworkStorageSession& session)
344 {
345     BEGIN_BLOCK_OBJC_EXCEPTIONS;
346
347     NSHTTPCookieAcceptPolicy cookieAcceptPolicy = httpCookieAcceptPolicy(session.cookieStorage().get());
348     return cookieAcceptPolicy == NSHTTPCookieAcceptPolicyAlways || cookieAcceptPolicy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain || cookieAcceptPolicy == NSHTTPCookieAcceptPolicyExclusivelyFromMainDocumentDomain;
349
350     END_BLOCK_OBJC_EXCEPTIONS;
351     return false;
352 }
353
354 bool getRawCookies(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<uint64_t> frameID, std::optional<uint64_t> pageID, Vector<Cookie>& rawCookies)
355 {
356     rawCookies.clear();
357     BEGIN_BLOCK_OBJC_EXCEPTIONS;
358
359     NSArray *cookies = cookiesForURL(session, firstParty, sameSiteInfo, url, frameID, pageID);
360     NSUInteger count = [cookies count];
361     rawCookies.reserveCapacity(count);
362     for (NSUInteger i = 0; i < count; ++i) {
363         NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];
364         rawCookies.uncheckedAppend({ cookie });
365     }
366
367     END_BLOCK_OBJC_EXCEPTIONS;
368     return true;
369 }
370
371 void deleteCookie(const NetworkStorageSession& session, const URL& url, const String& cookieName)
372 {
373     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
374
375     BEGIN_BLOCK_OBJC_EXCEPTIONS;
376
377     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = session.cookieStorage();
378     NSArray *cookies = httpCookiesForURL(cookieStorage.get(), nil, std::nullopt, url);
379
380     NSString *cookieNameString = cookieName;
381
382     NSUInteger count = [cookies count];
383     for (NSUInteger i = 0; i < count; ++i) {
384         NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];
385         if ([[cookie name] isEqualToString:cookieNameString])
386             deleteHTTPCookie(cookieStorage.get(), cookie);
387     }
388
389     END_BLOCK_OBJC_EXCEPTIONS;
390 }
391
392 void getHostnamesWithCookies(const NetworkStorageSession& session, HashSet<String>& hostnames)
393 {
394     BEGIN_BLOCK_OBJC_EXCEPTIONS;
395
396     NSArray *cookies = httpCookies(session.cookieStorage().get());
397     
398     for (NSHTTPCookie* cookie in cookies)
399         hostnames.add([cookie domain]);
400     
401     END_BLOCK_OBJC_EXCEPTIONS;
402 }
403
404 void deleteAllCookies(const NetworkStorageSession& session)
405 {
406     deleteAllHTTPCookies(session.cookieStorage().get());
407 }
408
409 void deleteCookiesForHostnames(const NetworkStorageSession& session, const Vector<String>& hostnames)
410 {
411     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
412
413     BEGIN_BLOCK_OBJC_EXCEPTIONS;
414
415     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = session.cookieStorage();
416     NSArray *cookies = httpCookies(cookieStorage.get());
417     if (!cookies)
418         return;
419
420     HashMap<String, Vector<RetainPtr<NSHTTPCookie>>> cookiesByDomain;
421     for (NSHTTPCookie* cookie in cookies) {
422         auto& cookies = cookiesByDomain.add(cookie.domain, Vector<RetainPtr<NSHTTPCookie>>()).iterator->value;
423         cookies.append(cookie);
424     }
425
426     for (const auto& hostname : hostnames) {
427         auto it = cookiesByDomain.find(hostname);
428         if (it == cookiesByDomain.end())
429             continue;
430
431         for (auto& cookie : it->value)
432             deleteHTTPCookie(cookieStorage.get(), cookie.get());
433     }
434
435     [session.nsCookieStorage() _saveCookies];
436
437     END_BLOCK_OBJC_EXCEPTIONS;
438 }
439
440 void deleteAllCookiesModifiedSince(const NetworkStorageSession& session, WallTime timePoint)
441 {
442     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
443
444     if (![NSHTTPCookieStorage instancesRespondToSelector:@selector(removeCookiesSinceDate:)])
445         return;
446
447     NSTimeInterval timeInterval = timePoint.secondsSinceEpoch().seconds();
448     NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
449
450     auto *storage = session.nsCookieStorage();
451
452     [storage removeCookiesSinceDate:date];
453     [storage _saveCookies];
454 }
455
456 }