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