authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-11-29 17:17:20+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 19:37:33-07:00
log16c40fc4713c195c7a6b8544c9dffbfc6201dc9d
tree2e17e3e675065d54f1e900c8bb3b714094ba2ab5
parent169f28d3e6a908717a0e42323ba1a0ee765976da

tar: add header chksum checking


1 files changed, 64 insertions(+), 38 deletions(-)

lib/std/tar.zig+64-38
......@@ -85,31 +85,6 @@ pub const Header = struct {
8585 _,
8686 };
8787
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
11388 /// Includes prefix concatenated, if any.
11489 /// Return value may point into Header buffer, or might point into the
11590 /// argument buffer.
......@@ -128,15 +103,27 @@ pub const Header = struct {
128103 }
129104
130105 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);
132115 }
133116
134117 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");
136123 }
137124
138125 pub fn prefix(header: Header) []const u8 {
139 return str(header, 345, 345 + 155);
126 return header.str(345, 155);
140127 }
141128
142129 pub fn fileType(header: Header) FileType {
......@@ -145,7 +132,8 @@ pub const Header = struct {
145132 return result;
146133 }
147134
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;
149137 var i: usize = start;
150138 while (i < end) : (i += 1) {
151139 if (header.bytes[i] == 0) break;
......@@ -153,11 +141,52 @@ pub const Header = struct {
153141 return header.bytes[start..i];
154142 }
155143
156 pub fn isZero(header: Header) bool {
157 for (header.bytes) |b| {
158 if (b != 0) return false;
144 fn numeric(header: Header, start: usize, len: usize) !u64 {
145 const raw = header.bytes[start..][0..len];
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);
159156 }
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;
161190 }
162191};
163192
......@@ -368,8 +397,8 @@ fn Iterator(comptime ReaderType: type) type {
368397 self.attrs.free();
369398
370399 while (try self.reader.readBlock()) |block_bytes| {
371 const block: Header = .{ .bytes = block_bytes[0..BLOCK_SIZE] };
372 if (block.isZero()) return null;
400 const block = Header{ .bytes = block_bytes[0..BLOCK_SIZE] };
401 if (try block.checkChksum() == 0) return null; // zero block found
373402 const file_type = block.fileType();
374403 const file_size = try block.fileSize();
375404
......@@ -578,9 +607,6 @@ test parsePaxAttribute {
578607 try expectError(error.InvalidPaxAttribute, parsePaxAttribute("", 0));
579608}
580609
581const std = @import("std");
582const assert = std.debug.assert;
583
584610const TestCase = struct {
585611 const File = struct {
586612 const empty_string = &[0]u8{};