Skip to content

Commit

Permalink
re-sanitizing for supportsSecureCoding
Browse files Browse the repository at this point in the history
  • Loading branch information
Coeur committed Aug 26, 2024
1 parent 28ac066 commit e424b2c
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 36 deletions.
73 changes: 38 additions & 35 deletions Sources/FoundationNetworking/URLResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ open class URLResponse : NSObject, NSSecureCoding, NSCopying, @unchecked Sendabl
guard let nsurl = aDecoder.decodeObject(of: NSURL.self, forKey: "NS.url") else { return nil }
self.url = nsurl as URL


if let mimetype = aDecoder.decodeObject(of: NSString.self, forKey: "NS.mimeType") {
self.mimeType = mimetype as String
}
Expand All @@ -46,12 +45,9 @@ open class URLResponse : NSObject, NSSecureCoding, NSCopying, @unchecked Sendabl
self.textEncodingName = encodedEncodingName as String
}

if let encodedFilename = aDecoder.decodeObject(of: NSString.self, forKey: "NS.suggestedFilename")?.lastPathComponent,
!encodedFilename.isEmpty {
self.suggestedFilename = encodedFilename
} else {
self.suggestedFilename = "Unknown"
}
// re-sanitizing with lastPathComponent because of supportsSecureCoding
let encodedFilename = aDecoder.decodeObject(of: NSString.self, forKey: "NS.suggestedFilename")?.lastPathComponent
self.suggestedFilename = encodedFilename?.isEmpty != false ? "Unknown" : encodedFilename
}

open func encode(with aCoder: NSCoder) {
Expand Down Expand Up @@ -180,6 +176,25 @@ open class URLResponse : NSObject, NSSecureCoding, NSCopying, @unchecked Sendabl
/// protocol responses.
open class HTTPURLResponse : URLResponse, @unchecked Sendable {

private static func sanitize(headerFields: [String: String]?) -> [String: String] {
// Canonicalize the header fields by capitalizing the field names, but not X- Headers
// This matches the behaviour of Darwin.
guard let headerFields = headerFields else { return [:] }
var canonicalizedFields: [String: String] = [:]

for (key, value) in headerFields {
if key.isEmpty { continue }
if key.hasPrefix("x-") || key.hasPrefix("X-") {
canonicalizedFields[key] = value
} else if key.caseInsensitiveCompare("WWW-Authenticate") == .orderedSame {
canonicalizedFields["WWW-Authenticate"] = value
} else {
canonicalizedFields[key.capitalized] = value
}
}
return canonicalizedFields
}

/// Initializer for HTTPURLResponse objects.
///
/// - Parameter url: the URL from which the response was generated.
Expand All @@ -189,30 +204,13 @@ open class HTTPURLResponse : URLResponse, @unchecked Sendable {
/// - Returns: the instance of the object, or `nil` if an error occurred during initialization.
public init?(url: URL, statusCode: Int, httpVersion: String?, headerFields: [String : String]?) {
self.statusCode = statusCode

self._allHeaderFields = {
// Canonicalize the header fields by capitalizing the field names, but not X- Headers
// This matches the behaviour of Darwin.
guard let headerFields = headerFields else { return [:] }
var canonicalizedFields: [String: String] = [:]

for (key, value) in headerFields {
if key.isEmpty { continue }
if key.hasPrefix("x-") || key.hasPrefix("X-") {
canonicalizedFields[key] = value
} else if key.caseInsensitiveCompare("WWW-Authenticate") == .orderedSame {
canonicalizedFields["WWW-Authenticate"] = value
} else {
canonicalizedFields[key.capitalized] = value
}
}
return canonicalizedFields
}()


self._allHeaderFields = HTTPURLResponse.sanitize(headerFields: headerFields)

super.init(url: url, mimeType: nil, expectedContentLength: 0, textEncodingName: nil)
expectedContentLength = getExpectedContentLength(fromHeaderFields: headerFields) ?? -1
suggestedFilename = getSuggestedFilename(fromHeaderFields: headerFields) ?? "Unknown"
if let type = ContentTypeComponents(headerFields: headerFields) {
expectedContentLength = getExpectedContentLength(fromHeaderFields: _allHeaderFields) ?? -1
suggestedFilename = getSuggestedFilename(fromHeaderFields: _allHeaderFields) ?? "Unknown"
if let type = ContentTypeComponents(headerFields: _allHeaderFields) {
mimeType = type.mimeType.lowercased()
textEncodingName = type.textEncoding?.lowercased()
}
Expand All @@ -225,13 +223,18 @@ open class HTTPURLResponse : URLResponse, @unchecked Sendable {

self.statusCode = aDecoder.decodeInteger(forKey: "NS.statusCode")

if aDecoder.containsValue(forKey: "NS.allHeaderFields") {
self._allHeaderFields = aDecoder.decodeObject(of: NSDictionary.self, forKey: "NS.allHeaderFields") as! [String: String]
} else {
self._allHeaderFields = [:]
}
// re-sanitizing dictionary because of supportsSecureCoding
self._allHeaderFields = HTTPURLResponse.sanitize(headerFields: aDecoder.decodeObject(of: NSDictionary.self, forKey: "NS.allHeaderFields") as? [String: String])

super.init(coder: aDecoder)

// re-sanitizing from _allHeaderFields because of supportsSecureCoding
expectedContentLength = getExpectedContentLength(fromHeaderFields: _allHeaderFields) ?? -1
suggestedFilename = getSuggestedFilename(fromHeaderFields: _allHeaderFields) ?? "Unknown"
if let type = ContentTypeComponents(headerFields: _allHeaderFields) {
mimeType = type.mimeType.lowercased()
textEncodingName = type.textEncoding?.lowercased()
}
}

open override func encode(with aCoder: NSCoder) {
Expand Down
3 changes: 2 additions & 1 deletion Tests/Foundation/TestHTTPURLResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ class TestHTTPURLResponse: XCTestCase {

func test_NSCoding() {
let url = URL(string: "https://apple.com")!
let f = ["Content-Type": "text/HTML; charset=ISO-8859-4"]
let f = ["Content-Type": "text/HTML; charset=ISO-8859-4",
"Content-Disposition": "attachment; filename=fname.ext"]

let responseA = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: f)!
let responseB = NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: responseA)) as! HTTPURLResponse
Expand Down

0 comments on commit e424b2c

Please sign in to comment.