| ... | ... | @@ -331,11 +331,6 @@ test "tar stripComponents" { |
| 331 | 331 | try expectEqualStrings("c", try stripComponents("a/b/c", 2)); |
| 332 | 332 | } |
| 333 | 333 | |
| 334 | | fn noNull(str: []const u8) ![]const u8 { |
| 335 | | if (std.mem.indexOfScalar(u8, str, 0)) |_| return error.InvalidPaxAttribute; |
| 336 | | return str; |
| 337 | | } |
| 338 | | |
| 339 | 334 | test "tar run Go test cases" { |
| 340 | 335 | const Case = struct { |
| 341 | 336 | const File = struct { |
| ... | ... | @@ -443,7 +438,7 @@ test "tar run Go test cases" { |
| 443 | 438 | .{ |
| 444 | 439 | // pax attribute don't end with \n |
| 445 | 440 | .path = "pax-bad-hdr-file.tar", |
| 446 | | .err = error.InvalidPaxAttribute, |
| 441 | .err = error.PaxInvalidAttributeEnd, |
| 447 | 442 | }, |
| 448 | 443 | .{ |
| 449 | 444 | // size is in pax attribute |
| ... | ... | @@ -579,11 +574,11 @@ test "tar run Go test cases" { |
| 579 | 574 | .{ |
| 580 | 575 | // null in pax key |
| 581 | 576 | .path = "pax-nul-xattrs.tar", |
| 582 | | .err = error.InvalidPaxAttribute, |
| 577 | .err = error.PaxNullInKeyword, |
| 583 | 578 | }, |
| 584 | 579 | .{ |
| 585 | 580 | .path = "pax-nul-path.tar", |
| 586 | | .err = error.InvalidPaxAttribute, |
| 581 | .err = error.PaxNullInValue, |
| 587 | 582 | }, |
| 588 | 583 | .{ |
| 589 | 584 | .path = "neg-size.tar", |
| ... | ... | @@ -715,7 +710,7 @@ fn paxReader(reader: anytype, size: usize) PaxReader(@TypeOf(reader)) { |
| 715 | 710 | }; |
| 716 | 711 | } |
| 717 | 712 | |
| 718 | | const PaxAttrKind = enum { |
| 713 | const PaxAttributeKind = enum { |
| 719 | 714 | path, |
| 720 | 715 | linkpath, |
| 721 | 716 | size, |
| ... | ... | @@ -723,54 +718,50 @@ const PaxAttrKind = enum { |
| 723 | 718 | |
| 724 | 719 | fn PaxReader(comptime ReaderType: type) type { |
| 725 | 720 | return struct { |
| 726 | | size: usize, |
| 721 | size: usize, // cumulative size of all pax attributes |
| 727 | 722 | reader: ReaderType, |
| 723 | // scratch buffer used for reading attribute length and keyword |
| 724 | scratch: [128]u8 = undefined, |
| 728 | 725 | |
| 729 | 726 | const Self = @This(); |
| 730 | 727 | |
| 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 |
| 735 | 732 | |
| 736 | 733 | // 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 { |
| 739 | 736 | assert(self.len <= dst.len); |
| 740 | 737 | const buf = dst[0..self.len]; |
| 741 | 738 | const n = try self.reader.readAll(buf); |
| 742 | 739 | 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; |
| 745 | 743 | } |
| 746 | 744 | }; |
| 747 | 745 | |
| 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: |
| 755 | 750 | // "%d %s=%s\n", <length>, <keyword>, <value> |
| 756 | 751 | 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")) |
| 774 | 765 | .path |
| 775 | 766 | else if (eql(keyword, "linkpath")) |
| 776 | 767 | .linkpath |
| ... | ... | @@ -778,10 +769,10 @@ fn PaxReader(comptime ReaderType: type) type { |
| 778 | 769 | .size |
| 779 | 770 | else { |
| 780 | 771 | try self.reader.skipBytes(value_len, .{}); |
| 781 | | try checkRecordEnd(self.reader); |
| 772 | try validateAttributeEnding(self.reader); |
| 782 | 773 | continue; |
| 783 | 774 | }; |
| 784 | | return Attr{ |
| 775 | return Attribute{ |
| 785 | 776 | .kind = kind, |
| 786 | 777 | .len = value_len, |
| 787 | 778 | .reader = self.reader, |
| ... | ... | @@ -791,24 +782,30 @@ fn PaxReader(comptime ReaderType: type) type { |
| 791 | 782 | return null; |
| 792 | 783 | } |
| 793 | 784 | |
| 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 | 791 | inline fn eql(a: []const u8, b: []const u8) bool { |
| 795 | 792 | return std.mem.eql(u8, a, b); |
| 796 | 793 | } |
| 797 | 794 | |
| 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; |
| 800 | 797 | } |
| 801 | 798 | |
| 802 | 799 | // 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; |
| 805 | 802 | } |
| 806 | 803 | }; |
| 807 | 804 | } |
| 808 | 805 | |
| 809 | 806 | test "tar PaxReader" { |
| 810 | 807 | const Attr = struct { |
| 811 | | kind: PaxAttrKind, |
| 808 | kind: PaxAttributeKind, |
| 812 | 809 | value: []const u8 = undefined, |
| 813 | 810 | err: ?anyerror = null, |
| 814 | 811 | }; |
| ... | ... | @@ -853,8 +850,21 @@ test "tar PaxReader" { |
| 853 | 850 | .attrs = &[_]Attr{ |
| 854 | 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 | 868 | .{ // too long size of the second key-value pair |
| 859 | 869 | .data = |
| 860 | 870 | \\13 path=name |
| ... | ... | @@ -864,7 +874,7 @@ test "tar PaxReader" { |
| 864 | 874 | , |
| 865 | 875 | .attrs = &[_]Attr{ |
| 866 | 876 | .{ .kind = .path, .value = "name" }, |
| 867 | | .{ .kind = .linkpath, .err = error.InvalidPaxAttribute }, |
| 877 | .{ .kind = .linkpath, .err = error.PaxInvalidAttributeEnd }, |
| 868 | 878 | }, |
| 869 | 879 | }, |
| 870 | 880 | .{ // null in keyword is not valid |
| ... | ... | @@ -872,12 +882,12 @@ test "tar PaxReader" { |
| 872 | 882 | .attrs = &[_]Attr{ |
| 873 | 883 | .{ .kind = .path, .value = "name" }, |
| 874 | 884 | }, |
| 875 | | .err = error.InvalidPaxAttribute, |
| 885 | .err = error.PaxNullInKeyword, |
| 876 | 886 | }, |
| 877 | 887 | .{ // null in value is not valid |
| 878 | 888 | .data = "23 path=name\x00with null\n", |
| 879 | 889 | .attrs = &[_]Attr{ |
| 880 | | .{ .kind = .path, .err = error.InvalidPaxAttribute }, |
| 890 | .{ .kind = .path, .err = error.PaxNullInValue }, |
| 881 | 891 | }, |
| 882 | 892 | }, |
| 883 | 893 | .{ // 1000 characters path |
| ... | ... | @@ -1019,6 +1029,16 @@ fn TarReader(comptime ReaderType: type) type { |
| 1019 | 1029 | return nullStr(buf); |
| 1020 | 1030 | } |
| 1021 | 1031 | |
| 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 | 1042 | // Externally, `next` iterates through the tar archive as if it is a |
| 1023 | 1043 | // series of files. Internally, the tar format often uses fake "files" |
| 1024 | 1044 | // to add meta data that describes the next file. These meta data |