@@ -22,6 +22,18 @@ import struct Foundation.Data
22
22
// relatively straightforward; reading an arbitrary tar file is more
23
23
// complicated because the reader must be prepared to handle all variants.
24
24
25
+ // Tar archives consist of 512-byte blocks, either containing member headers
26
+ // or file data. Blocks shorter than 512 bytes are padded with zeros.
27
+ let blockSize = 512
28
+
29
+ /// Returns the number of padding bytes to be appended to a file.
30
+ /// Each file in a tar archive must be padded to a multiple of the 512 byte block size.
31
+ /// - Parameter len: The length of the archive member.
32
+ /// - Returns: The number of zero bytes to append as padding.
33
+ func padding( _ len: Int ) -> Int {
34
+ ( blockSize - len % blockSize) % blockSize
35
+ }
36
+
25
37
enum TarError : Error , Equatable {
26
38
case invalidName( String )
27
39
}
@@ -134,7 +146,7 @@ func checksum(header: [UInt8]) -> Int {
134
146
// The checksum calculation can't overflow (maximum possible value 776) so we can use
135
147
// unchecked arithmetic.
136
148
137
- precondition ( header. count == 512 )
149
+ precondition ( header. count == blockSize )
138
150
return header. reduce ( 0 ) { $0 &+ Int ( $1) }
139
151
}
140
152
@@ -182,7 +194,7 @@ public enum MemberType: String {
182
194
183
195
// maybe limited string, octal6 and octal11 should be separate types
184
196
185
- /// Represents a single tar archive member
197
+ /// Represents a single tar archive member header
186
198
public struct TarHeader {
187
199
/// Member file name when unpacked
188
200
var name : String
@@ -232,7 +244,7 @@ public struct TarHeader {
232
244
/// Filename prefix - prepended to name
233
245
var prefix : String = " "
234
246
235
- init (
247
+ public init (
236
248
name: String ,
237
249
mode: Int = 0o555 ,
238
250
uid: Int = 0 ,
@@ -273,10 +285,7 @@ public struct TarHeader {
273
285
}
274
286
275
287
extension TarHeader {
276
- /// Creates a tar header for a single file
277
- /// - Parameters:
278
- /// - hdr: The header structure of the file
279
- /// - Returns: A tar header representing the file
288
+ /// The serialized byte representation of the header.
280
289
var bytes : [ UInt8 ] {
281
290
// A file entry consists of a file header followed by the
282
291
// contents of the file. The header includes information such as
@@ -285,7 +294,7 @@ extension TarHeader {
285
294
//
286
295
// The file data is padded with nulls to a multiple of 512 bytes.
287
296
288
- var bytes = [ UInt8] ( repeating: 0 , count: 512 )
297
+ var bytes = [ UInt8] ( repeating: 0 , count: blockSize )
289
298
290
299
// Construct a POSIX ustar header for the file
291
300
bytes. writeString ( self . name, inField: Field . name, withTermination: . null)
@@ -312,16 +321,6 @@ extension TarHeader {
312
321
}
313
322
}
314
323
315
- let blockSize = 512
316
-
317
- /// Returns the number of padding bytes to be appended to a file.
318
- /// Each file in a tar archive must be padded to a multiple of the 512 byte block size.
319
- /// - Parameter len: The length of the archive member.
320
- /// - Returns: The number of zero bytes to append as padding.
321
- func padding( _ len: Int ) -> Int {
322
- ( blockSize - len % blockSize) % blockSize
323
- }
324
-
325
324
/// Creates a tar archive containing a single file
326
325
/// - Parameters:
327
326
/// - bytes: The file's body data
@@ -339,7 +338,7 @@ public func tar(_ bytes: [UInt8], filename: String = "app") throws -> [UInt8] {
339
338
archive. append ( contentsOf: padding)
340
339
341
340
// Append the end of file marker
342
- let marker = [ UInt8] ( repeating: 0 , count: 2 * 512 )
341
+ let marker = [ UInt8] ( repeating: 0 , count: 2 * blockSize )
343
342
archive. append ( contentsOf: marker)
344
343
return archive
345
344
}
@@ -353,3 +352,126 @@ public func tar(_ bytes: [UInt8], filename: String = "app") throws -> [UInt8] {
353
352
public func tar( _ data: Data , filename: String ) throws -> [ UInt8 ] {
354
353
try tar ( [ UInt8] ( data) , filename: filename)
355
354
}
355
+
356
+ /// Represents a tar archive
357
+ public struct Archive {
358
+ /// The files, directories and other members of the archive
359
+ var members : [ ArchiveMember ]
360
+
361
+ /// Creates an empty Archive
362
+ public init ( ) {
363
+ members = [ ]
364
+ }
365
+
366
+ /// Appends a member to the archive
367
+ /// Parameters:
368
+ /// - member: The member to append
369
+ public mutating func append( _ member: ArchiveMember ) {
370
+ self . members. append ( member)
371
+ }
372
+
373
+ /// Returns a new archive made by appending a member to the receiver
374
+ /// Parameters:
375
+ /// - member: The member to append
376
+ /// Returns: A new archive made by appending `member` to the receiver.
377
+ public func appending( _ member: ArchiveMember ) -> Self {
378
+ var ret = self
379
+ ret. members += [ member]
380
+ return ret
381
+ }
382
+
383
+ /// The serialized byte representation of the archive, including padding and end-of-archive marker.
384
+ public var bytes : [ UInt8 ] {
385
+ var ret : [ UInt8 ] = [ ]
386
+ for member in members {
387
+ ret. append ( contentsOf: member. bytes)
388
+ }
389
+
390
+ // Append the end of file marker
391
+ let marker = [ UInt8] ( repeating: 0 , count: 2 * blockSize)
392
+ ret. append ( contentsOf: marker)
393
+
394
+ return ret
395
+ }
396
+ }
397
+
398
+ /// Represents a member of a tar archive
399
+ public struct ArchiveMember {
400
+ /// Member header containing metadata about the member
401
+ var header : TarHeader
402
+
403
+ /// File content
404
+ var contents : [ UInt8 ]
405
+
406
+ /// Creates a new ArchiveMember
407
+ /// Parameters:
408
+ /// - header: Member header containing metadata about the member
409
+ /// - data: File content
410
+ public init (
411
+ header: TarHeader ,
412
+ data: [ UInt8 ] = [ ]
413
+ ) {
414
+ self . header = header
415
+ self . contents = data
416
+ }
417
+
418
+ /// The serialized byte representation of the member, including padding.
419
+ public var bytes : [ UInt8 ] {
420
+ let padding = [ UInt8] ( repeating: 0 , count: padding ( contents. count) )
421
+ return header. bytes + self . contents + padding
422
+ }
423
+ }
424
+
425
+ extension Archive {
426
+ /// Adds a new file member at the end of the archive
427
+ /// parameters:
428
+ /// - name: File name
429
+ /// - prefix: Path prefix
430
+ /// - data: File contents
431
+ public mutating func appendFile( name: String , prefix: String = " " , data: [ UInt8 ] ) throws {
432
+ try append ( . init( header: . init( name: name, size: data. count, prefix: prefix) , data: data) )
433
+ }
434
+
435
+ /// Adds a new file member at the end of the archive
436
+ /// parameters:
437
+ /// - name: File name
438
+ /// - prefix: Path prefix
439
+ /// - data: File contents
440
+ public func appendingFile( name: String , prefix: String = " " , data: [ UInt8 ] ) throws -> Self {
441
+ try appending ( . init( header: . init( name: name, size: data. count, prefix: prefix) , data: data) )
442
+ }
443
+
444
+ /// Adds a new file member at the end of the archive
445
+ /// parameters:
446
+ /// - name: File name
447
+ /// - prefix: Path prefix
448
+ /// - data: File contents
449
+ public mutating func appendFile( name: String , prefix: String = " " , data: Data ) throws {
450
+ try append ( . init( header: . init( name: name, size: data. count, prefix: prefix) , data: [ UInt8] ( data) ) )
451
+ }
452
+
453
+ /// Adds a new file member at the end of the archive
454
+ /// parameters:
455
+ /// - name: File name
456
+ /// - prefix: Path prefix
457
+ /// - data: File contents
458
+ public func appendingFile( name: String , prefix: String = " " , data: Data ) throws -> Self {
459
+ try appending ( . init( header: . init( name: name, size: data. count, prefix: prefix) , data: [ UInt8] ( data) ) )
460
+ }
461
+
462
+ /// Adds a new directory member at the end of the archive
463
+ /// parameters:
464
+ /// - name: Directory name
465
+ /// - prefix: Path prefix
466
+ public mutating func appendDirectory( name: String , prefix: String = " " ) throws {
467
+ try append ( . init( header: . init( name: name, typeflag: . DIRTYPE, prefix: prefix) ) )
468
+ }
469
+
470
+ /// Adds a new directory member at the end of the archive
471
+ /// parameters:
472
+ /// - name: Directory name
473
+ /// - prefix: Path prefix
474
+ public func appendingDirectory( name: String , prefix: String = " " ) throws -> Self {
475
+ try self . appending ( . init( header: . init( name: name, typeflag: . DIRTYPE, prefix: prefix) ) )
476
+ }
477
+ }
0 commit comments