| ... | @@ -85,31 +85,6 @@ pub const Header = struct { | ... | @@ -85,31 +85,6 @@ pub const Header = struct { |
| 85 | _, | 85 | _, |
| 86 | }; | 86 | }; |
| 87 | | 87 | |
| 88 | pub fn fileSize(header: Header) !u64 { | | |
| 89 | const raw = header.bytes[124..][0..12]; | | |
| 90 | // If the leading byte is 0xff (255), all the bytes of the field | | |
| 91 | // (including the leading byte) are concatenated in big-endian order, | | |
| 92 | // with the result being a negative number expressed in two’s | | |
| 93 | // complement form. | | |
| 94 | if (raw[0] == 0xff) return error.SizeNegative; | | |
| 95 | // If the leading byte is 0x80 (128), the non-leading bytes of the | | |
| 96 | // field are concatenated in big-endian order. | | |
| 97 | if (raw[0] == 0x80) { | | |
| 98 | if (raw[1] + raw[2] + raw[3] != 0) return error.SizeTooBig; | | |
| 99 | return std.mem.readInt(u64, raw[4..12], .big); | | |
| 100 | } | | |
| 101 | // Zero-filled octal number in ASCII. Each numeric field of width w | | |
| 102 | // contains w minus 1 digits, and a null | | |
| 103 | const ltrimmed = std.mem.trimLeft(u8, raw, "0 "); | | |
| 104 | const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00"); | | |
| 105 | if (rtrimmed.len == 0) return 0; | | |
| 106 | return std.fmt.parseInt(u64, rtrimmed, 8); | | |
| 107 | } | | |
| 108 | | | |
| 109 | pub fn is_ustar(header: Header) bool { | | |
| 110 | return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00"); | | |
| 111 | } | | |
| 112 | | | |
| 113 | /// Includes prefix concatenated, if any. | 88 | /// Includes prefix concatenated, if any. |
| 114 | /// Return value may point into Header buffer, or might point into the | 89 | /// Return value may point into Header buffer, or might point into the |
| 115 | /// argument buffer. | 90 | /// argument buffer. |
| ... | @@ -128,15 +103,27 @@ pub const Header = struct { | ... | @@ -128,15 +103,27 @@ pub const Header = struct { |
| 128 | } | 103 | } |
| 129 | | 104 | |
| 130 | pub fn name(header: Header) []const u8 { | 105 | pub fn name(header: Header) []const u8 { |
| 131 | return str(header, 0, 0 + 100); | 106 | return header.str(0, 100); |
| | 107 | } |
| | 108 | |
| | 109 | pub fn fileSize(header: Header) !u64 { |
| | 110 | return header.numeric(124, 12); |
| | 111 | } |
| | 112 | |
| | 113 | pub fn chksum(header: Header) !u64 { |
| | 114 | return header.octal(148, 8); |
| 132 | } | 115 | } |
| 133 | | 116 | |
| 134 | pub fn linkName(header: Header) []const u8 { | 117 | pub fn linkName(header: Header) []const u8 { |
| 135 | return str(header, 157, 157 + 100); | 118 | return header.str(157, 100); |
| | 119 | } |
| | 120 | |
| | 121 | pub fn is_ustar(header: Header) bool { |
| | 122 | return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00"); |
| 136 | } | 123 | } |
| 137 | | 124 | |
| 138 | pub fn prefix(header: Header) []const u8 { | 125 | pub fn prefix(header: Header) []const u8 { |
| 139 | return str(header, 345, 345 + 155); | 126 | return header.str(345, 155); |
| 140 | } | 127 | } |
| 141 | | 128 | |
| 142 | pub fn fileType(header: Header) FileType { | 129 | pub fn fileType(header: Header) FileType { |
| ... | @@ -145,7 +132,8 @@ pub const Header = struct { | ... | @@ -145,7 +132,8 @@ pub const Header = struct { |
| 145 | return result; | 132 | return result; |
| 146 | } | 133 | } |
| 147 | | 134 | |
| 148 | fn str(header: Header, start: usize, end: usize) []const u8 { | 135 | fn str(header: Header, start: usize, len: usize) []const u8 { |
| | 136 | const end = start + len; |
| 149 | var i: usize = start; | 137 | var i: usize = start; |
| 150 | while (i < end) : (i += 1) { | 138 | while (i < end) : (i += 1) { |
| 151 | if (header.bytes[i] == 0) break; | 139 | if (header.bytes[i] == 0) break; |
| ... | @@ -153,11 +141,52 @@ pub const Header = struct { | ... | @@ -153,11 +141,52 @@ pub const Header = struct { |
| 153 | return header.bytes[start..i]; | 141 | return header.bytes[start..i]; |
| 154 | } | 142 | } |
| 155 | | 143 | |
| 156 | pub fn isZero(header: Header) bool { | 144 | fn numeric(header: Header, start: usize, len: usize) !u64 { |
| 157 | for (header.bytes) |b| { | 145 | const raw = header.bytes[start..][0..len]; |
| 158 | if (b != 0) return false; | 146 | // If the leading byte is 0xff (255), all the bytes of the field |
| | 147 | // (including the leading byte) are concatenated in big-endian order, |
| | 148 | // with the result being a negative number expressed in two’s |
| | 149 | // complement form. |
| | 150 | if (raw[0] == 0xff) return error.TarNumericValueNegative; |
| | 151 | // If the leading byte is 0x80 (128), the non-leading bytes of the |
| | 152 | // field are concatenated in big-endian order. |
| | 153 | if (raw[0] == 0x80) { |
| | 154 | if (raw[1] + raw[2] + raw[3] != 0) return error.TarNumericValueTooBig; |
| | 155 | return std.mem.readInt(u64, raw[4..12], .big); |
| 159 | } | 156 | } |
| 160 | return true; | 157 | return try header.octal(start, len); |
| | 158 | } |
| | 159 | |
| | 160 | fn octal(header: Header, start: usize, len: usize) !u64 { |
| | 161 | const raw = header.bytes[start..][0..len]; |
| | 162 | // Zero-filled octal number in ASCII. Each numeric field of width w |
| | 163 | // contains w minus 1 digits, and a null |
| | 164 | const ltrimmed = std.mem.trimLeft(u8, raw, "0 "); |
| | 165 | const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00"); |
| | 166 | if (rtrimmed.len == 0) return 0; |
| | 167 | return std.fmt.parseInt(u64, rtrimmed, 8); |
| | 168 | } |
| | 169 | |
| | 170 | // Sum of all bytes in the header block. The chksum field is treated as if |
| | 171 | // it were filled with spaces (ASCII 32). |
| | 172 | fn computeChksum(header: Header) u64 { |
| | 173 | var sum: u64 = 0; |
| | 174 | for (header.bytes, 0..) |b, i| { |
| | 175 | if (148 <= i and i < 156) continue; // skip chksum field bytes |
| | 176 | sum += b; |
| | 177 | } |
| | 178 | // Treating chksum bytes as spaces. 256 = 8 * 32, 8 spaces. |
| | 179 | return if (sum > 0) sum + 256 else 0; |
| | 180 | } |
| | 181 | |
| | 182 | // Checks calculated chksum with value of chksum field. |
| | 183 | // Returns error or chksum value. |
| | 184 | // Zero value indicates empty block. |
| | 185 | pub fn checkChksum(header: Header) !u64 { |
| | 186 | const field = try header.chksum(); |
| | 187 | const computed = header.computeChksum(); |
| | 188 | if (field != computed) return error.TarHeaderChksum; |
| | 189 | return field; |
| 161 | } | 190 | } |
| 162 | }; | 191 | }; |
| 163 | | 192 | |
| ... | @@ -368,8 +397,8 @@ fn Iterator(comptime ReaderType: type) type { | ... | @@ -368,8 +397,8 @@ fn Iterator(comptime ReaderType: type) type { |
| 368 | self.attrs.free(); | 397 | self.attrs.free(); |
| 369 | | 398 | |
| 370 | while (try self.reader.readBlock()) |block_bytes| { | 399 | while (try self.reader.readBlock()) |block_bytes| { |
| 371 | const block: Header = .{ .bytes = block_bytes[0..BLOCK_SIZE] }; | 400 | const block = Header{ .bytes = block_bytes[0..BLOCK_SIZE] }; |
| 372 | if (block.isZero()) return null; | 401 | if (try block.checkChksum() == 0) return null; // zero block found |
| 373 | const file_type = block.fileType(); | 402 | const file_type = block.fileType(); |
| 374 | const file_size = try block.fileSize(); | 403 | const file_size = try block.fileSize(); |
| 375 | | 404 | |
| ... | @@ -578,9 +607,6 @@ test parsePaxAttribute { | ... | @@ -578,9 +607,6 @@ test parsePaxAttribute { |
| 578 | try expectError(error.InvalidPaxAttribute, parsePaxAttribute("", 0)); | 607 | try expectError(error.InvalidPaxAttribute, parsePaxAttribute("", 0)); |
| 579 | } | 608 | } |
| 580 | | 609 | |
| 581 | const std = @import("std"); | | |
| 582 | const assert = std.debug.assert; | | |
| 583 | | | |
| 584 | const TestCase = struct { | 610 | const TestCase = struct { |
| 585 | const File = struct { | 611 | const File = struct { |
| 586 | const empty_string = &[0]u8{}; | 612 | const empty_string = &[0]u8{}; |