Skip to content

Commit f0c6ec0

Browse files
committed
ContainerRegistry: Switch to swift-http-types
1 parent dd38050 commit f0c6ec0

File tree

8 files changed

+105
-120
lines changed

8 files changed

+105
-120
lines changed

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ let package = Package(
2626
dependencies: [
2727
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0"..<"4.0.0"),
2828
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
29+
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.2.0"),
2930
],
3031
targets: [
3132
.target(
3233
name: "ContainerRegistry",
3334
dependencies: [
3435
.product(name: "Crypto", package: "swift-crypto", condition: .when(platforms: [.linux])),
36+
.product(name: "HTTPTypes", package: "swift-http-types"),
37+
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
3538
.target(
3639
name: "Basics" // AuthorizationProvider
3740
),

Sources/ContainerRegistry/AuthHandler.swift

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414

1515
import Basics
1616
import Foundation
17-
#if canImport(FoundationNetworking)
18-
import FoundationNetworking
19-
#endif
2017
import RegexBuilder
18+
import HTTPTypes
2119

2220
struct BearerTokenResponse: Codable {
2321
/// An opaque Bearer token that clients should supply to
@@ -130,7 +128,7 @@ public struct AuthHandler {
130128
}
131129

132130
/// Get locally-configured credentials, such as netrc or username/password, for a request
133-
func localCredentials(for request: URLRequest) -> String? {
131+
func localCredentials(for request: HTTPRequest) -> String? {
134132
guard let requestURL = request.url else { return nil }
135133

136134
if let netrcEntry = auth?.httpAuthorizationHeader(for: requestURL) { return netrcEntry }
@@ -149,7 +147,7 @@ public struct AuthHandler {
149147
/// In future it could provide cached responses from previous challenges.
150148
/// - Parameter request: The request to authorize.
151149
/// - Returns: The request, with an appropriate authorization header added, or nil if no credentials are available.
152-
public func auth(for request: URLRequest) -> URLRequest? { nil }
150+
public func auth(for request: HTTPRequest) -> HTTPRequest? { nil }
153151

154152
/// Add authorization to an HTTP rquest in response to a challenge from the server.
155153
/// - Parameters:
@@ -158,13 +156,13 @@ public struct AuthHandler {
158156
/// - client: An HTTP client, used to retrieve tokens if necessary.
159157
/// - Returns: The request, with an appropriate authorization header added, or nil if no credentials are available.
160158
/// - Throws: If an error occurs while retrieving a credential.
161-
public func auth(for request: URLRequest, withChallenge challenge: String, usingClient client: HTTPClient)
162-
async throws -> URLRequest?
159+
public func auth(for request: HTTPRequest, withChallenge challenge: String, usingClient client: HTTPClient)
160+
async throws -> HTTPRequest?
163161
{
164162
if challenge.lowercased().starts(with: "basic") {
165163
guard let authHeader = localCredentials(for: request) else { return nil }
166164
var request = request
167-
request.addValue(authHeader, forHTTPHeaderField: "Authorization")
165+
request.headerFields[.authorization] = authHeader
168166
return request
169167

170168
} else if challenge.lowercased().starts(with: "bearer") {
@@ -176,15 +174,15 @@ public struct AuthHandler {
176174
challenge.dropFirst("bearer".count).trimmingCharacters(in: .whitespacesAndNewlines)
177175
)
178176
guard let challengeURL = parsedChallenge.url else { return nil }
179-
var req = URLRequest(url: challengeURL)
180-
if let credentials = localCredentials(for: req) {
181-
req.addValue("\(credentials)", forHTTPHeaderField: "Authorization")
177+
var tokenRequest = HTTPRequest(url: challengeURL)
178+
if let credentials = localCredentials(for: tokenRequest) {
179+
tokenRequest.headerFields[.authorization] = credentials
182180
}
183181

184-
let (data, _) = try await client.executeRequestThrowing(req, expectingStatus: 200)
182+
let (data, _) = try await client.executeRequestThrowing(tokenRequest, expectingStatus: .ok)
185183
let tokenResponse = try JSONDecoder().decode(BearerTokenResponse.self, from: data)
186184
var request = request
187-
request.addValue("Bearer \(tokenResponse.token)", forHTTPHeaderField: "Authorization")
185+
request.headerFields[.authorization] = "Bearer \(tokenResponse.token)"
188186
return request
189187

190188
} else {

Sources/ContainerRegistry/Blobs.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,14 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Foundation
16+
import HTTPTypes
1617

1718
#if canImport(CryptoKit)
1819
import CryptoKit
1920
#else
2021
import Crypto
2122
#endif
2223

23-
#if canImport(FoundationNetworking)
24-
import FoundationNetworking
25-
#endif
26-
2724
/// Calculates the digest of a blob of data.
2825
/// - Parameter data: Blob of data to digest.
2926
/// - Returns: The blob's digest, in the format expected by the distribution protocol.
@@ -45,17 +42,20 @@ extension RegistryClient {
4542
// Response will include a 'Location' header telling us where to PUT the blob data.
4643
let httpResponse = try await executeRequestThrowing(
4744
.post(registryURLForPath("/v2/\(repository)/blobs/uploads/")),
48-
expectingStatus: 202, // expected response code for a two-shot upload
49-
decodingErrors: [404]
45+
expectingStatus: .accepted, // expected response code for a "two-shot" upload
46+
decodingErrors: [.notFound]
5047
)
5148

52-
guard let location = httpResponse.response.value(forHTTPHeaderField: "Location") else {
49+
guard let location = httpResponse.response.headerFields[.location] else {
5350
throw HTTPClientError.missingResponseHeader("Location")
5451
}
5552
return URLComponents(string: location)
5653
}
5754
}
5855

56+
// The spec says that Docker- prefix headers are no longer to be used, but also specifies that the registry digest is returned in this header.
57+
extension HTTPField.Name { static let dockerContentDigest = Self("Docker-Content-Digest")! }
58+
5959
public extension RegistryClient {
6060
func blobExists(repository: String, digest: String) async throws -> Bool {
6161
precondition(repository.count > 0, "repository must not be an empty string")
@@ -67,7 +67,7 @@ public extension RegistryClient {
6767
decodingErrors: [404]
6868
)
6969
return true
70-
} catch HTTPClientError.unexpectedStatusCode(status: 404, _, _) { return false }
70+
} catch HTTPClientError.unexpectedStatusCode(status: .notFound, _, _) { return false }
7171
}
7272

7373
/// Fetches an unstructured blob of data from the registry.
@@ -141,14 +141,14 @@ public extension RegistryClient {
141141
// All blob uploads have Content-Type: application/octet-stream on the wire, even if mediatype is different
142142
.put(uploadURL, contentType: "application/octet-stream"),
143143
uploading: data,
144-
expectingStatus: 201,
145-
decodingErrors: [400, 404]
144+
expectingStatus: .created,
145+
decodingErrors: [.badRequest, .notFound]
146146
)
147147

148148
// The registry could compute a different digest and we should use its value
149149
// as the canonical digest for linking blobs. If the registry sends a digest we
150150
// should check that it matches our locally-calculated digest.
151-
if let serverDigest = httpResponse.response.value(forHTTPHeaderField: "Docker-Content-Digest") {
151+
if let serverDigest = httpResponse.response.headerFields[.dockerContentDigest] {
152152
assert(digest == serverDigest)
153153
}
154154
return .init(mediaType: mediaType, digest: digest, size: Int64(data.count))

Sources/ContainerRegistry/CheckAPI.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ public extension RegistryClient {
2020
// The registry may require authentication on this endpoint.
2121
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#determining-support
2222
do {
23-
return try await executeRequestThrowing(.get(registryURLForPath("/v2/")), decodingErrors: [401, 404]).data
24-
== EmptyObject()
25-
} catch HTTPClientError.unexpectedStatusCode(status: 404, _, _) { return false }
23+
return try await executeRequestThrowing(
24+
.get(registryURLForPath("/v2/")),
25+
decodingErrors: [.unauthorized, .notFound]
26+
)
27+
.data == EmptyObject()
28+
} catch HTTPClientError.unexpectedStatusCode(status: .notFound, _, _) { return false }
2629
}
2730
}

0 commit comments

Comments
 (0)