mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-06-02 07:37:34 +00:00
Motivation: Streams length parameter is optional to allow cases were stream length is not known in advance, but we do not support this in request validation. This PR aims to address that. Modifications: Modifies request validation to default to chunked encoding if body length is zero or to passed in content-length header Adds a test Result: Closes #218
268 lines
13 KiB
Swift
268 lines
13 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the AsyncHTTPClient open source project
|
|
//
|
|
// Copyright (c) 2020 Apple Inc. and the AsyncHTTPClient project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
@testable import AsyncHTTPClient
|
|
import NIO
|
|
import NIOHTTP1
|
|
import XCTest
|
|
|
|
class RequestValidationTests: XCTestCase {
|
|
func testContentLengthHeaderIsRemovedFromGETIfNoBody() {
|
|
var headers = HTTPHeaders([("Content-Length", "0")])
|
|
XCTAssertNoThrow(try headers.validate(method: .GET, body: .none))
|
|
XCTAssertNil(headers.first(name: "Content-Length"))
|
|
}
|
|
|
|
func testContentLengthHeaderIsAddedToPOSTAndPUTWithNoBody() {
|
|
var putHeaders = HTTPHeaders()
|
|
XCTAssertNoThrow(try putHeaders.validate(method: .PUT, body: .none))
|
|
XCTAssertEqual(putHeaders.first(name: "Content-Length"), "0")
|
|
|
|
var postHeaders = HTTPHeaders()
|
|
XCTAssertNoThrow(try postHeaders.validate(method: .POST, body: .none))
|
|
XCTAssertEqual(postHeaders.first(name: "Content-Length"), "0")
|
|
}
|
|
|
|
func testContentLengthHeaderIsChangedIfBodyHasDifferentLength() {
|
|
var headers = HTTPHeaders([("Content-Length", "0")])
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 200)
|
|
buffer.writeBytes([UInt8](repeating: 12, count: 200))
|
|
XCTAssertNoThrow(try headers.validate(method: .PUT, body: .byteBuffer(buffer)))
|
|
XCTAssertEqual(headers.first(name: "Content-Length"), "200")
|
|
}
|
|
|
|
func testTRACERequestMustNotHaveBody() {
|
|
for header in [("Content-Length", "200"), ("Transfer-Encoding", "chunked")] {
|
|
var headers = HTTPHeaders([header])
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 200)
|
|
buffer.writeBytes([UInt8](repeating: 12, count: 200))
|
|
XCTAssertThrowsError(try headers.validate(method: .TRACE, body: .byteBuffer(buffer))) {
|
|
XCTAssertEqual($0 as? HTTPClientError, .traceRequestWithBody)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testGET_HEAD_DELETE_CONNECTRequestCanHaveBody() {
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 100)
|
|
buffer.writeBytes([UInt8](repeating: 12, count: 100))
|
|
|
|
// GET, HEAD, DELETE and CONNECT requests can have a payload. (though uncommon)
|
|
let allowedMethods: [HTTPMethod] = [.GET, .HEAD, .DELETE, .CONNECT]
|
|
var headers = HTTPHeaders()
|
|
for method in allowedMethods {
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(buffer)))
|
|
}
|
|
}
|
|
|
|
func testInvalidHeaderFieldNames() {
|
|
var headers = HTTPHeaders([
|
|
("Content-Length", "200"),
|
|
("User Agent", "Haha"),
|
|
])
|
|
|
|
XCTAssertThrowsError(try headers.validate(method: .GET, body: nil)) { error in
|
|
XCTAssertEqual(error as? HTTPClientError, HTTPClientError.invalidHeaderFieldNames(["User Agent"]))
|
|
}
|
|
}
|
|
|
|
func testValidHeaderFieldNames() {
|
|
var headers = HTTPHeaders([
|
|
("abcdefghijklmnopqrstuvwxyz", "Haha"),
|
|
("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "Haha"),
|
|
("0123456789", "Haha"),
|
|
("!#$%&'*+-.^_`|~", "Haha"),
|
|
])
|
|
|
|
XCTAssertNoThrow(try headers.validate(method: .GET, body: nil))
|
|
}
|
|
|
|
// MARK: - Content-Length/Transfer-Encoding Matrix
|
|
|
|
// Method kind User sets Body Expectation
|
|
// ----------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT, .TRACE nothing nil Neither CL nor chunked
|
|
// other nothing nil CL=0
|
|
func testNoHeadersNoBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] {
|
|
var headers: HTTPHeaders = .init()
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: nil))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init()
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: nil))
|
|
XCTAssertEqual(headers["content-length"].first, "0")
|
|
XCTAssertFalse(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// --------------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT, .TRACE nothing not nil CL or chunked
|
|
// other nothing not nil CL or chunked
|
|
func testNoHeadersHasBody() throws {
|
|
// Body length is known
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] {
|
|
var headers: HTTPHeaders = .init()
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
XCTAssertEqual(headers["content-length"].first, "1")
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
|
|
// Body length is _not_ known
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] {
|
|
var headers: HTTPHeaders = .init()
|
|
let body: HTTPClient.Body = .stream { writer in
|
|
writer.write(.byteBuffer(ByteBuffer(bytes: [0])))
|
|
}
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: body))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertTrue(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
|
|
// Body length is known
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init()
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
XCTAssertEqual(headers["content-length"].first, "1")
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
|
|
// Body length is _not_ known
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init()
|
|
let body: HTTPClient.Body = .stream { writer in
|
|
writer.write(.byteBuffer(ByteBuffer(bytes: [0])))
|
|
}
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: body))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertTrue(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// ------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT, .TRACE content-length nil Neither CL nor chunked
|
|
// other content-length nil CL=0
|
|
func testContentLengthHeaderNoBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: nil))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: nil))
|
|
XCTAssertEqual(headers["content-length"].first, "0")
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// --------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT content-length not nil CL=1
|
|
// other content-length nit nil CL=1
|
|
func testContentLengthHeaderHasBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
XCTAssertEqual(headers["content-length"].first, "1")
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
XCTAssertEqual(headers["content-length"].first, "1")
|
|
XCTAssertTrue(headers["transfer-encoding"].isEmpty)
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// ------------------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT, .TRACE transfer-encoding: chunked nil nil
|
|
// other transfer-encoding: chunked nil nil
|
|
func testTransferEncodingHeaderNoBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] {
|
|
var headers: HTTPHeaders = .init([("Transfer-Encoding", "chunked")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: nil))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertFalse(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init([("Transfer-Encoding", "chunked")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: nil))
|
|
XCTAssertEqual(headers["content-length"].first, "0")
|
|
XCTAssertFalse(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// --------------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT transfer-encoding: chunked not nil chunked
|
|
// other transfer-encoding: chunked not nil chunked
|
|
func testTransferEncodingHeaderHasBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] {
|
|
var headers: HTTPHeaders = .init([("Transfer-Encoding", "chunked")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertTrue(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init([("Transfer-Encoding", "chunked")])
|
|
XCTAssertNoThrow(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
XCTAssertTrue(headers["content-length"].isEmpty)
|
|
XCTAssertTrue(headers["transfer-encoding"].contains("chunked"))
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// ---------------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT, .TRACE CL & chunked (illegal) nil throws error
|
|
// other CL & chunked (illegal) nil throws error
|
|
func testBothHeadersNoBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1"), ("Transfer-Encoding", "chunked")])
|
|
XCTAssertThrowsError(try headers.validate(method: method, body: nil))
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1"), ("Transfer-Encoding", "chunked")])
|
|
XCTAssertThrowsError(try headers.validate(method: method, body: nil))
|
|
}
|
|
}
|
|
|
|
// Method kind User sets Body Expectation
|
|
// -------------------------------------------------------------------------------------------
|
|
// .GET, .HEAD, .DELETE, .CONNECT, .TRACE CL & chunked (illegal) not nil throws error
|
|
// other CL & chunked (illegal) not nil throws error
|
|
func testBothHeadersHasBody() throws {
|
|
for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1"), ("Transfer-Encoding", "chunked")])
|
|
XCTAssertThrowsError(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
}
|
|
|
|
for method: HTTPMethod in [.POST, .PUT] {
|
|
var headers: HTTPHeaders = .init([("Content-Length", "1"), ("Transfer-Encoding", "chunked")])
|
|
XCTAssertThrowsError(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0]))))
|
|
}
|
|
}
|
|
}
|