5fdd1c1bab5d1d76afd70495998f20c67d77447a
[WebKit.git] / Source / WebCore / platform / network / mac / CookieJarMac.mm
1 /*
2  * Copyright (C) 2003, 2006, 2008, 2012 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 "CFNetworkSPI.h"
30 #import "NetworkStorageSession.h"
31 #import "WebCoreSystemInterface.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 namespace WebCore {
43
44 static NSArray *httpCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSURL *firstParty, NSURL *url)
45 {
46     if (!cookieStorage)
47         cookieStorage = _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault);
48
49     bool secure = ![[url scheme] caseInsensitiveCompare:@"https"];
50     
51     auto cookies = adoptCF(_CFHTTPCookieStorageCopyCookiesForURLWithMainDocumentURL(cookieStorage, static_cast<CFURLRef>(url), static_cast<CFURLRef>(firstParty), secure));
52     NSArray *nsCookies = [NSHTTPCookie _cf2nsCookies:cookies.get()];
53
54     return nsCookies;
55 }
56
57     
58 static RetainPtr<NSArray> filterCookies(NSArray *unfilteredCookies)
59 {
60     NSUInteger count = [unfilteredCookies count];
61     RetainPtr<NSMutableArray> filteredCookies = adoptNS([[NSMutableArray alloc] initWithCapacity:count]);
62
63     for (NSUInteger i = 0; i < count; ++i) {
64         NSHTTPCookie *cookie = (NSHTTPCookie *)[unfilteredCookies objectAtIndex:i];
65
66         // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie,
67         // which would be sent as "Cookie: =". We have a workaround in setCookies() to prevent
68         // that, but we also need to avoid sending cookies that were previously stored, and
69         // there's no harm to doing this check because such a cookie is never valid.
70         if (![[cookie name] length])
71             continue;
72
73         if ([cookie isHTTPOnly])
74             continue;
75
76         [filteredCookies.get() addObject:cookie];
77     }
78
79     return filteredCookies;
80 }
81
82 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
83
84 static NSArray *applyPartitionToCookies(NSString *partition, NSArray *cookies)
85 {
86     // FIXME 24747739: CFNetwork should expose this key as SPI
87     static NSString * const partitionKey = @"StoragePartition";
88
89     NSMutableArray *partitionedCookies = [NSMutableArray arrayWithCapacity:cookies.count];
90     for (NSHTTPCookie *cookie in cookies) {
91         RetainPtr<NSMutableDictionary> properties = adoptNS([cookie.properties mutableCopy]);
92         [properties setObject:partition forKey:partitionKey];
93         [partitionedCookies addObject:[NSHTTPCookie cookieWithProperties:properties.get()]];
94     }
95
96     return partitionedCookies;
97 }
98
99 static NSArray *cookiesInPartitionForURL(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
100 {
101     String partition = session.cookieStoragePartition(firstParty, url);
102     if (partition.isEmpty())
103         return nil;
104
105     // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
106     // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
107     RetainPtr<NSHTTPCookieStorage> cookieStorage;
108     if (auto storage = session.cookieStorage())
109         cookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:storage.get()]);
110     else
111         cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
112
113     // The _getCookiesForURL: method calls the completionHandler synchronously.
114     std::optional<RetainPtr<NSArray *>> cookiesPtr;
115     [cookieStorage _getCookiesForURL:url mainDocumentURL:firstParty partition:partition completionHandler:[&cookiesPtr](NSArray *cookies) {
116         cookiesPtr = retainPtr(cookies);
117     }];
118     ASSERT(!!cookiesPtr);
119
120     return cookiesPtr->autorelease();
121 }
122
123 #endif // HAVE(CFNETWORK_STORAGE_PARTITIONING)
124     
125 static NSArray *cookiesForURL(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
126 {
127 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
128     if (NSArray *cookies = cookiesInPartitionForURL(session, firstParty, url))
129         return cookies;
130 #endif
131     return httpCookiesForURL(session.cookieStorage().get(), firstParty, url);
132 }
133
134 enum IncludeHTTPOnlyOrNot { DoNotIncludeHTTPOnly, IncludeHTTPOnly };
135 static String cookiesForSession(const NetworkStorageSession& session, const URL& firstParty, const URL& url, IncludeHTTPOnlyOrNot includeHTTPOnly)
136 {
137     BEGIN_BLOCK_OBJC_EXCEPTIONS;
138
139     NSArray *cookies = cookiesForURL(session, firstParty, url);
140     if (![cookies count])
141         return String(); // Return a null string, not an empty one that StringBuilder would create below.
142
143     StringBuilder cookiesBuilder;
144     for (NSHTTPCookie *cookie in cookies) {
145         if (![[cookie name] length])
146             continue;
147
148         if (!includeHTTPOnly && [cookie isHTTPOnly])
149             continue;
150
151         if (!cookiesBuilder.isEmpty())
152             cookiesBuilder.appendLiteral("; ");
153
154         cookiesBuilder.append([cookie name]);
155         cookiesBuilder.append('=');
156         cookiesBuilder.append([cookie value]);
157     }
158     return cookiesBuilder.toString();
159
160     END_BLOCK_OBJC_EXCEPTIONS;
161     return String();
162 }
163
164 String cookiesForDOM(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
165 {
166     return cookiesForSession(session, firstParty, url, DoNotIncludeHTTPOnly);
167 }
168
169 String cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const URL& firstParty, const URL& url)
170 {
171     return cookiesForSession(session, firstParty, url, IncludeHTTPOnly);
172 }
173
174 void setCookiesFromDOM(const NetworkStorageSession& session, const URL& firstParty, const URL& url, const String& cookieStr)
175 {
176     BEGIN_BLOCK_OBJC_EXCEPTIONS;
177
178     // <rdar://problem/5632883> On 10.5, NSHTTPCookieStorage would store an empty cookie,
179     // which would be sent as "Cookie: =".
180     if (cookieStr.isEmpty())
181         return;
182
183     // <http://bugs.webkit.org/show_bug.cgi?id=6531>, <rdar://4409034>
184     // cookiesWithResponseHeaderFields doesn't parse cookies without a value
185     String cookieString = cookieStr.contains('=') ? cookieStr : cookieStr + "=";
186
187     NSURL *cookieURL = url;
188     NSDictionary *headerFields = [NSDictionary dictionaryWithObject:cookieString forKey:@"Set-Cookie"];
189
190 #if PLATFORM(MAC)
191     NSArray *unfilteredCookies = [NSHTTPCookie _parsedCookiesWithResponseHeaderFields:headerFields forURL:cookieURL];
192 #else
193     NSArray *unfilteredCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:cookieURL];
194 #endif
195
196     RetainPtr<NSArray> filteredCookies = filterCookies(unfilteredCookies);
197     ASSERT([filteredCookies.get() count] <= 1);
198
199 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
200     String partition = session.cookieStoragePartition(firstParty, url);
201     if (!partition.isEmpty())
202         filteredCookies = applyPartitionToCookies(partition, filteredCookies.get());
203 #endif
204
205     wkSetHTTPCookiesForURL(session.cookieStorage().get(), filteredCookies.get(), cookieURL, firstParty);
206
207     END_BLOCK_OBJC_EXCEPTIONS;
208 }
209
210 bool cookiesEnabled(const NetworkStorageSession& session, const URL& /*firstParty*/, const URL& /*url*/)
211 {
212     BEGIN_BLOCK_OBJC_EXCEPTIONS;
213
214     NSHTTPCookieAcceptPolicy cookieAcceptPolicy = static_cast<NSHTTPCookieAcceptPolicy>(wkGetHTTPCookieAcceptPolicy(session.cookieStorage().get()));
215     return cookieAcceptPolicy == NSHTTPCookieAcceptPolicyAlways || cookieAcceptPolicy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain || cookieAcceptPolicy == NSHTTPCookieAcceptPolicyExclusivelyFromMainDocumentDomain;
216
217     END_BLOCK_OBJC_EXCEPTIONS;
218     return false;
219 }
220
221 bool getRawCookies(const NetworkStorageSession& session, const URL& firstParty, const URL& url, Vector<Cookie>& rawCookies)
222 {
223     rawCookies.clear();
224     BEGIN_BLOCK_OBJC_EXCEPTIONS;
225
226     NSArray *cookies = cookiesForURL(session, firstParty, url);
227     NSUInteger count = [cookies count];
228     rawCookies.reserveCapacity(count);
229     for (NSUInteger i = 0; i < count; ++i) {
230         NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];
231         rawCookies.uncheckedAppend({ cookie });
232     }
233
234     END_BLOCK_OBJC_EXCEPTIONS;
235     return true;
236 }
237
238 void deleteCookie(const NetworkStorageSession& session, const URL& url, const String& cookieName)
239 {
240     BEGIN_BLOCK_OBJC_EXCEPTIONS;
241
242     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = session.cookieStorage();
243     NSArray *cookies = httpCookiesForURL(cookieStorage.get(), nil, url);
244
245     NSString *cookieNameString = cookieName;
246
247     NSUInteger count = [cookies count];
248     for (NSUInteger i = 0; i < count; ++i) {
249         NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];
250         if ([[cookie name] isEqualToString:cookieNameString])
251             wkDeleteHTTPCookie(cookieStorage.get(), cookie);
252     }
253
254     END_BLOCK_OBJC_EXCEPTIONS;
255 }
256
257 void getHostnamesWithCookies(const NetworkStorageSession& session, HashSet<String>& hostnames)
258 {
259     BEGIN_BLOCK_OBJC_EXCEPTIONS;
260
261     NSArray *cookies = wkHTTPCookies(session.cookieStorage().get());
262     
263     for (NSHTTPCookie* cookie in cookies)
264         hostnames.add([cookie domain]);
265     
266     END_BLOCK_OBJC_EXCEPTIONS;
267 }
268
269 void deleteAllCookies(const NetworkStorageSession& session)
270 {
271     wkDeleteAllHTTPCookies(session.cookieStorage().get());
272 }
273
274 }
275
276 #endif // !USE(CFURLCONNECTION)
277
278 namespace WebCore {
279
280 void deleteCookiesForHostnames(const NetworkStorageSession& session, const Vector<String>& hostnames)
281 {
282     BEGIN_BLOCK_OBJC_EXCEPTIONS;
283
284     RetainPtr<CFHTTPCookieStorageRef> cookieStorage = session.cookieStorage();
285     NSArray *cookies = wkHTTPCookies(cookieStorage.get());
286     if (!cookies)
287         return;
288
289     HashMap<String, Vector<RetainPtr<NSHTTPCookie>>> cookiesByDomain;
290     for (NSHTTPCookie* cookie in cookies) {
291         auto& cookies = cookiesByDomain.add(cookie.domain, Vector<RetainPtr<NSHTTPCookie>>()).iterator->value;
292         cookies.append(cookie);
293     }
294
295     for (const auto& hostname : hostnames) {
296         auto it = cookiesByDomain.find(hostname);
297         if (it == cookiesByDomain.end())
298             continue;
299
300         for (auto& cookie : it->value)
301             wkDeleteHTTPCookie(cookieStorage.get(), cookie.get());
302     }
303
304     [session.nsCookieStorage() _saveCookies];
305
306     END_BLOCK_OBJC_EXCEPTIONS;
307 }
308
309 void deleteAllCookiesModifiedSince(const NetworkStorageSession& session, std::chrono::system_clock::time_point timePoint)
310 {
311     if (![NSHTTPCookieStorage instancesRespondToSelector:@selector(removeCookiesSinceDate:)])
312         return;
313
314     NSTimeInterval timeInterval = std::chrono::duration_cast<std::chrono::duration<double>>(timePoint.time_since_epoch()).count();
315     NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
316
317     auto *storage = session.nsCookieStorage();
318
319     [storage removeCookiesSinceDate:date];
320     [storage _saveCookies];
321 }
322
323 }