[WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document...
[WebKit.git] / Source / WebCore / platform / network / cocoa / NetworkStorageSessionCocoa.mm
1 /*
2  * Copyright (C) 2015-2020 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 "NetworkStorageSession.h"
28
29 #import "Cookie.h"
30 #import "CookieRequestHeaderFieldProxy.h"
31 #import "CookieStorageObserver.h"
32 #import "HTTPCookieAcceptPolicyCocoa.h"
33 #import "SameSiteInfo.h"
34 #import <pal/spi/cf/CFNetworkSPI.h>
35 #import <wtf/BlockObjCExceptions.h>
36 #import <wtf/Optional.h>
37 #import <wtf/ProcessPrivilege.h>
38 #import <wtf/URL.h>
39 #import <wtf/text/StringBuilder.h>
40
41 @interface NSURL ()
42 - (CFURLRef)_cfurl;
43 @end
44
45 namespace WebCore {
46
47 NetworkStorageSession::~NetworkStorageSession()
48 {
49 #if HAVE(COOKIE_CHANGE_LISTENER_API)
50     unregisterCookieChangeListenersIfNecessary();
51 #endif
52 }
53
54 void NetworkStorageSession::setCookie(const Cookie& cookie)
55 {
56     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
57
58     BEGIN_BLOCK_OBJC_EXCEPTIONS;
59     [nsCookieStorage() setCookie:(NSHTTPCookie *)cookie];
60     END_BLOCK_OBJC_EXCEPTIONS;
61 }
62
63 void NetworkStorageSession::setCookies(const Vector<Cookie>& cookies, const URL& url, const URL& mainDocumentURL)
64 {
65     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
66
67     RetainPtr<NSMutableArray> nsCookies = adoptNS([[NSMutableArray alloc] initWithCapacity:cookies.size()]);
68     for (const auto& cookie : cookies)
69         [nsCookies addObject:(NSHTTPCookie *)cookie];
70
71     BEGIN_BLOCK_OBJC_EXCEPTIONS;
72     [nsCookieStorage() setCookies:nsCookies.get() forURL:(NSURL *)url mainDocumentURL:(NSURL *)mainDocumentURL];
73     END_BLOCK_OBJC_EXCEPTIONS;
74 }
75
76 void NetworkStorageSession::deleteCookie(const Cookie& cookie)
77 {
78     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
79     [nsCookieStorage() deleteCookie:(NSHTTPCookie *)cookie];
80 }
81
82 static Vector<Cookie> nsCookiesToCookieVector(NSArray<NSHTTPCookie *> *nsCookies, const Function<bool(NSHTTPCookie *)>& filter = { })
83 {
84     Vector<Cookie> cookies;
85     cookies.reserveInitialCapacity(nsCookies.count);
86     for (NSHTTPCookie *nsCookie in nsCookies) {
87         if (!filter || filter(nsCookie))
88             cookies.uncheckedAppend(nsCookie);
89     }
90     return cookies;
91 }
92
93 Vector<Cookie> NetworkStorageSession::getAllCookies()
94 {
95     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
96     return nsCookiesToCookieVector(nsCookieStorage().cookies);
97 }
98
99 Vector<Cookie> NetworkStorageSession::getCookies(const URL& url)
100 {
101     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
102     return nsCookiesToCookieVector([nsCookieStorage() cookiesForURL:(NSURL *)url]);
103 }
104
105 void NetworkStorageSession::hasCookies(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler) const
106 {
107     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
108     
109     for (NSHTTPCookie *nsCookie in nsCookieStorage().cookies) {
110         if (RegistrableDomain::uncheckedCreateFromHost(nsCookie.domain) == domain) {
111             completionHandler(true);
112             return;
113         }
114     }
115
116     completionHandler(false);
117 }
118
119 void NetworkStorageSession::flushCookieStore()
120 {
121     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
122     [nsCookieStorage() _saveCookies];
123 }
124
125 NSHTTPCookieStorage *NetworkStorageSession::nsCookieStorage() const
126 {
127     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
128     auto cfCookieStorage = cookieStorage();
129     if (!cfCookieStorage || [NSHTTPCookieStorage sharedHTTPCookieStorage]._cookieStorage == cfCookieStorage)
130         return [NSHTTPCookieStorage sharedHTTPCookieStorage];
131
132     return [[[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cfCookieStorage.get()] autorelease];
133 }
134
135 CookieStorageObserver& NetworkStorageSession::cookieStorageObserver() const
136 {
137     if (!m_cookieStorageObserver)
138         m_cookieStorageObserver = makeUnique<CookieStorageObserver>(nsCookieStorage());
139
140     return *m_cookieStorageObserver;
141 }
142
143 CFURLStorageSessionRef createPrivateStorageSession(CFStringRef identifier)
144 {
145     const void* sessionPropertyKeys[] = { _kCFURLStorageSessionIsPrivate };
146     const void* sessionPropertyValues[] = { kCFBooleanTrue };
147     auto sessionProperties = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, sessionPropertyKeys, sessionPropertyValues, sizeof(sessionPropertyKeys) / sizeof(*sessionPropertyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
148     auto storageSession = adoptCF(_CFURLStorageSessionCreate(kCFAllocatorDefault, identifier, sessionProperties.get()));
149
150     if (!storageSession)
151         return nullptr;
152
153     // The private storage session should have the same properties as the default storage session,
154     // with the exception that it should be in-memory only storage.
155
156     // FIXME 9199649: If any of the storages do not exist, do no use the storage session.
157     // This could occur if there is an issue figuring out where to place a storage on disk (e.g. the
158     // sandbox does not allow CFNetwork access).
159
160     auto cache = adoptCF(_CFURLStorageSessionCopyCache(kCFAllocatorDefault, storageSession.get()));
161     if (!cache)
162         return nullptr;
163
164     CFURLCacheSetDiskCapacity(cache.get(), 0); // Setting disk cache size should not be necessary once <rdar://problem/12656814> is fixed.
165     CFURLCacheSetMemoryCapacity(cache.get(), [[NSURLCache sharedURLCache] memoryCapacity]);
166
167     auto cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, storageSession.get()));
168     if (!cookieStorage)
169         return nullptr;
170
171     // FIXME: Use _CFHTTPCookieStorageGetDefault when USE(CFNETWORK) is defined in WebKit for consistency.
172     CFHTTPCookieStorageSetCookieAcceptPolicy(cookieStorage.get(), [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy]);
173
174     return storageSession.leakRef();
175 }
176
177 NSArray *NetworkStorageSession::httpCookies(CFHTTPCookieStorageRef cookieStorage) const
178 {
179     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
180     if (!cookieStorage) {
181         RELEASE_ASSERT(!m_isInMemoryCookieStore);
182         return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
183     }
184     
185     auto cookies = adoptCF(CFHTTPCookieStorageCopyCookies(cookieStorage));
186     return [NSHTTPCookie _cf2nsCookies:cookies.get()];
187 }
188
189 void NetworkStorageSession::deleteHTTPCookie(CFHTTPCookieStorageRef cookieStorage, NSHTTPCookie *cookie) const
190 {
191     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
192     if (!cookieStorage) {
193         RELEASE_ASSERT(!m_isInMemoryCookieStore);
194         [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
195         return;
196     }
197     
198     CFHTTPCookieStorageDeleteCookie(cookieStorage, [cookie _GetInternalCFHTTPCookie]);
199 }
200
201 #if !(PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101400) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
202 static RetainPtr<NSDictionary> policyProperties(const SameSiteInfo& sameSiteInfo, NSURL *url)
203 {
204     static NSURL *emptyURL = [[NSURL alloc] initWithString:@""];
205     NSDictionary *policyProperties = @{
206         @"_kCFHTTPCookiePolicyPropertySiteForCookies": sameSiteInfo.isSameSite ? url : emptyURL,
207         @"_kCFHTTPCookiePolicyPropertyIsTopLevelNavigation": [NSNumber numberWithBool:sameSiteInfo.isTopSite],
208     };
209     return policyProperties;
210 }
211 #endif
212
213 static NSArray *cookiesForURL(NSHTTPCookieStorage *storage, NSURL *url, NSURL *mainDocumentURL, const Optional<SameSiteInfo>& sameSiteInfo, NSString *partition = nullptr)
214 {
215     // The _getCookiesForURL: method calls the completionHandler synchronously. We use Optional<> to ensure this invariant.
216     Optional<RetainPtr<NSArray>> cookiesPtr;
217     auto completionHandler = [&cookiesPtr] (NSArray *cookies) {
218         cookiesPtr = retainPtr(cookies);
219     };
220 #if !(PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101400) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
221     if ([storage respondsToSelector:@selector(_getCookiesForURL:mainDocumentURL:partition:policyProperties:completionHandler:)])
222         [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition policyProperties:sameSiteInfo ? policyProperties(sameSiteInfo.value(), url).get() : nullptr completionHandler:completionHandler];
223     else
224         [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition completionHandler:completionHandler];
225 #else
226     [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition completionHandler:completionHandler];
227     UNUSED_PARAM(sameSiteInfo);
228 #endif
229     ASSERT(!!cookiesPtr);
230     return cookiesPtr->autorelease();
231 }
232
233 void NetworkStorageSession::setHTTPCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSArray *cookies, NSURL *url, NSURL *mainDocumentURL, const SameSiteInfo& sameSiteInfo) const
234 {
235     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
236     if (!cookieStorage) {
237 #if !(PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101400) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
238         if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)])
239             [[NSHTTPCookieStorage sharedHTTPCookieStorage] _setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL policyProperties:policyProperties(sameSiteInfo, url).get()];
240         else
241 #endif
242             [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL];
243         return;
244     }
245 #if !(PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101400) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
246     if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)]) {
247         // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
248         // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
249         RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]);
250         [nsCookieStorage _setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL policyProperties:policyProperties(sameSiteInfo, url).get()];
251     } else {
252 #endif
253         auto cfCookies = adoptCF([NSHTTPCookie _ns2cfCookies:cookies]);
254         CFHTTPCookieStorageSetCookies(cookieStorage, cfCookies.get(), [url _cfurl], [mainDocumentURL _cfurl]);
255 #if !(PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101400) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
256     }
257 #else
258     UNUSED_PARAM(sameSiteInfo);
259 #endif
260 }
261
262 NSArray *NetworkStorageSession::httpCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSURL *firstParty, const Optional<SameSiteInfo>& sameSiteInfo, NSURL *url) const
263 {
264     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
265     if (!cookieStorage) {
266         RELEASE_ASSERT(!m_isInMemoryCookieStore);
267         cookieStorage = _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault);
268     }
269
270     // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
271     // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
272     RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]);
273     return WebCore::cookiesForURL(nsCookieStorage.get(), url, firstParty, sameSiteInfo);
274 }
275
276 static RetainPtr<NSArray> filterCookies(NSArray *unfilteredCookies, Optional<Seconds> cappedLifetime)
277 {
278     NSUInteger count = [unfilteredCookies count];
279     RetainPtr<NSMutableArray> filteredCookies = adoptNS([[NSMutableArray alloc] initWithCapacity:count]);
280
281     for (NSUInteger i = 0; i < count; ++i) {
282         NSHTTPCookie *cookie = (NSHTTPCookie *)[unfilteredCookies objectAtIndex:i];
283
284         // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie,
285         // which would be sent as "Cookie: =". We have a workaround in setCookies() to prevent
286         // that, but we also need to avoid sending cookies that were previously stored, and
287         // there's no harm to doing this check because such a cookie is never valid.
288         if (![[cookie name] length])
289             continue;
290
291         if ([cookie isHTTPOnly])
292             continue;
293
294         // Cap lifetime of persistent, client-side cookies to a week.
295         if (cappedLifetime && ![cookie isSessionOnly]) {
296             if (!cookie.expiresDate || cookie.expiresDate.timeIntervalSinceNow > cappedLifetime->seconds()) {
297                 RetainPtr<NSMutableDictionary<NSHTTPCookiePropertyKey, id>> properties = adoptNS([[cookie properties] mutableCopy]);
298                 RetainPtr<NSDate> dateInAWeek = adoptNS([[NSDate alloc] initWithTimeIntervalSinceNow:cappedLifetime->seconds()]);
299                 [properties setObject:dateInAWeek.get() forKey:NSHTTPCookieExpires];
300                 cookie = [NSHTTPCookie cookieWithProperties:properties.get()];
301             }
302         }
303
304         [filteredCookies.get() addObject:cookie];
305     }
306
307     return filteredCookies;
308 }
309
310 NSArray *NetworkStorageSession::cookiesForURL(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP) const
311 {
312 #if ENABLE(RESOURCE_LOAD_STATISTICS)
313     if (shouldAskITP == ShouldAskITP::Yes && shouldBlockCookies(firstParty, url, frameID, pageID))
314         return nil;
315 #else
316     UNUSED_PARAM(frameID);
317     UNUSED_PARAM(pageID);
318     UNUSED_PARAM(shouldAskITP);
319 #endif
320     return httpCookiesForURL(cookieStorage().get(), firstParty, sameSiteInfo, url);
321 }
322
323 std::pair<String, bool> NetworkStorageSession::cookiesForSession(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeHTTPOnlyOrNot includeHTTPOnly, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP) const
324 {
325     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
326
327     BEGIN_BLOCK_OBJC_EXCEPTIONS;
328
329     NSArray *cookies = cookiesForURL(firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP);
330     if (![cookies count])
331         return { String(), false }; // Return a null string, not an empty one that StringBuilder would create below.
332
333     StringBuilder cookiesBuilder;
334     bool didAccessSecureCookies = false;
335     for (NSHTTPCookie *cookie in cookies) {
336         if (![[cookie name] length])
337             continue;
338
339         if (!includeHTTPOnly && [cookie isHTTPOnly])
340             continue;
341
342         if ([cookie isSecure]) {
343             didAccessSecureCookies = true;
344             if (includeSecureCookies == IncludeSecureCookies::No)
345                 continue;
346         }
347
348         if (!cookiesBuilder.isEmpty())
349             cookiesBuilder.appendLiteral("; ");
350
351         cookiesBuilder.append([cookie name]);
352         cookiesBuilder.append('=');
353         cookiesBuilder.append([cookie value]);
354     }
355     return { cookiesBuilder.toString(), didAccessSecureCookies };
356
357     END_BLOCK_OBJC_EXCEPTIONS;
358     return { String(), false };
359 }
360
361 static void deleteAllHTTPCookies(CFHTTPCookieStorageRef cookieStorage)
362 {
363     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
364
365     if (!cookieStorage) {
366         NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
367         NSArray *cookies = [cookieStorage cookies];
368         if (!cookies)
369             return;
370
371         for (NSHTTPCookie *cookie in cookies)
372             [cookieStorage deleteCookie:cookie];
373         return;
374     }
375
376     CFHTTPCookieStorageDeleteAllCookies(cookieStorage);
377 }
378
379 std::pair<String, bool> NetworkStorageSession::cookiesForDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP) const
380 {
381     return cookiesForSession(firstParty, sameSiteInfo, url, frameID, pageID, DoNotIncludeHTTPOnly, includeSecureCookies, shouldAskITP);
382 }
383
384 std::pair<String, bool> NetworkStorageSession::cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP) const
385 {
386     return cookiesForSession(firstParty, sameSiteInfo, url, frameID, pageID, IncludeHTTPOnly, includeSecureCookies, shouldAskITP);
387 }
388
389 std::pair<String, bool> NetworkStorageSession::cookieRequestHeaderFieldValue(const CookieRequestHeaderFieldProxy& headerFieldProxy) const
390 {
391     return cookiesForSession(headerFieldProxy.firstParty, headerFieldProxy.sameSiteInfo, headerFieldProxy.url, headerFieldProxy.frameID, headerFieldProxy.pageID, IncludeHTTPOnly, headerFieldProxy.includeSecureCookies);
392 }
393
394 void NetworkStorageSession::setCookiesFromDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP, const String& cookieStr) const
395 {
396     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
397
398     BEGIN_BLOCK_OBJC_EXCEPTIONS;
399
400 #if ENABLE(RESOURCE_LOAD_STATISTICS)
401     if (shouldAskITP == ShouldAskITP::Yes && shouldBlockCookies(firstParty, url, frameID, pageID))
402         return;
403 #else
404     UNUSED_PARAM(frameID);
405     UNUSED_PARAM(pageID);
406     UNUSED_PARAM(shouldAskITP);
407 #endif
408
409     // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie,
410     // which would be sent as "Cookie: =".
411     if (cookieStr.isEmpty())
412         return;
413
414     // <http://bugs.webkit.org/show_bug.cgi?id=6531>, <rdar://4409034>
415     // cookiesWithResponseHeaderFields doesn't parse cookies without a value
416     String cookieString = cookieStr.contains('=') ? cookieStr : cookieStr + "=";
417
418     NSURL *cookieURL = url;
419     NSDictionary *headerFields = [NSDictionary dictionaryWithObject:cookieString forKey:@"Set-Cookie"];
420
421 #if PLATFORM(MAC)
422     NSArray *unfilteredCookies = [NSHTTPCookie _parsedCookiesWithResponseHeaderFields:headerFields forURL:cookieURL];
423 #else
424     NSArray *unfilteredCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:cookieURL];
425 #endif
426
427 #if ENABLE(RESOURCE_LOAD_STATISTICS)
428     RetainPtr<NSArray> filteredCookies = filterCookies(unfilteredCookies, clientSideCookieCap(RegistrableDomain { firstParty }, pageID));
429 #else
430     RetainPtr<NSArray> filteredCookies = filterCookies(unfilteredCookies, WTF::nullopt);
431 #endif
432     ASSERT([filteredCookies.get() count] <= 1);
433
434     setHTTPCookiesForURL(cookieStorage().get(), filteredCookies.get(), cookieURL, firstParty, sameSiteInfo);
435
436     END_BLOCK_OBJC_EXCEPTIONS;
437 }
438
439 static NSHTTPCookieAcceptPolicy httpCookieAcceptPolicy(CFHTTPCookieStorageRef cookieStorage)
440 {
441     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
442
443     if (!cookieStorage)
444         return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy];
445
446     return static_cast<NSHTTPCookieAcceptPolicy>(CFHTTPCookieStorageGetCookieAcceptPolicy(cookieStorage));
447 }
448
449 HTTPCookieAcceptPolicy NetworkStorageSession::cookieAcceptPolicy() const
450 {
451     BEGIN_BLOCK_OBJC_EXCEPTIONS;
452     auto policy = httpCookieAcceptPolicy(cookieStorage().get());
453     return toHTTPCookieAcceptPolicy(policy);
454     END_BLOCK_OBJC_EXCEPTIONS;
455
456     return HTTPCookieAcceptPolicy::Never;
457 }
458
459 bool NetworkStorageSession::getRawCookies(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP, Vector<Cookie>& rawCookies) const
460 {
461     rawCookies.clear();
462     BEGIN_BLOCK_OBJC_EXCEPTIONS;
463
464     NSArray *cookies = cookiesForURL(firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP);
465     NSUInteger count = [cookies count];
466     rawCookies.reserveCapacity(count);
467     for (NSUInteger i = 0; i < count; ++i) {
468         NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];
469         rawCookies.uncheckedAppend({ cookie });
470     }
471
472     END_BLOCK_OBJC_EXCEPTIONS;
473     return true;
474 }
475
476 void NetworkStorageSession::deleteCookie(const URL& url, const String& cookieName) const
477 {
478     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
479
480     BEGIN_BLOCK_OBJC_EXCEPTIONS;
481
482     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = this->cookieStorage();
483     NSArray *cookies = httpCookiesForURL(cookieStorage.get(), nil, WTF::nullopt, url);
484
485     NSString *cookieNameString = cookieName;
486
487     NSUInteger count = [cookies count];
488     for (NSUInteger i = 0; i < count; ++i) {
489         NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];
490         if ([[cookie name] isEqualToString:cookieNameString])
491             deleteHTTPCookie(cookieStorage.get(), cookie);
492     }
493
494     END_BLOCK_OBJC_EXCEPTIONS;
495 }
496
497 void NetworkStorageSession::getHostnamesWithCookies(HashSet<String>& hostnames)
498 {
499     BEGIN_BLOCK_OBJC_EXCEPTIONS;
500
501     NSArray *cookies = httpCookies(cookieStorage().get());
502     
503     for (NSHTTPCookie* cookie in cookies) {
504         if (NSString *domain = [cookie domain])
505             hostnames.add(domain);
506         else
507             ASSERT_NOT_REACHED();
508     }
509     
510     END_BLOCK_OBJC_EXCEPTIONS;
511 }
512
513 void NetworkStorageSession::deleteAllCookies()
514 {
515     deleteAllHTTPCookies(cookieStorage().get());
516 }
517
518 void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames)
519 {
520     deleteCookiesForHostnames(hostnames, IncludeHttpOnlyCookies::Yes);
521 }
522
523 void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames, IncludeHttpOnlyCookies includeHttpOnlyCookies)
524 {
525     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
526
527     BEGIN_BLOCK_OBJC_EXCEPTIONS;
528
529     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = this->cookieStorage();
530     NSArray *cookies = httpCookies(cookieStorage.get());
531     if (!cookies)
532         return;
533
534     HashMap<String, Vector<RetainPtr<NSHTTPCookie>>> cookiesByDomain;
535     for (NSHTTPCookie *cookie in cookies) {
536         if (!cookie.domain || (includeHttpOnlyCookies == IncludeHttpOnlyCookies::No && cookie.isHTTPOnly))
537             continue;
538         cookiesByDomain.ensure(cookie.domain, [] {
539             return Vector<RetainPtr<NSHTTPCookie>>();
540         }).iterator->value.append(cookie);
541     }
542
543     for (const auto& hostname : hostnames) {
544         auto it = cookiesByDomain.find(hostname);
545         if (it == cookiesByDomain.end())
546             continue;
547
548         for (auto& cookie : it->value)
549             deleteHTTPCookie(cookieStorage.get(), cookie.get());
550     }
551
552     [nsCookieStorage() _saveCookies];
553
554     END_BLOCK_OBJC_EXCEPTIONS;
555 }
556
557 void NetworkStorageSession::deleteAllCookiesModifiedSince(WallTime timePoint)
558 {
559     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
560
561     if (![NSHTTPCookieStorage instancesRespondToSelector:@selector(removeCookiesSinceDate:)])
562         return;
563
564     NSTimeInterval timeInterval = timePoint.secondsSinceEpoch().seconds();
565     NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
566
567     auto *storage = nsCookieStorage();
568
569     [storage removeCookiesSinceDate:date];
570     [storage _saveCookies];
571 }
572
573 Vector<Cookie> NetworkStorageSession::domCookiesForHost(const String& host)
574 {
575     NSArray *nsCookies = [nsCookieStorage() _getCookiesForDomain:(NSString *)host];
576     return nsCookiesToCookieVector(nsCookies, [](NSHTTPCookie *cookie) { return !cookie.HTTPOnly; });
577 }
578
579 #if HAVE(COOKIE_CHANGE_LISTENER_API)
580
581 void NetworkStorageSession::registerCookieChangeListenersIfNecessary()
582 {
583     if (m_didRegisterCookieListeners)
584         return;
585
586     m_didRegisterCookieListeners = true;
587     [nsCookieStorage() _setCookiesAddedHandler:^(NSArray<NSHTTPCookie *> * nsCookies, NSURL *urlForAddedCookies) {
588         Vector<Cookie> cookies = nsCookiesToCookieVector(nsCookies);
589         auto host = URL(urlForAddedCookies).host().toString();
590         RELEASE_ASSERT(!host.isNull());
591         auto it = m_cookieChangeObservers.find(host);
592         if (it == m_cookieChangeObservers.end())
593             return;
594         for (auto* observer : it->value)
595             observer->cookiesAdded(host, cookies);
596     } onQueue:dispatch_get_main_queue()];
597     [nsCookieStorage() _setCookiesDeletedHandler:^(NSArray<NSHTTPCookie *> *, bool /*deletedAllCookies*/) {
598         for (auto& observers : m_cookieChangeObservers.values()) {
599             for (auto* observer : observers)
600                 observer->cookiesDeleted();
601         }
602     } onQueue:dispatch_get_main_queue()];
603 }
604
605 void NetworkStorageSession::unregisterCookieChangeListenersIfNecessary()
606 {
607     if (!m_didRegisterCookieListeners)
608         return;
609
610     [nsCookieStorage() _setCookiesAddedHandler:nil onQueue:nil];
611     [nsCookieStorage() _setCookiesDeletedHandler:nil onQueue:nil];
612     [nsCookieStorage() _setSubscribedDomainsForCookieChanges:nil];
613     m_didRegisterCookieListeners = false;
614 }
615
616 void NetworkStorageSession::startListeningForCookieChangeNotifications(CookieChangeObserver& observer, const String& host)
617 {
618     registerCookieChangeListenersIfNecessary();
619
620     auto& observers = m_cookieChangeObservers.ensure(host, [] {
621         return HashSet<CookieChangeObserver*> { };
622     }).iterator->value;
623     ASSERT(!observers.contains(&observer));
624     observers.add(&observer);
625
626     if (!m_subscribedDomainsForCookieChanges)
627         m_subscribedDomainsForCookieChanges = adoptNS([[NSMutableSet alloc] init]);
628     else if ([m_subscribedDomainsForCookieChanges containsObject:(NSString *)host])
629         return;
630
631     [m_subscribedDomainsForCookieChanges addObject:(NSString *)host];
632     [nsCookieStorage() _setSubscribedDomainsForCookieChanges:m_subscribedDomainsForCookieChanges.get()];
633 }
634
635 void NetworkStorageSession::stopListeningForCookieChangeNotifications(CookieChangeObserver& observer, const HashSet<String>& hosts)
636 {
637     bool subscribedURLsChanged = false;
638     for (auto& host : hosts) {
639         auto it = m_cookieChangeObservers.find(host);
640         ASSERT(it != m_cookieChangeObservers.end());
641         if (it == m_cookieChangeObservers.end())
642             continue;
643
644         auto& observers = it->value;
645         ASSERT(observers.contains(&observer));
646         observers.remove(&observer);
647         if (observers.isEmpty()) {
648             m_cookieChangeObservers.remove(it);
649             ASSERT([m_subscribedDomainsForCookieChanges containsObject:(NSString *)host]);
650             [m_subscribedDomainsForCookieChanges removeObject:(NSString *)host];
651             subscribedURLsChanged = true;
652         }
653     }
654     if (subscribedURLsChanged)
655         [nsCookieStorage() _setSubscribedDomainsForCookieChanges:m_subscribedDomainsForCookieChanges.get()];
656 }
657
658 // FIXME: This can eventually go away, this is merely to ensure a smooth transition to the new API.
659 bool NetworkStorageSession::supportsCookieChangeListenerAPI() const
660 {
661     static const bool supportsAPI = [nsCookieStorage() respondsToSelector:@selector(_setSubscribedDomainsForCookieChanges:)];
662     return supportsAPI;
663 }
664
665 #endif // HAVE(COOKIE_CHANGE_LISTENER_API)
666
667 } // namespace WebCore