| ... | @@ -105,6 +105,8 @@ pub const Header = struct { | ... | @@ -105,6 +105,8 @@ pub const Header = struct { |
| 105 | // used to store the path or link name for the next file. | 105 | // used to store the path or link name for the next file. |
| 106 | gnu_long_name = 'L', | 106 | gnu_long_name = 'L', |
| 107 | gnu_long_link = 'K', | 107 | gnu_long_link = 'K', |
| | 108 | gnu_sparse = 'S', |
| | 109 | solaris_extended_header = 'X', |
| 108 | _, | 110 | _, |
| 109 | }; | 111 | }; |
| 110 | | 112 | |
| ... | @@ -194,16 +196,21 @@ pub const Header = struct { | ... | @@ -194,16 +196,21 @@ pub const Header = struct { |
| 194 | return std.fmt.parseInt(u64, rtrimmed, 8) catch return error.TarHeader; | 196 | return std.fmt.parseInt(u64, rtrimmed, 8) catch return error.TarHeader; |
| 195 | } | 197 | } |
| 196 | | 198 | |
| | 199 | const Chksums = struct { |
| | 200 | unsigned: u64, |
| | 201 | signed: i64, |
| | 202 | }; |
| | 203 | |
| 197 | // Sum of all bytes in the header block. The chksum field is treated as if | 204 | // Sum of all bytes in the header block. The chksum field is treated as if |
| 198 | // it were filled with spaces (ASCII 32). | 205 | // it were filled with spaces (ASCII 32). |
| 199 | fn computeChksum(header: Header) u64 { | 206 | fn computeChksum(header: Header) Chksums { |
| 200 | var sum: u64 = 0; | 207 | var cs: Chksums = .{ .signed = 0, .unsigned = 0 }; |
| 201 | for (header.bytes, 0..) |b, i| { | 208 | for (header.bytes, 0..) |v, i| { |
| 202 | if (148 <= i and i < 156) continue; // skip chksum field bytes | 209 | const b = if (148 <= i and i < 156) 32 else v; // Treating chksum bytes as spaces. |
| 203 | sum += b; | 210 | cs.unsigned += b; |
| | 211 | cs.signed += @as(i8, @bitCast(b)); |
| 204 | } | 212 | } |
| 205 | // Treating chksum bytes as spaces. 256 = 8 * 32, 8 spaces. | 213 | return cs; |
| 206 | return if (sum > 0) sum + 256 else 0; | | |
| 207 | } | 214 | } |
| 208 | | 215 | |
| 209 | // Checks calculated chksum with value of chksum field. | 216 | // Checks calculated chksum with value of chksum field. |
| ... | @@ -211,8 +218,9 @@ pub const Header = struct { | ... | @@ -211,8 +218,9 @@ pub const Header = struct { |
| 211 | // Zero value indicates empty block. | 218 | // Zero value indicates empty block. |
| 212 | pub fn checkChksum(header: Header) !u64 { | 219 | pub fn checkChksum(header: Header) !u64 { |
| 213 | const field = try header.chksum(); | 220 | const field = try header.chksum(); |
| 214 | const computed = header.computeChksum(); | 221 | const cs = header.computeChksum(); |
| 215 | if (field != computed) return error.TarHeaderChksum; | 222 | if (field == 0 and cs.unsigned == 256) return 0; |
| | 223 | if (field != cs.unsigned and field != cs.signed) return error.TarHeaderChksum; |
| 216 | return field; | 224 | return field; |
| 217 | } | 225 | } |
| 218 | }; | 226 | }; |
| ... | @@ -387,11 +395,25 @@ fn Iterator(comptime ReaderType: type) type { | ... | @@ -387,11 +395,25 @@ fn Iterator(comptime ReaderType: type) type { |
| 387 | .file_name = try d.allocator.dupe(u8, header.name()), | 395 | .file_name = try d.allocator.dupe(u8, header.name()), |
| 388 | .file_type = kind, | 396 | .file_type = kind, |
| 389 | } }); | 397 | } }); |
| | 398 | if (kind == .gnu_sparse) { |
| | 399 | try self.skipGnuSparseExtendedHeaders(header); |
| | 400 | } |
| | 401 | self.reader.skipBytes(size, .{}) catch return error.TarHeadersTooBig; |
| 390 | }, | 402 | }, |
| 391 | } | 403 | } |
| 392 | } | 404 | } |
| 393 | return null; | 405 | return null; |
| 394 | } | 406 | } |
| | 407 | |
| | 408 | fn skipGnuSparseExtendedHeaders(self: *Self, header: Header) !void { |
| | 409 | var is_extended = header.bytes[482] > 0; |
| | 410 | while (is_extended) { |
| | 411 | var buf: [Header.SIZE]u8 = undefined; |
| | 412 | const n = try self.reader.readAll(&buf); |
| | 413 | if (n < Header.SIZE) return error.UnexpectedEndOfStream; |
| | 414 | is_extended = buf[504] > 0; |
| | 415 | } |
| | 416 | } |
| 395 | }; | 417 | }; |
| 396 | } | 418 | } |
| 397 | | 419 | |