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" {
331331 try expectEqualStrings("c", try stripComponents("a/b/c", 2));
332332}
333333
334fn noNull(str: []const u8) ![]const u8 {
335 if (std.mem.indexOfScalar(u8, str, 0)) |_| return error.InvalidPaxAttribute;
336 return str;
337}
338
339334test "tar run Go test cases" {
340335 const Case = struct {
341336 const File = struct {
......@@ -443,7 +438,7 @@ test "tar run Go test cases" {
443438 .{
444439 // pax attribute don't end with \n
445440 .path = "pax-bad-hdr-file.tar",
446 .err = error.InvalidPaxAttribute,
441 .err = error.PaxInvalidAttributeEnd,
447442 },
448443 .{
449444 // size is in pax attribute
......@@ -579,11 +574,11 @@ test "tar run Go test cases" {
579574 .{
580575 // null in pax key
581576 .path = "pax-nul-xattrs.tar",
582 .err = error.InvalidPaxAttribute,
577 .err = error.PaxNullInKeyword,
583578 },
584579 .{
585580 .path = "pax-nul-path.tar",
586 .err = error.InvalidPaxAttribute,
581 .err = error.PaxNullInValue,
587582 },
588583 .{
589584 .path = "neg-size.tar",
......@@ -715,7 +710,7 @@ fn paxReader(reader: anytype, size: usize) PaxReader(@TypeOf(reader)) {
715710 };
716711}
717712
718const PaxAttrKind = enum {
713const PaxAttributeKind = enum {
719714 path,
720715 linkpath,
721716 size,
......@@ -723,54 +718,50 @@ const PaxAttrKind = enum {
723718
724719fn PaxReader(comptime ReaderType: type) type {
725720 return struct {
726 size: usize,
721 size: usize, // cumulative size of all pax attributes
727722 reader: ReaderType,
723 // scratch buffer used for reading attribute length and keyword
724 scratch: [128]u8 = undefined,
728725
729726 const Self = @This();
730727
731 const Attr = struct {
732 kind: PaxAttrKind,
733 len: usize,
734 reader: ReaderType,
728 const Attribute = struct {
729 kind: PaxAttributeKind,
730 len: usize, // length of the attribute value
731 reader: ReaderType, // reader positioned at value start
735732
736733 // Copies pax attribute value into destination buffer.
737 // Must be called with destination buffer of size at least value_len.
738 pub fn value(self: Attr, dst: []u8) ![]const u8 {
734 // Must be called with destination buffer of size at least Attribute.len.
735 pub fn value(self: Attribute, dst: []u8) ![]const u8 {
739736 assert(self.len <= dst.len);
740737 const buf = dst[0..self.len];
741738 const n = try self.reader.readAll(buf);
742739 if (n < self.len) return error.UnexpectedEndOfStream;
743 try checkRecordEnd(self.reader);
744 return noNull(buf);
740 try validateAttributeEnding(self.reader);
741 if (hasNull(buf)) return error.PaxNullInValue;
742 return buf;
745743 }
746744 };
747745
748 // Iterates over pax records. Returns known records. Caller has to call
749 // value in Record, to advance reader across value.
750 pub fn next(self: *Self) !?Attr {
751 var buf: [128]u8 = undefined;
752 var fbs = std.io.fixedBufferStream(&buf);
753
754 // An extended header consists of one or more records, each constructed as follows:
746 // Iterates over pax attributes. Returns known only known attributes.
747 // Caller has to call value in Attribute, to advance reader across value.
748 pub fn next(self: *Self) !?Attribute {
749 // Pax extended header consists of one or more attributes, each constructed as follows:
755750 // "%d %s=%s\n", <length>, <keyword>, <value>
756751 while (self.size > 0) {
757 fbs.reset();
758 // read length
759 try self.reader.streamUntilDelimiter(fbs.writer(), ' ', null);
760 const rec_len = try std.fmt.parseInt(usize, fbs.getWritten(), 10); // record len in bytes
761 var pos = try fbs.getPos() + 1; // bytes used for record len + separator
762 fbs.reset();
763 // read keyword
764 try self.reader.streamUntilDelimiter(fbs.writer(), '=', null);
765 const keyword = fbs.getWritten();
766 pos += try fbs.getPos() + 1; // keyword bytes + separator
767 try checkKeyword(keyword);
768 // get value_len
769 if (rec_len < pos + 1) return error.InvalidPaxAttribute;
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"))
752 const length_buf = try self.readUntil(' ');
753 const length = try std.fmt.parseInt(usize, length_buf, 10); // record length in bytes
754
755 const keyword = try self.readUntil('=');
756 if (hasNull(keyword)) return error.PaxNullInKeyword;
757
758 // calculate value_len
759 const value_start = length_buf.len + keyword.len + 2; // 2 separators
760 if (length < value_start + 1 or self.size < length) return error.UnexpectedEndOfStream;
761 const value_len = length - value_start - 1; // \n separator at end
762 self.size -= length;
763
764 const kind: PaxAttributeKind = if (eql(keyword, "path"))
774765 .path
775766 else if (eql(keyword, "linkpath"))
776767 .linkpath
......@@ -778,10 +769,10 @@ fn PaxReader(comptime ReaderType: type) type {
778769 .size
779770 else {
780771 try self.reader.skipBytes(value_len, .{});
781 try checkRecordEnd(self.reader);
772 try validateAttributeEnding(self.reader);
782773 continue;
783774 };
784 return Attr{
775 return Attribute{
785776 .kind = kind,
786777 .len = value_len,
787778 .reader = self.reader,
......@@ -791,24 +782,30 @@ fn PaxReader(comptime ReaderType: type) type {
791782 return null;
792783 }
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
794791 inline fn eql(a: []const u8, b: []const u8) bool {
795792 return std.mem.eql(u8, a, b);
796793 }
797794
798 fn checkKeyword(keyword: []const u8) !void {
799 if (std.mem.indexOfScalar(u8, keyword, 0)) |_| return error.InvalidPaxAttribute;
795 inline fn hasNull(str: []const u8) bool {
796 return (std.mem.indexOfScalar(u8, str, 0)) != null;
800797 }
801798
802799 // Checks that each record ends with new line.
803 fn checkRecordEnd(reader: ReaderType) !void {
804 if (try reader.readByte() != '\n') return error.InvalidPaxAttribute;
800 inline fn validateAttributeEnding(reader: ReaderType) !void {
801 if (try reader.readByte() != '\n') return error.PaxInvalidAttributeEnd;
805802 }
806803 };
807804}
808805
809806test "tar PaxReader" {
810807 const Attr = struct {
811 kind: PaxAttrKind,
808 kind: PaxAttributeKind,
812809 value: []const u8 = undefined,
813810 err: ?anyerror = null,
814811 };
......@@ -853,8 +850,21 @@ test "tar PaxReader" {
853850 .attrs = &[_]Attr{
854851 .{ .kind = .path, .value = "name" },
855852 },
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,
857866 },
867
858868 .{ // too long size of the second key-value pair
859869 .data =
860870 \\13 path=name
......@@ -864,7 +874,7 @@ test "tar PaxReader" {
864874 ,
865875 .attrs = &[_]Attr{
866876 .{ .kind = .path, .value = "name" },
867 .{ .kind = .linkpath, .err = error.InvalidPaxAttribute },
877 .{ .kind = .linkpath, .err = error.PaxInvalidAttributeEnd },
868878 },
869879 },
870880 .{ // null in keyword is not valid
......@@ -872,12 +882,12 @@ test "tar PaxReader" {
872882 .attrs = &[_]Attr{
873883 .{ .kind = .path, .value = "name" },
874884 },
875 .err = error.InvalidPaxAttribute,
885 .err = error.PaxNullInKeyword,
876886 },
877887 .{ // null in value is not valid
878888 .data = "23 path=name\x00with null\n",
879889 .attrs = &[_]Attr{
880 .{ .kind = .path, .err = error.InvalidPaxAttribute },
890 .{ .kind = .path, .err = error.PaxNullInValue },
881891 },
882892 },
883893 .{ // 1000 characters path
......@@ -1019,6 +1029,16 @@ fn TarReader(comptime ReaderType: type) type {
10191029 return nullStr(buf);
10201030 }
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
10221042 // Externally, `next` iterates through the tar archive as if it is a
10231043 // series of files. Internally, the tar format often uses fake "files"
10241044 // to add meta data that describes the next file. These meta data