@@ -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 : * [1014 ]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 : [1014 ]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,49 @@ 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
+ var data_off : usize = 0 ;
195
+ while (true ) {
196
+ const temp = try buffer .readChunk (reader , @intCast (rounded_file_size + 512 - data_off ));
197
+ if (temp .len == 0 ) return error .UnexpectedEndOfStream ;
198
+ const slice = temp [0.. @as (usize , @intCast (@min (file_size - data_off , temp .len )))];
199
+
200
+ const attribute = try parsePaxAttribute (slice , @intCast (file_size - data_off ));
201
+ if (std .mem .eql (u8 , attribute .key , "path" )) {
202
+ const path_value = attribute .value orelse return error .NameTooLong ;
203
+ @memcpy (file_name_buffer [0.. path_value .len ], path_value );
204
+ file_name_override_len = path_value .len ;
205
+ try buffer .skip (reader , @intCast (rounded_file_size - data_off ));
206
+ continue :header ;
207
+ }
208
+
209
+ try buffer .skip (reader , attribute .size );
210
+ data_off += attribute .size ;
211
+ if (data_off >= file_size ) {
212
+ try buffer .skip (reader , pad_len );
213
+ continue :header ;
214
+ }
215
+ }
216
+ },
217
+ .global_extended_header = > {
218
+ buffer .skip (reader , @intCast (rounded_file_size )) catch return error .TarHeadersTooBig ;
171
219
},
172
220
.hard_link = > return error .TarUnsupportedFileType ,
173
221
.symbolic_link = > return error .TarUnsupportedFileType ,
@@ -196,5 +244,40 @@ test stripComponents {
196
244
try expectEqualStrings ("c" , try stripComponents ("a/b/c" , 2 ));
197
245
}
198
246
247
+ const PaxAttribute = struct {
248
+ size : usize ,
249
+ key : []const u8 ,
250
+ value : ? []const u8 ,
251
+ };
252
+
253
+ fn parsePaxAttribute (data : []const u8 , max_size : usize ) ! PaxAttribute {
254
+ const pos_space = std .mem .indexOfScalar (u8 , data , ' ' ) orelse return error .InvalidPaxAttribute ;
255
+ const pos_equals = std .mem .indexOfScalarPos (u8 , data , pos_space , '=' ) orelse return error .InvalidPaxAttribute ;
256
+ const kv_size = try std .fmt .parseInt (u64 , data [0.. pos_space ], 10 );
257
+ if (kv_size > max_size ) {
258
+ return error .InvalidPaxAttribute ;
259
+ }
260
+ return .{
261
+ .size = kv_size ,
262
+ .key = data [pos_space + 1 .. pos_equals ],
263
+ .value = if (kv_size - 1 > data .len ) null else data [pos_equals + 1 .. kv_size - 1 ],
264
+ };
265
+ }
266
+
267
+ test parsePaxAttribute {
268
+ const expectEqual = std .testing .expectEqual ;
269
+ const expectEqualStrings = std .testing .expectEqualStrings ;
270
+ const expectError = std .testing .expectError ;
271
+ const file_name = "0123456789" ** 100 ;
272
+ const header = "1011 path=" ++ file_name ++ "\n " ;
273
+ const attribute = try parsePaxAttribute (header , 1011 );
274
+ try expectEqual (@as (u64 , 1011 ), attribute .size );
275
+ try expectEqualStrings ("path" , attribute .key );
276
+ try expectEqualStrings (file_name , attribute .value .? );
277
+ try expectEqual (attribute , try parsePaxAttribute (header , 1012 ));
278
+ try expectError (error .InvalidPaxAttribute , parsePaxAttribute (header , 1010 ));
279
+ try expectError (error .InvalidPaxAttribute , parsePaxAttribute ("" , 0 ));
280
+ }
281
+
199
282
const std = @import ("std.zig" );
200
283
const assert = std .debug .assert ;
0 commit comments