authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-12-11 17:47:19+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 19:37:33-07:00
log9f7dd323082941d66c18af3da88a432835c5e3e5
tree06035a89a7460907426d78bff6d1030ea6a2b377
parentdbab45cfc6a952aa4ec873d6a33c487cd431bc62

tar: refactor pax attribute

Make it little readable.

1 files changed, 73 insertions(+), 53 deletions(-)

lib/std/tar.zig+73-53
...@@ -331,11 +331,6 @@ test "tar stripComponents" {...@@ -331,11 +331,6 @@ test "tar stripComponents" {
331 try expectEqualStrings("c", try stripComponents("a/b/c", 2));331 try expectEqualStrings("c", try stripComponents("a/b/c", 2));
332}332}
333333
334fn noNull(str: []const u8) ![]const u8 {
335 if (std.mem.indexOfScalar(u8, str, 0)) |_| return error.InvalidPaxAttribute;
336 return str;
337}
338
339test "tar run Go test cases" {334test "tar run Go test cases" {
340 const Case = struct {335 const Case = struct {
341 const File = struct {336 const File = struct {
...@@ -443,7 +438,7 @@ test "tar run Go test cases" {...@@ -443,7 +438,7 @@ test "tar run Go test cases" {
443 .{438 .{
444 // pax attribute don't end with \n439 // pax attribute don't end with \n
445 .path = "pax-bad-hdr-file.tar",440 .path = "pax-bad-hdr-file.tar",
446 .err = error.InvalidPaxAttribute,441 .err = error.PaxInvalidAttributeEnd,
447 },442 },
448 .{443 .{
449 // size is in pax attribute444 // size is in pax attribute
...@@ -579,11 +574,11 @@ test "tar run Go test cases" {...@@ -579,11 +574,11 @@ test "tar run Go test cases" {
579 .{574 .{
580 // null in pax key575 // null in pax key
581 .path = "pax-nul-xattrs.tar",576 .path = "pax-nul-xattrs.tar",
582 .err = error.InvalidPaxAttribute,577 .err = error.PaxNullInKeyword,
583 },578 },
584 .{579 .{
585 .path = "pax-nul-path.tar",580 .path = "pax-nul-path.tar",
586 .err = error.InvalidPaxAttribute,581 .err = error.PaxNullInValue,
587 },582 },
588 .{583 .{
589 .path = "neg-size.tar",584 .path = "neg-size.tar",
...@@ -715,7 +710,7 @@ fn paxReader(reader: anytype, size: usize) PaxReader(@TypeOf(reader)) {...@@ -715,7 +710,7 @@ fn paxReader(reader: anytype, size: usize) PaxReader(@TypeOf(reader)) {
715 };710 };
716}711}
717712
718const PaxAttrKind = enum {713const PaxAttributeKind = enum {
719 path,714 path,
720 linkpath,715 linkpath,
721 size,716 size,
...@@ -723,54 +718,50 @@ const PaxAttrKind = enum {...@@ -723,54 +718,50 @@ const PaxAttrKind = enum {
723718
724fn PaxReader(comptime ReaderType: type) type {719fn PaxReader(comptime ReaderType: type) type {
725 return struct {720 return struct {
726 size: usize,721 size: usize, // cumulative size of all pax attributes
727 reader: ReaderType,722 reader: ReaderType,
723 // scratch buffer used for reading attribute length and keyword
724 scratch: [128]u8 = undefined,
728725
729 const Self = @This();726 const Self = @This();
730727
731 const Attr = struct {728 const Attribute = struct {
732 kind: PaxAttrKind,729 kind: PaxAttributeKind,
733 len: usize,730 len: usize, // length of the attribute value
734 reader: ReaderType,731 reader: ReaderType, // reader positioned at value start
735732
736 // Copies pax attribute value into destination buffer.733 // Copies pax attribute value into destination buffer.
737 // Must be called with destination buffer of size at least value_len.734 // Must be called with destination buffer of size at least Attribute.len.
738 pub fn value(self: Attr, dst: []u8) ![]const u8 {735 pub fn value(self: Attribute, dst: []u8) ![]const u8 {
739 assert(self.len <= dst.len);736 assert(self.len <= dst.len);
740 const buf = dst[0..self.len];737 const buf = dst[0..self.len];
741 const n = try self.reader.readAll(buf);738 const n = try self.reader.readAll(buf);
742 if (n < self.len) return error.UnexpectedEndOfStream;739 if (n < self.len) return error.UnexpectedEndOfStream;
743 try checkRecordEnd(self.reader);740 try validateAttributeEnding(self.reader);
744 return noNull(buf);741 if (hasNull(buf)) return error.PaxNullInValue;
742 return buf;
745 }743 }
746 };744 };
747745
748 // Iterates over pax records. Returns known records. Caller has to call746 // Iterates over pax attributes. Returns known only known attributes.
749 // value in Record, to advance reader across value.747 // Caller has to call value in Attribute, to advance reader across value.
750 pub fn next(self: *Self) !?Attr {748 pub fn next(self: *Self) !?Attribute {
751 var buf: [128]u8 = undefined;749 // Pax extended header consists of one or more attributes, each constructed as follows:
752 var fbs = std.io.fixedBufferStream(&buf);
753
754 // An extended header consists of one or more records, each constructed as follows:
755 // "%d %s=%s\n", <length>, <keyword>, <value>750 // "%d %s=%s\n", <length>, <keyword>, <value>
756 while (self.size > 0) {751 while (self.size > 0) {
757 fbs.reset();752 const length_buf = try self.readUntil(' ');
758 // read length753 const length = try std.fmt.parseInt(usize, length_buf, 10); // record length in bytes
759 try self.reader.streamUntilDelimiter(fbs.writer(), ' ', null);754
760 const rec_len = try std.fmt.parseInt(usize, fbs.getWritten(), 10); // record len in bytes755 const keyword = try self.readUntil('=');
761 var pos = try fbs.getPos() + 1; // bytes used for record len + separator756 if (hasNull(keyword)) return error.PaxNullInKeyword;
762 fbs.reset();757
763 // read keyword758 // calculate value_len
764 try self.reader.streamUntilDelimiter(fbs.writer(), '=', null);759 const value_start = length_buf.len + keyword.len + 2; // 2 separators
765 const keyword = fbs.getWritten();760 if (length < value_start + 1 or self.size < length) return error.UnexpectedEndOfStream;
766 pos += try fbs.getPos() + 1; // keyword bytes + separator761 const value_len = length - value_start - 1; // \n separator at end
767 try checkKeyword(keyword);762 self.size -= length;
768 // get value_len763
769 if (rec_len < pos + 1) return error.InvalidPaxAttribute;764 const kind: PaxAttributeKind = if (eql(keyword, "path"))
770 const value_len = rec_len - pos - 1; // pos = start of value, -1 => without \n record terminator
771
772 self.size -= rec_len;
773 const kind: PaxAttrKind = if (eql(keyword, "path"))
774 .path765 .path
775 else if (eql(keyword, "linkpath"))766 else if (eql(keyword, "linkpath"))
776 .linkpath767 .linkpath
...@@ -778,10 +769,10 @@ fn PaxReader(comptime ReaderType: type) type {...@@ -778,10 +769,10 @@ fn PaxReader(comptime ReaderType: type) type {
778 .size769 .size
779 else {770 else {
780 try self.reader.skipBytes(value_len, .{});771 try self.reader.skipBytes(value_len, .{});
781 try checkRecordEnd(self.reader);772 try validateAttributeEnding(self.reader);
782 continue;773 continue;
783 };774 };
784 return Attr{775 return Attribute{
785 .kind = kind,776 .kind = kind,
786 .len = value_len,777 .len = value_len,
787 .reader = self.reader,778 .reader = self.reader,
...@@ -791,24 +782,30 @@ fn PaxReader(comptime ReaderType: type) type {...@@ -791,24 +782,30 @@ fn PaxReader(comptime ReaderType: type) type {
791 return null;782 return null;
792 }783 }
793784
785 inline fn readUntil(self: *Self, delimiter: u8) ![]const u8 {
786 var fbs = std.io.fixedBufferStream(&self.scratch);
787 try self.reader.streamUntilDelimiter(fbs.writer(), delimiter, null);
788 return fbs.getWritten();
789 }
790
794 inline fn eql(a: []const u8, b: []const u8) bool {791 inline fn eql(a: []const u8, b: []const u8) bool {
795 return std.mem.eql(u8, a, b);792 return std.mem.eql(u8, a, b);
796 }793 }
797794
798 fn checkKeyword(keyword: []const u8) !void {795 inline fn hasNull(str: []const u8) bool {
799 if (std.mem.indexOfScalar(u8, keyword, 0)) |_| return error.InvalidPaxAttribute;796 return (std.mem.indexOfScalar(u8, str, 0)) != null;
800 }797 }
801798
802 // Checks that each record ends with new line.799 // Checks that each record ends with new line.
803 fn checkRecordEnd(reader: ReaderType) !void {800 inline fn validateAttributeEnding(reader: ReaderType) !void {
804 if (try reader.readByte() != '\n') return error.InvalidPaxAttribute;801 if (try reader.readByte() != '\n') return error.PaxInvalidAttributeEnd;
805 }802 }
806 };803 };
807}804}
808805
809test "tar PaxReader" {806test "tar PaxReader" {
810 const Attr = struct {807 const Attr = struct {
811 kind: PaxAttrKind,808 kind: PaxAttributeKind,
812 value: []const u8 = undefined,809 value: []const u8 = undefined,
813 err: ?anyerror = null,810 err: ?anyerror = null,
814 };811 };
...@@ -853,8 +850,21 @@ test "tar PaxReader" {...@@ -853,8 +850,21 @@ test "tar PaxReader" {
853 .attrs = &[_]Attr{850 .attrs = &[_]Attr{
854 .{ .kind = .path, .value = "name" },851 .{ .kind = .path, .value = "name" },
855 },852 },
856 .err = error.InvalidPaxAttribute,853 .err = error.UnexpectedEndOfStream,
854 },
855 .{ // too long size of the second key-value pair
856 .data =
857 \\13 path=name
858 \\6 k=1
859 \\19 linkpath=value
860 \\
861 ,
862 .attrs = &[_]Attr{
863 .{ .kind = .path, .value = "name" },
864 },
865 .err = error.UnexpectedEndOfStream,
857 },866 },
867
858 .{ // too long size of the second key-value pair868 .{ // too long size of the second key-value pair
859 .data =869 .data =
860 \\13 path=name870 \\13 path=name
...@@ -864,7 +874,7 @@ test "tar PaxReader" {...@@ -864,7 +874,7 @@ test "tar PaxReader" {
864 ,874 ,
865 .attrs = &[_]Attr{875 .attrs = &[_]Attr{
866 .{ .kind = .path, .value = "name" },876 .{ .kind = .path, .value = "name" },
867 .{ .kind = .linkpath, .err = error.InvalidPaxAttribute },877 .{ .kind = .linkpath, .err = error.PaxInvalidAttributeEnd },
868 },878 },
869 },879 },
870 .{ // null in keyword is not valid880 .{ // null in keyword is not valid
...@@ -872,12 +882,12 @@ test "tar PaxReader" {...@@ -872,12 +882,12 @@ test "tar PaxReader" {
872 .attrs = &[_]Attr{882 .attrs = &[_]Attr{
873 .{ .kind = .path, .value = "name" },883 .{ .kind = .path, .value = "name" },
874 },884 },
875 .err = error.InvalidPaxAttribute,885 .err = error.PaxNullInKeyword,
876 },886 },
877 .{ // null in value is not valid887 .{ // null in value is not valid
878 .data = "23 path=name\x00with null\n",888 .data = "23 path=name\x00with null\n",
879 .attrs = &[_]Attr{889 .attrs = &[_]Attr{
880 .{ .kind = .path, .err = error.InvalidPaxAttribute },890 .{ .kind = .path, .err = error.PaxNullInValue },
881 },891 },
882 },892 },
883 .{ // 1000 characters path893 .{ // 1000 characters path
...@@ -1019,6 +1029,16 @@ fn TarReader(comptime ReaderType: type) type {...@@ -1019,6 +1029,16 @@ fn TarReader(comptime ReaderType: type) type {
1019 return nullStr(buf);1029 return nullStr(buf);
1020 }1030 }
10211031
1032 fn reset(self: *Self) void {
1033 self.file = File{
1034 .name = self.file_name_buffer[0..0],
1035 .link_name = self.link_name_buffer[0..0],
1036 .size = 0,
1037 .file_type = 0xff,
1038 .mode = 0,
1039 };
1040 }
1041
1022 // Externally, `next` iterates through the tar archive as if it is a1042 // Externally, `next` iterates through the tar archive as if it is a
1023 // series of files. Internally, the tar format often uses fake "files"1043 // series of files. Internally, the tar format often uses fake "files"
1024 // to add meta data that describes the next file. These meta data1044 // to add meta data that describes the next file. These meta data