Skip to content

Commit 21cb2ca

Browse files
committed
tar: Disallow empty filenames
1 parent 65e3de7 commit 21cb2ca

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-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 {
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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,54 @@ 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 testSingleEmptyFile() async throws {
79+
let hdr = try tarHeader(filesize: 0, filename: "filename")
80+
#expect(hdr.count == 512)
81+
#expect(
82+
hdr == [
83+
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,
84+
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,
85+
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,
86+
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,
87+
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,
88+
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,
89+
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,
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+
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,
92+
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,
93+
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,
94+
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,
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+
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,
98+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
99+
]
100+
)
101+
}
102+
103+
@Test func testSingle1kBFile() async throws {
104+
let hdr = try tarHeader(filesize: 1024, filename: "filename")
105+
#expect(hdr.count == 512)
106+
#expect(
107+
hdr == [
108+
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,
109+
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,
110+
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,
111+
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,
112+
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,
113+
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,
114+
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,
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+
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,
117+
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,
118+
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,
119+
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,
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+
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,
123+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124+
]
125+
)
126+
}
77127
}

0 commit comments

Comments
 (0)