Files
async-http-client/Tests/AsyncHTTPClientTests/RequestValidationTests.swift
T
Artem RedkinandGitHub 61a80a2d34 assume chunked on a stream with no length (#247)
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
2020-06-23 16:42:16 +01:00

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]))))
}
}
}