Skip to content

Commit 444cf5b

Browse files
committed
introduced AuthenticationProviding interface to permit netrc injection to Downloader
1 parent ba4a28e commit 444cf5b

File tree

3 files changed

+40
-39
lines changed

3 files changed

+40
-39
lines changed

Sources/TSCUtility/Downloader.swift

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ public protocol Downloader {
4343
/// - Parameters:
4444
/// - url: The `URL` to the file to download.
4545
/// - destination: The `AbsolutePath` to download the file to.
46+
/// - authorizationProvider: Optional provider supplying `Authorization` header to be added to `URLRequest`.
4647
/// - progress: A closure to receive the download's progress as number of bytes.
4748
/// - completion: A closure to be notifed of the completion of the download.
4849
func downloadFile(
4950
at url: Foundation.URL,
5051
to destination: AbsolutePath,
52+
withAuthorizationProvider authorizationProvider: AuthorizationProviding?,
5153
progress: @escaping Progress,
5254
completion: @escaping Completion
5355
)
@@ -109,33 +111,24 @@ public final class FoundationDownloader: NSObject, Downloader {
109111
public func downloadFile(
110112
at url: Foundation.URL,
111113
to destination: AbsolutePath,
114+
withAuthorizationProvider authorizationProvider: AuthorizationProviding? = nil,
112115
progress: @escaping Downloader.Progress,
113116
completion: @escaping Downloader.Completion
114117
) {
115-
queue.addOperation {
118+
queue.addOperation { [self] in
116119
var request = URLRequest(url: url)
117120

118-
if #available(OSX 10.13, *) {
119-
switch Netrc.load() {
120-
case let .success(netrc):
121-
if let authorization = netrc.authorization(for: url) {
122-
request.addValue(authorization, forHTTPHeaderField: "Authorization")
123-
}
124-
case .failure(_):
125-
break // Failure cases unhandled
126-
}
127-
} else {
128-
// Netrc loading is not supported for OSX < 10.13; continue with task-
129-
// initialization without attempting to append netrc-based credentials.
121+
if let authorization = authorizationProvider?.authorization(for: url) {
122+
request.addValue(authorization, forHTTPHeaderField: "Authorization")
130123
}
131124

132-
let task = self.session.downloadTask(with: request)
125+
let task = session.downloadTask(with: request)
133126
let download = Download(
134127
task: task,
135128
destination: destination,
136129
progress: progress,
137130
completion: completion)
138-
self.downloads[task.taskIdentifier] = download
131+
downloads[task.taskIdentifier] = download
139132
task.resume()
140133
}
141134
}

Sources/TSCUtility/Netrc.swift

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import Foundation
2+
import TSCBasic
3+
4+
5+
/// Supplies `Authorization` header, typically to be appended to `URLRequest`
6+
public protocol AuthorizationProviding {
7+
/// Optional `Authorization` header, likely added to `URLRequest`
8+
func authorization(for url: Foundation.URL) -> String?
9+
}
10+
11+
extension AuthorizationProviding {
12+
func authorization(for url: Foundation.URL) -> String? {
13+
return nil
14+
}
15+
}
216

317
@available (OSX 10.13, *)
418
/// Container of parsed netrc connection settings
5-
public struct Netrc {
19+
public struct Netrc: AuthorizationProviding {
620

721
/// Representation of `machine` connection settings & `default` connection settings. If `default` connection settings present, they will be last element.
822
public let machines: [Machine]
@@ -11,10 +25,10 @@ public struct Netrc {
1125
self.machines = machines
1226
}
1327

14-
/// Testing API. Not for productive use.
15-
/// See: [Remove @testable from codebase](https://github.com/apple/swift-package-manager/commit/b6349d516d2f9b2f26ddae9de2c594ede24af7d6)
16-
public static var _mock: Netrc? = nil
17-
28+
// /// Testing API. Not for productive use.
29+
// /// See: [Remove @testable from codebase](https://github.com/apple/swift-package-manager/commit/b6349d516d2f9b2f26ddae9de2c594ede24af7d6)
30+
// public static var _mock: Netrc? = nil
31+
1832
/// Basic authorization header string
1933
/// - Parameter url: URI of network resource to be accessed
2034
/// - Returns: (optional) Basic Authorization header string to be added to the request
@@ -29,13 +43,15 @@ public struct Netrc {
2943
///
3044
/// - Parameter fileURL: Location of netrc file, defaults to `~/.netrc`
3145
/// - Returns: `Netrc` container with parsed connection settings, or error
32-
public static func load(from fileURL: Foundation.URL = Foundation.URL(fileURLWithPath: "\(NSHomeDirectory())/.netrc")) -> Result<Netrc, Netrc.Error> {
33-
34-
guard _mock == nil else { return .success(_mock!) }
46+
public static func load(fromFileAtPath filePath: AbsolutePath? = nil) -> Result<Netrc, Netrc.Error> {
47+
48+
guard let filePath = filePath ?? AbsolutePath("\(NSHomeDirectory())/.netrc") else {
49+
return .failure(.invalidFilePath)
50+
}
3551

36-
guard FileManager.default.fileExists(atPath: fileURL.path) else { return .failure(.fileNotFound(fileURL)) }
37-
guard FileManager.default.isReadableFile(atPath: fileURL.path),
38-
let fileContents = try? String(contentsOf: fileURL, encoding: .utf8) else { return .failure(.unreadableFile(fileURL)) }
52+
guard FileManager.default.fileExists(atPath: filePath.pathString) else { return .failure(.fileNotFound(filePath)) }
53+
guard FileManager.default.isReadableFile(atPath: filePath.pathString),
54+
let fileContents = try? String(contentsOf: filePath.asURL, encoding: .utf8) else { return .failure(.unreadableFile(filePath)) }
3955

4056
return Netrc.from(fileContents)
4157
}
@@ -84,13 +100,13 @@ public struct Netrc {
84100
public extension Netrc {
85101

86102
enum Error: Swift.Error {
87-
case fileNotFound(Foundation.URL)
88-
case unreadableFile(Foundation.URL)
103+
case invalidFilePath
104+
case fileNotFound(AbsolutePath)
105+
case unreadableFile(AbsolutePath)
89106
case machineNotFound
90107
case invalidDefaultMachinePosition
91108
}
92109

93-
94110
/// Representation of connection settings
95111
/// - important: Default connection settings are stored in machine named `default`
96112
struct Machine: Equatable {

Tests/TSCUtilityTests/DownloaderTests.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@ import FoundationNetworking
1818
#endif
1919

2020
class DownloaderTests: XCTestCase {
21-
22-
override func tearDown() {
23-
if #available(OSX 10.13, *) {
24-
Netrc._mock = nil
25-
}
26-
}
2721

2822
func testSuccess() {
2923
// FIXME: Remove once https://github.com/apple/swift-corelibs-foundation/pull/2593 gets inside a toolchain.
@@ -89,7 +83,6 @@ class DownloaderTests: XCTestCase {
8983
}
9084
let authData = "anonymous:qwerty".data(using: .utf8)!
9185
let testAuthHeader = "Basic \(authData.base64EncodedString())"
92-
Netrc._mock = netrc
9386

9487
#if os(macOS)
9588
let configuration = URLSessionConfiguration.default
@@ -106,7 +99,7 @@ class DownloaderTests: XCTestCase {
10699
let successExpectation = XCTestExpectation(description: "success")
107100
MockAuthenticatingURLProtocol.notifyDidStartLoading(for: url, completion: { didStartLoadingExpectation.fulfill() })
108101

109-
downloader.downloadFile(at: url, to: destination, progress: { bytesDownloaded, totalBytesToDownload in
102+
downloader.downloadFile(at: url, to: destination, withAuthorizationProvider: netrc, progress: { bytesDownloaded, totalBytesToDownload in
110103

111104
XCTAssertEqual(MockAuthenticatingURLProtocol.authenticationHeader(for: url), testAuthHeader)
112105

@@ -156,7 +149,6 @@ class DownloaderTests: XCTestCase {
156149
}
157150
let authData = "default:default".data(using: .utf8)!
158151
let testAuthHeader = "Basic \(authData.base64EncodedString())"
159-
Netrc._mock = netrc
160152

161153
#if os(macOS)
162154
let configuration = URLSessionConfiguration.default
@@ -173,7 +165,7 @@ class DownloaderTests: XCTestCase {
173165
let successExpectation = XCTestExpectation(description: "success")
174166
MockAuthenticatingURLProtocol.notifyDidStartLoading(for: url, completion: { didStartLoadingExpectation.fulfill() })
175167

176-
downloader.downloadFile(at: url, to: destination, progress: { bytesDownloaded, totalBytesToDownload in
168+
downloader.downloadFile(at: url, to: destination, withAuthorizationProvider: netrc, progress: { bytesDownloaded, totalBytesToDownload in
177169

178170
XCTAssertEqual(MockAuthenticatingURLProtocol.authenticationHeader(for: url), testAuthHeader)
179171

0 commit comments

Comments
 (0)