Skip to content

Check for invalid URLRequest header values #4650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Sources/FoundationNetworking/NSURLRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ 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) {
// Check that the header value is valid
if let value = value, !httpHeaderValueIsValid(value) {
return
}
// Store the field name capitalized to match native Foundation
let capitalizedFieldName = field.capitalized
var f: [String : String] = allHTTPHeaderFields ?? [:]
Expand All @@ -494,6 +498,10 @@ 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) {
// Check that the header value is valid
if !httpHeaderValueIsValid(value) {
return
}
// Store the field name capitalized to match native Foundation
let capitalizedFieldName = field.capitalized
var f: [String : String] = allHTTPHeaderFields ?? [:]
Expand Down Expand Up @@ -571,6 +579,42 @@ open class NSMutableURLRequest : NSURLRequest {
var protocolProperties: [String: Any] = [:]
}

/// Checks if a header value is valid.
/// Allows header line folding, but rejects other invalid uses of CR or LF.
private func httpHeaderValueIsValid(_ value: String) -> Bool {
// Use a state machine to process the header value, transitioning on special
// characters such as CR and LF. The begin state is the accept state. CRLF
// must be followed by a SP or HTAB for valid header line folding.
enum CRLFState {
case begin
case crlf
}
var state = CRLFState.begin
for ch in value {
switch ch {
case "\0", "\r", "\n":
return false
case "\r\n": // Treated as a single Character in Swift
if state == .begin {
state = .crlf
} else {
return false
}
case " ", "\t":
if state == .crlf {
state = .begin
}
break
default:
if state != .begin {
return false
}
break
}
}
return state == .begin
}

/// Returns an existing key-value pair inside the header fields if it exists.
private func existingHeaderField(_ key: String, inHeaderFields fields: [String : String]) -> (String, String)? {
for (k, v) in fields {
Expand Down
39 changes: 39 additions & 0 deletions Tests/Foundation/Tests/TestNSURLRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class TestNSURLRequest : XCTestCase {
("test_NSCoding_3", test_NSCoding_3),
("test_methodNormalization", test_methodNormalization),
("test_description", test_description),
("test_invalidHeaderValues", test_invalidHeaderValues),
("test_validLineFoldedHeaderValues", test_validLineFoldedHeaderValues),
]
}

Expand Down Expand Up @@ -329,4 +331,41 @@ class TestNSURLRequest : XCTestCase {
XCTFail("description of nil URL should contain (null)")
}
}

func test_invalidHeaderValues() {
let url = URL(string: "http://swift.org")!
let request = NSMutableURLRequest(url: url)

let invalidHeaderValues = [
"\r\nevil: hello\r\n\r\nGET /other HTTP/1.1\r\nevil: hello",
"invalid\0NUL",
"invalid\rCR",
"invalidCR\r",
"invalid\nLF",
"invalidLF\n",
"invalid\r\nCRLF",
"invalidCRLF\r\n",
"invalid\r\rCRCR"
]

for (i, value) in invalidHeaderValues.enumerated() {
request.setValue("Bar\(value)", forHTTPHeaderField: "Foo\(i)")
XCTAssertNil(request.value(forHTTPHeaderField: "Foo\(i)"))
request.addValue("Bar\(value)", forHTTPHeaderField: "Foo\(i)")
XCTAssertNil(request.value(forHTTPHeaderField: "Foo\(i)"))
}
}

func test_validLineFoldedHeaderValues() {
let url = URL(string: "http://swift.org")!
let request = NSMutableURLRequest(url: url)

let validHeaderValueLineFoldedTab = "Bar\r\n\tBuz"
request.setValue(validHeaderValueLineFoldedTab, forHTTPHeaderField: "FooTab")
XCTAssertEqual(request.value(forHTTPHeaderField: "FooTab"), validHeaderValueLineFoldedTab)

let validHeaderValueLineFoldedSpace = "Bar\r\n Buz"
request.setValue(validHeaderValueLineFoldedSpace, forHTTPHeaderField: "FooSpace")
XCTAssertEqual(request.value(forHTTPHeaderField: "FooSpace"), validHeaderValueLineFoldedSpace)
}
}
39 changes: 39 additions & 0 deletions Tests/Foundation/Tests/TestURLRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class TestURLRequest : XCTestCase {
("test_methodNormalization", test_methodNormalization),
("test_description", test_description),
("test_relativeURL", test_relativeURL),
("test_invalidHeaderValues", test_invalidHeaderValues),
("test_validLineFoldedHeaderValues", test_validLineFoldedHeaderValues),
]
}

Expand Down Expand Up @@ -315,4 +317,41 @@ class TestURLRequest : XCTestCase {
XCTAssertEqual(nsreq.url?.absoluteURL.relativeString, "http://httpbin.org/get")
XCTAssertEqual(nsreq.url?.absoluteURL.absoluteString, "http://httpbin.org/get")
}

func test_invalidHeaderValues() {
let url = URL(string: "http://swift.org")!
var request = URLRequest(url: url)

let invalidHeaderValues = [
"\r\nevil: hello\r\n\r\nGET /other HTTP/1.1\r\nevil: hello",
"invalid\0NUL",
"invalid\rCR",
"invalidCR\r",
"invalid\nLF",
"invalidLF\n",
"invalid\r\nCRLF",
"invalidCRLF\r\n",
"invalid\r\rCRCR"
]

for (i, value) in invalidHeaderValues.enumerated() {
request.setValue("Bar\(value)", forHTTPHeaderField: "Foo\(i)")
XCTAssertNil(request.value(forHTTPHeaderField: "Foo\(i)"))
request.addValue("Bar\(value)", forHTTPHeaderField: "Foo\(i)")
XCTAssertNil(request.value(forHTTPHeaderField: "Foo\(i)"))
}
}

func test_validLineFoldedHeaderValues() {
let url = URL(string: "http://swift.org")!
var request = URLRequest(url: url)

let validHeaderValueLineFoldedTab = "Bar\r\n\tBuz"
request.setValue(validHeaderValueLineFoldedTab, forHTTPHeaderField: "FooTab")
XCTAssertEqual(request.value(forHTTPHeaderField: "FooTab"), validHeaderValueLineFoldedTab)

let validHeaderValueLineFoldedSpace = "Bar\r\n Buz"
request.setValue(validHeaderValueLineFoldedSpace, forHTTPHeaderField: "FooSpace")
XCTAssertEqual(request.value(forHTTPHeaderField: "FooSpace"), validHeaderValueLineFoldedSpace)
}
}