Skip to content

Commit 4c3ca09

Browse files
spevansparkera
authored andcommitted
Fix more tests to run correctly on Darwin's native Foundation (swiftlang#1610)
* DarwinCompatibility: Disable tests that wont work on native Foundation * DarwinCompatibility: NSURL checkResourceIsReachable() - If URL is a file, throw error with code .fileReadUnsupportedScheme instead of .fileNoSuchFile to match Darwin. * DarwinCompatibilty: NSURLRequest: Capitalise header names - Correctly override httpAdditionalHeaders headers with allHTTPHeaderFields. * DarwinCompatibility: HTTPCookie fixes for version 0 cookies - For expiry date, prefer maximum-age over expires-date but only use maximum-age for version 1 cookies. - For version 0 cookies only return the first port number, if available.
1 parent 50a7974 commit 4c3ca09

12 files changed

+87
-72
lines changed

Foundation/HTTPCookie.swift

+33-28
Original file line numberDiff line numberDiff line change
@@ -266,54 +266,55 @@ open class HTTPCookie : NSObject {
266266
}
267267
_version = version
268268

269-
if let portString = properties[.port] as? String, _version == 1 {
270-
_portList = portString.split(separator: ",")
269+
if let portString = properties[.port] as? String {
270+
let portList = portString.split(separator: ",")
271271
.compactMap { Int(String($0)) }
272272
.map { NSNumber(value: $0) }
273+
if version == 1 {
274+
_portList = portList
275+
} else {
276+
// Version 0 only stores a single port number
277+
_portList = portList.count > 0 ? [portList[0]] : nil
278+
}
273279
} else {
274280
_portList = nil
275281
}
276282

277-
// TODO: factor into a utility function
278-
if version == 0 {
283+
var expDate: Date? = nil
284+
// Maximum-Age is prefered over expires-Date but only version 1 cookies use Maximum-Age
285+
if let maximumAge = properties[.maximumAge] as? String,
286+
let secondsFromNow = Int(maximumAge) {
287+
if version == 1 {
288+
expDate = Date(timeIntervalSinceNow: Double(secondsFromNow))
289+
}
290+
} else {
279291
let expiresProperty = properties[.expires]
280292
if let date = expiresProperty as? Date {
281-
_expiresDate = date
293+
expDate = date
282294
} else if let dateString = expiresProperty as? String {
283295
let formatter = DateFormatter()
284296
formatter.locale = Locale(identifier: "en_US_POSIX")
285297
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss O" // per RFC 6265 '<rfc1123-date, defined in [RFC2616], Section 3.3.1>'
286298
let timeZone = TimeZone(abbreviation: "GMT")
287299
formatter.timeZone = timeZone
288-
_expiresDate = formatter.date(from: dateString)
289-
} else {
290-
_expiresDate = nil
300+
expDate = formatter.date(from: dateString)
291301
}
292-
} else if
293-
let maximumAge = properties[.maximumAge] as? String,
294-
let secondsFromNow = Int(maximumAge), _version == 1 {
295-
_expiresDate = Date(timeIntervalSinceNow: Double(secondsFromNow))
296-
} else {
297-
_expiresDate = nil
298302
}
303+
_expiresDate = expDate
299304

300305
if let discardString = properties[.discard] as? String {
301306
_sessionOnly = discardString == "TRUE"
302307
} else {
303308
_sessionOnly = properties[.maximumAge] == nil && version >= 1
304309
}
305-
if version == 0 {
306-
_comment = nil
307-
_commentURL = nil
310+
311+
_comment = properties[.comment] as? String
312+
if let commentURL = properties[.commentURL] as? URL {
313+
_commentURL = commentURL
314+
} else if let commentURL = properties[.commentURL] as? String {
315+
_commentURL = URL(string: commentURL)
308316
} else {
309-
_comment = properties[.comment] as? String
310-
if let commentURL = properties[.commentURL] as? URL {
311-
_commentURL = commentURL
312-
} else if let commentURL = properties[.commentURL] as? String {
313-
_commentURL = URL(string: commentURL)
314-
} else {
315-
_commentURL = nil
316-
}
317+
_commentURL = nil
317318
}
318319
_HTTPOnly = false
319320

@@ -363,7 +364,11 @@ open class HTTPCookie : NSObject {
363364
cookieString.removeLast()
364365
cookieString.removeLast()
365366
}
366-
return ["Cookie": cookieString]
367+
if cookieString == "" {
368+
return [:]
369+
} else {
370+
return ["Cookie": cookieString]
371+
}
367372
}
368373

369374
/// Return an array of cookies parsed from the specified response header fields and URL.
@@ -418,9 +423,9 @@ open class HTTPCookie : NSObject {
418423
properties[canonicalize(name)] = value
419424
}
420425

421-
//if domain wasn't provided use the URL
426+
// If domain wasn't provided, extract it from the URL
422427
if properties[.domain] == nil {
423-
properties[.domain] = url.absoluteString
428+
properties[.domain] = url.host
424429
}
425430

426431
//the default Path is "/"

Foundation/NSURL.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
611611
guard isFileURL,
612612
let path = path else {
613613
throw NSError(domain: NSCocoaErrorDomain,
614-
code: CocoaError.Code.fileNoSuchFile.rawValue)
615-
//return false
614+
code: CocoaError.Code.fileReadUnsupportedScheme.rawValue)
616615
}
617616

618617
guard FileManager.default.fileExists(atPath: path) else {
@@ -621,7 +620,6 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
621620
userInfo: [
622621
"NSURL" : self,
623622
"NSFilePath" : path])
624-
//return false
625623
}
626624

