Skip to content

Commit 5ffe9fd

Browse files
committed
ContainerRegistry: Parse digest algorithm and value into separate fields
1 parent 63e03a5 commit 5ffe9fd

File tree

3 files changed

+85
-13
lines changed

3 files changed

+85
-13
lines changed

Sources/ContainerRegistry/ImageReference.swift

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -214,45 +214,51 @@ extension ImageReference {
214214

215215
/// Digest identifies a specific blob by the hash of the blob's contents.
216216
public struct Digest: Reference, Sendable, Equatable, CustomStringConvertible, CustomDebugStringConvertible {
217+
var algorithm: String
217218
var value: String
218219

219220
public enum ValidationError: Error, Equatable {
220221
case emptyString
221222
case invalidReferenceFormat(String)
222-
case tooLong(String)
223223
}
224224

225225
public init(_ rawValue: String) throws {
226226
guard rawValue.count > 0 else {
227227
throw ValidationError.emptyString
228228
}
229229

230-
if rawValue.count > 7 + 64 {
231-
throw ValidationError.tooLong(rawValue)
230+
// https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md#sha-256
231+
let sha256digest = /(sha256):([a-fA-F0-9]{64})/
232+
if let match = try sha256digest.wholeMatch(in: rawValue) {
233+
algorithm = String(match.1)
234+
value = String(match.2)
235+
return
232236
}
233237

234-
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
235-
let regex = /sha256:[a-fA-F0-9]{64}/
236-
if try regex.wholeMatch(in: rawValue) == nil {
237-
throw ValidationError.invalidReferenceFormat(rawValue)
238+
// https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md#sha-512
239+
let sha512digest = /(sha512):([a-fA-F0-9]{128})/
240+
if let match = try sha512digest.wholeMatch(in: rawValue) {
241+
algorithm = String(match.1)
242+
value = String(match.2)
243+
return
238244
}
239245

240-
value = rawValue
246+
throw ValidationError.invalidReferenceFormat(rawValue)
241247
}
242248

243249
public static func == (lhs: Digest, rhs: Digest) -> Bool {
244-
lhs.value == rhs.value
250+
lhs.algorithm == rhs.algorithm && lhs.value == rhs.value
245251
}
246252

247253
public var separator: String = "@"
248254

249255
public var description: String {
250-
"\(value)"
256+
"\(algorithm):\(value)"
251257
}
252258

253259
/// Printable description in a form suitable for debugging.
254260
public var debugDescription: String {
255-
"Digest(\(value))"
261+
"Digest(\(algorithm):\(value))"
256262
}
257263
}
258264
}

Sources/containertool/Extensions/Errors+CustomStringConvertible.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ extension ContainerRegistry.ImageReference.Digest.ValidationError: Swift.CustomS
8181
switch self {
8282
case .emptyString:
8383
return "Invalid reference format: digest cannot be empty"
84-
case .tooLong(let rawValue):
85-
return "Invalid reference format: digest (\(rawValue)) is too long"
8684
case .invalidReferenceFormat(let rawValue):
8785
return "Invalid reference format: digest (\(rawValue)) is not a valid digest"
8886
}

Tests/ContainerRegistryTests/ImageReferenceTests.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ struct ReferenceTests {
135135
)
136136
),
137137

138+
ReferenceTestCase(
139+
reference:
140+
"example.com/foo@sha512:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
141+
expected: try! ImageReference(
142+
registry: "example.com",
143+
repository: ImageReference.Repository("foo"),
144+
reference: ImageReference.Digest(
145+
"sha512:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
146+
)
147+
)
148+
),
149+
138150
ReferenceTestCase(
139151
reference: "foo:1234/bar:1234",
140152
expected: try! ImageReference(
@@ -262,3 +274,59 @@ struct ReferenceTests {
262274
)
263275
}
264276
}
277+
278+
struct DigestTests {
279+
@Test(arguments: [
280+
(
281+
digest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
282+
algorithm: "sha256", value: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
283+
),
284+
(
285+
digest: "sha512:"
286+
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
287+
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
288+
algorithm: "sha512",
289+
value: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
290+
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
291+
),
292+
])
293+
func testParseValidDigest(digest: String, algorithm: String, value: String) throws {
294+
let parsed = try! ImageReference.Digest(digest)
295+
296+
#expect(parsed.algorithm == algorithm)
297+
#expect(parsed.value == value)
298+
#expect("\(parsed)" == digest)
299+
}
300+
301+
@Test(arguments: [
302+
"sha256:0123456789abcdef0123456789abcdef0123456789abcdef", // short digest
303+
"foo:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", // bad algorithm
304+
"sha256-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", // bad separator
305+
])
306+
func testParseInvalidDigest(digest: String) throws {
307+
#expect(throws: ImageReference.Digest.ValidationError.invalidReferenceFormat(digest)) {
308+
try ImageReference.Digest(digest)
309+
}
310+
}
311+
312+
@Test
313+
func testDigestEquality() throws {
314+
let digest1 = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
315+
let digest2 = "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
316+
let digest3 =
317+
"sha512:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
318+
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
319+
320+
#expect(try ImageReference.Digest(digest1) != ImageReference.Digest(digest2))
321+
#expect(try ImageReference.Digest(digest1) != ImageReference.Digest(digest3))
322+
323+
// Same string, parsed twice, should yield the same digest
324+
let sha256left = try ImageReference.Digest(digest1)
325+
let sha256right = try ImageReference.Digest(digest1)
326+
#expect(sha256left == sha256right)
327+
328+
let sha512left = try ImageReference.Digest(digest3)
329+
let sha512right = try ImageReference.Digest(digest3)
330+
#expect(sha512left == sha512right)
331+
}
332+
}

0 commit comments

Comments
 (0)