Verify header field names (#191)

* HTTPRequest without body: Content-Length shall not be send
* Verify field names comply with RFC7230
This commit is contained in:
Fabian Fett
2020-04-01 17:48:01 +02:00
committed by GitHub
parent 2fcf0a9fdd
commit 2d88de3eb6
7 changed files with 238 additions and 32 deletions
+79 -19
View File
@@ -2,7 +2,7 @@
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors
// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -16,7 +16,7 @@ import NIO
import NIOHTTP1
extension HTTPHeaders {
mutating func validate(body: HTTPClient.Body?) throws {
mutating func validate(method: HTTPMethod, body: HTTPClient.Body?) throws {
// validate transfer encoding and content length (https://tools.ietf.org/html/rfc7230#section-3.3.1)
var transferEncoding: String?
var contentLength: Int?
@@ -29,34 +29,94 @@ extension HTTPHeaders {
self.remove(name: "Transfer-Encoding")
self.remove(name: "Content-Length")
if let body = body {
guard (encodings.filter { $0 == "chunked" }.count <= 1) else {
throw HTTPClientError.chunkedSpecifiedMultipleTimes
}
try self.validateFieldNames()
if encodings.isEmpty {
guard let body = body else {
// if we don't have a body we might not need to send the Content-Length field
// https://tools.ietf.org/html/rfc7230#section-3.3.2
switch method {
case .GET, .HEAD, .DELETE, .CONNECT, .TRACE:
// A user agent SHOULD NOT send a Content-Length header field when the request
// message does not contain a payload body and the method semantics do not
// anticipate such a body.
return
default:
// A user agent SHOULD send a Content-Length in a request message when
// no Transfer-Encoding is sent and the request method defines a meaning
// for an enclosed payload body.
self.add(name: "Content-Length", value: "0")
return
}
}
if case .TRACE = method {
// A client MUST NOT send a message body in a TRACE request.
// https://tools.ietf.org/html/rfc7230#section-4.3.8
throw HTTPClientError.traceRequestWithBody
}
guard (encodings.filter { $0 == "chunked" }.count <= 1) else {
throw HTTPClientError.chunkedSpecifiedMultipleTimes
}
if encodings.isEmpty {
guard let length = body.length else {
throw HTTPClientError.contentLengthMissing
}
contentLength = length
} else {
transferEncoding = encodings.joined(separator: ", ")
if !encodings.contains("chunked") {
guard let length = body.length else {
throw HTTPClientError.contentLengthMissing
}
contentLength = length
} else {
transferEncoding = encodings.joined(separator: ", ")
if !encodings.contains("chunked") {
guard let length = body.length else {
throw HTTPClientError.contentLengthMissing
}
contentLength = length
}
}
} else {
contentLength = 0
}
// add headers if required
if let enc = transferEncoding {
self.add(name: "Transfer-Encoding", value: enc)
}
if let length = contentLength {
} else if let length = contentLength {
// A sender MUST NOT send a Content-Length header field in any message
// that contains a Transfer-Encoding header field.
self.add(name: "Content-Length", value: String(length))
}
}
func validateFieldNames() throws {
let invalidFieldNames = self.compactMap { (name, _) -> String? in
let satisfy = name.utf8.allSatisfy { (char) -> Bool in
switch char {
case UInt8(ascii: "a")...UInt8(ascii: "z"),
UInt8(ascii: "A")...UInt8(ascii: "Z"),
UInt8(ascii: "0")...UInt8(ascii: "9"),
UInt8(ascii: "!"),
UInt8(ascii: "#"),
UInt8(ascii: "$"),
UInt8(ascii: "%"),
UInt8(ascii: "&"),
UInt8(ascii: "'"),
UInt8(ascii: "*"),
UInt8(ascii: "+"),
UInt8(ascii: "-"),
UInt8(ascii: "."),
UInt8(ascii: "^"),
UInt8(ascii: "_"),
UInt8(ascii: "`"),
UInt8(ascii: "|"),
UInt8(ascii: "~"):
return true
default:
return false
}
}
return satisfy ? nil : name
}
guard invalidFieldNames.count == 0 else {
throw HTTPClientError.invalidHeaderFieldNames(invalidFieldNames)
}
}
}