627625
return true

Foundation/NSURLRequest.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -454,11 +454,13 @@ open class NSMutableURLRequest : NSURLRequest {
454454
/// - Parameter value: the header field value.
455455
/// - Parameter field: the header field name (case-insensitive).
456456
open func setValue(_ value: String?, forHTTPHeaderField field: String) {
457+
// Store the field name capitalized to match native Foundation
458+
let capitalizedFieldName = field.capitalized
457459
var f: [String : String] = allHTTPHeaderFields ?? [:]
458-
if let old = existingHeaderField(field, inHeaderFields: f) {
460+
if let old = existingHeaderField(capitalizedFieldName, inHeaderFields: f) {
459461
f.removeValue(forKey: old.0)
460462
}
461-
f[field] = value
463+
f[capitalizedFieldName] = value
462464
allHTTPHeaderFields = f
463465
}
464466

@@ -474,11 +476,13 @@ open class NSMutableURLRequest : NSURLRequest {
474476
/// - Parameter value: the header field value.
475477
/// - Parameter field: the header field name (case-insensitive).
476478
open func addValue(_ value: String, forHTTPHeaderField field: String) {
479+
// Store the field name capitalized to match native Foundation
480+
let capitalizedFieldName = field.capitalized
477481
var f: [String : String] = allHTTPHeaderFields ?? [:]
478-
if let old = existingHeaderField(field, inHeaderFields: f) {
482+
if let old = existingHeaderField(capitalizedFieldName, inHeaderFields: f) {
479483
f[old.0] = old.1 + "," + value
480484
} else {
481-
f[field] = value
485+
f[capitalizedFieldName] = value
482486
}
483487
allHTTPHeaderFields = f
484488
}

Foundation/URLSession/Configuration.swift

-4
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ internal extension URLSession._Configuration {
102102
internal extension URLSession._Configuration {
103103
func configure(request: URLRequest) -> URLRequest {
104104
var request = request
105-
httpAdditionalHeaders?.forEach {
106-
guard request.value(forHTTPHeaderField: $0.0) == nil else { return }
107-
request.setValue($0.1, forHTTPHeaderField: $0.0)
108-
}
109105
return setCookies(on: request)
110106
}
111107

Foundation/URLSession/http/HTTPURLProtocol.swift

+8
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,14 @@ internal class _HTTPURLProtocol: _NativeProtocol {
125125
httpHeaders = hh
126126
} else {
127127
hh.forEach {
128+
// When adding a header, remove any current entry with the same header name regardless of case
129+
let newKey = $0.lowercased()
130+
for key in httpHeaders!.keys {
131+
if newKey == (key as! String).lowercased() {
132+
httpHeaders?.removeValue(forKey: key)
133+
break
134+
}
135+
}
128136
httpHeaders![$0] = $1
129137
}
130138
}

TestFoundation/TestBundle.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -432,20 +432,22 @@ class TestBundle : XCTestCase {
432432
XCTAssertNotNil(bundle.executableURL)
433433
}
434434
}
435-
435+
436436
func test_bundleFindAuxiliaryExecutables() {
437437
_withEachPlaygroundLayout { (playground) in
438438
let bundle = Bundle(path: playground.bundlePath)!
439439
XCTAssertNotNil(bundle.url(forAuxiliaryExecutable: _auxiliaryExecutable))
440440
XCTAssertNil(bundle.url(forAuxiliaryExecutable: "does_not_exist_at_all"))
441441
}
442442
}
443-
443+
444444
func test_mainBundleExecutableURL() {
445+
#if !DARWIN_COMPATIBILITY_TESTS // _CFProcessPath() is unavailable on native Foundation
445446
let maybeURL = Bundle.main.executableURL
446447
XCTAssertNotNil(maybeURL)
447448
guard let url = maybeURL else { return }
448449

449450
XCTAssertEqual(url.path, String(cString: _CFProcessPath()))
451+
#endif
450452
}
451453
}

TestFoundation/TestHTTPCookie.swift

+15-16
Original file line numberDiff line numberDiff line change
@@ -67,31 +67,29 @@ class TestHTTPCookie: XCTestCase {
6767
.domain: "apple.com",
6868
.originURL: URL(string: "https://apple.com")!,
6969
.comment: "This comment should be nil since this is a v0 cookie.",
70-
.commentURL: URL(string: "https://apple.com")!,
70+
.commentURL: "https://apple.com",
7171
.discard: "TRUE",
7272
.expires: Date(timeIntervalSince1970: 1000),
7373
.maximumAge: "2000",
7474
.port: "443,8443",
7575
.secure: "YES"
7676
])
77-
XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.comment)
78-
XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.commentURL)
77+
XCTAssertEqual(versionZeroCookieWithInvalidVersionOneProps?.version, 0)
78+
XCTAssertNotNil(versionZeroCookieWithInvalidVersionOneProps?.comment)
79+
XCTAssertNotNil(versionZeroCookieWithInvalidVersionOneProps?.commentURL)
7980
XCTAssert(versionZeroCookieWithInvalidVersionOneProps?.isSessionOnly == true)
8081

8182
// v0 should never use NSHTTPCookieMaximumAge
82-
XCTAssert(
83-
versionZeroCookieWithInvalidVersionOneProps?.expiresDate?.timeIntervalSince1970 ==
84-
Date(timeIntervalSince1970: 1000).timeIntervalSince1970
85-
)
83+
XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.expiresDate?.timeIntervalSince1970)
8684

