@@ -48,7 +48,7 @@ pub const Header = struct {
48
48
/// Return value may point into Header buffer, or might point into the
49
49
/// argument buffer.
50
50
/// 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 {
52
52
const n = name (header );
53
53
if (! is_ustar (header ))
54
54
return n ;
@@ -83,6 +83,44 @@ pub const Header = struct {
83
83
}
84
84
};
85
85
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
+
86
124
pub fn pipeToFileSystem (dir : std.fs.Dir , reader : anytype , options : Options ) ! void {
87
125
switch (options .mode_mode ) {
88
126
.ignore = > {},
@@ -95,30 +133,27 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
95
133
@panic ("TODO: unimplemented: tar ModeMode.executable_bit_only" );
96
134
},
97
135
}
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 = .{};
102
139
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 ) {
112
142
0 = > return ,
113
143
1... 511 = > return error .UnexpectedEndOfStream ,
114
144
else = > {},
115
145
}
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] };
118
149
const file_size = try header .fileSize ();
119
150
const rounded_file_size = std .mem .alignForward (u64 , file_size , 512 );
120
151
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 ;
122
157
switch (header .fileType ()) {
123
158
.directory = > {
124
159
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
138
173
139
174
var file_off : usize = 0 ;
140
175
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 )))];
157
179
try file .writeAll (slice );
180
+
158
181
file_off += slice .len ;
159
- start += slice .len ;
182
+ buffer . advance ( slice .len ) ;
160
183
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 );
164
185
continue :header ;
165
186
}
166
187
}
167
188
},
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 ;
171
229
},
172
230
.hard_link = > return error .TarUnsupportedFileType ,
173
231
.symbolic_link = > return error .TarUnsupportedFileType ,
@@ -196,5 +254,44 @@ test stripComponents {
196
254
try expectEqualStrings ("c" , try stripComponents ("a/b/c" , 2 ));
197
255
}
198
256
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
+
199
296
const std = @import ("std.zig" );
200
297
const assert = std .debug .assert ;
0 commit comments