From 18910f09eedca576f69ea11156385e9e4114b3e8 Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:29:54 -0500 Subject: [PATCH 01/16] Added .defaultMaxBodySize to RouteBuilder protocol --- Sources/Vapor/Routing/Routes.swift | 4 +++- Sources/Vapor/Routing/RoutesBuilder+Group.swift | 12 ++++++++---- Sources/Vapor/Routing/RoutesBuilder.swift | 6 ++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Sources/Vapor/Routing/Routes.swift b/Sources/Vapor/Routing/Routes.swift index 8db15565d6..08c7f6b49e 100644 --- a/Sources/Vapor/Routing/Routes.swift +++ b/Sources/Vapor/Routing/Routes.swift @@ -1,11 +1,13 @@ public final class Routes: RoutesBuilder, CustomStringConvertible { + public var defaultMaxBodySize: Int? public var all: [Route] public var description: String { return self.all.description } - + public init() { + self.defaultMaxBodySize = 1_000_000 self.all = [] } diff --git a/Sources/Vapor/Routing/RoutesBuilder+Group.swift b/Sources/Vapor/Routing/RoutesBuilder+Group.swift index 0ca8939eb7..d1e951fda5 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Group.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Group.swift @@ -13,7 +13,7 @@ extension RoutesBuilder { /// - path: Group path components separated by commas. /// - returns: Newly created `Router` wrapped in the path. public func grouped(_ path: PathComponent...) -> RoutesBuilder { - return HTTPRoutesGroup(root: self, path: path) + return HTTPRoutesGroup(root: self, path: path, defaultMaxBodySize: self.defaultMaxBodySize) } /// Creates a new `Router` that will automatically prepend the supplied path components. @@ -29,7 +29,7 @@ extension RoutesBuilder { /// - path: Group path components separated by commas. /// - configure: Closure to configure the newly created `Router`. public func group(_ path: PathComponent..., configure: (RoutesBuilder) throws -> ()) rethrows { - try configure(HTTPRoutesGroup(root: self, path: path)) + try configure(HTTPRoutesGroup(root: self, path: path, defaultMaxBodySize: self.defaultMaxBodySize)) } } @@ -40,11 +40,15 @@ private final class HTTPRoutesGroup: RoutesBuilder { /// Additional components. let path: [PathComponent] - + + /// The default max body size for requests in the group. + let defaultMaxBodySize: Int? + /// Creates a new `PathGroup`. - init(root: RoutesBuilder, path: [PathComponent]) { + init(root: RoutesBuilder, path: [PathComponent] = [], defaultMaxBodySize: Int? = nil) { self.root = root self.path = path + self.defaultMaxBodySize = defaultMaxBodySize } /// See `HTTPRoutesBuilder`. diff --git a/Sources/Vapor/Routing/RoutesBuilder.swift b/Sources/Vapor/Routing/RoutesBuilder.swift index 362cfd9d89..f112fad966 100644 --- a/Sources/Vapor/Routing/RoutesBuilder.swift +++ b/Sources/Vapor/Routing/RoutesBuilder.swift @@ -1,7 +1,13 @@ public protocol RoutesBuilder { + var defaultMaxBodySize: Int? { get } + func add(_ route: Route) } +extension RoutesBuilder { + public var defaultMaxBodySize: Int? { 1_000_000 } +} + extension UUID: LosslessStringConvertible { public init?(_ description: String) { self.init(uuidString: description) From a86288f0099daf11b003322f1b62823226ba971e Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:30:33 -0500 Subject: [PATCH 02/16] Added .group(maxSize:) and .grouped(maxSize:configure:) methods to RouteBuilder protocol --- .../Vapor/Routing/RoutesBuilder+Group.swift | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Sources/Vapor/Routing/RoutesBuilder+Group.swift b/Sources/Vapor/Routing/RoutesBuilder+Group.swift index d1e951fda5..e4cb055b6a 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Group.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Group.swift @@ -31,6 +31,35 @@ extension RoutesBuilder { public func group(_ path: PathComponent..., configure: (RoutesBuilder) throws -> ()) rethrows { try configure(HTTPRoutesGroup(root: self, path: path, defaultMaxBodySize: self.defaultMaxBodySize)) } + + /// Creates a new `Router` with a different default max body size for its routes than the rest of the application. + /// + /// let large = router.group(maxSize: 1_000_000) + /// large.post("image", use: self.uploadImage) + /// + /// - parameters: + /// - defaultMaxBodySize: The maximum number of bytes that a request body can contain in the new group + /// `nil` means there is no limit. + /// - returns: A newly created `Router` with a new max body size. + public func group(maxSize defaultMaxBodySize: Int?) -> RoutesBuilder { + return HTTPRoutesGroup(root: self, defaultMaxBodySize: defaultMaxBodySize) + } + + /// Creates a new `Router` with a different default max body size for its routes than the rest of the application. + /// + /// router.grouped(maxSize: 1_000_000) { large + /// large.post("image", use: self.uploadImage) + /// } + /// + /// - parameters: + /// - defaultMaxBodySize: The maximum number of bytes that a request body can contain in the new group + /// `nil` means there is no limit. + /// - configure: Closure to configure the newly created `Router`. + /// - builder: The new builder with the new max body size. + /// - returns: A newly created `Router` with a new max body size. + public func grouped(maxSize defaultMaxBodySize: Int?, configure: (_ builder: RoutesBuilder) throws -> ()) rethrows { + try configure(HTTPRoutesGroup(root: self, defaultMaxBodySize: defaultMaxBodySize)) + } } /// Groups routes From f297a9bdc944e9e83a6cc1aae73757ea5af9d598 Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:31:29 -0500 Subject: [PATCH 03/16] Verify body size in Request.Body.collect(max:) method on .collected case --- Sources/Vapor/Request/Request+Body.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Vapor/Request/Request+Body.swift b/Sources/Vapor/Request/Request+Body.swift index 7e1fb97073..f48b46e349 100644 --- a/Sources/Vapor/Request/Request+Body.swift +++ b/Sources/Vapor/Request/Request+Body.swift @@ -42,6 +42,10 @@ extension Request { return buffer } case .collected(let buffer): + if let max = max, buffer.readableBytes <= max { + return self.request.eventLoop.future(error: Abort(.payloadTooLarge)) + } + return self.request.eventLoop.makeSucceededFuture(buffer) case .none: return self.request.eventLoop.makeSucceededFuture(nil) From baa184caa57714a0c47af4064baaf99c4112fc28 Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:32:24 -0500 Subject: [PATCH 04/16] Changed default .collect maxSize to 1 megabyte --- Sources/Vapor/Routing/RoutesBuilder+Method.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index b2d8b77958..39f416ca52 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -2,11 +2,11 @@ public enum HTTPBodyStreamStrategy { /// The HTTP request's body will be collected into memory before the route handler is /// called. The max size will determine how much data can be collected. The default is - /// `1 << 14`. + /// 1,000,000 bytes, or 1 megabyte. /// /// See `collect(maxSize:)` to set a lower max body size. public static var collect: HTTPBodyStreamStrategy { - return .collect(maxSize: 1 << 14) + return .collect(maxSize: 1_000_000) } /// The HTTP request's body will not be collected first before the route handler is called From eb89bf767aa8354f1eae06e52aaefc68a642d79f Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:33:38 -0500 Subject: [PATCH 05/16] Use group/router default request body sizes in RoutesBuilder.on responder There was also an issue when the request body existed before we get to the responder, the size of the body was not checked. We check it now --- .../Vapor/Routing/RoutesBuilder+Method.swift | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index 39f416ca52..5f74ec2d5f 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -126,7 +126,7 @@ extension RoutesBuilder { public func on( _ method: HTTPMethod, _ path: PathComponent..., - body: HTTPBodyStreamStrategy = .collect, + body: HTTPBodyStreamStrategy? = nil, use closure: @escaping (Request) throws -> Response ) -> Route where Response: ResponseEncodable @@ -140,21 +140,33 @@ extension RoutesBuilder { public func on( _ method: HTTPMethod, _ path: [PathComponent], - body: HTTPBodyStreamStrategy = .collect, + body: HTTPBodyStreamStrategy? = nil, use closure: @escaping (Request) throws -> Response ) -> Route where Response: ResponseEncodable { + let streamStrategy = body ?? .collect(maxSize: self.defaultMaxBodySize) let responder = BasicResponder { request in - if case .collect(let max) = body, request.body.data == nil { - return request.body.collect(max: max).flatMapThrowing { _ in + switch streamStrategy { + case let .collect(maxSize): + let collected: EventLoopFuture + + if let data = request.body.data { + collected = request.eventLoop.tryFuture { + if let max = maxSize, data.readableBytes <= max { throw Abort(.payloadTooLarge) } + } + } else { + collected = request.body.collect(max: maxSize).transform(to: ()) + } + + return collected.flatMapThrowing { return try closure(request) }.encodeResponse(for: request) - } else { - return try closure(request) - .encodeResponse(for: request) + case .stream: + return try closure(request).encodeResponse(for: request) } } + let route = Route( method: method, path: path, From aa6bdecd70a0872d7fce43d1546f17fccdea837c Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:33:59 -0500 Subject: [PATCH 06/16] Created RouteTests.testConfigurableMaxBodySize test case --- Tests/VaporTests/RouteTests.swift | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Tests/VaporTests/RouteTests.swift b/Tests/VaporTests/RouteTests.swift index 7457bd0fbe..0e451b5d32 100644 --- a/Tests/VaporTests/RouteTests.swift +++ b/Tests/VaporTests/RouteTests.swift @@ -268,4 +268,57 @@ final class RouteTests: XCTestCase { XCTAssertEqual(res.body.string, "bar") } } + + func testConfigurableMaxBodySize() throws { + let app = Application(.testing) + defer { app.shutdown() } + + XCTAssertEqual(app.routes.defaultMaxBodySize, 1_000_000) + + let group = app.routes.group(maxSize: 1_000_000_000).grouped("uploads") + XCTAssertEqual(group.defaultMaxBodySize, 1_000_000_000) + + group.on(.POST, "small", body: .collect(maxSize: 1_000) , use: { request in return "small" }) + group.on(.POST, "medium", body: .collect(maxSize: 1_000_000) , use: { request in return "medium" }) + group.post("large", use: { request in return "large" }) + + + var smallBuffer = ByteBufferAllocator().buffer(capacity: 1_001) + smallBuffer.writeBytes(Array(repeating: 48, count: 1_000)) + try app.test(.POST, "/uploads/small", body: smallBuffer, afterResponse: { response in + XCTAssertEqual(response.status, .ok) + }) + + smallBuffer.clear() + smallBuffer.writeBytes(Array(repeating: 48, count: 1_001)) + try app.test(.POST, "/uploads/small", body: smallBuffer, afterResponse: { response in + XCTAssertEqual(response.status, .payloadTooLarge) + }) + + + var mediumBuffer = ByteBufferAllocator().buffer(capacity: 1_000_001) + mediumBuffer.writeBytes(Array(repeating: 48, count: 1_000_000)) + try app.test(.POST, "/uploads/medium", body: mediumBuffer, afterResponse: { response in + XCTAssertEqual(response.status, .ok) + }) + + mediumBuffer.clear() + mediumBuffer.writeBytes(Array(repeating: 48, count: 1_000_001)) + try app.test(.POST, "/uploads/medium", body: mediumBuffer, afterResponse: { response in + XCTAssertEqual(response.status, .payloadTooLarge) + }) + + + var largeBuffer = ByteBufferAllocator().buffer(capacity: 1_000_000_001) + largeBuffer.writeBytes(Array(repeating: 48, count: 1_000_000_000)) + try app.test(.POST, "/uploads/large", body: largeBuffer, afterResponse: { response in + XCTAssertEqual(response.status, .ok) + }) + + largeBuffer.clear() + largeBuffer.writeBytes(Array(repeating: 48, count: 1_000_000_001)) + try app.test(.POST, "/uploads/large", body: largeBuffer, afterResponse: { response in + XCTAssertEqual(response.status, .payloadTooLarge) + }) + } } From 8b32960cdfcc6405a68931bfa0afce2de39bb065 Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:37:07 -0500 Subject: [PATCH 07/16] Added check for app.routes.defaultMaxBodySize setter in RouteTests.testConfigurableMaxBodySize test case --- Tests/VaporTests/RouteTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/VaporTests/RouteTests.swift b/Tests/VaporTests/RouteTests.swift index 0e451b5d32..80823a3ccb 100644 --- a/Tests/VaporTests/RouteTests.swift +++ b/Tests/VaporTests/RouteTests.swift @@ -274,6 +274,8 @@ final class RouteTests: XCTestCase { defer { app.shutdown() } XCTAssertEqual(app.routes.defaultMaxBodySize, 1_000_000) + app.routes.defaultMaxBodySize = 50 + XCTAssertEqual(app.routes.defaultMaxBodySize, 50) let group = app.routes.group(maxSize: 1_000_000_000).grouped("uploads") XCTAssertEqual(group.defaultMaxBodySize, 1_000_000_000) From 713bb1b1f56952b682e19018bf635386ea9dc46e Mon Sep 17 00:00:00 2001 From: Caleb Kleveter Date: Tue, 14 Apr 2020 10:41:47 -0500 Subject: [PATCH 08/16] Fix maxSize and body.readableBytes comperison operators --- Sources/Vapor/Request/Request+Body.swift | 2 +- Sources/Vapor/Routing/RoutesBuilder+Method.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Vapor/Request/Request+Body.swift b/Sources/Vapor/Request/Request+Body.swift index f48b46e349..8495b09664 100644 --- a/Sources/Vapor/Request/Request+Body.swift +++ b/Sources/Vapor/Request/Request+Body.swift @@ -42,7 +42,7 @@ extension Request { return buffer } case .collected(let buffer): - if let max = max, buffer.readableBytes <= max { + if let max = max, buffer.readableBytes > max { return self.request.eventLoop.future(error: Abort(.payloadTooLarge)) } diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index 5f74ec2d5f..cb18d270fb 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -153,7 +153,7 @@ extension RoutesBuilder { if let data = request.body.data { collected = request.eventLoop.tryFuture { - if let max = maxSize, data.readableBytes <= max { throw Abort(.payloadTooLarge) } + if let max = maxSize, data.readableBytes > max { throw Abort(.payloadTooLarge) } } } else { collected = request.body.collect(max: maxSize).transform(to: ()) From d130d057ea566433cc01f1330d51eba3fdbb9de3 Mon Sep 17 00:00:00 2001 From: Jimmy McDermott Date: Fri, 24 Apr 2020 22:39:10 -0500 Subject: [PATCH 09/16] Address comments --- Sources/Vapor/Routing/Routes.swift | 2 +- .../Vapor/Routing/RoutesBuilder+Group.swift | 4 +- .../Vapor/Routing/RoutesBuilder+Method.swift | 14 ++-- Sources/Vapor/Utilities/ByteCount.swift | 71 +++++++++++++++++++ Tests/VaporTests/RouteTests.swift | 4 +- Tests/VaporTests/UtilityTests.swift | 17 +++++ 6 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 Sources/Vapor/Utilities/ByteCount.swift diff --git a/Sources/Vapor/Routing/Routes.swift b/Sources/Vapor/Routing/Routes.swift index 08c7f6b49e..5cff5d682a 100644 --- a/Sources/Vapor/Routing/Routes.swift +++ b/Sources/Vapor/Routing/Routes.swift @@ -7,7 +7,7 @@ public final class Routes: RoutesBuilder, CustomStringConvertible { } public init() { - self.defaultMaxBodySize = 1_000_000 + self.defaultMaxBodySize = 1 << 20 //1MB self.all = [] } diff --git a/Sources/Vapor/Routing/RoutesBuilder+Group.swift b/Sources/Vapor/Routing/RoutesBuilder+Group.swift index e4cb055b6a..b27c9e22c8 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Group.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Group.swift @@ -41,7 +41,7 @@ extension RoutesBuilder { /// - defaultMaxBodySize: The maximum number of bytes that a request body can contain in the new group /// `nil` means there is no limit. /// - returns: A newly created `Router` with a new max body size. - public func group(maxSize defaultMaxBodySize: Int?) -> RoutesBuilder { + public func grouped(defaultMaxBodySize: Int?) -> RoutesBuilder { return HTTPRoutesGroup(root: self, defaultMaxBodySize: defaultMaxBodySize) } @@ -57,7 +57,7 @@ extension RoutesBuilder { /// - configure: Closure to configure the newly created `Router`. /// - builder: The new builder with the new max body size. /// - returns: A newly created `Router` with a new max body size. - public func grouped(maxSize defaultMaxBodySize: Int?, configure: (_ builder: RoutesBuilder) throws -> ()) rethrows { + public func group(defaultMaxBodySize: Int?, configure: (_ builder: RoutesBuilder) throws -> ()) rethrows { try configure(HTTPRoutesGroup(root: self, defaultMaxBodySize: defaultMaxBodySize)) } } diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index cb18d270fb..c59947879b 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -6,7 +6,7 @@ public enum HTTPBodyStreamStrategy { /// /// See `collect(maxSize:)` to set a lower max body size. public static var collect: HTTPBodyStreamStrategy { - return .collect(maxSize: 1_000_000) + return .collect(maxSize: nil) } /// The HTTP request's body will not be collected first before the route handler is called @@ -18,7 +18,7 @@ public enum HTTPBodyStreamStrategy { /// /// If a `maxSize` is supplied, the request body size in bytes will be limited. Requests /// exceeding that size will result in an error. - case collect(maxSize: Int?) + case collect(maxSize: ByteCount?) } extension RoutesBuilder { @@ -126,7 +126,7 @@ extension RoutesBuilder { public func on( _ method: HTTPMethod, _ path: PathComponent..., - body: HTTPBodyStreamStrategy? = nil, + body: HTTPBodyStreamStrategy = .collect, use closure: @escaping (Request) throws -> Response ) -> Route where Response: ResponseEncodable @@ -140,12 +140,12 @@ extension RoutesBuilder { public func on( _ method: HTTPMethod, _ path: [PathComponent], - body: HTTPBodyStreamStrategy? = nil, + body: HTTPBodyStreamStrategy = .collect, use closure: @escaping (Request) throws -> Response ) -> Route where Response: ResponseEncodable { - let streamStrategy = body ?? .collect(maxSize: self.defaultMaxBodySize) + let streamStrategy = body let responder = BasicResponder { request in switch streamStrategy { case let .collect(maxSize): @@ -153,10 +153,10 @@ extension RoutesBuilder { if let data = request.body.data { collected = request.eventLoop.tryFuture { - if let max = maxSize, data.readableBytes > max { throw Abort(.payloadTooLarge) } + if let max = maxSize, data.readableBytes > max.value { throw Abort(.payloadTooLarge) } } } else { - collected = request.body.collect(max: maxSize).transform(to: ()) + collected = request.body.collect(max: maxSize?.value).transform(to: ()) } return collected.flatMapThrowing { diff --git a/Sources/Vapor/Utilities/ByteCount.swift b/Sources/Vapor/Utilities/ByteCount.swift new file mode 100644 index 0000000000..7834764f93 --- /dev/null +++ b/Sources/Vapor/Utilities/ByteCount.swift @@ -0,0 +1,71 @@ +// +// ByteCount.swift +// +// +// Created by Jimmy McDermott on 4/24/20. +// + +import Foundation + +/// Represents a number of bytes: +/// +/// let bytes: ByteCount = "1mb" +/// print(bytes.value) // 1048576 +/// +/// let bytes: ByteCount = 1_000_000 +/// print(bytes.value) // 1000000 + +/// let bytes: ByteCount = "2kib" +/// print(bytes.value) // 2048 +public struct ByteCount { + + /// The value in Bytes + public let value: Int +} + +extension ByteCount: ExpressibleByIntegerLiteral { + + /// Initializes the `ByteCount` with the raw byte count + /// - Parameter value: The number of bytes + public init(integerLiteral value: Int) { + self.value = value + } +} + +extension ByteCount: ExpressibleByStringLiteral { + + /// Initializes the `ByteCount` via a descriptive string. Available suffixes are: + /// `kib`, `mb`, `gb`, `tb` + /// - Parameter value: The string value (`1mb`) + public init(stringLiteral value: String) { + // Short path if it's an int wrapped in a string + if let intValue = Int(value) { + self.value = intValue + return + } + + let validSuffixes = [ + "kib": 10, + "mb": 20, + "gb": 30, + "tb": 40 + ] + + for suffix in validSuffixes { + guard value.hasSuffix(suffix.key) else { continue } + guard let stringIntValue = value.components(separatedBy: suffix.key).first else { + fatalError("Invalid string format") + } + + guard let intValue = Int(stringIntValue) else { + fatalError("Invalid int value: \(stringIntValue)") + } + + self.value = intValue << suffix.value + return + } + + // Assert failure here because all cases are handled in the above loop + fatalError("Could not parse byte count string: \(value)") + } +} diff --git a/Tests/VaporTests/RouteTests.swift b/Tests/VaporTests/RouteTests.swift index 80823a3ccb..5b4227e9f1 100644 --- a/Tests/VaporTests/RouteTests.swift +++ b/Tests/VaporTests/RouteTests.swift @@ -273,11 +273,11 @@ final class RouteTests: XCTestCase { let app = Application(.testing) defer { app.shutdown() } - XCTAssertEqual(app.routes.defaultMaxBodySize, 1_000_000) + XCTAssertEqual(app.routes.defaultMaxBodySize, 1048576) app.routes.defaultMaxBodySize = 50 XCTAssertEqual(app.routes.defaultMaxBodySize, 50) - let group = app.routes.group(maxSize: 1_000_000_000).grouped("uploads") + let group = app.routes.grouped(defaultMaxBodySize: 1_000_000_000).grouped("uploads") XCTAssertEqual(group.defaultMaxBodySize, 1_000_000_000) group.on(.POST, "small", body: .collect(maxSize: 1_000) , use: { request in return "small" }) diff --git a/Tests/VaporTests/UtilityTests.swift b/Tests/VaporTests/UtilityTests.swift index 03a2d0a7b8..92ff1b32f0 100644 --- a/Tests/VaporTests/UtilityTests.swift +++ b/Tests/VaporTests/UtilityTests.swift @@ -21,4 +21,21 @@ final class UtilityTests: XCTestCase { XCTAssertEqual(data.base32EncodedString(), "AEBAGBA") XCTAssertEqual(Data(base32Encoded: "AEBAGBA"), data) } + + func testByteCount() throws { + let twoKib: ByteCount = "2kib" + XCTAssertEqual(twoKib.value, 2_048) + + let oneMb: ByteCount = "1mb" + XCTAssertEqual(oneMb.value, 1_048_576) + + let oneGb: ByteCount = "1gb" + XCTAssertEqual(oneGb.value, 1_073_741_824) + + let oneTb: ByteCount = "1tb" + XCTAssertEqual(oneTb.value, 1_099_511_627_776) + + let intBytes: ByteCount = 1_000_000 + XCTAssertEqual(intBytes.value, 1_000_000) + } } From e949d4dc0026ade7cde1d4c8cdd4e969deaa96a4 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 28 Apr 2020 11:50:14 -0400 Subject: [PATCH 10/16] update --- Sources/Vapor/Request/Request+Body.swift | 4 - .../Vapor/Routing/Application+Routes.swift | 25 +++++++ Sources/Vapor/Routing/Routes.swift | 23 ------ .../Routing/RoutesBuilder+BodySize.swift | 1 + .../Vapor/Routing/RoutesBuilder+Group.swift | 39 +--------- .../Vapor/Routing/RoutesBuilder+Method.swift | 27 +++---- Sources/Vapor/Routing/RoutesBuilder.swift | 6 -- Sources/Vapor/Utilities/ByteCount.swift | 22 ++---- Tests/VaporTests/RouteTests.swift | 73 +++++++------------ Tests/VaporTests/ServerTests.swift | 2 +- Tests/VaporTests/UtilityTests.swift | 2 +- 11 files changed, 74 insertions(+), 150 deletions(-) delete mode 100644 Sources/Vapor/Routing/Routes.swift create mode 100644 Sources/Vapor/Routing/RoutesBuilder+BodySize.swift diff --git a/Sources/Vapor/Request/Request+Body.swift b/Sources/Vapor/Request/Request+Body.swift index 8495b09664..7e1fb97073 100644 --- a/Sources/Vapor/Request/Request+Body.swift +++ b/Sources/Vapor/Request/Request+Body.swift @@ -42,10 +42,6 @@ extension Request { return buffer } case .collected(let buffer): - if let max = max, buffer.readableBytes > max { - return self.request.eventLoop.future(error: Abort(.payloadTooLarge)) - } - return self.request.eventLoop.makeSucceededFuture(buffer) case .none: return self.request.eventLoop.makeSucceededFuture(nil) diff --git a/Sources/Vapor/Routing/Application+Routes.swift b/Sources/Vapor/Routing/Application+Routes.swift index 296a6f497d..528af00dc0 100644 --- a/Sources/Vapor/Routing/Application+Routes.swift +++ b/Sources/Vapor/Routing/Application+Routes.swift @@ -13,3 +13,28 @@ extension Application { typealias Value = Routes } } + +public final class Routes: RoutesBuilder, CustomStringConvertible { + public var all: [Route] + + public var defaultMaxBodySize: ByteCount + + public var description: String { + return self.all.description + } + + public init() { + self.all = [] + self.defaultMaxBodySize = "16kb" + } + + public func add(_ route: Route) { + self.all.append(route) + } +} + +extension Application: RoutesBuilder { + public func add(_ route: Route) { + self.routes.add(route) + } +} diff --git a/Sources/Vapor/Routing/Routes.swift b/Sources/Vapor/Routing/Routes.swift deleted file mode 100644 index 5cff5d682a..0000000000 --- a/Sources/Vapor/Routing/Routes.swift +++ /dev/null @@ -1,23 +0,0 @@ -public final class Routes: RoutesBuilder, CustomStringConvertible { - public var defaultMaxBodySize: Int? - public var all: [Route] - - public var description: String { - return self.all.description - } - - public init() { - self.defaultMaxBodySize = 1 << 20 //1MB - self.all = [] - } - - public func add(_ route: Route) { - self.all.append(route) - } -} - -extension Application: RoutesBuilder { - public func add(_ route: Route) { - self.routes.add(route) - } -} diff --git a/Sources/Vapor/Routing/RoutesBuilder+BodySize.swift b/Sources/Vapor/Routing/RoutesBuilder+BodySize.swift new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Sources/Vapor/Routing/RoutesBuilder+BodySize.swift @@ -0,0 +1 @@ + diff --git a/Sources/Vapor/Routing/RoutesBuilder+Group.swift b/Sources/Vapor/Routing/RoutesBuilder+Group.swift index b27c9e22c8..e52ae02e55 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Group.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Group.swift @@ -13,7 +13,7 @@ extension RoutesBuilder { /// - path: Group path components separated by commas. /// - returns: Newly created `Router` wrapped in the path. public func grouped(_ path: PathComponent...) -> RoutesBuilder { - return HTTPRoutesGroup(root: self, path: path, defaultMaxBodySize: self.defaultMaxBodySize) + return HTTPRoutesGroup(root: self, path: path) } /// Creates a new `Router` that will automatically prepend the supplied path components. @@ -29,36 +29,7 @@ extension RoutesBuilder { /// - path: Group path components separated by commas. /// - configure: Closure to configure the newly created `Router`. public func group(_ path: PathComponent..., configure: (RoutesBuilder) throws -> ()) rethrows { - try configure(HTTPRoutesGroup(root: self, path: path, defaultMaxBodySize: self.defaultMaxBodySize)) - } - - /// Creates a new `Router` with a different default max body size for its routes than the rest of the application. - /// - /// let large = router.group(maxSize: 1_000_000) - /// large.post("image", use: self.uploadImage) - /// - /// - parameters: - /// - defaultMaxBodySize: The maximum number of bytes that a request body can contain in the new group - /// `nil` means there is no limit. - /// - returns: A newly created `Router` with a new max body size. - public func grouped(defaultMaxBodySize: Int?) -> RoutesBuilder { - return HTTPRoutesGroup(root: self, defaultMaxBodySize: defaultMaxBodySize) - } - - /// Creates a new `Router` with a different default max body size for its routes than the rest of the application. - /// - /// router.grouped(maxSize: 1_000_000) { large - /// large.post("image", use: self.uploadImage) - /// } - /// - /// - parameters: - /// - defaultMaxBodySize: The maximum number of bytes that a request body can contain in the new group - /// `nil` means there is no limit. - /// - configure: Closure to configure the newly created `Router`. - /// - builder: The new builder with the new max body size. - /// - returns: A newly created `Router` with a new max body size. - public func group(defaultMaxBodySize: Int?, configure: (_ builder: RoutesBuilder) throws -> ()) rethrows { - try configure(HTTPRoutesGroup(root: self, defaultMaxBodySize: defaultMaxBodySize)) + try configure(HTTPRoutesGroup(root: self, path: path)) } } @@ -70,14 +41,10 @@ private final class HTTPRoutesGroup: RoutesBuilder { /// Additional components. let path: [PathComponent] - /// The default max body size for requests in the group. - let defaultMaxBodySize: Int? - /// Creates a new `PathGroup`. - init(root: RoutesBuilder, path: [PathComponent] = [], defaultMaxBodySize: Int? = nil) { + init(root: RoutesBuilder, path: [PathComponent] = []) { self.root = root self.path = path - self.defaultMaxBodySize = defaultMaxBodySize } /// See `HTTPRoutesBuilder`. diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index c59947879b..498e1de3b9 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -18,6 +18,7 @@ public enum HTTPBodyStreamStrategy { /// /// If a `maxSize` is supplied, the request body size in bytes will be limited. Requests /// exceeding that size will result in an error. + /// Passing `nil` results in the application's default max body size being used. case collect(maxSize: ByteCount?) } @@ -145,28 +146,18 @@ extension RoutesBuilder { ) -> Route where Response: ResponseEncodable { - let streamStrategy = body let responder = BasicResponder { request in - switch streamStrategy { - case let .collect(maxSize): - let collected: EventLoopFuture - - if let data = request.body.data { - collected = request.eventLoop.tryFuture { - if let max = maxSize, data.readableBytes > max.value { throw Abort(.payloadTooLarge) } - } - } else { - collected = request.body.collect(max: maxSize?.value).transform(to: ()) - } - - return collected.flatMapThrowing { - return try closure(request) + if case .collect(let max) = body, request.body.data == nil { + return request.body.collect( + max: max?.value ?? request.application.routes.defaultMaxBodySize.value + ).flatMapThrowing { _ in + try closure(request) }.encodeResponse(for: request) - case .stream: - return try closure(request).encodeResponse(for: request) + } else { + return try closure(request) + .encodeResponse(for: request) } } - let route = Route( method: method, path: path, diff --git a/Sources/Vapor/Routing/RoutesBuilder.swift b/Sources/Vapor/Routing/RoutesBuilder.swift index f112fad966..362cfd9d89 100644 --- a/Sources/Vapor/Routing/RoutesBuilder.swift +++ b/Sources/Vapor/Routing/RoutesBuilder.swift @@ -1,13 +1,7 @@ public protocol RoutesBuilder { - var defaultMaxBodySize: Int? { get } - func add(_ route: Route) } -extension RoutesBuilder { - public var defaultMaxBodySize: Int? { 1_000_000 } -} - extension UUID: LosslessStringConvertible { public init?(_ description: String) { self.init(uuidString: description) diff --git a/Sources/Vapor/Utilities/ByteCount.swift b/Sources/Vapor/Utilities/ByteCount.swift index 7834764f93..62c4d9fac8 100644 --- a/Sources/Vapor/Utilities/ByteCount.swift +++ b/Sources/Vapor/Utilities/ByteCount.swift @@ -1,10 +1,3 @@ -// -// ByteCount.swift -// -// -// Created by Jimmy McDermott on 4/24/20. -// - import Foundation /// Represents a number of bytes: @@ -15,16 +8,18 @@ import Foundation /// let bytes: ByteCount = 1_000_000 /// print(bytes.value) // 1000000 -/// let bytes: ByteCount = "2kib" +/// let bytes: ByteCount = "2kb" /// print(bytes.value) // 2048 -public struct ByteCount { - +public struct ByteCount: Equatable { /// The value in Bytes public let value: Int + + public init(value: Int) { + self.value = value + } } extension ByteCount: ExpressibleByIntegerLiteral { - /// Initializes the `ByteCount` with the raw byte count /// - Parameter value: The number of bytes public init(integerLiteral value: Int) { @@ -33,9 +28,8 @@ extension ByteCount: ExpressibleByIntegerLiteral { } extension ByteCount: ExpressibleByStringLiteral { - /// Initializes the `ByteCount` via a descriptive string. Available suffixes are: - /// `kib`, `mb`, `gb`, `tb` + /// `kb`, `mb`, `gb`, `tb` /// - Parameter value: The string value (`1mb`) public init(stringLiteral value: String) { // Short path if it's an int wrapped in a string @@ -45,7 +39,7 @@ extension ByteCount: ExpressibleByStringLiteral { } let validSuffixes = [ - "kib": 10, + "kb": 10, "mb": 20, "gb": 30, "tb": 40 diff --git a/Tests/VaporTests/RouteTests.swift b/Tests/VaporTests/RouteTests.swift index 5b4227e9f1..d83de880f1 100644 --- a/Tests/VaporTests/RouteTests.swift +++ b/Tests/VaporTests/RouteTests.swift @@ -273,54 +273,33 @@ final class RouteTests: XCTestCase { let app = Application(.testing) defer { app.shutdown() } - XCTAssertEqual(app.routes.defaultMaxBodySize, 1048576) - app.routes.defaultMaxBodySize = 50 - XCTAssertEqual(app.routes.defaultMaxBodySize, 50) + XCTAssertEqual(app.routes.defaultMaxBodySize, 16384) + app.routes.defaultMaxBodySize = 1 + XCTAssertEqual(app.routes.defaultMaxBodySize, 1) - let group = app.routes.grouped(defaultMaxBodySize: 1_000_000_000).grouped("uploads") - XCTAssertEqual(group.defaultMaxBodySize, 1_000_000_000) - - group.on(.POST, "small", body: .collect(maxSize: 1_000) , use: { request in return "small" }) - group.on(.POST, "medium", body: .collect(maxSize: 1_000_000) , use: { request in return "medium" }) - group.post("large", use: { request in return "large" }) - - - var smallBuffer = ByteBufferAllocator().buffer(capacity: 1_001) - smallBuffer.writeBytes(Array(repeating: 48, count: 1_000)) - try app.test(.POST, "/uploads/small", body: smallBuffer, afterResponse: { response in - XCTAssertEqual(response.status, .ok) - }) - - smallBuffer.clear() - smallBuffer.writeBytes(Array(repeating: 48, count: 1_001)) - try app.test(.POST, "/uploads/small", body: smallBuffer, afterResponse: { response in - XCTAssertEqual(response.status, .payloadTooLarge) - }) - - - var mediumBuffer = ByteBufferAllocator().buffer(capacity: 1_000_001) - mediumBuffer.writeBytes(Array(repeating: 48, count: 1_000_000)) - try app.test(.POST, "/uploads/medium", body: mediumBuffer, afterResponse: { response in - XCTAssertEqual(response.status, .ok) - }) - - mediumBuffer.clear() - mediumBuffer.writeBytes(Array(repeating: 48, count: 1_000_001)) - try app.test(.POST, "/uploads/medium", body: mediumBuffer, afterResponse: { response in - XCTAssertEqual(response.status, .payloadTooLarge) - }) - - - var largeBuffer = ByteBufferAllocator().buffer(capacity: 1_000_000_001) - largeBuffer.writeBytes(Array(repeating: 48, count: 1_000_000_000)) - try app.test(.POST, "/uploads/large", body: largeBuffer, afterResponse: { response in - XCTAssertEqual(response.status, .ok) - }) + app.on(.POST, "default") { request in + HTTPStatus.ok + } + app.on(.POST, "1kb", body: .collect(maxSize: "1kb")) { request in + HTTPStatus.ok + } + app.on(.POST, "1mb", body: .collect(maxSize: "1mb")) { request in + HTTPStatus.ok + } + app.on(.POST, "1gb", body: .collect(maxSize: "1gb")) { request in + HTTPStatus.ok + } - largeBuffer.clear() - largeBuffer.writeBytes(Array(repeating: 48, count: 1_000_000_001)) - try app.test(.POST, "/uploads/large", body: largeBuffer, afterResponse: { response in - XCTAssertEqual(response.status, .payloadTooLarge) - }) + var buffer = ByteBufferAllocator().buffer(capacity: 0) + buffer.writeBytes(Array(repeating: 0, count: 500_000)) + try app.testable(method: .running).test(.POST, "/default", body: buffer) { res in + XCTAssertEqual(res.status, .payloadTooLarge) + }.test(.POST, "/1kb", body: buffer) { res in + XCTAssertEqual(res.status, .payloadTooLarge) + }.test(.POST, "/1mb", body: buffer) { res in + XCTAssertEqual(res.status, .ok) + }.test(.POST, "/1gb", body: buffer) { res in + XCTAssertEqual(res.status, .ok) + } } } diff --git a/Tests/VaporTests/ServerTests.swift b/Tests/VaporTests/ServerTests.swift index 81846c3ab4..b030ab3262 100644 --- a/Tests/VaporTests/ServerTests.swift +++ b/Tests/VaporTests/ServerTests.swift @@ -95,7 +95,7 @@ final class ServerTests: XCTestCase { let payload = [UInt8].random(count: 1 << 20) - app.on(.POST, "payload", body: .collect(maxSize: nil)) { req -> HTTPStatus in + app.on(.POST, "payload", body: .collect(maxSize: "1gb")) { req -> HTTPStatus in guard let data = req.body.data else { throw Abort(.internalServerError) } diff --git a/Tests/VaporTests/UtilityTests.swift b/Tests/VaporTests/UtilityTests.swift index 92ff1b32f0..512d94eacf 100644 --- a/Tests/VaporTests/UtilityTests.swift +++ b/Tests/VaporTests/UtilityTests.swift @@ -23,7 +23,7 @@ final class UtilityTests: XCTestCase { } func testByteCount() throws { - let twoKib: ByteCount = "2kib" + let twoKib: ByteCount = "2kb" XCTAssertEqual(twoKib.value, 2_048) let oneMb: ByteCount = "1mb" From 21d1dc3f9d9844e2a0ebba654ebdf2d335478cdb Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 28 Apr 2020 11:51:00 -0400 Subject: [PATCH 11/16] updates --- .../Vapor/Routing/Application+Routes.swift | 25 ------------------- Sources/Vapor/Routing/Routes.swift | 24 ++++++++++++++++++ 2 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 Sources/Vapor/Routing/Routes.swift diff --git a/Sources/Vapor/Routing/Application+Routes.swift b/Sources/Vapor/Routing/Application+Routes.swift index 528af00dc0..296a6f497d 100644 --- a/Sources/Vapor/Routing/Application+Routes.swift +++ b/Sources/Vapor/Routing/Application+Routes.swift @@ -13,28 +13,3 @@ extension Application { typealias Value = Routes } } - -public final class Routes: RoutesBuilder, CustomStringConvertible { - public var all: [Route] - - public var defaultMaxBodySize: ByteCount - - public var description: String { - return self.all.description - } - - public init() { - self.all = [] - self.defaultMaxBodySize = "16kb" - } - - public func add(_ route: Route) { - self.all.append(route) - } -} - -extension Application: RoutesBuilder { - public func add(_ route: Route) { - self.routes.add(route) - } -} diff --git a/Sources/Vapor/Routing/Routes.swift b/Sources/Vapor/Routing/Routes.swift new file mode 100644 index 0000000000..abe8d844ef --- /dev/null +++ b/Sources/Vapor/Routing/Routes.swift @@ -0,0 +1,24 @@ +public final class Routes: RoutesBuilder, CustomStringConvertible { + public var all: [Route] + + public var defaultMaxBodySize: ByteCount + + public var description: String { + return self.all.description + } + + public init() { + self.all = [] + self.defaultMaxBodySize = "16kb" + } + + public func add(_ route: Route) { + self.all.append(route) + } +} + +extension Application: RoutesBuilder { + public func add(_ route: Route) { + self.routes.add(route) + } +} From b55cf053ecb6169b312269dd52b212064ed9c7cd Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 28 Apr 2020 11:58:58 -0400 Subject: [PATCH 12/16] updates --- Sources/Vapor/Routing/Routes.swift | 3 ++- .../Vapor/Routing/RoutesBuilder+BodySize.swift | 1 - Sources/Vapor/Routing/RoutesBuilder+Group.swift | 2 +- Sources/Vapor/Routing/RoutesBuilder+Method.swift | 15 ++++++++------- 4 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 Sources/Vapor/Routing/RoutesBuilder+BodySize.swift diff --git a/Sources/Vapor/Routing/Routes.swift b/Sources/Vapor/Routing/Routes.swift index abe8d844ef..63bd978009 100644 --- a/Sources/Vapor/Routing/Routes.swift +++ b/Sources/Vapor/Routing/Routes.swift @@ -1,6 +1,7 @@ public final class Routes: RoutesBuilder, CustomStringConvertible { public var all: [Route] - + + /// Default value used by `HTTPBodyStreamStrategy.collect` when `maxSize` is `nil`. public var defaultMaxBodySize: ByteCount public var description: String { diff --git a/Sources/Vapor/Routing/RoutesBuilder+BodySize.swift b/Sources/Vapor/Routing/RoutesBuilder+BodySize.swift deleted file mode 100644 index 8b13789179..0000000000 --- a/Sources/Vapor/Routing/RoutesBuilder+BodySize.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Sources/Vapor/Routing/RoutesBuilder+Group.swift b/Sources/Vapor/Routing/RoutesBuilder+Group.swift index e52ae02e55..90f06d457c 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Group.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Group.swift @@ -42,7 +42,7 @@ private final class HTTPRoutesGroup: RoutesBuilder { let path: [PathComponent] /// Creates a new `PathGroup`. - init(root: RoutesBuilder, path: [PathComponent] = []) { + init(root: RoutesBuilder, path: [PathComponent]) { self.root = root self.path = path } diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index 498e1de3b9..b71c5e5ffd 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -1,10 +1,10 @@ /// Determines how an incoming HTTP request's body is collected. public enum HTTPBodyStreamStrategy { - /// The HTTP request's body will be collected into memory before the route handler is - /// called. The max size will determine how much data can be collected. The default is - /// 1,000,000 bytes, or 1 megabyte. + /// The HTTP request's body will be collected into memory up to a maximum size + /// before the route handler is called The application's configured default max body + /// size will be used unless otherwise specified. /// - /// See `collect(maxSize:)` to set a lower max body size. + /// See `collect(maxSize:)` to specify a custom max collection size. public static var collect: HTTPBodyStreamStrategy { return .collect(maxSize: nil) } @@ -16,9 +16,10 @@ public enum HTTPBodyStreamStrategy { /// The HTTP request's body will be collected into memory before the route handler is /// called. /// - /// If a `maxSize` is supplied, the request body size in bytes will be limited. Requests - /// exceeding that size will result in an error. - /// Passing `nil` results in the application's default max body size being used. + /// `maxSize` Limits the maximum amount of memory in bytes that will be used to + /// collect a streaming body. Streaming requests exceeding that size will result in an error. + /// Passing `nil` results in the application's default max body size being used. This + /// parameter does not affect non-streaming requests. case collect(maxSize: ByteCount?) } From 00240747b4c149c535e5d17c78ed70ab67d8d342 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 28 Apr 2020 12:00:00 -0400 Subject: [PATCH 13/16] updates --- Sources/Vapor/Routing/RoutesBuilder+Method.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index b71c5e5ffd..ef3158cb0a 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -1,7 +1,7 @@ /// Determines how an incoming HTTP request's body is collected. public enum HTTPBodyStreamStrategy { /// The HTTP request's body will be collected into memory up to a maximum size - /// before the route handler is called The application's configured default max body + /// before the route handler is called. The application's configured default max body /// size will be used unless otherwise specified. /// /// See `collect(maxSize:)` to specify a custom max collection size. From deb02849affecd13b72547f81c15323d809d9834 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 28 Apr 2020 12:00:31 -0400 Subject: [PATCH 14/16] updates --- Sources/Vapor/Routing/RoutesBuilder+Method.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Vapor/Routing/RoutesBuilder+Method.swift b/Sources/Vapor/Routing/RoutesBuilder+Method.swift index ef3158cb0a..958e9c94df 100644 --- a/Sources/Vapor/Routing/RoutesBuilder+Method.swift +++ b/Sources/Vapor/Routing/RoutesBuilder+Method.swift @@ -16,7 +16,7 @@ public enum HTTPBodyStreamStrategy { /// The HTTP request's body will be collected into memory before the route handler is /// called. /// - /// `maxSize` Limits the maximum amount of memory in bytes that will be used to + /// `maxSize` Limits the maximum amount of memory in bytes that will be used to /// collect a streaming body. Streaming requests exceeding that size will result in an error. /// Passing `nil` results in the application's default max body size being used. This /// parameter does not affect non-streaming requests. From b09b40aaff8649e08273dae7b405d2c29af86a89 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 28 Apr 2020 12:01:24 -0400 Subject: [PATCH 15/16] updates --- Tests/VaporTests/UtilityTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/VaporTests/UtilityTests.swift b/Tests/VaporTests/UtilityTests.swift index 512d94eacf..5823be309a 100644 --- a/Tests/VaporTests/UtilityTests.swift +++ b/Tests/VaporTests/UtilityTests.swift @@ -23,8 +23,8 @@ final class UtilityTests: XCTestCase { } func testByteCount() throws { - let twoKib: ByteCount = "2kb" - XCTAssertEqual(twoKib.value, 2_048) + let twoKb: ByteCount = "2kb" + XCTAssertEqual(twoKb.value, 2_048) let oneMb: ByteCount = "1mb" XCTAssertEqual(oneMb.value, 1_048_576) From 86e817d9accb1ed37e7aab3fd0c4a2dcf5bfa736 Mon Sep 17 00:00:00 2001 From: Jimmy McDermott Date: Tue, 28 Apr 2020 16:55:37 -0500 Subject: [PATCH 16/16] Fuzzy --- Sources/Vapor/Utilities/ByteCount.swift | 5 +++-- Tests/VaporTests/UtilityTests.swift | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/Vapor/Utilities/ByteCount.swift b/Sources/Vapor/Utilities/ByteCount.swift index 62c4d9fac8..b18e8cda76 100644 --- a/Sources/Vapor/Utilities/ByteCount.swift +++ b/Sources/Vapor/Utilities/ByteCount.swift @@ -45,9 +45,10 @@ extension ByteCount: ExpressibleByStringLiteral { "tb": 40 ] + let cleanValue = value.lowercased().trimmingCharacters(in: .whitespaces).replacingOccurrences(of: " ", with: "") for suffix in validSuffixes { - guard value.hasSuffix(suffix.key) else { continue } - guard let stringIntValue = value.components(separatedBy: suffix.key).first else { + guard cleanValue.hasSuffix(suffix.key) else { continue } + guard let stringIntValue = cleanValue.components(separatedBy: suffix.key).first else { fatalError("Invalid string format") } diff --git a/Tests/VaporTests/UtilityTests.swift b/Tests/VaporTests/UtilityTests.swift index 5823be309a..a218cfddd6 100644 --- a/Tests/VaporTests/UtilityTests.swift +++ b/Tests/VaporTests/UtilityTests.swift @@ -23,6 +23,9 @@ final class UtilityTests: XCTestCase { } func testByteCount() throws { + let twoKbUpper: ByteCount = "2 KB" + XCTAssertEqual(twoKbUpper.value, 2_048) + let twoKb: ByteCount = "2kb" XCTAssertEqual(twoKb.value, 2_048)