688d99567541987ad1fc8f5f1424da4dcf00411d
[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     if ([policy isEqualToString:NSHTTPCookieSameSiteLax])
93         return Cookie::SameSitePolicy::Lax;
94     if ([policy isEqualToString:NSHTTPCookieSameSiteStrict])
95         return Cookie::SameSitePolicy::Strict;
96     ASSERT_NOT_REACHED();
97     return Cookie::SameSitePolicy::None;
98 }
99
100 static NSHTTPCookieStringPolicy _Nullable nsSameSitePolicy(Cookie::SameSitePolicy policy)
101 {
102     switch (policy) {
103     case Cookie::SameSitePolicy::None:
104         return nil;
105     case Cookie::SameSitePolicy::Lax:
106         return NSHTTPCookieSameSiteLax;
107     case Cookie::SameSitePolicy::Strict:
108         return NSHTTPCookieSameSiteStrict;
109     }
110 }
111 #endif
112
113 Cookie::Cookie(NSHTTPCookie *cookie)
114     : name { cookie.name }
115     , value { cookie.value }
116     , domain { cookie.domain }
117     , path { cookie.path }
118     , created { cookieCreated(cookie) }
119     , expires { [cookie.expiresDate timeIntervalSince1970] * 1000.0 }
120     , httpOnly { static_cast<bool>(cookie.HTTPOnly) }
121     , secure { static_cast<bool>(cookie.secure) }
122     , session { static_cast<bool>(cookie.sessionOnly) }
123     , comment { cookie.comment }
124     , commentURL { cookie.commentURL }
125     , ports { portVectorFromList(cookie.portList) }
126 {
127 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
128     if ([cookie respondsToSelector:@selector(sameSitePolicy)])
129         sameSite = coreSameSitePolicy(cookie.sameSitePolicy);
130 #endif
131 }
132
133 Cookie::operator NSHTTPCookie * _Nullable () const
134 {
135     if (isNull())
136         return nil;
137
138     NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:14];
139
140     if (!comment.isNull())
141         [properties setObject:(NSString *)comment forKey:NSHTTPCookieComment];
142
143     if (!commentURL.isNull())
144         [properties setObject:(NSURL *)commentURL forKey:NSHTTPCookieCommentURL];
145
146     if (!domain.isNull())
147         [properties setObject:(NSString *)domain forKey:NSHTTPCookieDomain];
148
149     if (!name.isNull())
150         [properties setObject:(NSString *)name forKey:NSHTTPCookieName];
151
152     if (!path.isNull())
153         [properties setObject:(NSString *)path forKey:NSHTTPCookiePath];
154
155     if (!value.isNull())
156         [properties setObject:(NSString *)value forKey:NSHTTPCookieValue];
157
158     NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:expires / 1000.0];
159     auto maxAge = ceil([expirationDate timeIntervalSinceNow]);
160     if (maxAge > 0)
161         [properties setObject:[NSString stringWithFormat:@"%f", maxAge] forKey:NSHTTPCookieMaximumAge];
162
163 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
164     [properties setObject:[NSNumber numberWithDouble:created / 1000.0 - NSTimeIntervalSince1970] forKey:@"Created"];
165 #endif
166
167     auto* portString = portStringFromVector(ports);
168     if (portString)
169         [properties setObject:portString forKey:NSHTTPCookiePort];
170
171     if (secure)
172         [properties setObject:@YES forKey:NSHTTPCookieSecure];
173
174     if (session)
175         [properties setObject:@YES forKey:NSHTTPCookieDiscard];
176     
177     if (httpOnly)
178         [properties setObject:@YES forKey:@"HttpOnly"];
179
180 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)
181     if (auto* sameSitePolicy = nsSameSitePolicy(sameSite))
182         [properties setObject:sameSitePolicy forKey:@"SameSite"];
183 #endif
184
185     [properties setObject:@"1" forKey:NSHTTPCookieVersion];
186
187     return [NSHTTPCookie cookieWithProperties:properties];
188 }
189     
190 bool Cookie::operator==(const Cookie& other) const
191 {
192     ASSERT(!name.isHashTableDeletedValue());
193     bool thisNull = isNull();
194     bool otherNull = other.isNull();
195     if (thisNull || otherNull)
196         return thisNull == otherNull;
197     return [static_cast<NSHTTPCookie *>(*this) isEqual:other];
198 }
199     
200 unsigned Cookie::hash() const
201 {
202     ASSERT(!name.isHashTableDeletedValue());
203     ASSERT(!isNull());
204     return static_cast<NSHTTPCookie *>(*this).hash;
205 }
206
207 NS_ASSUME_NONNULL_END
208
209 } // namespace WebCore