From 4d5a598599b2917befb0a16448815b60b453bebc Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Sun, 9 Jan 2022 17:12:55 +0100 Subject: [PATCH] readUntilDelimiter*: read only if buffer not full Ref.: #9594 --- lib/std/io/reader.zig | 296 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 285 insertions(+), 11 deletions(-) diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig index 2839552668..b356ec0629 100644 --- a/lib/std/io/reader.zig +++ b/lib/std/io/reader.zig @@ -107,16 +107,16 @@ pub fn Reader( ) !void { array_list.shrinkRetainingCapacity(0); while (true) { + if (array_list.items.len == max_size) { + return error.StreamTooLong; + } + var byte: u8 = try self.readByte(); if (byte == delimiter) { return; } - if (array_list.items.len == max_size) { - return error.StreamTooLong; - } - try array_list.append(byte); } } @@ -139,17 +139,20 @@ pub fn Reader( /// Reads from the stream until specified byte is found. If the buffer is not /// large enough to hold the entire contents, `error.StreamTooLong` is returned. + /// If end-of-stream is found, `error.EndOfStream` is returned. /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The - /// delimiter byte is not included in the returned slice. + /// delimiter byte is written to the output buffer but is not included + /// in the returned slice. pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) ![]u8 { var index: usize = 0; while (true) { - const byte = try self.readByte(); - - if (byte == delimiter) return buf[0..index]; if (index >= buf.len) return error.StreamTooLong; + const byte = try self.readByte(); buf[index] = byte; + + if (byte == delimiter) return buf[0..index]; + index += 1; } } @@ -185,10 +188,13 @@ pub fn Reader( /// If end-of-stream is found, returns the rest of the stream. If this /// function is called again after that, returns null. /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The - /// delimiter byte is not included in the returned slice. + /// delimiter byte is written to the output buffer but is not included + /// in the returned slice. pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) !?[]u8 { var index: usize = 0; while (true) { + if (index >= buf.len) return error.StreamTooLong; + const byte = self.readByte() catch |err| switch (err) { error.EndOfStream => { if (index == 0) { @@ -199,11 +205,10 @@ pub fn Reader( }, else => |e| return e, }; + buf[index] = byte; if (byte == delimiter) return buf[0..index]; - if (index >= buf.len) return error.StreamTooLong; - buf[index] = byte; index += 1; } } @@ -363,3 +368,272 @@ test "Reader.skipBytes" { try reader.skipBytes(0, .{}); try testing.expectError(error.EndOfStream, reader.skipBytes(1, .{})); } + +test "Reader.readUntilDelimiterArrayList returns ArrayLists with bytes read until the delimiter, then EndOfStream" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + const reader = std.io.fixedBufferStream("0000\n1234\n").reader(); + + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("0000", list.items); + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("1234", list.items); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); +} + +test "Reader.readUntilDelimiterArrayList returns an empty ArrayList" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + const reader = std.io.fixedBufferStream("\n").reader(); + + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("", list.items); +} + +test "Reader.readUntilDelimiterArrayList returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + const reader = std.io.fixedBufferStream("1234567\n").reader(); + + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterArrayList(&list, '\n', 5)); + try std.testing.expectEqualStrings("12345", list.items); + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("67", list.items); +} + +test "Reader.readUntilDelimiterArrayList returns EndOfStream" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + const reader = std.io.fixedBufferStream("1234").reader(); + + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); + try std.testing.expectEqualStrings("1234", list.items); +} + +test "Reader.readUntilDelimiterAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("0000\n1234\n").reader(); + + { + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("0000", result); + } + + { + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("1234", result); + } + + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); +} + +test "Reader.readUntilDelimiterAlloc returns an empty ArrayList" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("\n").reader(); + + { + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("", result); + } +} + +test "Reader.readUntilDelimiterAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("1234567\n").reader(); + + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterAlloc(a, '\n', 5)); + + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("67", result); +} + +test "Reader.readUntilDelimiterAlloc returns EndOfStream" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("1234").reader(); + + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); +} + +test "Reader.readUntilDelimiter returns bytes read until the delimiter" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("0000\n1234\n").reader(); + try std.testing.expectEqualStrings("0000", try reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns an empty string" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("\n").reader(); + try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns StreamTooLong, then an empty string" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("12345\n").reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns StreamTooLong, then bytes read until the delimiter" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234567\n").reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("67", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns EndOfStream" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("").reader(); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns bytes read until delimiter, then EndOfStream" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234\n").reader(); + try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns EndOfStream" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234").reader(); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns StreamTooLong, then EndOfStream" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("12345").reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter writes all bytes read to the output buffer" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("0000\n12345").reader(); + _ = try reader.readUntilDelimiter(&buf, '\n'); + try std.testing.expectEqualStrings("0000\n", &buf); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("12345", &buf); +} + +test "Reader.readUntilDelimiterOrEofAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("0000\n1234\n").reader(); + + { + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("0000", result); + } + + { + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("1234", result); + } + + try std.testing.expect((try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)) == null); +} + +test "Reader.readUntilDelimiterOrEofAlloc returns an empty ArrayList" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("\n").reader(); + + { + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("", result); + } +} + +test "Reader.readUntilDelimiterOrEofAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { + const a = std.testing.allocator; + + const reader = std.io.fixedBufferStream("1234567\n").reader(); + + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)); + + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("67", result); +} + +test "Reader.readUntilDelimiterOrEof returns bytes read until the delimiter" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("0000\n1234\n").reader(); + try std.testing.expectEqualStrings("0000", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); + try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns an empty string" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("\n").reader(); + try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then an empty string" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("12345\n").reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then bytes read until the delimiter" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234567\n").reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns null" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("").reader(); + try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); +} + +test "Reader.readUntilDelimiterOrEof returns bytes read until delimiter, then null" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234\n").reader(); + try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); + try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); +} + +test "Reader.readUntilDelimiterOrEof returns bytes read until end-of-stream" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234").reader(); + try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then bytes read until end-of-stream" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("1234567").reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof writes all bytes read to the output buffer" { + var buf: [5]u8 = undefined; + const reader = std.io.fixedBufferStream("0000\n12345").reader(); + _ = try reader.readUntilDelimiterOrEof(&buf, '\n'); + try std.testing.expectEqualStrings("0000\n", &buf); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("12345", &buf); +}