Storage Access API: UI process should update network process about granted access
[WebKit-https.git] / Source / WebCore / platform / network / cf / NetworkStorageSessionCFNet.cpp
1 /*
2  * Copyright (C) 2012-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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkStorageSession.h"
28
29 #include <wtf/MainThread.h>
30 #include <wtf/NeverDestroyed.h>
31 #include <wtf/ProcessID.h>
32
33 #if PLATFORM(COCOA)
34 #include "PublicSuffix.h"
35 #include "ResourceRequest.h"
36 #else
37 #include <WebKitSystemInterface/WebKitSystemInterface.h>
38 #endif
39
40 namespace WebCore {
41
42 static bool cookieStoragePartitioningEnabled;
43 static bool storageAccessAPIEnabled;
44
45 static RetainPtr<CFURLStorageSessionRef> createCFStorageSessionForIdentifier(CFStringRef identifier)
46 {
47     auto storageSession = adoptCF(_CFURLStorageSessionCreate(kCFAllocatorDefault, identifier, nullptr));
48
49     if (!storageSession)
50         return nullptr;
51
52     auto cache = adoptCF(_CFURLStorageSessionCopyCache(kCFAllocatorDefault, storageSession.get()));
53     if (!cache)
54         return nullptr;
55
56     CFURLCacheSetDiskCapacity(cache.get(), 0);
57
58     auto sharedCache = adoptCF(CFURLCacheCopySharedURLCache());
59     CFURLCacheSetMemoryCapacity(cache.get(), CFURLCacheMemoryCapacity(sharedCache.get()));
60
61     auto cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, storageSession.get()));
62     if (!cookieStorage)
63         return nullptr;
64
65     auto sharedCookieStorage = _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault);
66     auto sharedPolicy = CFHTTPCookieStorageGetCookieAcceptPolicy(sharedCookieStorage);
67     CFHTTPCookieStorageSetCookieAcceptPolicy(cookieStorage.get(), sharedPolicy);
68
69     return storageSession;
70 }
71
72 NetworkStorageSession::NetworkStorageSession(PAL::SessionID sessionID, RetainPtr<CFURLStorageSessionRef>&& platformSession, RetainPtr<CFHTTPCookieStorageRef>&& platformCookieStorage)
73     : m_sessionID(sessionID)
74     , m_platformSession(WTFMove(platformSession))
75 {
76     m_platformCookieStorage = platformCookieStorage ? WTFMove(platformCookieStorage) : cookieStorage();
77 }
78
79 static std::unique_ptr<NetworkStorageSession>& defaultNetworkStorageSession()
80 {
81     ASSERT(isMainThread());
82     static NeverDestroyed<std::unique_ptr<NetworkStorageSession>> session;
83     return session;
84 }
85
86 void NetworkStorageSession::switchToNewTestingSession()
87 {
88     // Session name should be short enough for shared memory region name to be under the limit, otehrwise sandbox rules won't work (see <rdar://problem/13642852>).
89     String sessionName = String::format("WebKit Test-%u", static_cast<uint32_t>(getCurrentProcessID()));
90
91     RetainPtr<CFURLStorageSessionRef> session;
92 #if PLATFORM(COCOA)
93     session = adoptCF(createPrivateStorageSession(sessionName.createCFString().get()));
94 #else
95     session = adoptCF(wkCreatePrivateStorageSession(sessionName.createCFString().get(), defaultStorageSession().platformSession()));
96 #endif
97
98     RetainPtr<CFHTTPCookieStorageRef> cookieStorage;
99     if (session)
100         cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, session.get()));
101
102     defaultNetworkStorageSession() = std::make_unique<NetworkStorageSession>(PAL::SessionID::defaultSessionID(), WTFMove(session), WTFMove(cookieStorage));
103 }
104
105 NetworkStorageSession& NetworkStorageSession::defaultStorageSession()
106 {
107     if (!defaultNetworkStorageSession())
108         defaultNetworkStorageSession() = std::make_unique<NetworkStorageSession>(PAL::SessionID::defaultSessionID(), nullptr, nullptr);
109     return *defaultNetworkStorageSession();
110 }
111
112 void NetworkStorageSession::ensureSession(PAL::SessionID sessionID, const String& identifierBase, RetainPtr<CFHTTPCookieStorageRef>&& cookieStorage)
113 {
114     auto addResult = globalSessionMap().add(sessionID, nullptr);
115     if (!addResult.isNewEntry)
116         return;
117
118     RetainPtr<CFStringRef> cfIdentifier = String(identifierBase + ".PrivateBrowsing").createCFString();
119
120     RetainPtr<CFURLStorageSessionRef> storageSession;
121     if (sessionID.isEphemeral()) {
122 #if PLATFORM(COCOA)
123         storageSession = adoptCF(createPrivateStorageSession(cfIdentifier.get()));
124 #else
125         storageSession = adoptCF(wkCreatePrivateStorageSession(cfIdentifier.get(), defaultNetworkStorageSession()->platformSession()));
126 #endif
127     } else
128         storageSession = createCFStorageSessionForIdentifier(cfIdentifier.get());
129
130     if (!cookieStorage && storageSession)
131         cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, storageSession.get()));
132
133     addResult.iterator->value = std::make_unique<NetworkStorageSession>(sessionID, WTFMove(storageSession), WTFMove(cookieStorage));
134 }
135
136 void NetworkStorageSession::ensureSession(PAL::SessionID sessionID, const String& identifierBase)
137 {
138     ensureSession(sessionID, identifierBase, nullptr);
139 }
140
141 RetainPtr<CFHTTPCookieStorageRef> NetworkStorageSession::cookieStorage() const
142 {
143     if (m_platformCookieStorage)
144         return m_platformCookieStorage;
145
146     if (m_platformSession)
147         return adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, m_platformSession.get()));
148
149 #if USE(CFURLCONNECTION)
150     return _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault);
151 #else
152     // When using NSURLConnection, we also use its shared cookie storage.
153     return nullptr;
154 #endif
155 }
156
157 void NetworkStorageSession::setCookieStoragePartitioningEnabled(bool enabled)
158 {
159     cookieStoragePartitioningEnabled = enabled;
160 }
161
162 void NetworkStorageSession::setStorageAccessAPIEnabled(bool enabled)
163 {
164     storageAccessAPIEnabled = enabled;
165 }
166
167 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
168
169 String NetworkStorageSession::cookieStoragePartition(const ResourceRequest& request) const
170 {
171     return cookieStoragePartition(request.firstPartyForCookies(), request.url());
172 }
173
174 static inline String getPartitioningDomain(const URL& url) 
175 {
176 #if ENABLE(PUBLIC_SUFFIX_LIST)
177     auto domain = topPrivatelyControlledDomain(url.host());
178     if (domain.isEmpty())
179         domain = url.host();
180 #else
181     auto domain = url.host();
182 #endif
183     return domain;
184 }
185
186 String NetworkStorageSession::cookieStoragePartition(const URL& firstPartyForCookies, const URL& resource) const
187 {
188     if (!cookieStoragePartitioningEnabled)
189         return emptyString();
190     
191     auto resourceDomain = getPartitioningDomain(resource);
192     if (!shouldPartitionCookies(resourceDomain))
193         return emptyString();
194
195     auto firstPartyDomain = getPartitioningDomain(firstPartyForCookies);
196     if (firstPartyDomain == resourceDomain)
197         return emptyString();
198
199     if (storageAccessAPIEnabled && isStorageAccessGranted(resourceDomain, firstPartyDomain))
200         return emptyString();
201
202     return firstPartyDomain;
203 }
204
205 bool NetworkStorageSession::shouldPartitionCookies(const String& topPrivatelyControlledDomain) const
206 {
207     if (topPrivatelyControlledDomain.isEmpty())
208         return false;
209
210     return m_topPrivatelyControlledDomainsToPartition.contains(topPrivatelyControlledDomain);
211 }
212
213 bool NetworkStorageSession::shouldBlockThirdPartyCookies(const String& topPrivatelyControlledDomain) const
214 {
215     if (topPrivatelyControlledDomain.isEmpty())
216         return false;
217
218     return m_topPrivatelyControlledDomainsToBlock.contains(topPrivatelyControlledDomain);
219 }
220
221 bool NetworkStorageSession::shouldBlockCookies(const ResourceRequest& request) const
222 {
223     if (!cookieStoragePartitioningEnabled)
224         return false;
225
226     return shouldBlockCookies(request.firstPartyForCookies(), request.url());
227 }
228     
229 bool NetworkStorageSession::shouldBlockCookies(const URL& firstPartyForCookies, const URL& resource) const
230 {
231     if (!cookieStoragePartitioningEnabled)
232         return false;
233     
234     auto firstPartyDomain = getPartitioningDomain(firstPartyForCookies);
235     if (firstPartyDomain.isEmpty())
236         return false;
237
238     auto resourceDomain = getPartitioningDomain(resource);
239     if (resourceDomain.isEmpty())
240         return false;
241
242     if (firstPartyDomain == resourceDomain)
243         return false;
244
245     return shouldBlockThirdPartyCookies(resourceDomain);
246 }
247
248 void NetworkStorageSession::setPrevalentDomainsToPartitionOrBlockCookies(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, bool clearFirst)
249 {
250     if (clearFirst) {
251         m_topPrivatelyControlledDomainsToPartition.clear();
252         m_topPrivatelyControlledDomainsToBlock.clear();
253         m_domainsGrantedStorageAccess.clear();
254     }
255
256     for (auto& domain : domainsToPartition) {
257         m_topPrivatelyControlledDomainsToPartition.add(domain);
258         if (!clearFirst)
259             m_topPrivatelyControlledDomainsToBlock.remove(domain);
260     }
261
262     for (auto& domain : domainsToBlock) {
263         // FIXME: https://bugs.webkit.org/show_bug.cgi?id=177394
264         // m_topPrivatelyControlledDomainsToBlock.add(domain);
265         // if (!clearFirst)
266         //     m_topPrivatelyControlledDomainsToPartition.remove(domain);
267         m_topPrivatelyControlledDomainsToPartition.add(domain);
268     }
269     
270     if (!clearFirst) {
271         for (auto& domain : domainsToNeitherPartitionNorBlock) {
272             m_topPrivatelyControlledDomainsToPartition.remove(domain);
273             m_topPrivatelyControlledDomainsToBlock.remove(domain);
274         }
275     }
276 }
277
278 void NetworkStorageSession::removePrevalentDomains(const Vector<String>& domains)
279 {
280     for (auto& domain : domains) {
281         m_topPrivatelyControlledDomainsToPartition.remove(domain);
282         m_topPrivatelyControlledDomainsToBlock.remove(domain);
283     }
284 }
285
286 bool NetworkStorageSession::isStorageAccessGranted(const String& resourceDomain, const String& firstPartyDomain) const
287 {
288     auto it = m_domainsGrantedStorageAccess.find(firstPartyDomain);
289     if (it == m_domainsGrantedStorageAccess.end())
290         return false;
291
292     return it->value.contains(resourceDomain);
293 }
294
295 void NetworkStorageSession::setStorageAccessGranted(const String& resourceDomain, const String& firstPartyDomain, bool value)
296 {
297     auto iterator = m_domainsGrantedStorageAccess.find(firstPartyDomain);
298     if (value) {
299         if (iterator == m_domainsGrantedStorageAccess.end())
300             auto accessEntry = m_domainsGrantedStorageAccess.add(firstPartyDomain, HashSet<String>({ resourceDomain }));
301         else
302             iterator->value.add(resourceDomain);
303     } else {
304         if (iterator == m_domainsGrantedStorageAccess.end())
305             return;
306         iterator->value.remove(resourceDomain);
307         if (iterator->value.isEmpty())
308             m_domainsGrantedStorageAccess.remove(firstPartyDomain);
309     }
310 }
311
312 #endif // HAVE(CFNETWORK_STORAGE_PARTITIONING)
313
314 #if !PLATFORM(COCOA)
315 void NetworkStorageSession::setCookies(const Vector<Cookie>&, const URL&, const URL&)
316 {
317     // FIXME: Implement this. <https://webkit.org/b/156298>
318 }
319 #endif
320
321 }