blob: 5a2211d6d7255efd15dd6631f4c3780445f251a5 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
/*!
@const NSHTTPCookieName
@discussion Key for cookie name
*/
public let NSHTTPCookieName: String = "Name"
/*!
@const NSHTTPCookieValue
@discussion Key for cookie value
*/
public let NSHTTPCookieValue: String = "Value"
/*!
@const NSHTTPCookieOriginURL
@discussion Key for cookie origin URL
*/
public let NSHTTPCookieOriginURL: String = "OriginURL"
/*!
@const NSHTTPCookieVersion
@discussion Key for cookie version
*/
public let NSHTTPCookieVersion: String = "Version"
/*!
@const NSHTTPCookieDomain
@discussion Key for cookie domain
*/
public let NSHTTPCookieDomain: String = "Domain"
/*!
@const NSHTTPCookiePath
@discussion Key for cookie path
*/
public let NSHTTPCookiePath: String = "Path"
/*!
@const NSHTTPCookieSecure
@discussion Key for cookie secure flag
*/
public let NSHTTPCookieSecure: String = "Secure"
/*!
@const NSHTTPCookieExpires
@discussion Key for cookie expiration date
*/
public let NSHTTPCookieExpires: String = "Expires"
/*!
@const NSHTTPCookieComment
@discussion Key for cookie comment text
*/
public let NSHTTPCookieComment: String = "Comment"
/*!
@const NSHTTPCookieCommentURL
@discussion Key for cookie comment URL
*/
public let NSHTTPCookieCommentURL: String = "CommentURL"
/*!
@const NSHTTPCookieDiscard
@discussion Key for cookie discard (session-only) flag
*/
public let NSHTTPCookieDiscard: String = "Discard"
/*!
@const NSHTTPCookieMaximumAge
@discussion Key for cookie maximum age (an alternate way of specifying the expiration)
*/
public let NSHTTPCookieMaximumAge: String = "Max-Age"
/*!
@const NSHTTPCookiePort
@discussion Key for cookie ports
*/
public let NSHTTPCookiePort: String = "Port"
/*!
@class NSHTTPCookie
@abstract NSHTTPCookie represents an http cookie.
@discussion A NSHTTPCookie instance represents a single http cookie. It is
an immutable object initialized from a dictionary that contains
the various cookie attributes. It has accessors to get the various
attributes of a cookie.
*/
public class NSHTTPCookie : NSObject {
private struct Cookie {
let comment: String?
let commentURL: NSURL?
let domain: String
let expiresDate: NSDate?
let HTTPOnly: Bool
let secure: Bool
let sessionOnly: Bool
let name: String
let path: String
let portList: [NSNumber]?
let properties: [String: Any]?
let value: String
let version: Int
}
private let cookieRepresentation: Cookie
/*!
@method initWithProperties:
@abstract Initialize a NSHTTPCookie object with a dictionary of
parameters
@param properties The dictionary of properties to be used to
initialize this cookie.
@discussion Supported dictionary keys and value types for the
given dictionary are as follows.
All properties can handle an NSString value, but some can also
handle other types.
<table border=1 cellspacing=2 cellpadding=4>
<tr>
<th>Property key constant</th>
<th>Type of value</th>
<th>Required</th>
<th>Description</th>
</tr>
<tr>
<td>NSHTTPCookieComment</td>
<td>NSString</td>
<td>NO</td>
<td>Comment for the cookie. Only valid for version 1 cookies and
later. Default is nil.</td>
</tr>
<tr>
<td>NSHTTPCookieCommentURL</td>
<td>NSURL or NSString</td>
<td>NO</td>
<td>Comment URL for the cookie. Only valid for version 1 cookies
and later. Default is nil.</td>
</tr>
<tr>
<td>NSHTTPCookieDomain</td>
<td>NSString</td>
<td>Special, a value for either NSHTTPCookieOriginURL or
NSHTTPCookieDomain must be specified.</td>
<td>Domain for the cookie. Inferred from the value for
NSHTTPCookieOriginURL if not provided.</td>
</tr>
<tr>
<td>NSHTTPCookieDiscard</td>
<td>NSString</td>
<td>NO</td>
<td>A string stating whether the cookie should be discarded at
the end of the session. String value must be either "TRUE" or
"FALSE". Default is "FALSE", unless this is cookie is version
1 or greater and a value for NSHTTPCookieMaximumAge is not
specified, in which case it is assumed "TRUE".</td>
</tr>
<tr>
<td>NSHTTPCookieExpires</td>
<td>NSDate or NSString</td>
<td>NO</td>
<td>Expiration date for the cookie. Used only for version 0
cookies. Ignored for version 1 or greater.</td>
</tr>
<tr>
<td>NSHTTPCookieMaximumAge</td>
<td>NSString</td>
<td>NO</td>
<td>A string containing an integer value stating how long in
seconds the cookie should be kept, at most. Only valid for
version 1 cookies and later. Default is "0".</td>
</tr>
<tr>
<td>NSHTTPCookieName</td>
<td>NSString</td>
<td>YES</td>
<td>Name of the cookie</td>
</tr>
<tr>
<td>NSHTTPCookieOriginURL</td>
<td>NSURL or NSString</td>
<td>Special, a value for either NSHTTPCookieOriginURL or
NSHTTPCookieDomain must be specified.</td>
<td>URL that set this cookie. Used as default for other fields
as noted.</td>
</tr>
<tr>
<td>NSHTTPCookiePath</td>
<td>NSString</td>
<td>NO</td>
<td>Path for the cookie. Inferred from the value for
NSHTTPCookieOriginURL if not provided. Default is "/".</td>
</tr>
<tr>
<td>NSHTTPCookiePort</td>
<td>NSString</td>
<td>NO</td>
<td>comma-separated integer values specifying the ports for the
cookie. Only valid for version 1 cookies and later. Default is
empty string ("").</td>
</tr>
<tr>
<td>NSHTTPCookieSecure</td>
<td>NSString</td>
<td>NO</td>
<td>A string stating whether the cookie should be transmitted
only over secure channels. String value must be either "TRUE"
or "FALSE". Default is "FALSE".</td>
</tr>
<tr>
<td>NSHTTPCookieValue</td>
<td>NSString</td>
<td>YES</td>
<td>Value of the cookie</td>
</tr>
<tr>
<td>NSHTTPCookieVersion</td>
<td>NSString</td>
<td>NO</td>
<td>Specifies the version of the cookie. Must be either "0" or
"1". Default is "0".</td>
</tr>
</table>
<p>
All other keys are ignored.
@result An initialized NSHTTPCookie, or nil if the set of
dictionary keys is invalid, for example because a required key is
missing, or a recognized key maps to an illegal value.
*/
/// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative
/// - Note: Since this API is under consideration it may be either removed or revised in the near future
public init?(properties: [String : Any]) {
guard let
path = properties[NSHTTPCookiePath] as? String,
name = properties[NSHTTPCookieName] as? String,
value = properties[NSHTTPCookieValue] as? String
else {
return nil
}
let canonicalDomain: String
if let domain = properties[NSHTTPCookieDomain] as? String {
canonicalDomain = domain
} else if let
originURL = properties[NSHTTPCookieOriginURL] as? NSURL,
host = originURL.host
{
canonicalDomain = host
} else {
return nil
}
let secure: Bool
if let
secureString = properties[NSHTTPCookieSecure] as? String
where secureString.characters.count > 0
{
secure = true
} else {
secure = false
}
let version: Int
if let
versionString = properties[NSHTTPCookieSecure] as? String
where versionString == "1"
{
version = 1
} else {
version = 0
}
let portList: [NSNumber]?
if let portString = properties[NSHTTPCookiePort] as? String {
portList = portString.characters
.split(separator: ",")
.flatMap { Int(String($0)) }
.map { NSNumber(integer: $0) }
} else {
portList = nil
}
// TODO: factor into a utility function
let expiresDate: NSDate?
if version == 0 {
let expiresProperty = properties[NSHTTPCookieExpires]
if let date = expiresProperty as? NSDate {
// If the dictionary value is already an NSDate,
// nothing left to do
expiresDate = date
} else if let dateString = expiresProperty as? String {
// If the dictionary value is a string, parse it
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm"
let timeZone = NSTimeZone(abbreviation: "GMT")
formatter.timeZone = timeZone
expiresDate = formatter.dateFromString(dateString)
} else {
expiresDate = nil
}
} else if version == 1 {
if let
maximumAge = properties[NSHTTPCookieMaximumAge] as? String,
secondsFromNow = Double(maximumAge)
{
expiresDate = NSDate(timeIntervalSinceNow: secondsFromNow)
} else {
expiresDate = nil
}
} else {
expiresDate = nil
}
var discard = false
if let discardString = properties[NSHTTPCookieDiscard] as? String {
discard = discardString == "TRUE"
} else if let
_ = properties[NSHTTPCookieMaximumAge] as? String
where version >= 1
{
discard = false
}
// TODO: commentURL can be a string or NSURL
self.cookieRepresentation = Cookie(
comment: version == 1 ?
properties[NSHTTPCookieComment] as? String : nil,
commentURL: version == 1 ?
properties[NSHTTPCookieCommentURL] as? NSURL : nil,
domain: canonicalDomain,
expiresDate: expiresDate,
HTTPOnly: secure,
secure: secure,
sessionOnly: discard,
name: name,
path: path,
portList: version == 1 ? portList : nil,
properties: properties,
value: value,
version: version
)
}
/*!
@method cookieWithProperties:
@abstract Allocates and initializes an NSHTTPCookie with the given
dictionary.
@discussion See the NSHTTPCookie <tt>-initWithProperties:</tt>
method for more information on the constraints imposed on the
dictionary, and for descriptions of the supported keys and values.
@param properties The dictionary to use to initialize this cookie.
@result A newly-created and autoreleased NSHTTPCookie instance, or
nil if the set of dictionary keys is invalid, for example because
a required key is missing, or a recognized key maps to an illegal
value.
*/
/*!
@method requestHeaderFieldsWithCookies:
@abstract Return a dictionary of header fields that can be used to add the
specified cookies to the request.
@param cookies The cookies to turn into request headers.
@result An NSDictionary where the keys are header field names, and the values
are the corresponding header field values.
*/
public class func requestHeaderFieldsWithCookies(cookies: [NSHTTPCookie]) -> [String : String] {
var cookieString = cookies.reduce("") { (sum, next) -> String in
return sum + "\(next.cookieRepresentation.name)=\(next.cookieRepresentation.value); "
}
//Remove the final trailing semicolon and whitespace
if ( cookieString.length > 0 ) {
cookieString.remove(at: cookieString.endIndex.predecessor())
cookieString.remove(at: cookieString.endIndex.predecessor())
}
return ["Cookie": cookieString]
}
/*!
@method cookiesWithResponseHeaderFields:forURL:
@abstract Return an array of cookies parsed from the specified response header fields and URL.
@param headerFields The response header fields to check for cookies.
@param URL The URL that the cookies came from - relevant to how the cookies are interpeted.
@result An NSArray of NSHTTPCookie objects
@discussion This method will ignore irrelevant header fields so
you can pass a dictionary containing data other than cookie data.
*/
public class func cookiesWithResponseHeaderFields(headerFields: [String : String], forURL URL: NSURL) -> [NSHTTPCookie] { NSUnimplemented() }
/*!
@method properties
@abstract Returns a dictionary representation of the receiver.
@discussion This method returns a dictionary representation of the
NSHTTPCookie which can be saved and passed to
<tt>-initWithProperties:</tt> or <tt>+cookieWithProperties:</tt>
later to reconstitute an equivalent cookie.
<p>See the NSHTTPCookie <tt>-initWithProperties:</tt> method for
more information on the constraints imposed on the dictionary, and
for descriptions of the supported keys and values.
@result The dictionary representation of the receiver.
*/
/// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative
/// - Note: Since this API is under consideration it may be either removed or revised in the near future
public var properties: [String : Any]? {
return self.cookieRepresentation.properties
}
/*!
@method version
@abstract Returns the version of the receiver.
@discussion Version 0 maps to "old-style" Netscape cookies.
Version 1 maps to RFC2965 cookies. There may be future versions.
@result the version of the receiver.
*/
public var version: Int {
return self.cookieRepresentation.version
}
/*!
@method name
@abstract Returns the name of the receiver.
@result the name of the receiver.
*/
public var name: String {
return self.cookieRepresentation.name
}
/*!
@method value
@abstract Returns the value of the receiver.
@result the value of the receiver.
*/
public var value: String {
return self.cookieRepresentation.value
}
/*!
@method expiresDate
@abstract Returns the expires date of the receiver.
@result the expires date of the receiver.
@discussion The expires date is the date when the cookie should be
deleted. The result will be nil if there is no specific expires
date. This will be the case only for "session-only" cookies.
@result The expires date of the receiver.
*/
/*@NSCopying*/ public var expiresDate: NSDate? {
return self.cookieRepresentation.expiresDate
}
/*!
@method isSessionOnly
@abstract Returns whether the receiver is session-only.
@result YES if this receiver should be discarded at the end of the
session (regardless of expiration date), NO if receiver need not
be discarded at the end of the session.
*/
public var sessionOnly: Bool {
return self.cookieRepresentation.sessionOnly
}
/*!
@method domain
@abstract Returns the domain of the receiver.
@discussion This value specifies URL domain to which the cookie
should be sent. A domain with a leading dot means the cookie
should be sent to subdomains as well, assuming certain other
restrictions are valid. See RFC 2965 for more detail.
@result The domain of the receiver.
*/
public var domain: String {
return self.cookieRepresentation.domain
}
/*!
@method path
@abstract Returns the path of the receiver.
@discussion This value specifies the URL path under the cookie's
domain for which this cookie should be sent. The cookie will also
be sent for children of that path, so "/" is the most general.
@result The path of the receiver.
*/
public var path: String {
return self.cookieRepresentation.path
}
/*!
@method isSecure
@abstract Returns whether the receiver should be sent only over
secure channels
@discussion Cookies may be marked secure by a server (or by a javascript).
Cookies marked as such must only be sent via an encrypted connection to
trusted servers (i.e. via SSL or TLS), and should not be delievered to any
javascript applications to prevent cross-site scripting vulnerabilities.
@result YES if this cookie should be sent only over secure channels,
NO otherwise.
*/
public var secure: Bool {
return self.cookieRepresentation.secure
}
/*!
@method isHTTPOnly
@abstract Returns whether the receiver should only be sent to HTTP servers
per RFC 2965
@discussion Cookies may be marked as HTTPOnly by a server (or by a javascript).
Cookies marked as such must only be sent via HTTP Headers in HTTP Requests
for URL's that match both the path and domain of the respective Cookies.
Specifically these cookies should not be delivered to any javascript
applications to prevent cross-site scripting vulnerabilities.
@result YES if this cookie should only be sent via HTTP headers,
NO otherwise.
*/
public var HTTPOnly: Bool {
return self.cookieRepresentation.HTTPOnly
}
/*!
@method comment
@abstract Returns the comment of the receiver.
@discussion This value specifies a string which is suitable for
presentation to the user explaining the contents and purpose of this
cookie. It may be nil.
@result The comment of the receiver, or nil if the receiver has no
comment.
*/
public var comment: String? {
return self.cookieRepresentation.comment
}
/*!
@method commentURL
@abstract Returns the comment URL of the receiver.
@discussion This value specifies a URL which is suitable for
presentation to the user as a link for further information about
this cookie. It may be nil.
@result The comment URL of the receiver, or nil if the receiver
has no comment URL.
*/
/*@NSCopying*/ public var commentURL: NSURL? {
return self.cookieRepresentation.commentURL
}
/*!
@method portList
@abstract Returns the list ports to which the receiver should be
sent.
@discussion This value specifies an NSArray of NSNumbers
(containing integers) which specify the only ports to which this
cookie should be sent.
@result The list ports to which the receiver should be sent. The
array may be nil, in which case this cookie can be sent to any
port.
*/
public var portList: [NSNumber]? {
return self.cookieRepresentation.portList
}
}