87-
XCTAssertNil(versionZeroCookieWithInvalidVersionOneProps?.portList)
85+
XCTAssertEqual(versionZeroCookieWithInvalidVersionOneProps?.portList, [NSNumber(value: 443)])
8886
XCTAssert(versionZeroCookieWithInvalidVersionOneProps?.isSecure == true)
8987
XCTAssert(versionZeroCookieWithInvalidVersionOneProps?.version == 0)
9088
}
9189

9290
func test_RequestHeaderFields() {
9391
let noCookies: [HTTPCookie] = []
94-
XCTAssertEqual(HTTPCookie.requestHeaderFields(with: noCookies)["Cookie"], "")
92+
XCTAssertNil(HTTPCookie.requestHeaderFields(with: noCookies)["Cookie"])
9593

9694
let basicCookies: [HTTPCookie] = [
9795
HTTPCookie(properties: [
@@ -117,7 +115,7 @@ class TestHTTPCookie: XCTestCase {
117115
"Set-Cookie": "fr=anjd&232; Max-Age=7776000; path=/; domain=.example.com; secure; httponly",
118116
"header2":"value2",
119117
"header3":"value3"]
120-
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!)
118+
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!)
121119
XCTAssertEqual(cookies.count, 1)
122120
XCTAssertEqual(cookies[0].name, "fr")
123121
XCTAssertEqual(cookies[0].value, "anjd&232")
@@ -134,19 +132,20 @@ class TestHTTPCookie: XCTestCase {
134132
"Set-Cookie": "fr=a&2@#; Max-Age=1186000; path=/; domain=.example.com; secure, xd=plm!@#;path=/;domain=.example2.com",
135133
"header2":"value2",
136134
"header3":"value3"]
137-
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!)
135+
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!)
138136
XCTAssertEqual(cookies.count, 2)
139137
XCTAssertTrue(cookies[0].isSecure)
140138
XCTAssertFalse(cookies[1].isSecure)
141139
}
142140

