Skip to content

Commit 90e62ec

Browse files
authored
tar: Disallow empty filenames (#66)
Motivation ---------- A tar archive member name cannot be empty because a Unix filename cannot be the empty string: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170 There would be no way to restore such a file to the filesystem. Modifications ------------- Throw an error if the archive member name is the empty string. Result ------ Invalid archives containing empty filenames cannot be created. Test Plan --------- All existing tests pass. A new test checks that an error is thrown if the member name is empty.
1 parent 65e3de7 commit 90e62ec

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed

Sources/Tar/tar.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import struct Foundation.Data
2222
// relatively straightforward; reading an arbitrary tar file is more
2323
// complicated because the reader must be prepared to handle all variants.
2424

25+
enum TarError: Error, Equatable {
26+
case invalidName(String)
27+
}
28+
2529
enum Termination {
2630
case null
2731
case nullAndSpace
@@ -156,14 +160,21 @@ let XGLTYPE = "g" // Global extended header
156160
/// - filesize: The size of the file
157161
/// - filename: The file's name in the archive
158162
/// - Returns: A tar header representing the file
159-
public func tarHeader(filesize: Int, filename: String = "app") -> [UInt8] {
163+
/// - Throws: If the filename is invalid
164+
public func tarHeader(filesize: Int, filename: String = "app") throws -> [UInt8] {
160165
// A file entry consists of a file header followed by the
161166
// contents of the file. The header includes information such as
162167
// the file name, size and permissions. Different versions of
163168
// tar added extra header fields.
164169
//
165170
// The file data is padded with nulls to a multiple of 512 bytes.
166171

172+
// Archive member name cannot be empty because a Unix filename cannot be the empty string
173+
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170
174+
guard filename.count > 0 else {
175+
throw TarError.invalidName(filename)
176+
}
177+
167178
var hdr = [UInt8](repeating: 0, count: 512)
168179

169180
// Construct a POSIX ustar header for the file
@@ -195,8 +206,9 @@ public func tarHeader(filesize: Int, filename: String = "app") -> [UInt8] {
195206
/// - bytes: The file's body data
196207
/// - filename: The file's name in the archive
197208
/// - Returns: A tar archive containing the file
198-
public func tar(_ bytes: [UInt8], filename: String = "app") -> [UInt8] {
199-
var hdr = tarHeader(filesize: bytes.count, filename: filename)
209+
/// - Throws: If the filename is invalid
210+
public func tar(_ bytes: [UInt8], filename: String = "app") throws -> [UInt8] {
211+
var hdr = try tarHeader(filesize: bytes.count, filename: filename)
200212

201213
// Append the file data to the header
202214
hdr.append(contentsOf: bytes)
@@ -216,4 +228,7 @@ public func tar(_ bytes: [UInt8], filename: String = "app") -> [UInt8] {
216228
/// - data: The file's body data
217229
/// - filename: The file's name in the archive
218230
/// - Returns: A tar archive containing the file
219-
public func tar(_ data: Data, filename: String) -> [UInt8] { tar([UInt8](data), filename: filename) }
231+
/// - Throws: If the filename is invalid
232+
public func tar(_ data: Data, filename: String) throws -> [UInt8] {
233+
try tar([UInt8](data), filename: filename)
234+
}

Sources/containertool/containertool.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
149149
// MARK: Build the application layer
150150

151151
let payload_name = executableURL.lastPathComponent
152-
let tardiff = tar(payload, filename: payload_name)
152+
let tardiff = try tar(payload, filename: payload_name)
153153
log("Built application layer")
154154

155155
// MARK: Upload the application layer

Tests/TarTests/TarUnitTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,60 @@ let trailerLen = 2 * blocksize
7474
hdr == [97, 98, 99, 100, 101, 102, 0, 103, 104, 105, 32, 106, 107, 108, 0, 32, 109, 110, 111, 32, 0]
7575
)
7676
}
77+
78+
@Test func testEmptyName() async throws {
79+
#expect(throws: TarError.invalidName("")) {
80+
let _ = try tarHeader(filesize: 0, filename: "")
81+
}
82+
}
83+
84+
@Test func testSingleEmptyFile() async throws {
85+
let hdr = try tarHeader(filesize: 0, filename: "filename")
86+
#expect(hdr.count == 512)
87+
#expect(
88+
hdr == [
89+
102, 105, 108, 101, 110, 97, 109, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
90+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
91+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
92+
48, 48, 48, 53, 53, 53, 32, 0, 48, 48, 48, 48, 48, 48, 32, 0, 48, 48, 48, 48, 48, 48, 32, 0, 48, 48, 48,
93+
48, 48, 48, 48, 48, 48, 48, 48, 32, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 32, 48, 49, 48, 54, 53,
94+
55, 0, 32, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
95+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
96+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
97+
117, 115, 116, 97, 114, 0, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
98+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
99+
0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 32, 0, 48, 48, 48, 48, 48, 48, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
101+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
102+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
103+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
104+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
105+
]
106+
)
107+
}
108+
109+
@Test func testSingle1kBFile() async throws {
110+
let hdr = try tarHeader(filesize: 1024, filename: "filename")
111+
#expect(hdr.count == 512)
112+
#expect(
113+
hdr == [
114+
102, 105, 108, 101, 110, 97, 109, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
115+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
116+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
117+
48, 48, 48, 53, 53, 53, 32, 0, 48, 48, 48, 48, 48, 48, 32, 0, 48, 48, 48, 48, 48, 48, 32, 0, 48, 48, 48,
118+
48, 48, 48, 48, 50, 48, 48, 48, 32, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 32, 48, 49, 48, 54, 54,
119+
49, 0, 32, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
120+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
121+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122+
117, 115, 116, 97, 114, 0, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
123+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124+
0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 32, 0, 48, 48, 48, 48, 48, 48, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
125+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
126+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
127+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
128+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
130+
]
131+
)
132+
}
77133
}

0 commit comments

Comments
 (0)