| /* |
| * Copyright (C) 2015-2020 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "NetworkStorageSession.h" |
| |
| #import "Cookie.h" |
| #import "CookieRequestHeaderFieldProxy.h" |
| #import "CookieStorageObserver.h" |
| #import "HTTPCookieAcceptPolicyCocoa.h" |
| #import "SameSiteInfo.h" |
| #import <pal/spi/cf/CFNetworkSPI.h> |
| #import <wtf/BlockObjCExceptions.h> |
| #import <wtf/BlockPtr.h> |
| #import <wtf/ProcessPrivilege.h> |
| #import <wtf/URL.h> |
| #import <wtf/cocoa/VectorCocoa.h> |
| #import <wtf/text/StringBuilder.h> |
| #import <wtf/text/cf/StringConcatenateCF.h> |
| |
| @interface NSURL () |
| - (CFURLRef)_cfurl; |
| @end |
| |
| namespace WebCore { |
| |
| NetworkStorageSession::~NetworkStorageSession() |
| { |
| #if HAVE(COOKIE_CHANGE_LISTENER_API) |
| unregisterCookieChangeListenersIfNecessary(); |
| #endif |
| } |
| |
| void NetworkStorageSession::setCookie(const Cookie& cookie) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| [nsCookieStorage() setCookie:(NSHTTPCookie *)cookie]; |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void NetworkStorageSession::setCookies(const Vector<Cookie>& cookies, const URL& url, const URL& mainDocumentURL) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| |
| auto nsCookies = createNSArray(cookies, [] (auto& cookie) -> NSHTTPCookie * { |
| return cookie; |
| }); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| [nsCookieStorage() setCookies:nsCookies.get() forURL:(NSURL *)url mainDocumentURL:(NSURL *)mainDocumentURL]; |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void NetworkStorageSession::deleteCookie(const Cookie& cookie) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| [nsCookieStorage() deleteCookie:(NSHTTPCookie *)cookie]; |
| } |
| |
| static Vector<Cookie> nsCookiesToCookieVector(NSArray<NSHTTPCookie *> *nsCookies, const Function<bool(NSHTTPCookie *)>& filter = { }) |
| { |
| Vector<Cookie> cookies; |
| cookies.reserveInitialCapacity(nsCookies.count); |
| for (NSHTTPCookie *nsCookie in nsCookies) { |
| if (!filter || filter(nsCookie)) |
| cookies.uncheckedAppend(nsCookie); |
| } |
| return cookies; |
| } |
| |
| Vector<Cookie> NetworkStorageSession::getAllCookies() |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| return nsCookiesToCookieVector(nsCookieStorage().cookies); |
| } |
| |
| Vector<Cookie> NetworkStorageSession::getCookies(const URL& url) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| return nsCookiesToCookieVector([nsCookieStorage() cookiesForURL:(NSURL *)url]); |
| } |
| |
| void NetworkStorageSession::hasCookies(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| |
| for (NSHTTPCookie *nsCookie in nsCookieStorage().cookies) { |
| if (RegistrableDomain::uncheckedCreateFromHost(nsCookie.domain) == domain) { |
| completionHandler(true); |
| return; |
| } |
| } |
| |
| completionHandler(false); |
| } |
| |
| void NetworkStorageSession::setAllCookiesToSameSiteStrict(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| |
| RetainPtr<NSMutableArray<NSHTTPCookie *>> oldCookiesToDelete = adoptNS([[NSMutableArray alloc] init]); |
| RetainPtr<NSMutableArray<NSHTTPCookie *>> newCookiesToAdd = adoptNS([[NSMutableArray alloc] init]); |
| |
| for (NSHTTPCookie *nsCookie in nsCookieStorage().cookies) { |
| if (RegistrableDomain::uncheckedCreateFromHost(nsCookie.domain) == domain && nsCookie.sameSitePolicy != NSHTTPCookieSameSiteStrict) { |
| [oldCookiesToDelete addObject:nsCookie]; |
| RetainPtr<NSMutableDictionary<NSHTTPCookiePropertyKey, id>> mutableProperties = adoptNS([[nsCookie properties] mutableCopy]); |
| mutableProperties.get()[NSHTTPCookieSameSitePolicy] = NSHTTPCookieSameSiteStrict; |
| NSHTTPCookie *strictCookie = [NSHTTPCookie cookieWithProperties:mutableProperties.get()]; |
| [newCookiesToAdd addObject:strictCookie]; |
| } |
| } |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| for (NSHTTPCookie *oldCookie in oldCookiesToDelete.get()) |
| deleteHTTPCookie(cookieStorage().get(), oldCookie); |
| |
| for (NSHTTPCookie *newCookie in newCookiesToAdd.get()) |
| [nsCookieStorage() setCookie:newCookie]; |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| completionHandler(); |
| } |
| |
| void NetworkStorageSession::flushCookieStore() |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| [nsCookieStorage() _saveCookies]; |
| } |
| |
| NSHTTPCookieStorage *NetworkStorageSession::nsCookieStorage() const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| auto cfCookieStorage = cookieStorage(); |
| ASSERT(cfCookieStorage || !m_isInMemoryCookieStore); |
| if (!m_isInMemoryCookieStore && (!cfCookieStorage || [NSHTTPCookieStorage sharedHTTPCookieStorage]._cookieStorage == cfCookieStorage)) |
| return [NSHTTPCookieStorage sharedHTTPCookieStorage]; |
| |
| return adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cfCookieStorage.get()]).autorelease(); |
| } |
| |
| CookieStorageObserver& NetworkStorageSession::cookieStorageObserver() const |
| { |
| if (!m_cookieStorageObserver) |
| m_cookieStorageObserver = makeUnique<CookieStorageObserver>(nsCookieStorage()); |
| |
| return *m_cookieStorageObserver; |
| } |
| |
| RetainPtr<CFURLStorageSessionRef> createPrivateStorageSession(CFStringRef identifier, std::optional<HTTPCookieAcceptPolicy> cookieAcceptPolicy, NetworkStorageSession::ShouldDisableCFURLCache shouldDisableCFURLCache) |
| { |
| const void* sessionPropertyKeys[] = { _kCFURLStorageSessionIsPrivate }; |
| const void* sessionPropertyValues[] = { kCFBooleanTrue }; |
| auto sessionProperties = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, sessionPropertyKeys, sessionPropertyValues, sizeof(sessionPropertyKeys) / sizeof(*sessionPropertyKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| auto storageSession = adoptCF(_CFURLStorageSessionCreate(kCFAllocatorDefault, identifier, sessionProperties.get())); |
| |
| if (!storageSession) |
| return nullptr; |
| |
| if (shouldDisableCFURLCache == NetworkStorageSession::ShouldDisableCFURLCache::Yes) { |
| #if HAVE(CFNETWORK_DISABLE_CACHE_SPI) |
| _CFURLStorageSessionDisableCache(storageSession.get()); |
| #else |
| shouldDisableCFURLCache = NetworkStorageSession::ShouldDisableCFURLCache::No; |
| #endif |
| } |
| |
| // The private storage session should have the same properties as the default storage session, |
| // with the exception that it should be in-memory only storage. |
| |
| // FIXME 9199649: If any of the storages do not exist, do no use the storage session. |
| // This could occur if there is an issue figuring out where to place a storage on disk (e.g. the |
| // sandbox does not allow CFNetwork access). |
| |
| if (shouldDisableCFURLCache == NetworkStorageSession::ShouldDisableCFURLCache::No) { |
| auto cache = adoptCF(_CFURLStorageSessionCopyCache(kCFAllocatorDefault, storageSession.get())); |
| if (!cache) |
| return nullptr; |
| |
| CFURLCacheSetMemoryCapacity(cache.get(), [[NSURLCache sharedURLCache] memoryCapacity]); |
| } |
| |
| auto cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, storageSession.get())); |
| if (!cookieStorage) |
| return nullptr; |
| |
| NSHTTPCookieAcceptPolicy nsCookieAcceptPolicy; |
| if (cookieAcceptPolicy) |
| nsCookieAcceptPolicy = toNSHTTPCookieAcceptPolicy(*cookieAcceptPolicy); |
| else |
| nsCookieAcceptPolicy = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy]; |
| |
| // FIXME: Use _CFHTTPCookieStorageGetDefault when USE(CFNETWORK) is defined in WebKit for consistency. |
| CFHTTPCookieStorageSetCookieAcceptPolicy(cookieStorage.get(), nsCookieAcceptPolicy); |
| |
| return storageSession; |
| } |
| |
| RetainPtr<NSArray> NetworkStorageSession::httpCookies(CFHTTPCookieStorageRef cookieStorage) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| if (!cookieStorage) { |
| RELEASE_ASSERT(!m_isInMemoryCookieStore); |
| return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; |
| } |
| |
| auto cookies = adoptCF(CFHTTPCookieStorageCopyCookies(cookieStorage)); |
| return [NSHTTPCookie _cf2nsCookies:cookies.get()]; |
| } |
| |
| void NetworkStorageSession::deleteHTTPCookie(CFHTTPCookieStorageRef cookieStorage, NSHTTPCookie *cookie) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| if (!cookieStorage) { |
| RELEASE_ASSERT(!m_isInMemoryCookieStore); |
| [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; |
| return; |
| } |
| |
| CFHTTPCookieStorageDeleteCookie(cookieStorage, [cookie _GetInternalCFHTTPCookie]); |
| } |
| |
| #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| static RetainPtr<NSDictionary> policyProperties(const SameSiteInfo& sameSiteInfo, NSURL *url) |
| { |
| static NSURL *emptyURL = [[NSURL alloc] initWithString:@""]; |
| NSDictionary *policyProperties = @{ |
| @"_kCFHTTPCookiePolicyPropertySiteForCookies": sameSiteInfo.isSameSite ? url : emptyURL, |
| @"_kCFHTTPCookiePolicyPropertyIsTopLevelNavigation": [NSNumber numberWithBool:sameSiteInfo.isTopSite], |
| }; |
| return policyProperties; |
| } |
| #endif |
| |
| static RetainPtr<NSArray> cookiesForURL(NSHTTPCookieStorage *storage, NSURL *url, NSURL *mainDocumentURL, const std::optional<SameSiteInfo>& sameSiteInfo, NSString *partition = nullptr) |
| { |
| // The _getCookiesForURL: method calls the completionHandler synchronously. We use std::optional<> to check this invariant and crash if it's not met. |
| std::optional<RetainPtr<NSArray>> cookiesPtr; |
| auto completionHandler = [&cookiesPtr] (NSArray *cookies) { |
| cookiesPtr = retainPtr(cookies); |
| }; |
| // FIXME: Seems like this newer code path can be used for watchOS and tvOS too. |
| #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| if ([storage respondsToSelector:@selector(_getCookiesForURL:mainDocumentURL:partition:policyProperties:completionHandler:)]) |
| [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition policyProperties:sameSiteInfo ? policyProperties(sameSiteInfo.value(), url).get() : nullptr completionHandler:completionHandler]; |
| else |
| [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition completionHandler:completionHandler]; |
| #else |
| [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition completionHandler:completionHandler]; |
| UNUSED_PARAM(sameSiteInfo); |
| #endif |
| RELEASE_ASSERT(!!cookiesPtr); |
| return WTFMove(*cookiesPtr); |
| } |
| |
| void NetworkStorageSession::setHTTPCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSArray *cookies, NSURL *url, NSURL *mainDocumentURL, const SameSiteInfo& sameSiteInfo) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| if (!cookieStorage) { |
| // FIXME: Seems like this newer code path can be used for watchOS and tvOS too. |
| #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)]) |
| [[NSHTTPCookieStorage sharedHTTPCookieStorage] _setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL policyProperties:policyProperties(sameSiteInfo, url).get()]; |
| else |
| #endif |
| [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL]; |
| return; |
| } |
| // FIXME: Seems like this newer code path can be used for watchOS and tvOS too. |
| #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)]) { |
| // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar. |
| // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us. |
| RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]); |
| [nsCookieStorage _setCookies:cookies forURL:url mainDocumentURL:mainDocumentURL policyProperties:policyProperties(sameSiteInfo, url).get()]; |
| } else { |
| #endif |
| auto cfCookies = adoptCF([NSHTTPCookie _ns2cfCookies:cookies]); |
| CFHTTPCookieStorageSetCookies(cookieStorage, cfCookies.get(), [url _cfurl], [mainDocumentURL _cfurl]); |
| #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| } |
| #else |
| UNUSED_PARAM(sameSiteInfo); |
| #endif |
| } |
| |
| RetainPtr<NSArray> NetworkStorageSession::httpCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSURL *firstParty, const std::optional<SameSiteInfo>& sameSiteInfo, NSURL *url) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| if (!cookieStorage) { |
| RELEASE_ASSERT(!m_isInMemoryCookieStore); |
| cookieStorage = _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault); |
| } |
| |
| // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar. |
| // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us. |
| RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]); |
| return WebCore::cookiesForURL(nsCookieStorage.get(), url, firstParty, sameSiteInfo); |
| } |
| |
| NSHTTPCookie *NetworkStorageSession::capExpiryOfPersistentCookie(NSHTTPCookie *cookie, Seconds cap) |
| { |
| if ([cookie isSessionOnly]) |
| return cookie; |
| |
| if (!cookie.expiresDate || cookie.expiresDate.timeIntervalSinceNow > cap.seconds()) { |
| auto properties = adoptNS([[cookie properties] mutableCopy]); |
| auto date = adoptNS([[NSDate alloc] initWithTimeIntervalSinceNow:cap.seconds()]); |
| [properties setObject:date.get() forKey:NSHTTPCookieExpires]; |
| cookie = [NSHTTPCookie cookieWithProperties:properties.get()]; |
| } |
| return cookie; |
| } |
| |
| RetainPtr<NSArray> NetworkStorageSession::cookiesForURL(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking) const |
| { |
| #if ENABLE(INTELLIGENT_TRACKING_PREVENTION) |
| if (shouldAskITP == ShouldAskITP::Yes && shouldBlockCookies(firstParty, url, frameID, pageID, shouldRelaxThirdPartyCookieBlocking)) |
| return nil; |
| #else |
| UNUSED_PARAM(frameID); |
| UNUSED_PARAM(pageID); |
| UNUSED_PARAM(shouldAskITP); |
| #endif |
| return httpCookiesForURL(cookieStorage().get(), firstParty, sameSiteInfo, url); |
| } |
| |
| std::pair<String, bool> NetworkStorageSession::cookiesForSession(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, IncludeHTTPOnlyOrNot includeHTTPOnly, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| auto cookies = cookiesForURL(firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP, shouldRelaxThirdPartyCookieBlocking); |
| if (![cookies count]) |
| return { String(), false }; // Return a null string; StringBuilder below would create an empty one. |
| |
| StringBuilder cookiesBuilder; |
| bool didAccessSecureCookies = false; |
| for (NSHTTPCookie *cookie in cookies.get()) { |
| if (![[cookie name] length]) |
| continue; |
| if (!includeHTTPOnly && [cookie isHTTPOnly]) |
| continue; |
| if ([cookie isSecure]) { |
| didAccessSecureCookies = true; |
| if (includeSecureCookies == IncludeSecureCookies::No) |
| continue; |
| } |
| cookiesBuilder.append(cookiesBuilder.isEmpty() ? "" : "; ", [cookie name], '=', [cookie value]); |
| } |
| return { cookiesBuilder.toString(), didAccessSecureCookies }; |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| return { String(), false }; |
| } |
| |
| static void deleteAllHTTPCookies(CFHTTPCookieStorageRef cookieStorage) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| |
| if (!cookieStorage) { |
| NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; |
| NSArray *cookies = [cookieStorage cookies]; |
| if (!cookies) |
| return; |
| |
| for (NSHTTPCookie *cookie in cookies) |
| [cookieStorage deleteCookie:cookie]; |
| return; |
| } |
| |
| CFHTTPCookieStorageDeleteAllCookies(cookieStorage); |
| } |
| |
| std::pair<String, bool> NetworkStorageSession::cookiesForDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking) const |
| { |
| return cookiesForSession(firstParty, sameSiteInfo, url, frameID, pageID, DoNotIncludeHTTPOnly, includeSecureCookies, shouldAskITP, shouldRelaxThirdPartyCookieBlocking); |
| } |
| |
| std::pair<String, bool> NetworkStorageSession::cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking) const |
| { |
| return cookiesForSession(firstParty, sameSiteInfo, url, frameID, pageID, IncludeHTTPOnly, includeSecureCookies, shouldAskITP, shouldRelaxThirdPartyCookieBlocking); |
| } |
| |
| std::pair<String, bool> NetworkStorageSession::cookieRequestHeaderFieldValue(const CookieRequestHeaderFieldProxy& headerFieldProxy) const |
| { |
| return cookiesForSession(headerFieldProxy.firstParty, headerFieldProxy.sameSiteInfo, headerFieldProxy.url, headerFieldProxy.frameID, headerFieldProxy.pageID, IncludeHTTPOnly, headerFieldProxy.includeSecureCookies, ShouldAskITP::Yes, ShouldRelaxThirdPartyCookieBlocking::No); |
| } |
| |
| static NSHTTPCookie *parseDOMCookie(String cookieString, NSURL* cookieURL, std::optional<Seconds> cappedLifetime) |
| { |
| // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie, |
| // which would be sent as "Cookie: =". |
| if (cookieString.isEmpty()) |
| return nil; |
| |
| // <http://bugs.webkit.org/show_bug.cgi?id=6531>, <rdar://4409034> |
| // cookiesWithResponseHeaderFields doesn't parse cookies without a value |
| cookieString = cookieString.contains('=') ? cookieString : cookieString + "="; |
| |
| NSHTTPCookie *cookie = [NSHTTPCookie _cookieForSetCookieString:cookieString forURL:cookieURL partition:nil]; |
| if (!cookie) |
| return nil; |
| |
| // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie, |
| // which would be sent as "Cookie: =". We have a workaround in setCookies() to prevent |
| // that, but we also need to avoid sending cookies that were previously stored, and |
| // there's no harm to doing this check because such a cookie is never valid. |
| if (![[cookie name] length]) |
| return nil; |
| |
| if ([cookie isHTTPOnly]) |
| return nil; |
| |
| // Cap lifetime of persistent, client-side cookies. |
| if (cappedLifetime) |
| return NetworkStorageSession::capExpiryOfPersistentCookie(cookie, *cappedLifetime); |
| |
| return cookie; |
| } |
| |
| void NetworkStorageSession::setCookiesFromDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP, const String& cookieString, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| #if ENABLE(INTELLIGENT_TRACKING_PREVENTION) |
| if (shouldAskITP == ShouldAskITP::Yes && shouldBlockCookies(firstParty, url, frameID, pageID, shouldRelaxThirdPartyCookieBlocking)) |
| return; |
| #else |
| UNUSED_PARAM(frameID); |
| UNUSED_PARAM(pageID); |
| UNUSED_PARAM(shouldAskITP); |
| #endif |
| |
| NSURL *cookieURL = url; |
| |
| std::optional<Seconds> cookieCap; |
| #if ENABLE(INTELLIGENT_TRACKING_PREVENTION) |
| cookieCap = clientSideCookieCap(RegistrableDomain { firstParty }, pageID); |
| #endif |
| |
| NSHTTPCookie *cookie = parseDOMCookie(cookieString, cookieURL, cookieCap); |
| if (!cookie) |
| return; |
| |
| setHTTPCookiesForURL(cookieStorage().get(), @[cookie], cookieURL, firstParty, sameSiteInfo); |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| static NSHTTPCookieAcceptPolicy httpCookieAcceptPolicy(CFHTTPCookieStorageRef cookieStorage) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| |
| if (!cookieStorage) |
| return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy]; |
| |
| return static_cast<NSHTTPCookieAcceptPolicy>(CFHTTPCookieStorageGetCookieAcceptPolicy(cookieStorage)); |
| } |
| |
| HTTPCookieAcceptPolicy NetworkStorageSession::cookieAcceptPolicy() const |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| auto policy = httpCookieAcceptPolicy(cookieStorage().get()); |
| return toHTTPCookieAcceptPolicy(policy); |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| return HTTPCookieAcceptPolicy::Never; |
| } |
| |
| bool NetworkStorageSession::getRawCookies(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, Vector<Cookie>& rawCookies) const |
| { |
| rawCookies.clear(); |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| RetainPtr<NSArray> cookies = cookiesForURL(firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP, shouldRelaxThirdPartyCookieBlocking); |
| NSUInteger count = [cookies count]; |
| rawCookies.reserveCapacity(count); |
| for (NSUInteger i = 0; i < count; ++i) { |
| NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i]; |
| rawCookies.uncheckedAppend({ cookie }); |
| } |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| return true; |
| } |
| |
| void NetworkStorageSession::deleteCookie(const URL& url, const String& cookieName) const |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| RetainPtr<CFHTTPCookieStorageRef> cookieStorage = this->cookieStorage(); |
| RetainPtr<NSArray> cookies = httpCookiesForURL(cookieStorage.get(), nil, std::nullopt, url); |
| |
| NSString *cookieNameString = cookieName; |
| |
| NSUInteger count = [cookies count]; |
| for (NSUInteger i = 0; i < count; ++i) { |
| NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i]; |
| if ([[cookie name] isEqualToString:cookieNameString]) |
| deleteHTTPCookie(cookieStorage.get(), cookie); |
| } |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void NetworkStorageSession::getHostnamesWithCookies(HashSet<String>& hostnames) |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| RetainPtr<NSArray> cookies = httpCookies(cookieStorage().get()); |
| |
| for (NSHTTPCookie* cookie in cookies.get()) { |
| if (NSString *domain = [cookie domain]) |
| hostnames.add(domain); |
| else |
| ASSERT_NOT_REACHED(); |
| } |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void NetworkStorageSession::deleteAllCookies() |
| { |
| deleteAllHTTPCookies(cookieStorage().get()); |
| } |
| |
| void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames) |
| { |
| deleteCookiesForHostnames(hostnames, IncludeHttpOnlyCookies::Yes); |
| } |
| |
| void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames, IncludeHttpOnlyCookies includeHttpOnlyCookies) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| RetainPtr<CFHTTPCookieStorageRef> cookieStorage = this->cookieStorage(); |
| RetainPtr<NSArray> cookies = httpCookies(cookieStorage.get()); |
| if (!cookies) |
| return; |
| |
| HashMap<String, Vector<RetainPtr<NSHTTPCookie>>> cookiesByDomain; |
| for (NSHTTPCookie *cookie in cookies.get()) { |
| if (!cookie.domain || (includeHttpOnlyCookies == IncludeHttpOnlyCookies::No && cookie.isHTTPOnly)) |
| continue; |
| cookiesByDomain.ensure(cookie.domain, [] { |
| return Vector<RetainPtr<NSHTTPCookie>>(); |
| }).iterator->value.append(cookie); |
| } |
| |
| for (const auto& hostname : hostnames) { |
| auto it = cookiesByDomain.find(hostname); |
| if (it == cookiesByDomain.end()) |
| continue; |
| |
| for (auto& cookie : it->value) |
| deleteHTTPCookie(cookieStorage.get(), cookie.get()); |
| } |
| |
| [nsCookieStorage() _saveCookies]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void NetworkStorageSession::deleteAllCookiesModifiedSince(WallTime timePoint) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies)); |
| |
| if (![NSHTTPCookieStorage instancesRespondToSelector:@selector(removeCookiesSinceDate:)]) |
| return; |
| |
| NSTimeInterval timeInterval = timePoint.secondsSinceEpoch().seconds(); |
| NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval]; |
| |
| auto *storage = nsCookieStorage(); |
| |
| [storage removeCookiesSinceDate:date]; |
| [storage _saveCookies]; |
| } |
| |
| Vector<Cookie> NetworkStorageSession::domCookiesForHost(const String& host) |
| { |
| NSArray *nsCookies = [nsCookieStorage() _getCookiesForDomain:(NSString *)host]; |
| return nsCookiesToCookieVector(nsCookies, [](NSHTTPCookie *cookie) { return !cookie.HTTPOnly; }); |
| } |
| |
| #if HAVE(COOKIE_CHANGE_LISTENER_API) |
| |
| void NetworkStorageSession::registerCookieChangeListenersIfNecessary() |
| { |
| if (m_didRegisterCookieListeners) |
| return; |
| |
| m_didRegisterCookieListeners = true; |
| |
| [nsCookieStorage() _setCookiesChangedHandler:makeBlockPtr([this, weakThis = WeakPtr { *this }](NSArray<NSHTTPCookie *> *addedCookies, NSString *domainForChangedCookie) { |
| if (!weakThis) |
| return; |
| String host = domainForChangedCookie; |
| auto it = m_cookieChangeObservers.find(host); |
| if (it == m_cookieChangeObservers.end()) |
| return; |
| auto cookies = nsCookiesToCookieVector(addedCookies, [](NSHTTPCookie *cookie) { return !cookie.HTTPOnly; }); |
| if (cookies.isEmpty()) |
| return; |
| for (auto* observer : it->value) |
| observer->cookiesAdded(host, cookies); |
| }).get() onQueue:dispatch_get_main_queue()]; |
| |
| [nsCookieStorage() _setCookiesRemovedHandler:makeBlockPtr([this, weakThis = WeakPtr { *this }](NSArray<NSHTTPCookie *> *removedCookies, NSString *domainForRemovedCookies, bool removeAllCookies) { |
| if (!weakThis) |
| return; |
| if (removeAllCookies) { |
| for (auto& observers : m_cookieChangeObservers.values()) { |
| for (auto* observer : observers) |
| observer->allCookiesDeleted(); |
| } |
| return; |
| } |
| |
| String host = domainForRemovedCookies; |
| auto it = m_cookieChangeObservers.find(host); |
| if (it == m_cookieChangeObservers.end()) |
| return; |
| |
| auto cookies = nsCookiesToCookieVector(removedCookies, [](NSHTTPCookie *cookie) { return !cookie.HTTPOnly; }); |
| if (cookies.isEmpty()) |
| return; |
| for (auto* observer : it->value) |
| observer->cookiesDeleted(host, cookies); |
| }).get() onQueue:dispatch_get_main_queue()]; |
| } |
| |
| void NetworkStorageSession::unregisterCookieChangeListenersIfNecessary() |
| { |
| if (!m_didRegisterCookieListeners) |
| return; |
| |
| [nsCookieStorage() _setCookiesChangedHandler:nil onQueue:nil]; |
| [nsCookieStorage() _setCookiesRemovedHandler:nil onQueue:nil]; |
| |
| [nsCookieStorage() _setSubscribedDomainsForCookieChanges:nil]; |
| m_didRegisterCookieListeners = false; |
| } |
| |
| void NetworkStorageSession::startListeningForCookieChangeNotifications(CookieChangeObserver& observer, const String& host) |
| { |
| registerCookieChangeListenersIfNecessary(); |
| |
| auto& observers = m_cookieChangeObservers.ensure(host, [] { |
| return HashSet<CookieChangeObserver*> { }; |
| }).iterator->value; |
| ASSERT(!observers.contains(&observer)); |
| observers.add(&observer); |
| |
| if (!m_subscribedDomainsForCookieChanges) |
| m_subscribedDomainsForCookieChanges = adoptNS([[NSMutableSet alloc] init]); |
| else if ([m_subscribedDomainsForCookieChanges containsObject:(NSString *)host]) |
| return; |
| |
| [m_subscribedDomainsForCookieChanges addObject:(NSString *)host]; |
| [nsCookieStorage() _setSubscribedDomainsForCookieChanges:m_subscribedDomainsForCookieChanges.get()]; |
| } |
| |
| void NetworkStorageSession::stopListeningForCookieChangeNotifications(CookieChangeObserver& observer, const HashSet<String>& hosts) |
| { |
| bool subscribedURLsChanged = false; |
| for (auto& host : hosts) { |
| auto it = m_cookieChangeObservers.find(host); |
| ASSERT(it != m_cookieChangeObservers.end()); |
| if (it == m_cookieChangeObservers.end()) |
| continue; |
| |
| auto& observers = it->value; |
| ASSERT(observers.contains(&observer)); |
| observers.remove(&observer); |
| if (observers.isEmpty()) { |
| m_cookieChangeObservers.remove(it); |
| ASSERT([m_subscribedDomainsForCookieChanges containsObject:(NSString *)host]); |
| [m_subscribedDomainsForCookieChanges removeObject:(NSString *)host]; |
| subscribedURLsChanged = true; |
| } |
| } |
| if (subscribedURLsChanged) |
| [nsCookieStorage() _setSubscribedDomainsForCookieChanges:m_subscribedDomainsForCookieChanges.get()]; |
| } |
| |
| // FIXME: This can eventually go away, this is merely to ensure a smooth transition to the new API. |
| bool NetworkStorageSession::supportsCookieChangeListenerAPI() const |
| { |
| static const bool supportsAPI = [nsCookieStorage() respondsToSelector:@selector(_setCookiesChangedHandler:onQueue:)]; |
| return supportsAPI; |
| } |
| |
| #endif // HAVE(COOKIE_CHANGE_LISTENER_API) |
| |
| } // namespace WebCore |