/* * Copyright (C) 2015-2018 Apple, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "Cookie.h" // FIXME: Remove NS_ASSUME_NONNULL_BEGIN/END and all _Nullable annotations once we remove the NSHTTPCookie forward declaration below. NS_ASSUME_NONNULL_BEGIN #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 && __MAC_OS_X_VERSION_MAX_ALLOWED < 101500) typedef NSString * NSHTTPCookieStringPolicy; @interface NSHTTPCookie (Staging) @property (nullable, readonly, copy) NSHTTPCookieStringPolicy sameSitePolicy; @end static NSString * const NSHTTPCookieSameSiteLax = @"lax"; static NSString * const NSHTTPCookieSameSiteStrict = @"strict"; #endif namespace WebCore { static Vector<uint16_t> portVectorFromList(NSArray<NSNumber *> *portList) { Vector<uint16_t> ports; ports.reserveInitialCapacity(portList.count); for (NSNumber *port : portList) ports.uncheckedAppend(port.unsignedShortValue); return ports; } static NSString * _Nullable portStringFromVector(const Vector<uint16_t>& ports) { if (ports.isEmpty()) return nil; auto *string = [NSMutableString stringWithCapacity:ports.size() * 5]; for (size_t i = 0; i < ports.size() - 1; ++i) [string appendFormat:@"%" PRIu16 ", ", ports[i]]; [string appendFormat:@"%" PRIu16, ports.last()]; return string; } static double cookieCreated(NSHTTPCookie *cookie) { id value = cookie.properties[@"Created"]; auto toCanonicalFormat = [](double referenceFormat) { return 1000.0 * (referenceFormat + NSTimeIntervalSince1970); }; if ([value isKindOfClass:[NSNumber class]]) return toCanonicalFormat(((NSNumber *)value).doubleValue); if ([value isKindOfClass:[NSString class]]) return toCanonicalFormat(((NSString *)value).doubleValue); return 0; } #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || PLATFORM(IOS_FAMILY) static Cookie::SameSitePolicy coreSameSitePolicy(NSHTTPCookieStringPolicy _Nullable policy) { if (!policy) return Cookie::SameSitePolicy::None; ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN if ([policy isEqualToString:NSHTTPCookieSameSiteLax]) return Cookie::SameSitePolicy::Lax; if ([policy isEqualToString:NSHTTPCookieSameSiteStrict]) return Cookie::SameSitePolicy::Strict; ALLOW_NEW_API_WITHOUT_GUARDS_END ASSERT_NOT_REACHED(); return Cookie::SameSitePolicy::None; } static NSHTTPCookieStringPolicy _Nullable nsSameSitePolicy(Cookie::SameSitePolicy policy) { switch (policy) { case Cookie::SameSitePolicy::None: return nil; ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN case Cookie::SameSitePolicy::Lax: return NSHTTPCookieSameSiteLax; case Cookie::SameSitePolicy::Strict: return NSHTTPCookieSameSiteStrict; ALLOW_NEW_API_WITHOUT_GUARDS_END } } #endif Cookie::Cookie(NSHTTPCookie *cookie) : name { cookie.name } , value { cookie.value } , domain { cookie.domain } , path { cookie.path } , created { cookieCreated(cookie) } , expires { [cookie.expiresDate timeIntervalSince1970] * 1000.0 } , httpOnly { static_cast<bool>(cookie.HTTPOnly) } , secure { static_cast<bool>(cookie.secure) } , session { static_cast<bool>(cookie.sessionOnly) } , comment { cookie.comment } , commentURL { cookie.commentURL } , ports { portVectorFromList(cookie.portList) } { #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || PLATFORM(IOS_FAMILY) ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN if ([cookie respondsToSelector:@selector(sameSitePolicy)]) sameSite = coreSameSitePolicy(cookie.sameSitePolicy); ALLOW_NEW_API_WITHOUT_GUARDS_END #endif } Cookie::operator NSHTTPCookie * _Nullable () const { if (isNull()) return nil; NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:14]; if (!comment.isNull()) [properties setObject:(NSString *)comment forKey:NSHTTPCookieComment]; if (!commentURL.isNull()) [properties setObject:(NSURL *)commentURL forKey:NSHTTPCookieCommentURL]; if (!domain.isNull()) [properties setObject:(NSString *)domain forKey:NSHTTPCookieDomain]; if (!name.isNull()) [properties setObject:(NSString *)name forKey:NSHTTPCookieName]; if (!path.isNull()) [properties setObject:(NSString *)path forKey:NSHTTPCookiePath]; if (!value.isNull()) [properties setObject:(NSString *)value forKey:NSHTTPCookieValue]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:expires / 1000.0]; auto maxAge = ceil([expirationDate timeIntervalSinceNow]); if (maxAge > 0) [properties setObject:[NSString stringWithFormat:@"%f", maxAge] forKey:NSHTTPCookieMaximumAge]; #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || PLATFORM(IOS_FAMILY) [properties setObject:[NSNumber numberWithDouble:created / 1000.0 - NSTimeIntervalSince1970] forKey:@"Created"]; #endif auto* portString = portStringFromVector(ports); if (portString) [properties setObject:portString forKey:NSHTTPCookiePort]; if (secure) [properties setObject:@YES forKey:NSHTTPCookieSecure]; if (session) [properties setObject:@YES forKey:NSHTTPCookieDiscard]; if (httpOnly) [properties setObject:@YES forKey:@"HttpOnly"]; #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || PLATFORM(IOS_FAMILY) if (auto* sameSitePolicy = nsSameSitePolicy(sameSite)) [properties setObject:sameSitePolicy forKey:@"SameSite"]; #endif [properties setObject:@"1" forKey:NSHTTPCookieVersion]; return [NSHTTPCookie cookieWithProperties:properties]; } bool Cookie::operator==(const Cookie& other) const { ASSERT(!name.isHashTableDeletedValue()); bool thisNull = isNull(); bool otherNull = other.isNull(); if (thisNull || otherNull) return thisNull == otherNull; return [static_cast<NSHTTPCookie *>(*this) isEqual:other]; } unsigned Cookie::hash() const { ASSERT(!name.isHashTableDeletedValue()); ASSERT(!isNull()); return static_cast<NSHTTPCookie *>(*this).hash; } NS_ASSUME_NONNULL_END } // namespace WebCore