diff --git a/Foundation/HTTPCookie.swift b/Foundation/HTTPCookie.swift index 6e4d88d024..12f739b407 100644 --- a/Foundation/HTTPCookie.swift +++ b/Foundation/HTTPCookie.swift @@ -266,54 +266,55 @@ open class HTTPCookie : NSObject { } _version = version - if let portString = properties[.port] as? String, _version == 1 { - _portList = portString.split(separator: ",") + if let portString = properties[.port] as? String { + let portList = portString.split(separator: ",") .compactMap { Int(String($0)) } .map { NSNumber(value: $0) } + if version == 1 { + _portList = portList + } else { + // Version 0 only stores a single port number + _portList = portList.count > 0 ? [portList[0]] : nil + } } else { _portList = nil } - // TODO: factor into a utility function - if version == 0 { + var expDate: Date? = nil + // Maximum-Age is prefered over expires-Date but only version 1 cookies use Maximum-Age + if let maximumAge = properties[.maximumAge] as? String, + let secondsFromNow = Int(maximumAge) { + if version == 1 { + expDate = Date(timeIntervalSinceNow: Double(secondsFromNow)) + } + } else { let expiresProperty = properties[.expires] if let date = expiresProperty as? Date { - _expiresDate = date + expDate = date } else if let dateString = expiresProperty as? String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss O" // per RFC 6265 '' let timeZone = TimeZone(abbreviation: "GMT") formatter.timeZone = timeZone - _expiresDate = formatter.date(from: dateString) - } else { - _expiresDate = nil + expDate = formatter.date(from: dateString) } - } else if - let maximumAge = properties[.maximumAge] as? String, - let secondsFromNow = Int(maximumAge), _version == 1 { - _expiresDate = Date(timeIntervalSinceNow: Double(secondsFromNow)) - } else { - _expiresDate = nil } + _expiresDate = expDate if let discardString = properties[.discard] as? String { _sessionOnly = discardString == "TRUE" } else { _sessionOnly = properties[.maximumAge] == nil && version >= 1 } - if version == 0 { - _comment = nil - _commentURL = nil + + _comment = properties[.comment] as? String + if let commentURL = properties[.commentURL] as? URL { + _commentURL = commentURL + } else if let commentURL = properties[.commentURL] as? String { + _commentURL = URL(string: commentURL) } else { - _comment = properties[.comment] as? String - if let commentURL = properties[.commentURL] as? URL { - _commentURL = commentURL - } else if let commentURL = properties[.commentURL] as? String { - _commentURL = URL(string: commentURL) - } else { - _commentURL = nil - } + _commentURL = nil } _HTTPOnly = false @@ -363,7 +364,11 @@ open class HTTPCookie : NSObject { cookieString.removeLast() cookieString.removeLast() } - return ["Cookie": cookieString] + if cookieString == "" { + return [:] + } else { + return ["Cookie": cookieString] + } } /// Return an array of cookies parsed from the specified response header fields and URL. @@ -418,9 +423,9 @@ open class HTTPCookie : NSObject { properties[canonicalize(name)] = value } - //if domain wasn't provided use the URL + // If domain wasn't provided, extract it from the URL if properties[.domain] == nil { - properties[.domain] = url.absoluteString + properties[.domain] = url.host } //the default Path is "/" diff --git a/Foundation/NSURL.swift b/Foundation/NSURL.swift index e847be619b..f519981133 100644 --- a/Foundation/NSURL.swift +++ b/Foundation/NSURL.swift @@ -611,8 +611,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying { guard isFileURL, let path = path else { throw NSError(domain: NSCocoaErrorDomain, - code: CocoaError.Code.fileNoSuchFile.rawValue) - //return false + code: CocoaError.Code.fileReadUnsupportedScheme.rawValue) } guard FileManager.default.fileExists(atPath: path) else { @@ -621,7 +620,6 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying { userInfo: [ "NSURL" : self, "NSFilePath" : path]) - //return false } return true diff --git a/Foundation/NSURLRequest.swift b/Foundation/NSURLRequest.swift index 567ead540f..ac823230ae 100644 --- a/Foundation/NSURLRequest.swift +++ b/Foundation/NSURLRequest.swift @@ -454,11 +454,13 @@ open class NSMutableURLRequest : NSURLRequest { /// - Parameter value: the header field value. /// - Parameter field: the header field name (case-insensitive). open func setValue(_ value: String?, forHTTPHeaderField field: String) { + // Store the field name capitalized to match native Foundation + let capitalizedFieldName = field.capitalized var f: [String : String] = allHTTPHeaderFields ?? [:] - if let old = existingHeaderField(field, inHeaderFields: f) { + if let old = existingHeaderField(capitalizedFieldName, inHeaderFields: f) { f.removeValue(forKey: old.0) } - f[field] = value + f[capitalizedFieldName] = value allHTTPHeaderFields = f } @@ -474,11 +476,13 @@ open class NSMutableURLRequest : NSURLRequest { /// - Parameter value: the header field value. /// - Parameter field: the header field name (case-insensitive). open func addValue(_ value: String, forHTTPHeaderField field: String) { + // Store the field name capitalized to match native Foundation + let capitalizedFieldName = field.capitalized var f: [String : String] = allHTTPHeaderFields ?? [:] - if let old = existingHeaderField(field, inHeaderFields: f) { + if let old = existingHeaderField(capitalizedFieldName, inHeaderFields: f) { f[old.0] = old.1 + "," + value } else { - f[field] = value + f[capitalizedFieldName] = value } allHTTPHeaderFields = f } diff --git a/Foundation/URLSession/Configuration.swift b/Foundation/URLSession/Configuration.swift index 7ec13ab829..5dba5b37b7 100644 --- a/Foundation/URLSession/Configuration.swift +++ b/Foundation/URLSession/Configuration.swift @@ -102,10 +102,6 @@ internal extension URLSession._Configuration { internal extension URLSession._Configuration { func configure(request: URLRequest) -> URLRequest { var request = request - httpAdditionalHeaders?.forEach { - guard request.value(forHTTPHeaderField: $0.0) == nil else { return } - request.setValue($0.1, forHTTPHeaderField: $0.0) - } return setCookies(on: request) } diff --git a/Foundation/URLSession/http/HTTPURLProtocol.swift b/Foundation/URLSession/http/HTTPURLProtocol.swift index 0f4c63042a..ba40424f81 100644 --- a/Foundation/URLSession/http/HTTPURLProtocol.swift +++ b/Foundation/URLSession/http/HTTPURLProtocol.swift @@ -125,6 +125,14 @@ internal class _HTTPURLProtocol: _NativeProtocol { httpHeaders = hh } else { hh.forEach { + // When adding a header, remove any current entry with the same header name regardless of case + let newKey = $0.lowercased() + for key in httpHeaders!.keys { + if newKey == (key as! String).lowercased() { + httpHeaders?.removeValue(forKey: key) + break + } + } httpHeaders![$0] = $1 } } diff --git a/TestFoundation/TestBundle.swift b/TestFoundation/TestBundle.swift index d5e8f5f6ea..d7d5d78e89 100644 --- a/TestFoundation/TestBundle.swift +++ b/TestFoundation/TestBundle.swift @@ -432,7 +432,7 @@ class TestBundle : XCTestCase { XCTAssertNotNil(bundle.executableURL) } } - + func test_bundleFindAuxiliaryExecutables() { _withEachPlaygroundLayout { (playground) in let bundle = Bundle(path: playground.bundlePath)! @@ -440,12 +440,14 @@ class TestBundle : XCTestCase { XCTAssertNil(bundle.url(forAuxiliaryExecutable: "does_not_exist_at_all")) } } - + func test_mainBundleExecutableURL() { +#if !DARWIN_COMPATIBILITY_TESTS // _CFProcessPath() is unavailable on native Foundation let maybeURL = Bundle.main.executableURL XCTAssertNotNil(maybeURL) guard let url = maybeURL else { return } XCTAssertEqual(url.path, String(cString: _CFProcessPath())) +#endif } } diff --git a/TestFoundation/TestHTTPCookie.swift b/TestFoundation/TestHTTPCookie.swift index 5ca445fbaf..b46e373087 100644 --- a/TestFoundation/TestHTTPCookie.swift +++ b/TestFoundation/TestHTTPCookie.swift @@ -67,31 +67,29 @@ class TestHTTPCookie: XCTestCase { .domain: "apple.com", .originURL: URL(string: "https://apple.com")!, .comment: "This comment should be nil since this is a v0 cookie.", - .commentURL: URL(string: "https://apple.com")!, + .commentURL: "https://apple.com", .discard: "TRUE", .expires: Date(timeIntervalSince1970: 1000), .maximumAge: "2000", .port: "443,8443", .secure: "YES" ]) - XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.comment) - XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.commentURL) + XCTAssertEqual(versionZeroCookieWithInvalidVersionOneProps?.version, 0) + XCTAssertNotNil(versionZeroCookieWithInvalidVersionOneProps?.comment) + XCTAssertNotNil(versionZeroCookieWithInvalidVersionOneProps?.commentURL) XCTAssert(versionZeroCookieWithInvalidVersionOneProps?.isSessionOnly == true) // v0 should never use NSHTTPCookieMaximumAge - XCTAssert( - versionZeroCookieWithInvalidVersionOneProps?.expiresDate?.timeIntervalSince1970 == - Date(timeIntervalSince1970: 1000).timeIntervalSince1970 - ) + XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.expiresDate?.timeIntervalSince1970) - XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.portList) + XCTAssertEqual(versionZeroCookieWithInvalidVersionOneProps?.portList, [NSNumber(value: 443)]) XCTAssert(versionZeroCookieWithInvalidVersionOneProps?.isSecure == true) XCTAssert(versionZeroCookieWithInvalidVersionOneProps?.version == 0) } func test_RequestHeaderFields() { let noCookies: [HTTPCookie] = [] - XCTAssertEqual(HTTPCookie.requestHeaderFields(with: noCookies)["Cookie"], "") + XCTAssertNil(HTTPCookie.requestHeaderFields(with: noCookies)["Cookie"]) let basicCookies: [HTTPCookie] = [ HTTPCookie(properties: [ @@ -117,7 +115,7 @@ class TestHTTPCookie: XCTestCase { "Set-Cookie": "fr=anjd&232; Max-Age=7776000; path=/; domain=.example.com; secure; httponly", "header2":"value2", "header3":"value3"] - let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!) + let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!) XCTAssertEqual(cookies.count, 1) XCTAssertEqual(cookies[0].name, "fr") XCTAssertEqual(cookies[0].value, "anjd&232") @@ -134,7 +132,7 @@ class TestHTTPCookie: XCTestCase { "Set-Cookie": "fr=a&2@#; Max-Age=1186000; path=/; domain=.example.com; secure, xd=plm!@#;path=/;domain=.example2.com", "header2":"value2", "header3":"value3"] - let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!) + let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!) XCTAssertEqual(cookies.count, 2) XCTAssertTrue(cookies[0].isSecure) XCTAssertFalse(cookies[1].isSecure) @@ -142,11 +140,12 @@ class TestHTTPCookie: XCTestCase { func test_cookiesWithResponseHeaderNoDomain() { let header = ["header1":"value1", - "Set-Cookie": "fr=anjd&232; expires=Wed, 21 Sep 2016 05:33:00 GMT; Max-Age=7776000; path=/; secure; httponly", + "Set-Cookie": "fr=anjd&232; expires=Wed, 21 Sep 2016 05:33:00 GMT; path=/; secure; httponly", "header2":"value2", "header3":"value3"] - let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!) - XCTAssertEqual(cookies[0].domain, "http://example.com") + let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!) + XCTAssertEqual(cookies[0].version, 0) + XCTAssertEqual(cookies[0].domain, "example.com") XCTAssertNotNil(cookies[0].expiresDate) let formatter = DateFormatter() @@ -165,8 +164,8 @@ class TestHTTPCookie: XCTestCase { "Set-Cookie": "fr=tx; expires=Wed, 21-Sep-2016 05:33:00 GMT; Max-Age=7776000; secure; httponly", "header2":"value2", "header3":"value3"] - let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!) - XCTAssertEqual(cookies[0].domain, "http://example.com") + let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!) + XCTAssertEqual(cookies[0].domain, "example.com") XCTAssertEqual(cookies[0].path, "/") } } diff --git a/TestFoundation/TestHTTPCookieStorage.swift b/TestFoundation/TestHTTPCookieStorage.swift index 74d574df0d..4289782f4a 100644 --- a/TestFoundation/TestHTTPCookieStorage.swift +++ b/TestFoundation/TestHTTPCookieStorage.swift @@ -273,7 +273,7 @@ class TestHTTPCookieStorage: XCTestCase { } func test_cookieInXDGSpecPath() { -#if !os(Android) +#if !os(Android) && !DARWIN_COMPATIBILITY_TESTS // No XDG on native Foundation //Test without setting the environment variable let testCookie = HTTPCookie(properties: [ .name: "TestCookie0", diff --git a/TestFoundation/TestNSURLRequest.swift b/TestFoundation/TestNSURLRequest.swift index 2154dbb289..b409f12bdc 100644 --- a/TestFoundation/TestNSURLRequest.swift +++ b/TestFoundation/TestNSURLRequest.swift @@ -65,14 +65,14 @@ class TestNSURLRequest : XCTestCase { XCTAssertNotNil(request.allHTTPHeaderFields) XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/json") - // Setting "accept" should remove "Accept" + // Setting "accept" should update "Accept" request.setValue("application/xml", forHTTPHeaderField: "accept") - XCTAssertNil(request.allHTTPHeaderFields?["Accept"]) - XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml") + XCTAssertNil(request.allHTTPHeaderFields?["accept"]) + XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml") - // Adding to "Accept" should add to "accept" + // Adding to "Accept" should add to "Accept" request.addValue("text/html", forHTTPHeaderField: "Accept") - XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml,text/html") + XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml,text/html") } func test_copy() { diff --git a/TestFoundation/TestURL.swift b/TestFoundation/TestURL.swift index 6cdbcc4aa8..bad7a0ad47 100644 --- a/TestFoundation/TestURL.swift +++ b/TestFoundation/TestURL.swift @@ -432,7 +432,7 @@ class TestURL : XCTestCase { XCTFail() } catch let error as NSError { XCTAssertEqual(NSCocoaErrorDomain, error.domain) - XCTAssertEqual(CocoaError.Code.fileNoSuchFile.rawValue, error.code) + XCTAssertEqual(CocoaError.Code.fileReadUnsupportedScheme.rawValue, error.code) } catch { XCTFail() } @@ -461,7 +461,7 @@ class TestURL : XCTestCase { XCTFail() } catch let error as NSError { XCTAssertEqual(NSCocoaErrorDomain, error.domain) - XCTAssertEqual(CocoaError.Code.fileNoSuchFile.rawValue, error.code) + XCTAssertEqual(CocoaError.Code.fileReadUnsupportedScheme.rawValue, error.code) } catch { XCTFail() } diff --git a/TestFoundation/TestURLRequest.swift b/TestFoundation/TestURLRequest.swift index abad59f3c4..55ca13fa7c 100644 --- a/TestFoundation/TestURLRequest.swift +++ b/TestFoundation/TestURLRequest.swift @@ -60,16 +60,17 @@ class TestURLRequest : XCTestCase { request.setValue("application/json", forHTTPHeaderField: "Accept") XCTAssertNotNil(request.allHTTPHeaderFields) + XCTAssertNil(request.allHTTPHeaderFields?["accept"]) XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/json") - // Setting "accept" should remove "Accept" + // Setting "accept" should update "Accept" request.setValue("application/xml", forHTTPHeaderField: "accept") - XCTAssertNil(request.allHTTPHeaderFields?["Accept"]) - XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml") + XCTAssertNil(request.allHTTPHeaderFields?["accept"]) + XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml") - // Adding to "Accept" should add to "accept" + // Adding to "Accept" should add to "Accept" request.addValue("text/html", forHTTPHeaderField: "Accept") - XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml,text/html") + XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml,text/html") } func test_copy() { diff --git a/TestFoundation/TestURLSession.swift b/TestFoundation/TestURLSession.swift index 87d544b953..6cf9c05541 100644 --- a/TestFoundation/TestURLSession.swift +++ b/TestFoundation/TestURLSession.swift @@ -268,12 +268,12 @@ class TestURLSession : LoopbackServerTest { func test_verifyHttpAdditionalHeaders() { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 5 - config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3"] + config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3", "header4": "svalue4"] let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/requestHeaders" let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) var expect = expectation(description: "POST \(urlString) with additional headers") var req = URLRequest(url: URL(string: urlString)!) - let headers = ["header1": "rvalue1", "header2": "rvalue2"] + let headers = ["header1": "rvalue1", "header2": "rvalue2", "Header4": "rvalue4"] req.httpMethod = "POST" req.allHTTPHeaderFields = headers var task = session.dataTask(with: req) { (data, _, error) -> Void in @@ -284,6 +284,8 @@ class TestURLSession : LoopbackServerTest { XCTAssertNotNil(headers.range(of: "header1: rvalue1")) XCTAssertNotNil(headers.range(of: "header2: rvalue2")) XCTAssertNotNil(headers.range(of: "header3: svalue3")) + XCTAssertNotNil(headers.range(of: "Header4: rvalue4")) + XCTAssertNil(headers.range(of: "header4: svalue")) } task.resume()