143141
func test_cookiesWithResponseHeaderNoDomain() {
144142
let header = ["header1":"value1",
145-
"Set-Cookie": "fr=anjd&232; expires=Wed, 21 Sep 2016 05:33:00 GMT; Max-Age=7776000; path=/; secure; httponly",
143+
"Set-Cookie": "fr=anjd&232; expires=Wed, 21 Sep 2016 05:33:00 GMT; path=/; secure; httponly",
146144
"header2":"value2",
147145
"header3":"value3"]
148-
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!)
149-
XCTAssertEqual(cookies[0].domain, "http://example.com")
146+
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!)
147+
XCTAssertEqual(cookies[0].version, 0)
148+
XCTAssertEqual(cookies[0].domain, "example.com")
150149
XCTAssertNotNil(cookies[0].expiresDate)
151150

152151
let formatter = DateFormatter()
@@ -165,8 +164,8 @@ class TestHTTPCookie: XCTestCase {
165164
"Set-Cookie": "fr=tx; expires=Wed, 21-Sep-2016 05:33:00 GMT; Max-Age=7776000; secure; httponly",
166165
"header2":"value2",
167166
"header3":"value3"]
168-
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "http://example.com")!)
169-
XCTAssertEqual(cookies[0].domain, "http://example.com")
167+
let cookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: URL(string: "https://example.com")!)
168+
XCTAssertEqual(cookies[0].domain, "example.com")
170169
XCTAssertEqual(cookies[0].path, "/")
171170
}
172171
}

TestFoundation/TestHTTPCookieStorage.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ class TestHTTPCookieStorage: XCTestCase {
273273
}
274274

