Skip to content

Commit be4f9aa

Browse files
committed
check body length
1 parent 5e3b77b commit be4f9aa

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

Sources/AsyncHTTPClient/HTTPClient.swift

+6-3
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
925925
case uncleanShutdown
926926
case traceRequestWithBody
927927
case invalidHeaderFieldNames([String])
928+
case bodyLengthMismatch
928929
}
929930

930931
private var code: Code
@@ -969,10 +970,12 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
969970
public static let redirectLimitReached = HTTPClientError(code: .redirectLimitReached)
970971
/// Redirect Cycle detected.
971972
public static let redirectCycleDetected = HTTPClientError(code: .redirectCycleDetected)
972-
/// Unclean shutdown
973+
/// Unclean shutdown.
973974
public static let uncleanShutdown = HTTPClientError(code: .uncleanShutdown)
974-
/// A body was sent in a request with method TRACE
975+
/// A body was sent in a request with method TRACE.
975976
public static let traceRequestWithBody = HTTPClientError(code: .traceRequestWithBody)
976-
/// Header field names contain invalid characters
977+
/// Header field names contain invalid characters.
977978
public static func invalidHeaderFieldNames(_ names: [String]) -> HTTPClientError { return HTTPClientError(code: .invalidHeaderFieldNames(names)) }
979+
/// Body length is not equal to `Content-Length`.
980+
public static let bodyLengthMismatch = HTTPClientError(code: .bodyLengthMismatch)
978981
}

Sources/AsyncHTTPClient/HTTPHandler.swift

+11
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,8 @@ internal class TaskHandler<Delegate: HTTPClientResponseDelegate>: RemovableChann
651651
let logger: Logger // We are okay to store the logger here because a TaskHandler is just for one request.
652652

653653
var state: State = .idle
654+
var expectedBodyLength: Int? = nil
655+
var actualBodyLength: Int = 0
654656
var pendingRead = false
655657
var mayRead = true
656658
var closing = false {
@@ -794,11 +796,19 @@ extension TaskHandler: ChannelDuplexHandler {
794796
assert(head.version == HTTPVersion(major: 1, minor: 1),
795797
"Sending a request in HTTP version \(head.version) which is unsupported by the above `if`")
796798

799+
self.expectedBodyLength = head.headers[canonicalForm: "content-length"].first.flatMap { Int($0) }
800+
797801
context.write(wrapOutboundOut(.head(head))).map {
798802
self.callOutToDelegateFireAndForget(value: head, self.delegate.didSendRequestHead)
799803
}.flatMap {
800804
self.writeBody(request: request, context: context)
801805
}.flatMap {
806+
if let expectedBodyLength = self.expectedBodyLength, expectedBodyLength != self.actualBodyLength {
807+
self.state = .end
808+
let error = HTTPClientError.bodyLengthMismatch
809+
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
810+
return context.eventLoop.makeFailedFuture(error)
811+
}
802812
context.eventLoop.assertInEventLoop()
803813
return context.writeAndFlush(self.wrapOutboundOut(.end(nil)))
804814
}.map {
@@ -836,6 +846,7 @@ extension TaskHandler: ChannelDuplexHandler {
836846
}
837847

838848
return promise.futureResult.map {
849+
self.actualBodyLength += part.readableBytes
839850
self.callOutToDelegateFireAndForget(value: part, self.delegate.didSendRequestPart)
840851
}
841852
})

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+31
Original file line numberDiff line numberDiff line change
@@ -2051,4 +2051,35 @@ class HTTPClientTests: XCTestCase {
20512051

20522052
XCTAssertNoThrow(try future.wait())
20532053
}
2054+
2055+
func testContentLengthTooLongFails() {
2056+
let url = self.defaultHTTPBinURLPrefix + "/post"
2057+
XCTAssertThrowsError(
2058+
try self.defaultClient.execute(request:
2059+
Request(url: url,
2060+
body: .stream(length: 10) { streamWriter in
2061+
streamWriter.write(.byteBuffer(ByteBuffer(string: "1")))
2062+
})).wait()) { error in
2063+
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch)
2064+
}
2065+
// Quickly try another request and check that it works.
2066+
XCTAssertNoThrow(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "/get").wait())
2067+
}
2068+
2069+
// currently gets stuck because of #250 the server just never replies
2070+
func testContentLengthTooShortFails() {
2071+
let url = self.defaultHTTPBinURLPrefix + "/post"
2072+
let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n"
2073+
XCTAssertThrowsError(
2074+
try self.defaultClient.execute(request:
2075+
Request(url: url,
2076+
body: .stream(length: 1) { streamWriter in
2077+
streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong)))
2078+
})).wait()) { error in
2079+
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch)
2080+
}
2081+
// Quickly try another request and check that it works. If we by accident wrote some extra bytes into the
2082+
// stream (and reuse the connection) that could cause problems.
2083+
XCTAssertNoThrow(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "/get").wait())
2084+
}
20542085
}

0 commit comments

Comments
 (0)