diff --git a/Sources/NIOFileSystem/ByteCount.swift b/Sources/NIOFileSystem/ByteCount.swift index 64fe76a309..983b7c0380 100644 --- a/Sources/NIOFileSystem/ByteCount.swift +++ b/Sources/NIOFileSystem/ByteCount.swift @@ -80,6 +80,21 @@ public struct ByteCount: Hashable, Sendable { } } +extension ByteCount { + #if arch(arm) || arch(i386) || arch(arm64_32) + // on 32-bit platforms we can't make use of a whole UInt32.max (as it doesn't fit in an Int) + private static let byteBufferMaxIndex = UInt32(Int.max) + #else + // on 64-bit platforms we're good + private static let byteBufferMaxIndex = UInt32.max + #endif + + /// A ``ByteCount`` for the maximum amount of bytes that can be written to `ByteBuffer`. + internal static var byteBufferCapacity: ByteCount { + ByteCount(bytes: Int64(byteBufferMaxIndex)) + } +} + extension ByteCount: AdditiveArithmetic { public static var zero: ByteCount { ByteCount(bytes: 0) } diff --git a/Sources/NIOFileSystem/FileHandleProtocol.swift b/Sources/NIOFileSystem/FileHandleProtocol.swift index d58e33065a..94dc4a349b 100644 --- a/Sources/NIOFileSystem/FileHandleProtocol.swift +++ b/Sources/NIOFileSystem/FileHandleProtocol.swift @@ -322,7 +322,9 @@ extension ReadableFileHandleProtocol { /// /// - Important: This method checks whether the file is seekable or not (i.e., whether it's a socket, /// pipe or FIFO), and will throw ``FileSystemError/Code-swift.struct/unsupported`` if - /// an offset other than zero is passed. + /// an offset other than zero is passed. Also, it will throw + /// ``FileSystemError/Code-swift.struct/resourceExhausted`` if `maximumSizeAllowed` is more than can be + /// written to `ByteBuffer`. /// /// - Parameters: /// - offset: The absolute offset into the file to read from. Defaults to zero. @@ -340,6 +342,19 @@ extension ReadableFileHandleProtocol { let fileSize = Int64(info.size) let readSize = max(Int(fileSize - offset), 0) + if maximumSizeAllowed > .byteBufferCapacity { + throw FileSystemError( + code: .resourceExhausted, + message: """ + The maximum size allowed (\(maximumSizeAllowed)) is more than the maximum \ + amount of bytes that can be written to ByteBuffer \ + (\(ByteCount.byteBufferCapacity)). + """, + cause: nil, + location: .here() + ) + } + if readSize > maximumSizeAllowed.bytes { throw FileSystemError( code: .resourceExhausted, diff --git a/Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift b/Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift index 397e9b76ca..f0e6aee74a 100644 --- a/Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift +++ b/Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift @@ -1803,6 +1803,21 @@ extension FileSystemTests { XCTAssertEqual(byteCount, Int(size)) } } + + func testReadMoreThanByteBufferCapacity() async throws { + let path = try await self.fs.temporaryFilePath() + + try await self.fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in + await XCTAssertThrowsFileSystemErrorAsync { + // Set `maximumSizeAllowed` to 1 byte more than can be written to `ByteBuffer`. + try await fileHandle.readToEnd( + maximumSizeAllowed: .byteBufferCapacity + .bytes(1) + ) + } onError: { error in + XCTAssertEqual(error.code, .resourceExhausted) + } + } + } } #if !canImport(Darwin) && swift(<5.9.2)