Commit 16c40fc471

Igor Anić <igor.anic@gmail.com>
2023-11-29 17:17:20
tar: add header chksum checking
1 parent 169f28d
Changed files (1)
lib
lib/std/tar.zig
@@ -85,31 +85,6 @@ pub const Header = struct {
         _,
     };
 
-    pub fn fileSize(header: Header) !u64 {
-        const raw = header.bytes[124..][0..12];
-        //  If the leading byte is 0xff (255), all the bytes of the field
-        //  (including the leading byte) are concatenated in big-endian order,
-        //  with the result being a negative number expressed in two’s
-        //  complement form.
-        if (raw[0] == 0xff) return error.SizeNegative;
-        // If the leading byte is 0x80 (128), the non-leading bytes of the
-        // field are concatenated in big-endian order.
-        if (raw[0] == 0x80) {
-            if (raw[1] + raw[2] + raw[3] != 0) return error.SizeTooBig;
-            return std.mem.readInt(u64, raw[4..12], .big);
-        }
-        // Zero-filled octal number in ASCII. Each numeric field of width w
-        // contains w minus 1 digits, and a null
-        const ltrimmed = std.mem.trimLeft(u8, raw, "0 ");
-        const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00");
-        if (rtrimmed.len == 0) return 0;
-        return std.fmt.parseInt(u64, rtrimmed, 8);
-    }
-
-    pub fn is_ustar(header: Header) bool {
-        return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00");
-    }
-
     /// Includes prefix concatenated, if any.
     /// Return value may point into Header buffer, or might point into the
     /// argument buffer.
@@ -128,15 +103,27 @@ pub const Header = struct {
     }
 
     pub fn name(header: Header) []const u8 {
-        return str(header, 0, 0 + 100);
+        return header.str(0, 100);
+    }
+
+    pub fn fileSize(header: Header) !u64 {
+        return header.numeric(124, 12);
+    }
+
+    pub fn chksum(header: Header) !u64 {
+        return header.octal(148, 8);
     }
 
     pub fn linkName(header: Header) []const u8 {
-        return str(header, 157, 157 + 100);
+        return header.str(157, 100);
+    }
+
+    pub fn is_ustar(header: Header) bool {
+        return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00");
     }
 
     pub fn prefix(header: Header) []const u8 {
-        return str(header, 345, 345 + 155);
+        return header.str(345, 155);
     }
 
     pub fn fileType(header: Header) FileType {
@@ -145,7 +132,8 @@ pub const Header = struct {
         return result;
     }
 
-    fn str(header: Header, start: usize, end: usize) []const u8 {
+    fn str(header: Header, start: usize, len: usize) []const u8 {
+        const end = start + len;
         var i: usize = start;
         while (i < end) : (i += 1) {
             if (header.bytes[i] == 0) break;
@@ -153,11 +141,52 @@ pub const Header = struct {
         return header.bytes[start..i];
     }
 
-    pub fn isZero(header: Header) bool {
-        for (header.bytes) |b| {
-            if (b != 0) return false;
+    fn numeric(header: Header, start: usize, len: usize) !u64 {
+        const raw = header.bytes[start..][0..len];
+        //  If the leading byte is 0xff (255), all the bytes of the field
+        //  (including the leading byte) are concatenated in big-endian order,
+        //  with the result being a negative number expressed in two’s
+        //  complement form.
+        if (raw[0] == 0xff) return error.TarNumericValueNegative;
+        // If the leading byte is 0x80 (128), the non-leading bytes of the
+        // field are concatenated in big-endian order.
+        if (raw[0] == 0x80) {
+            if (raw[1] + raw[2] + raw[3] != 0) return error.TarNumericValueTooBig;
+            return std.mem.readInt(u64, raw[4..12], .big);
         }
-        return true;
+        return try header.octal(start, len);
+    }
+
+    fn octal(header: Header, start: usize, len: usize) !u64 {
+        const raw = header.bytes[start..][0..len];
+        // Zero-filled octal number in ASCII. Each numeric field of width w
+        // contains w minus 1 digits, and a null
+        const ltrimmed = std.mem.trimLeft(u8, raw, "0 ");
+        const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00");
+        if (rtrimmed.len == 0) return 0;
+        return std.fmt.parseInt(u64, rtrimmed, 8);
+    }
+
+    // Sum of all bytes in the header block. The chksum field is treated as if
+    // it were filled with spaces (ASCII 32).
+    fn computeChksum(header: Header) u64 {
+        var sum: u64 = 0;
+        for (header.bytes, 0..) |b, i| {
+            if (148 <= i and i < 156) continue; // skip chksum field bytes
+            sum += b;
+        }
+        // Treating chksum bytes as spaces. 256 = 8 * 32, 8 spaces.
+        return if (sum > 0) sum + 256 else 0;
+    }
+
+    // Checks calculated chksum with value of chksum field.
+    // Returns error or chksum value.
+    // Zero value indicates empty block.
+    pub fn checkChksum(header: Header) !u64 {
+        const field = try header.chksum();
+        const computed = header.computeChksum();
+        if (field != computed) return error.TarHeaderChksum;
+        return field;
     }
 };
 
@@ -368,8 +397,8 @@ fn Iterator(comptime ReaderType: type) type {
             self.attrs.free();
 
             while (try self.reader.readBlock()) |block_bytes| {
-                const block: Header = .{ .bytes = block_bytes[0..BLOCK_SIZE] };
-                if (block.isZero()) return null;
+                const block = Header{ .bytes = block_bytes[0..BLOCK_SIZE] };
+                if (try block.checkChksum() == 0) return null; // zero block found
                 const file_type = block.fileType();
                 const file_size = try block.fileSize();
 
@@ -578,9 +607,6 @@ test parsePaxAttribute {
     try expectError(error.InvalidPaxAttribute, parsePaxAttribute("", 0));
 }
 
-const std = @import("std");
-const assert = std.debug.assert;
-
 const TestCase = struct {
     const File = struct {
         const empty_string = &[0]u8{};