| ... | ... | @@ -82,6 +82,10 @@ pub const Header = struct { |
| 82 | 82 | contiguous = '7', |
| 83 | 83 | global_extended_header = 'g', |
| 84 | 84 | extended_header = 'x', |
| 85 | // Types 'L' and 'K' are used by the GNU format for a meta file |
| 86 | // used to store the path or link name for the next file. |
| 87 | gnu_long_name = 'L', |
| 88 | gnu_long_link = 'K', |
| 85 | 89 | _, |
| 86 | 90 | }; |
| 87 | 91 | |
| ... | ... | @@ -119,7 +123,8 @@ pub const Header = struct { |
| 119 | 123 | } |
| 120 | 124 | |
| 121 | 125 | pub fn is_ustar(header: Header) bool { |
| 122 | | return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00"); |
| 126 | const magic = header.bytes[257..][0..6]; |
| 127 | return std.mem.eql(u8, magic[0..5], "ustar") and (magic[5] == 0 or magic[5] == ' '); |
| 123 | 128 | } |
| 124 | 129 | |
| 125 | 130 | pub fn prefix(header: Header) []const u8 { |
| ... | ... | @@ -133,12 +138,7 @@ pub const Header = struct { |
| 133 | 138 | } |
| 134 | 139 | |
| 135 | 140 | fn str(header: Header, start: usize, len: usize) []const u8 { |
| 136 | | const end = start + len; |
| 137 | | var i: usize = start; |
| 138 | | while (i < end) : (i += 1) { |
| 139 | | if (header.bytes[i] == 0) break; |
| 140 | | } |
| 141 | | return header.bytes[start..i]; |
| 141 | return nullStr(header.bytes[start .. start + len]); |
| 142 | 142 | } |
| 143 | 143 | |
| 144 | 144 | fn numeric(header: Header, start: usize, len: usize) !u64 { |
| ... | ... | @@ -190,6 +190,14 @@ pub const Header = struct { |
| 190 | 190 | } |
| 191 | 191 | }; |
| 192 | 192 | |
| 193 | // break string on first null char |
| 194 | fn nullStr(str: []const u8) []const u8 { |
| 195 | for (str, 0..) |c, i| { |
| 196 | if (c == 0) return str[0..i]; |
| 197 | } |
| 198 | return str; |
| 199 | } |
| 200 | |
| 193 | 201 | fn BufferedReader(comptime ReaderType: type) type { |
| 194 | 202 | return struct { |
| 195 | 203 | unbuffered_reader: ReaderType, |
| ... | ... | @@ -274,7 +282,7 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 274 | 282 | reader: *Self, |
| 275 | 283 | auto_advance: bool, |
| 276 | 284 | |
| 277 | | fn next(self: *@This()) !?[]const u8 { |
| 285 | pub fn next(self: *@This()) !?[]const u8 { |
| 278 | 286 | if (self.offset >= self.size) return null; |
| 279 | 287 | |
| 280 | 288 | const temp = try self.reader.readChunk(self.chunk_size - self.offset); |
| ... | ... | @@ -284,22 +292,22 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 284 | 292 | return slice; |
| 285 | 293 | } |
| 286 | 294 | |
| 287 | | fn advance(self: *@This(), len: usize) !void { |
| 295 | pub fn advance(self: *@This(), len: usize) !void { |
| 288 | 296 | self.offset += len; |
| 289 | 297 | try self.reader.skip(len); |
| 290 | 298 | } |
| 291 | 299 | |
| 292 | | fn byte(self: *@This()) u8 { |
| 300 | pub fn byte(self: *@This()) u8 { |
| 293 | 301 | return self.reader.buffer[self.reader.start]; |
| 294 | 302 | } |
| 295 | 303 | |
| 296 | | fn copy(self: *@This(), dst: []u8) ![]const u8 { |
| 304 | pub fn copy(self: *@This(), dst: []u8) ![]const u8 { |
| 297 | 305 | _ = try self.reader.copy(dst); |
| 298 | 306 | self.offset += dst.len; |
| 299 | 307 | return dst; |
| 300 | 308 | } |
| 301 | 309 | |
| 302 | | fn remainingSize(self: *@This()) usize { |
| 310 | pub fn remainingSize(self: *@This()) usize { |
| 303 | 311 | return self.size - self.offset; |
| 304 | 312 | } |
| 305 | 313 | }; |
| ... | ... | @@ -443,6 +451,14 @@ fn Iterator(comptime ReaderType: type) type { |
| 443 | 451 | } |
| 444 | 452 | try self.reader.skipPadding(file_size); |
| 445 | 453 | }, |
| 454 | .gnu_long_name => { |
| 455 | file.name = nullStr(try self.reader.copy(try self.attrs.alloc(file_size))); |
| 456 | try self.reader.skipPadding(file_size); |
| 457 | }, |
| 458 | .gnu_long_link => { |
| 459 | file.link_name = nullStr(try self.reader.copy(try self.attrs.alloc(file_size))); |
| 460 | try self.reader.skipPadding(file_size); |
| 461 | }, |
| 446 | 462 | .hard_link => return error.TarUnsupportedFileType, |
| 447 | 463 | else => { |
| 448 | 464 | const d = self.diagnostics orelse return error.TarUnsupportedFileType; |
| ... | ... | @@ -624,22 +640,20 @@ test "parsePaxAttribute" { |
| 624 | 640 | |
| 625 | 641 | const TestCase = struct { |
| 626 | 642 | const File = struct { |
| 627 | | const empty_string = &[0]u8{}; |
| 628 | | |
| 629 | 643 | name: []const u8, |
| 630 | 644 | size: usize = 0, |
| 631 | | link_name: []const u8 = empty_string, |
| 645 | link_name: []const u8 = &[0]u8{}, |
| 632 | 646 | file_type: Header.FileType = .normal, |
| 633 | 647 | truncated: bool = false, // when there is no file body, just header, usefull for huge files |
| 634 | 648 | }; |
| 635 | 649 | |
| 636 | | path: []const u8, |
| 637 | | files: []const File = &[_]TestCase.File{}, |
| 638 | | chksums: []const []const u8 = &[_][]const u8{}, |
| 639 | | err: ?anyerror = null, |
| 650 | path: []const u8, // path to the tar archive file on dis |
| 651 | files: []const File = &[_]TestCase.File{}, // expected files to found in archive |
| 652 | chksums: []const []const u8 = &[_][]const u8{}, // chksums of files content |
| 653 | err: ?anyerror = null, // parsing should fail with this error |
| 640 | 654 | }; |
| 641 | 655 | |
| 642 | | test "Go test cases" { |
| 656 | test "tar: Go test cases" { |
| 643 | 657 | const test_dir = try std.fs.openDirAbsolute("/usr/local/go/src/archive/tar/testdata", .{}); |
| 644 | 658 | const cases = [_]TestCase{ |
| 645 | 659 | .{ |
| ... | ... | @@ -718,12 +732,6 @@ test "Go test cases" { |
| 718 | 732 | .{ |
| 719 | 733 | // pax attribute don't end with \n |
| 720 | 734 | .path = "pax-bad-hdr-file.tar", |
| 721 | | // .files = &[_]TestCase.File{ |
| 722 | | // .{ |
| 723 | | // .name = "PAX1/PAX1/long-path-name", |
| 724 | | // .size = 684, |
| 725 | | // }, |
| 726 | | // }, |
| 727 | 735 | .err = error.InvalidPaxAttribute, |
| 728 | 736 | }, |
| 729 | 737 | // |
| ... | ... | @@ -808,9 +816,16 @@ test "Go test cases" { |
| 808 | 816 | }, |
| 809 | 817 | .{ |
| 810 | 818 | .path = "gnu-multi-hdrs.tar", |
| 811 | | .err = error.TarUnsupportedFileType, |
| 819 | .files = &[_]TestCase.File{ |
| 820 | .{ |
| 821 | .name = "GNU2/GNU2/long-path-name", |
| 822 | .link_name = "GNU4/GNU4/long-linkpath-name", |
| 823 | .file_type = .symbolic_link, |
| 824 | }, |
| 825 | }, |
| 812 | 826 | }, |
| 813 | 827 | .{ |
| 828 | // has gnu type D (directory) and S (sparse) blocks |
| 814 | 829 | .path = "gnu-incremental.tar", |
| 815 | 830 | .err = error.TarUnsupportedFileType, |
| 816 | 831 | }, |
| ... | ... | @@ -825,23 +840,22 @@ test "Go test cases" { |
| 825 | 840 | }, |
| 826 | 841 | }, |
| 827 | 842 | }, |
| 828 | | // .{ |
| 829 | | // .path = "gnu-long-nul.tar", |
| 830 | | // .files = &[_]TestCase.File{ |
| 831 | | // .{ |
| 832 | | // .name = "012233456789", |
| 833 | | // }, |
| 834 | | // }, |
| 835 | | // }, |
| 836 | | // .{ |
| 837 | | // .path = "gnu-utf8.tar", |
| 838 | | // .files = &[_]TestCase.File{ |
| 839 | | // .{ |
| 840 | | // .name = "012233456789", |
| 841 | | // }, |
| 842 | | // }, |
| 843 | | // }, |
| 844 | | // |
| 843 | .{ |
| 844 | .path = "gnu-long-nul.tar", |
| 845 | .files = &[_]TestCase.File{ |
| 846 | .{ |
| 847 | .name = "0123456789", |
| 848 | }, |
| 849 | }, |
| 850 | }, |
| 851 | .{ |
| 852 | .path = "gnu-utf8.tar", |
| 853 | .files = &[_]TestCase.File{ |
| 854 | .{ |
| 855 | .name = "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹", |
| 856 | }, |
| 857 | }, |
| 858 | }, |
| 845 | 859 | .{ |
| 846 | 860 | .path = "gnu-not-utf8.tar", |
| 847 | 861 | .files = &[_]TestCase.File{ |
| ... | ... | @@ -851,19 +865,47 @@ test "Go test cases" { |
| 851 | 865 | }, |
| 852 | 866 | }, |
| 853 | 867 | .{ |
| 854 | | .path = "neg-size.tar", |
| 855 | | .err = error.TarHeader, |
| 868 | // null in pax key |
| 869 | .path = "pax-nul-xattrs.tar", |
| 870 | .err = error.InvalidPaxAttribute, |
| 856 | 871 | }, |
| 857 | 872 | .{ |
| 858 | 873 | .path = "pax-nul-path.tar", |
| 859 | 874 | .err = error.InvalidPaxAttribute, |
| 860 | 875 | }, |
| 861 | 876 | .{ |
| 862 | | .path = "pax-nul-xattrs.tar", |
| 863 | | .err = error.InvalidPaxAttribute, |
| 877 | .path = "neg-size.tar", |
| 878 | .err = error.TarHeader, |
| 879 | }, |
| 880 | .{ |
| 881 | .path = "issue10968.tar", |
| 882 | .err = error.TarHeader, |
| 883 | }, |
| 884 | .{ |
| 885 | .path = "issue11169.tar", |
| 886 | .err = error.TarHeader, |
| 887 | }, |
| 888 | .{ |
| 889 | .path = "issue12435.tar", |
| 890 | .err = error.TarHeaderChksum, |
| 891 | }, |
| 892 | .{ |
| 893 | // has magic with space at end instead of null |
| 894 | .path = "invalid-go17.tar", |
| 895 | .files = &[_]TestCase.File{ |
| 896 | .{ |
| 897 | .name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/foo", |
| 898 | }, |
| 899 | }, |
| 900 | }, |
| 901 | .{ |
| 902 | .path = "ustar-file-devs.tar", |
| 903 | .files = &[_]TestCase.File{ |
| 904 | .{ |
| 905 | .name = "file", |
| 906 | }, |
| 907 | }, |
| 864 | 908 | }, |
| 865 | | // TODO some files with errors: |
| 866 | | // issue10968.tar, issue11169.tar, issue12435.tar |
| 867 | 909 | .{ |
| 868 | 910 | .path = "trailing-slash.tar", |
| 869 | 911 | .files = &[_]TestCase.File{ |