From f85c01d4c7520d2242626f4c0684ab97e47af373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sat, 21 Jan 2023 15:47:31 -0800 Subject: [PATCH] Implement gzip header CRC check. From RFC 1952: > If FHCRC is set, a CRC16 for the gzip header is present, > immediately before the compressed data. The CRC16 consists > of the two least significant bytes of the CRC32 for all > bytes of the gzip header up to and not including the CRC16. --- lib/std/compress.zig | 30 ++++++++++++++++++++++++++++++ lib/std/compress/gzip.zig | 32 ++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/std/compress.zig b/lib/std/compress.zig index 7fa25175d5ed83e5b6077d23e84c86839cc65e74..3c52002cfc86420b0bd630558c8a8525b86a669e 100644 --- a/lib/std/compress.zig +++ b/lib/std/compress.zig @@ -4,6 +4,36 @@ pub const deflate = @import("compress/deflate.zig"); pub const gzip = @import("compress/gzip.zig"); pub const zlib = @import("compress/zlib.zig"); +pub fn HashedReader( + comptime ReaderType: anytype, + comptime HasherType: anytype, +) type { + return struct { + child_reader: ReaderType, + hasher: HasherType, + + pub const Error = ReaderType.Error; + pub const Reader = std.io.Reader(*@This(), Error, read); + + pub fn read(self: *@This(), buf: []u8) Error!usize { + const amt = try self.child_reader.read(buf); + self.hasher.update(buf); + return amt; + } + + pub fn reader(self: *@This()) Reader { + return .{ .context = self }; + } + }; +} + +pub fn hashedReader( + reader: anytype, + hasher: anytype, +) HashedReader(@TypeOf(reader), @TypeOf(hasher)) { + return .{ .child_reader = reader, .hasher = hasher }; +} + test { _ = deflate; _ = gzip; diff --git a/lib/std/compress/gzip.zig b/lib/std/compress/gzip.zig index e9e57cc550e2ee165cac86ce2935b5104849ebdc..4d1f8e28f498397c9a49925f8275c01d052feaef 100644 --- a/lib/std/compress/gzip.zig +++ b/lib/std/compress/gzip.zig @@ -44,8 +44,11 @@ pub fn GzipStream(comptime ReaderType: type) type { }, fn init(allocator: mem.Allocator, source: ReaderType) !Self { + var hasher = std.compress.hashedReader(source, std.hash.Crc32.init()); + const hashed_reader = hasher.reader(); + // gzip header format is specified in RFC1952 - const header = try source.readBytesNoEof(10); + const header = try hashed_reader.readBytesNoEof(10); // Check the ID1/ID2 fields if (header[0] != 0x1f or header[1] != 0x8b) @@ -66,31 +69,31 @@ pub fn GzipStream(comptime ReaderType: type) type { _ = XFL; const extra = if (FLG & FEXTRA != 0) blk: { - const len = try source.readIntLittle(u16); + const len = try hashed_reader.readIntLittle(u16); const tmp_buf = try allocator.alloc(u8, len); errdefer allocator.free(tmp_buf); - try source.readNoEof(tmp_buf); + try hashed_reader.readNoEof(tmp_buf); break :blk tmp_buf; } else null; errdefer if (extra) |p| allocator.free(p); const filename = if (FLG & FNAME != 0) - try source.readUntilDelimiterAlloc(allocator, 0, max_string_len) + try hashed_reader.readUntilDelimiterAlloc(allocator, 0, max_string_len) else null; errdefer if (filename) |p| allocator.free(p); const comment = if (FLG & FCOMMENT != 0) - try source.readUntilDelimiterAlloc(allocator, 0, max_string_len) + try hashed_reader.readUntilDelimiterAlloc(allocator, 0, max_string_len) else null; errdefer if (comment) |p| allocator.free(p); if (FLG & FHCRC != 0) { - // TODO: Evaluate and check the header checksum. The stdlib has - // no CRC16 yet :( - _ = try source.readIntLittle(u16); + const hash = try source.readIntLittle(u16); + if (hash != @truncate(u16, hasher.hasher.final())) + return error.WrongChecksum; } return Self{ @@ -230,3 +233,16 @@ test "sanity checks" { }, ""), ); } + +test "header checksum" { + try testReader(&[_]u8{ + // GZIP header + 0x1f, 0x8b, 0x08, 0x12, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, + + // header.FHCRC (should cover entire header) + 0x99, 0xd6, + + // GZIP data + 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, ""); +} -- 2.54.0