Skip to content

Commit 39a98dc

Browse files
189900Vexu
authored andcommitted
std.tar: add support for file path in pax attributes
Handles .extended_header type to parse PAX attributes and check if they override the path of the next file. Increases file path limit to std.fs.MAX_PATH_BYTES. Fixes #15342
1 parent fa46750 commit 39a98dc

File tree

1 file changed

+137
-40
lines changed

1 file changed

+137
-40
lines changed

lib/std/tar.zig

Lines changed: 137 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub const Header = struct {
4848
/// Return value may point into Header buffer, or might point into the
4949
/// argument buffer.
5050
/// TODO: check against "../" and other nefarious things
51-
pub fn fullFileName(header: Header, buffer: *[255]u8) ![]const u8 {
51+
pub fn fullFileName(header: Header, buffer: *[std.fs.MAX_PATH_BYTES]u8) ![]const u8 {
5252
const n = name(header);
5353
if (!is_ustar(header))
5454
return n;
@@ -83,6 +83,44 @@ pub const Header = struct {
8383
}
8484
};
8585

86+
const Buffer = struct {
87+
buffer: [512 * 8]u8 = undefined,
88+
start: usize = 0,
89+
end: usize = 0,
90+
91+
pub fn readChunk(b: *Buffer, reader: anytype, count: usize) ![]const u8 {
92+
b.ensureCapacity(1024);
93+
94+
const ask = @min(b.buffer.len - b.end, count -| (b.end - b.start));
95+
b.end += try reader.readAtLeast(b.buffer[b.end..], ask);
96+
97+
return b.buffer[b.start..b.end];
98+
}
99+
100+
pub fn advance(b: *Buffer, count: usize) void {
101+
b.start += count;
102+
assert(b.start <= b.end);
103+
}
104+
105+
pub fn skip(b: *Buffer, reader: anytype, count: usize) !void {
106+
if (b.start + count > b.end) {
107+
try reader.skipBytes(b.start + count - b.end, .{});
108+
b.start = b.end;
109+
} else {
110+
b.advance(count);
111+
}
112+
}
113+
114+
inline fn ensureCapacity(b: *Buffer, count: usize) void {
115+
if (b.buffer.len - b.start < count) {
116+
const dest_end = b.end - b.start;
117+
@memcpy(b.buffer[0..dest_end], b.buffer[b.start..b.end]);
118+
b.end = dest_end;
119+
b.start = 0;
120+
}
121+
}
122+
};
123+
86124
pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {
87125
switch (options.mode_mode) {
88126
.ignore => {},
@@ -95,30 +133,27 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
95133
@panic("TODO: unimplemented: tar ModeMode.executable_bit_only");
96134
},
97135
}
98-
var file_name_buffer: [255]u8 = undefined;
99-
var buffer: [512 * 8]u8 = undefined;
100-
var start: usize = 0;
101-
var end: usize = 0;
136+
var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
137+
var file_name_override_len: usize = 0;
138+
var buffer: Buffer = .{};
102139
header: while (true) {
103-
if (buffer.len - start < 1024) {
104-
const dest_end = end - start;
105-
@memcpy(buffer[0..dest_end], buffer[start..end]);
106-
end = dest_end;
107-
start = 0;
108-
}
109-
const ask_header = @min(buffer.len - end, 1024 -| (end - start));
110-
end += try reader.readAtLeast(buffer[end..], ask_header);
111-
switch (end - start) {
140+
const chunk = try buffer.readChunk(reader, 1024);
141+
switch (chunk.len) {
112142
0 => return,
113143
1...511 => return error.UnexpectedEndOfStream,
114144
else => {},
115145
}
116-
const header: Header = .{ .bytes = buffer[start..][0..512] };
117-
start += 512;
146+
buffer.advance(512);
147+
148+
const header: Header = .{ .bytes = chunk[0..512] };
118149
const file_size = try header.fileSize();
119150
const rounded_file_size = std.mem.alignForward(u64, file_size, 512);
120151
const pad_len = @as(usize, @intCast(rounded_file_size - file_size));
121-
const unstripped_file_name = try header.fullFileName(&file_name_buffer);
152+
const unstripped_file_name = if (file_name_override_len > 0)
153+
file_name_buffer[0..file_name_override_len]
154+
else
155+
try header.fullFileName(&file_name_buffer);
156+
file_name_override_len = 0;
122157
switch (header.fileType()) {
123158
.directory => {
124159
const file_name = try stripComponents(unstripped_file_name, options.strip_components);
@@ -138,36 +173,59 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
138173

139174
var file_off: usize = 0;
140175
while (true) {
141-
if (buffer.len - start < 1024) {
142-
const dest_end = end - start;
143-
@memcpy(buffer[0..dest_end], buffer[start..end]);
144-
end = dest_end;
145-
start = 0;
146-
}
147-
// Ask for the rounded up file size + 512 for the next header.
148-
// TODO: https://github.com/ziglang/zig/issues/14039
149-
const ask = @as(usize, @intCast(@min(
150-
buffer.len - end,
151-
rounded_file_size + 512 - file_off -| (end - start),
152-
)));
153-
end += try reader.readAtLeast(buffer[end..], ask);
154-
if (end - start < ask) return error.UnexpectedEndOfStream;
155-
// TODO: https://github.com/ziglang/zig/issues/14039
156-
const slice = buffer[start..@as(usize, @intCast(@min(file_size - file_off + start, end)))];
176+
const temp = try buffer.readChunk(reader, @intCast(rounded_file_size + 512 - file_off));
177+
if (temp.len == 0) return error.UnexpectedEndOfStream;
178+
const slice = temp[0..@as(usize, @intCast(@min(file_size - file_off, temp.len)))];
157179
try file.writeAll(slice);
180+
158181
file_off += slice.len;
159-
start += slice.len;
182+
buffer.advance(slice.len);
160183
if (file_off >= file_size) {
161-
start += pad_len;
162-
// Guaranteed since we use a buffer divisible by 512.
163-
assert(start <= end);
184+
buffer.advance(pad_len);
164185
continue :header;
165186
}
166187
}
167188
},
168-
.global_extended_header, .extended_header => {
169-
if (start + rounded_file_size > end) return error.TarHeadersTooBig;
170-
start = @as(usize, @intCast(start + rounded_file_size));
189+
.extended_header => {
190+
if (file_size == 0) {
191+
buffer.advance(@intCast(rounded_file_size));
192+
continue;
193+
}
194+
195+
const chunk_size: usize = @intCast(rounded_file_size + 512);
196+
var data_off: usize = 0;
197+
file_name_override_len = while (data_off < file_size) {
198+
const slice = try buffer.readChunk(reader, chunk_size - data_off);
199+
if (slice.len == 0) return error.UnexpectedEndOfStream;
200+
const remaining_size: usize = @intCast(file_size - data_off);
201+
const attr_info = try parsePaxAttribute(slice[0..@min(remaining_size, slice.len)], remaining_size);
202+
203+
if (std.mem.eql(u8, attr_info.key, "path")) {
204+
if (attr_info.value_len > file_name_buffer.len) return error.NameTooLong;
205+
buffer.advance(attr_info.value_off);
206+
data_off += attr_info.value_off;
207+
break attr_info.value_len;
208+
}
209+
210+
try buffer.skip(reader, attr_info.size);
211+
data_off += attr_info.size;
212+
} else 0;
213+
214+
var i: usize = 0;
215+
while (i < file_name_override_len) {
216+
const slice = try buffer.readChunk(reader, chunk_size - data_off - i);
217+
if (slice.len == 0) return error.UnexpectedEndOfStream;
218+
const copy_size: usize = @intCast(@min(file_name_override_len - i, slice.len));
219+
@memcpy(file_name_buffer[i .. i + copy_size], slice[0..copy_size]);
220+
buffer.advance(copy_size);
221+
i += copy_size;
222+
}
223+
224+
try buffer.skip(reader, @intCast(rounded_file_size - data_off - file_name_override_len));
225+
continue :header;
226+
},
227+
.global_extended_header => {
228+
buffer.skip(reader, @intCast(rounded_file_size)) catch return error.TarHeadersTooBig;
171229
},
172230
.hard_link => return error.TarUnsupportedFileType,
173231
.symbolic_link => return error.TarUnsupportedFileType,
@@ -196,5 +254,44 @@ test stripComponents {
196254
try expectEqualStrings("c", try stripComponents("a/b/c", 2));
197255
}
198256

257+
const PaxAttributeInfo = struct {
258+
size: usize,
259+
key: []const u8,
260+
value_off: usize,
261+
value_len: usize,
262+
};
263+
264+
fn parsePaxAttribute(data: []const u8, max_size: usize) !PaxAttributeInfo {
265+
const pos_space = std.mem.indexOfScalar(u8, data, ' ') orelse return error.InvalidPaxAttribute;
266+
const pos_equals = std.mem.indexOfScalarPos(u8, data, pos_space, '=') orelse return error.InvalidPaxAttribute;
267+
const kv_size = try std.fmt.parseInt(usize, data[0..pos_space], 10);
268+
if (kv_size > max_size) {
269+
return error.InvalidPaxAttribute;
270+
}
271+
return .{
272+
.size = kv_size,
273+
.key = data[pos_space + 1 .. pos_equals],
274+
.value_off = pos_equals + 1,
275+
.value_len = kv_size - pos_equals - 2,
276+
};
277+
}
278+
279+
test parsePaxAttribute {
280+
const expectEqual = std.testing.expectEqual;
281+
const expectEqualStrings = std.testing.expectEqualStrings;
282+
const expectError = std.testing.expectError;
283+
const prefix = "1011 path=";
284+
const file_name = "0123456789" ** 100;
285+
const header = prefix ++ file_name ++ "\n";
286+
const attr_info = try parsePaxAttribute(header, 1011);
287+
try expectEqual(@as(usize, 1011), attr_info.size);
288+
try expectEqualStrings("path", attr_info.key);
289+
try expectEqual(prefix.len, attr_info.value_off);
290+
try expectEqual(file_name.len, attr_info.value_len);
291+
try expectEqual(attr_info, try parsePaxAttribute(header, 1012));
292+
try expectError(error.InvalidPaxAttribute, parsePaxAttribute(header, 1010));
293+
try expectError(error.InvalidPaxAttribute, parsePaxAttribute("", 0));
294+
}
295+
199296
const std = @import("std.zig");
200297
const assert = std.debug.assert;

0 commit comments

Comments
 (0)