275275
func test_cookieInXDGSpecPath() {
276-
#if !os(Android)
276+
#if !os(Android) && !DARWIN_COMPATIBILITY_TESTS // No XDG on native Foundation
277277
//Test without setting the environment variable
278278
let testCookie = HTTPCookie(properties: [
279279
.name: "TestCookie0",

TestFoundation/TestNSURLRequest.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ class TestNSURLRequest : XCTestCase {
6565
XCTAssertNotNil(request.allHTTPHeaderFields)
6666
XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/json")
6767

68-
// Setting "accept" should remove "Accept"
68+
// Setting "accept" should update "Accept"
6969
request.setValue("application/xml", forHTTPHeaderField: "accept")
70-
XCTAssertNil(request.allHTTPHeaderFields?["Accept"])
71-
XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml")
70+
XCTAssertNil(request.allHTTPHeaderFields?["accept"])
71+
XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml")
7272

73-
// Adding to "Accept" should add to "accept"
73+
// Adding to "Accept" should add to "Accept"
7474
request.addValue("text/html", forHTTPHeaderField: "Accept")
75-
XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml,text/html")
75+
XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml,text/html")
7676
}
7777

7878
func test_copy() {

TestFoundation/TestURL.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ class TestURL : XCTestCase {
432432
XCTFail()
433433
} catch let error as NSError {
434434
XCTAssertEqual(NSCocoaErrorDomain, error.domain)
435-
XCTAssertEqual(CocoaError.Code.fileNoSuchFile.rawValue, error.code)
435+
XCTAssertEqual(CocoaError.Code.fileReadUnsupportedScheme.rawValue, error.code)
436436
} catch {
437437
XCTFail()
438438
}
@@ -461,7 +461,7 @@ class TestURL : XCTestCase {
461461
XCTFail()
462462
} catch let error as NSError {
463463
XCTAssertEqual(NSCocoaErrorDomain, error.domain)
464-
XCTAssertEqual(CocoaError.Code.fileNoSuchFile.rawValue, error.code)
464+
XCTAssertEqual(CocoaError.Code.fileReadUnsupportedScheme.rawValue, error.code)
465465
} catch {
466466
XCTFail()
467467
}

TestFoundation/TestURLRequest.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,17 @@ class TestURLRequest : XCTestCase {
6060

6161
request.setValue("application/json", forHTTPHeaderField: "Accept")
6262
XCTAssertNotNil(request.allHTTPHeaderFields)
63+
XCTAssertNil(request.allHTTPHeaderFields?["accept"])
6364
XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/json")
6465

65-
// Setting "accept" should remove "Accept"
66+
// Setting "accept" should update "Accept"
6667
request.setValue("application/xml", forHTTPHeaderField: "accept")
67-
XCTAssertNil(request.allHTTPHeaderFields?["Accept"])
68-
XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml")
68+
XCTAssertNil(request.allHTTPHeaderFields?["accept"])
69+
XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml")
6970

70-
// Adding to "Accept" should add to "accept"
71+
// Adding to "Accept" should add to "Accept"
7172
request.addValue("text/html", forHTTPHeaderField: "Accept")
72-
XCTAssertEqual(request.allHTTPHeaderFields?["accept"], "application/xml,text/html")
73+
XCTAssertEqual(request.allHTTPHeaderFields?["Accept"], "application/xml,text/html")
7374
}
7475

7576
func test_copy() {

TestFoundation/TestURLSession.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,12 @@ class TestURLSession : LoopbackServerTest {
268268
func test_verifyHttpAdditionalHeaders() {
269269
let config = URLSessionConfiguration.default
270270
config.timeoutIntervalForRequest = 5
271-
config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3"]
271+
config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3", "header4": "svalue4"]
272272
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/requestHeaders"
273273
let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
274274
var expect = expectation(description: "POST \(urlString) with additional headers")
275275
var req = URLRequest(url: URL(string: urlString)!)
276-
let headers = ["header1": "rvalue1", "header2": "rvalue2"]
276+
let headers = ["header1": "rvalue1", "header2": "rvalue2", "Header4": "rvalue4"]
277277
req.httpMethod = "POST"
278278
req.allHTTPHeaderFields = headers
279279
var task = session.dataTask(with: req) { (data, _, error) -> Void in
@@ -284,6 +284,8 @@ class TestURLSession : LoopbackServerTest {
284284
XCTAssertNotNil(headers.range(of: "header1: rvalue1"))
285285
XCTAssertNotNil(headers.range(of: "header2: rvalue2"))
286286
XCTAssertNotNil(headers.range(of: "header3: svalue3"))
287+
XCTAssertNotNil(headers.range(of: "Header4: rvalue4"))
288+
XCTAssertNil(headers.range(of: "header4: svalue"))
287289
}
288290
task.resume()
289291

0 commit comments

Comments
 (0)