diff --git a/Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift b/Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift index 3130786111..82e5bf7085 100644 --- a/Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift +++ b/Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift @@ -263,11 +263,12 @@ extension HTTPHeaders.Range.Value { } return .withinWithLimit(start: (limit - end), end: limit - 1, limit: limit) case .within(let start, let end): - guard start >= 0, end >= 0, start <= end, start <= limit, end <= limit else { + guard start >= 0, end >= 0, start < end, start <= limit else { throw Abort(.badRequest) } - return .withinWithLimit(start: start, end: end, limit: limit) + let endToRequest = min(end, limit) + return .withinWithLimit(start: start, end: endToRequest, limit: limit) } } } diff --git a/Sources/Vapor/Utilities/FileIO.swift b/Sources/Vapor/Utilities/FileIO.swift index 2c8b3b5ec8..3d38dc32e4 100644 --- a/Sources/Vapor/Utilities/FileIO.swift +++ b/Sources/Vapor/Utilities/FileIO.swift @@ -275,6 +275,7 @@ public struct FileIO { extension HTTPHeaders.Range.Value { fileprivate func asByteBufferBounds(withMaxSize size: Int, logger: Logger) throws -> (offset: Int64, byteCount: Int) { + switch self { case .start(let value): guard value <= size, value >= 0 else { @@ -289,7 +290,7 @@ extension HTTPHeaders.Range.Value { } return (offset: numericCast(size - value), byteCount: value) case .within(let start, let end): - guard start >= 0, end >= 0, start <= end, start <= size, end <= size else { + guard start >= 0, end >= 0, start < end, start <= size else { logger.debug("Requested range was invalid: \(start)-\(end)") throw Abort(.badRequest) } @@ -298,7 +299,12 @@ extension HTTPHeaders.Range.Value { logger.debug("Requested range was invalid: \(start)-\(end)") throw Abort(.badRequest) } - return (offset: numericCast(start), byteCount: byteCount) + var byteCountToReqeust = byteCount + // Request past EOF, return up to EOF bytes + if (end > size) { + byteCountToReqeust = byteCount - (end - size) + } + return (offset: numericCast(start), byteCount: byteCountToReqeust) } } } diff --git a/Tests/VaporTests/HTTPHeaderTests.swift b/Tests/VaporTests/HTTPHeaderTests.swift index efad5108ea..e0da8547c5 100644 --- a/Tests/VaporTests/HTTPHeaderTests.swift +++ b/Tests/VaporTests/HTTPHeaderTests.swift @@ -359,6 +359,16 @@ final class HTTPHeaderTests: XCTestCase { ) } + func testRangeLimit() throws { + let contentRanges = HTTPHeaders.Range(unit: .bytes, ranges: [ + .within(start: 200, end: 1000) + ]) + + let firstRange = contentRanges.ranges.first + let accept = try firstRange!.asResponseContentRange(limit: 600) + XCTAssertEqual(accept.serialize(), "200-600/600") + } + func testLinkHeaderParsing() throws { let headers = HTTPHeaders([ ("link", #"; rel="next", ; rel="last"; custom1="whatever", ; rel=related, ; rel=related"#)