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