Support building WebKit for macOS Mojave using a newer SDK
[WebKit-https.git] / Source / WebCore / platform / network / cocoa / CookieCocoa.mm
1 /*
2  * Copyright (C) 2015-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 "Cookie.h"
28
29 // FIXME: Remove NS_ASSUME_NONNULL_BEGIN/END and all _Nullable annotations once we remove the NSHTTPCookie forward declaration below.
30 NS_ASSUME_NONNULL_BEGIN
31
32 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 && __MAC_OS_X_VERSION_MAX_ALLOWED < 101500) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000 && __IPHONE_OS_VERSION_MAX_ALLOWED < 130000)
33 typedef NSString * NSHTTPCookieStringPolicy;
34 @interface NSHTTPCookie (Staging)
35 @property (nullable, readonly, copy) NSHTTPCookieStringPolicy sameSitePolicy;
36 @end
37
38 static NSString * const NSHTTPCookieSameSiteLax = @"lax";
39 static NSString * const NSHTTPCookieSameSiteStrict = @"strict";
40 #endif
41
42 namespace WebCore {
43
44 static Vector<uint16_t> portVectorFromList(NSArray<NSNumber *> *portList)
45 {
46     Vector<uint16_t> ports;
47     ports.reserveInitialCapacity(portList.count);
48
49     for (NSNumber *port : portList)
50         ports.uncheckedAppend(port.unsignedShortValue);
51
52     return ports;
53 }
54
55 static NSString * _Nullable portStringFromVector(const Vector<uint16_t>& ports)
56 {
57     if (ports.isEmpty())
58         return nil;
59
60     auto *string = [NSMutableString stringWithCapacity:ports.size() * 5];
61
62     for (size_t i = 0; i < ports.size() - 1; ++i)
63         [string appendFormat:@"%" PRIu16 ", ", ports[i]];
64
65     [string appendFormat:@"%" PRIu16, ports.last()];
66
67     return string;
68 }
69
70 static double cookieCreated(NSHTTPCookie *cookie)
71 {
72     id value = cookie.properties[@"Created"];
73
74     auto toCanonicalFormat = [](double referenceFormat) {
75         return 1000.0 * (referenceFormat + NSTimeIntervalSince1970);
76     };
77
78     if ([value isKindOfClass:[NSNumber class]])
79         return toCanonicalFormat(((NSNumber *)value).doubleValue);
80
81     if ([value isKindOfClass:[NSString class]])
82         return toCanonicalFormat(((NSString *)value).doubleValue);
83
84     return 0;
85 }
86
87 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
88 static Cookie::SameSitePolicy coreSameSitePolicy(NSHTTPCookieStringPolicy _Nullable policy)
89 {
90     if (!policy)
91         return Cookie::SameSitePolicy::None;
92     ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN
93     if ([policy isEqualToString:NSHTTPCookieSameSiteLax])
94         return Cookie::SameSitePolicy::Lax;
95     if ([policy isEqualToString:NSHTTPCookieSameSiteStrict])
96         return Cookie::SameSitePolicy::Strict;
97     ALLOW_NEW_API_WITHOUT_GUARDS_END
98     ASSERT_NOT_REACHED();
99     return Cookie::SameSitePolicy::None;
100 }
101
102 static NSHTTPCookieStringPolicy _Nullable nsSameSitePolicy(Cookie::SameSitePolicy policy)
103 {
104     switch (policy) {
105     case Cookie::SameSitePolicy::None:
106         return nil;
107     ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN
108     case Cookie::SameSitePolicy::Lax:
109         return NSHTTPCookieSameSiteLax;
110     case Cookie::SameSitePolicy::Strict:
111         return NSHTTPCookieSameSiteStrict;
112     ALLOW_NEW_API_WITHOUT_GUARDS_END
113     }
114 }
115 #endif
116
117 Cookie::Cookie(NSHTTPCookie *cookie)
118     : name { cookie.name }
119     , value { cookie.value }
120     , domain { cookie.domain }
121     , path { cookie.path }
122     , created { cookieCreated(cookie) }
123     , expires { [cookie.expiresDate timeIntervalSince1970] * 1000.0 }
124     , httpOnly { static_cast<bool>(cookie.HTTPOnly) }
125     , secure { static_cast<bool>(cookie.secure) }
126     , session { static_cast<bool>(cookie.sessionOnly) }
127     , comment { cookie.comment }
128     , commentURL { cookie.commentURL }
129     , ports { portVectorFromList(cookie.portList) }
130 {
131 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
132     ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN
133     if ([cookie respondsToSelector:@selector(sameSitePolicy)])
134         sameSite = coreSameSitePolicy(cookie.sameSitePolicy);
135     ALLOW_NEW_API_WITHOUT_GUARDS_END
136 #endif
137 }
138
139 Cookie::operator NSHTTPCookie * _Nullable () const
140 {
141     if (isNull())
142         return nil;
143
144     NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:14];
145
146     if (!comment.isNull())
147         [properties setObject:(NSString *)comment forKey:NSHTTPCookieComment];
148
149     if (!commentURL.isNull())
150         [properties setObject:(NSURL *)commentURL forKey:NSHTTPCookieCommentURL];
151
152     if (!domain.isNull())
153         [properties setObject:(NSString *)domain forKey:NSHTTPCookieDomain];
154
155     if (!name.isNull())
156         [properties setObject:(NSString *)name forKey:NSHTTPCookieName];
157
158     if (!path.isNull())
159         [properties setObject:(NSString *)path forKey:NSHTTPCookiePath];
160
161     if (!value.isNull())
162         [properties setObject:(NSString *)value forKey:NSHTTPCookieValue];
163
164     NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:expires / 1000.0];
165     auto maxAge = ceil([expirationDate timeIntervalSinceNow]);
166     if (maxAge > 0)
167         [properties setObject:[NSString stringWithFormat:@"%f", maxAge] forKey:NSHTTPCookieMaximumAge];
168
169 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
170     [properties setObject:[NSNumber numberWithDouble:created / 1000.0 - NSTimeIntervalSince1970] forKey:@"Created"];
171 #endif
172
173     auto* portString = portStringFromVector(ports);
174     if (portString)
175         [properties setObject:portString forKey:NSHTTPCookiePort];
176
177     if (secure)
178         [properties setObject:@YES forKey:NSHTTPCookieSecure];
179
180     if (session)
181         [properties setObject:@YES forKey:NSHTTPCookieDiscard];
182     
183     if (httpOnly)
184         [properties setObject:@YES forKey:@"HttpOnly"];
185
186 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
187     if (auto* sameSitePolicy = nsSameSitePolicy(sameSite))
188         [properties setObject:sameSitePolicy forKey:@"SameSite"];
189 #endif
190
191     [properties setObject:@"1" forKey:NSHTTPCookieVersion];
192
193     return [NSHTTPCookie cookieWithProperties:properties];
194 }
195     
196 bool Cookie::operator==(const Cookie& other) const
197 {
198     ASSERT(!name.isHashTableDeletedValue());
199     bool thisNull = isNull();
200     bool otherNull = other.isNull();
201     if (thisNull || otherNull)
202         return thisNull == otherNull;
203     return [static_cast<NSHTTPCookie *>(*this) isEqual:other];
204 }
205     
206 unsigned Cookie::hash() const
207 {
208     ASSERT(!name.isHashTableDeletedValue());
209     ASSERT(!isNull());
210     return static_cast<NSHTTPCookie *>(*this).hash;
211 }
212
213 NS_ASSUME_NONNULL_END
214
215 } // namespace WebCore