authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-11-29 21:37:13+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 19:37:33-07:00
logc761dfc1761b38be8d1dc72dd4c0cbf07d2c0eed
treea8aabf72c25ff848494460d7c7874fb2b3420022
parent48b160c1bf75f602acabc3b43eca56b8aa4abf4f

tar: add gnu path and link extensions handling


1 files changed, 92 insertions(+), 50 deletions(-)

lib/std/tar.zig+92-50
......@@ -82,6 +82,10 @@ pub const Header = struct {
8282 contiguous = '7',
8383 global_extended_header = 'g',
8484 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',
8589 _,
8690 };
8791
......@@ -119,7 +123,8 @@ pub const Header = struct {
119123 }
120124
121125 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] == ' ');
123128 }
124129
125130 pub fn prefix(header: Header) []const u8 {
......@@ -133,12 +138,7 @@ pub const Header = struct {
133138 }
134139
135140 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]);
142142 }
143143
144144 fn numeric(header: Header, start: usize, len: usize) !u64 {
......@@ -190,6 +190,14 @@ pub const Header = struct {
190190 }
191191};
192192
193// break string on first null char
194fn 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
193201fn BufferedReader(comptime ReaderType: type) type {
194202 return struct {
195203 unbuffered_reader: ReaderType,
......@@ -274,7 +282,7 @@ fn BufferedReader(comptime ReaderType: type) type {
274282 reader: *Self,
275283 auto_advance: bool,
276284
277 fn next(self: *@This()) !?[]const u8 {
285 pub fn next(self: *@This()) !?[]const u8 {
278286 if (self.offset >= self.size) return null;
279287
280288 const temp = try self.reader.readChunk(self.chunk_size - self.offset);
......@@ -284,22 +292,22 @@ fn BufferedReader(comptime ReaderType: type) type {
284292 return slice;
285293 }
286294
287 fn advance(self: *@This(), len: usize) !void {
295 pub fn advance(self: *@This(), len: usize) !void {
288296 self.offset += len;
289297 try self.reader.skip(len);
290298 }
291299
292 fn byte(self: *@This()) u8 {
300 pub fn byte(self: *@This()) u8 {
293301 return self.reader.buffer[self.reader.start];
294302 }
295303
296 fn copy(self: *@This(), dst: []u8) ![]const u8 {
304 pub fn copy(self: *@This(), dst: []u8) ![]const u8 {
297305 _ = try self.reader.copy(dst);
298306 self.offset += dst.len;
299307 return dst;
300308 }
301309
302 fn remainingSize(self: *@This()) usize {
310 pub fn remainingSize(self: *@This()) usize {
303311 return self.size - self.offset;
304312 }
305313 };
......@@ -443,6 +451,14 @@ fn Iterator(comptime ReaderType: type) type {
443451 }
444452 try self.reader.skipPadding(file_size);
445453 },
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 },
446462 .hard_link => return error.TarUnsupportedFileType,
447463 else => {
448464 const d = self.diagnostics orelse return error.TarUnsupportedFileType;
......@@ -624,22 +640,20 @@ test "parsePaxAttribute" {
624640
625641const TestCase = struct {
626642 const File = struct {
627 const empty_string = &[0]u8{};
628
629643 name: []const u8,
630644 size: usize = 0,
631 link_name: []const u8 = empty_string,
645 link_name: []const u8 = &[0]u8{},
632646 file_type: Header.FileType = .normal,
633647 truncated: bool = false, // when there is no file body, just header, usefull for huge files
634648 };
635649
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
640654};
641655
642test "Go test cases" {
656test "tar: Go test cases" {
643657 const test_dir = try std.fs.openDirAbsolute("/usr/local/go/src/archive/tar/testdata", .{});
644658 const cases = [_]TestCase{
645659 .{
......@@ -718,12 +732,6 @@ test "Go test cases" {
718732 .{
719733 // pax attribute don't end with \n
720734 .path = "pax-bad-hdr-file.tar",
721 // .files = &[_]TestCase.File{
722 // .{
723 // .name = "PAX1/PAX1/long-path-name",
724 // .size = 684,
725 // },
726 // },
727735 .err = error.InvalidPaxAttribute,
728736 },
729737 //
......@@ -808,9 +816,16 @@ test "Go test cases" {
808816 },
809817 .{
810818 .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 },
812826 },
813827 .{
828 // has gnu type D (directory) and S (sparse) blocks
814829 .path = "gnu-incremental.tar",
815830 .err = error.TarUnsupportedFileType,
816831 },
......@@ -825,23 +840,22 @@ test "Go test cases" {
825840 },
826841 },
827842 },
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 },
845859 .{
846860 .path = "gnu-not-utf8.tar",
847861 .files = &[_]TestCase.File{
......@@ -851,19 +865,47 @@ test "Go test cases" {
851865 },
852866 },
853867 .{
854 .path = "neg-size.tar",
855 .err = error.TarHeader,
868 // null in pax key
869 .path = "pax-nul-xattrs.tar",
870 .err = error.InvalidPaxAttribute,
856871 },
857872 .{
858873 .path = "pax-nul-path.tar",
859874 .err = error.InvalidPaxAttribute,
860875 },
861876 .{
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 },
864908 },
865 // TODO some files with errors:
866 // issue10968.tar, issue11169.tar, issue12435.tar
867909 .{
868910 .path = "trailing-slash.tar",
869911 .files = &[_]TestCase.File{