From 66237912dbc609a068609006b30bf5cb57173946 Mon Sep 17 00:00:00 2001 From: Rohithmatham12 Date: Fri, 12 Jun 2026 13:40:38 -0400 Subject: [PATCH] Send registry User-Agent header --- .../Client/RegistryClient.swift | 15 +++++++++++++-- .../RegistryClientTests.swift | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Sources/ContainerizationOCI/Client/RegistryClient.swift b/Sources/ContainerizationOCI/Client/RegistryClient.swift index 14f3142c..06a567c7 100644 --- a/Sources/ContainerizationOCI/Client/RegistryClient.swift +++ b/Sources/ContainerizationOCI/Client/RegistryClient.swift @@ -167,8 +167,8 @@ public final class RegistryClient: ContentClient { request.headers.add(name: "Authorization", value: "\(token)") } - // Add any arbitrary headers - headers?.forEach { (k, v) in request.headers.add(name: k, value: v) } + // Add any arbitrary headers. + requestHeaders(merging: headers).forEach { (k, v) in request.headers.add(name: k, value: v) } var retryCount = 0 var response: HTTPClientResponse? while true { @@ -258,6 +258,17 @@ public final class RegistryClient: ContentClient { return try await closure(response) } + internal func requestHeaders(merging headers: [(String, String)]?) -> [(String, String)] { + var merged: [(String, String)] = [] + if headers?.contains(where: { $0.0.caseInsensitiveCompare("User-Agent") == .orderedSame }) != true { + merged.append(("User-Agent", clientID)) + } + if let headers { + merged.append(contentsOf: headers) + } + return merged + } + internal func requestData( components: URLComponents, headers: [(String, String)]? = nil diff --git a/Tests/ContainerizationOCITests/RegistryClientTests.swift b/Tests/ContainerizationOCITests/RegistryClientTests.swift index f25fcb17..b0450517 100644 --- a/Tests/ContainerizationOCITests/RegistryClientTests.swift +++ b/Tests/ContainerizationOCITests/RegistryClientTests.swift @@ -100,6 +100,25 @@ struct OCIClientTests: ~Copyable { try await client.ping() } + @Test func requestHeadersAddDefaultUserAgent() async throws { + let client = RegistryClient(host: "ghcr.io", clientID: "containerization-tests") + let headers = client.requestHeaders(merging: [("Accept", "*/*")]) + + #expect(headers.contains { $0.0 == "User-Agent" && $0.1 == "containerization-tests" }) + #expect(headers.contains { $0.0 == "Accept" && $0.1 == "*/*" }) + } + + @Test func requestHeadersKeepExplicitUserAgent() async throws { + let client = RegistryClient(host: "ghcr.io", clientID: "containerization-tests") + let headers = client.requestHeaders(merging: [ + ("Accept", "*/*"), + ("user-agent", "custom-client"), + ]) + + #expect(!headers.contains { $0.0 == "User-Agent" && $0.1 == "containerization-tests" }) + #expect(headers.contains { $0.0 == "user-agent" && $0.1 == "custom-client" }) + } + @Test func resolve() async throws { let client = RegistryClient(host: "ghcr.io") let descriptor = try await client.resolve(name: "apple/containerization/dockermanifestimage", tag: "0.0.2")