@@ -2,10 +2,10 @@ import CryptoKit
2
2
import Foundation
3
3
4
4
public protocol Validator : Sendable {
5
- func validate( path: URL ) async throws
5
+ func validate( path: URL ) async throws ( ValidationError )
6
6
}
7
7
8
- public enum ValidationError : LocalizedError {
8
+ public enum ValidationError : Error {
9
9
case fileNotFound
10
10
case unableToCreateStaticCode
11
11
case invalidSignature
@@ -51,58 +51,58 @@ public struct SignatureValidator: Validator {
51
51
52
52
public init ( ) { }
53
53
54
- public func validate( path: URL ) throws {
54
+ public func validate( path: URL ) throws ( ValidationError ) {
55
55
guard FileManager . default. fileExists ( atPath: path. path) else {
56
- throw ValidationError . fileNotFound
56
+ throw . fileNotFound
57
57
}
58
58
59
59
var staticCode : SecStaticCode ?
60
60
let status = SecStaticCodeCreateWithPath ( path as CFURL , SecCSFlags ( ) , & staticCode)
61
61
guard status == errSecSuccess, let code = staticCode else {
62
- throw ValidationError . unableToCreateStaticCode
62
+ throw . unableToCreateStaticCode
63
63
}
64
64
65
65
let validateStatus = SecStaticCodeCheckValidity ( code, SecCSFlags ( ) , nil )
66
66
guard validateStatus == errSecSuccess else {
67
- throw ValidationError . invalidSignature
67
+ throw . invalidSignature
68
68
}
69
69
70
70
var information : CFDictionary ?
71
71
let infoStatus = SecCodeCopySigningInformation ( code, signInfoFlags, & information)
72
72
guard infoStatus == errSecSuccess, let info = information as? [ String : Any ] else {
73
- throw ValidationError . unableToRetrieveInfo
73
+ throw . unableToRetrieveInfo
74
74
}
75
75
76
76
guard let identifier = info [ kSecCodeInfoIdentifier as String ] as? String ,
77
77
identifier == expectedIdentifier
78
78
else {
79
- throw ValidationError . invalidIdentifier ( identifier: info [ kSecCodeInfoIdentifier as String ] as? String )
79
+ throw . invalidIdentifier( identifier: info [ kSecCodeInfoIdentifier as String ] as? String )
80
80
}
81
81
82
82
guard let teamIdentifier = info [ kSecCodeInfoTeamIdentifier as String ] as? String ,
83
83
teamIdentifier == expectedTeamIdentifier
84
84
else {
85
- throw ValidationError . invalidTeamIdentifier (
85
+ throw . invalidTeamIdentifier(
86
86
identifier: info [ kSecCodeInfoTeamIdentifier as String ] as? String
87
87
)
88
88
}
89
89
90
90
guard let infoPlist = info [ kSecCodeInfoPList as String ] as? [ String : AnyObject ] else {
91
- throw ValidationError . missingInfoPList
91
+ throw . missingInfoPList
92
92
}
93
93
94
94
guard let plistIdent = infoPlist [ infoIdentifierKey] as? String , plistIdent == expectedIdentifier else {
95
- throw ValidationError . invalidIdentifier ( identifier: infoPlist [ infoIdentifierKey] as? String )
95
+ throw . invalidIdentifier( identifier: infoPlist [ infoIdentifierKey] as? String )
96
96
}
97
97
98
98
guard let plistName = infoPlist [ infoNameKey] as? String , plistName == expectedName else {
99
- throw ValidationError . invalidIdentifier ( identifier: infoPlist [ infoNameKey] as? String )
99
+ throw . invalidIdentifier( identifier: infoPlist [ infoNameKey] as? String )
100
100
}
101
101
102
102
guard let dylibVersion = infoPlist [ infoShortVersionKey] as? String ,
103
103
minDylibVersion. compare ( dylibVersion, options: . numeric) != . orderedDescending
104
104
else {
105
- throw ValidationError . invalidVersion ( version: infoPlist [ infoShortVersionKey] as? String )
105
+ throw . invalidVersion( version: infoPlist [ infoShortVersionKey] as? String )
106
106
}
107
107
}
108
108
}
@@ -113,36 +113,50 @@ public actor Downloader {
113
113
self . validator = validator
114
114
}
115
115
116
- public func download( src: URL , dest: URL ) async throws {
116
+ public func download( src: URL , dest: URL ) async throws ( DownloadError ) {
117
117
var req = URLRequest ( url: src)
118
118
if FileManager . default. fileExists ( atPath: dest. path) {
119
119
if let existingFileData = try ? Data ( contentsOf: dest) {
120
120
req. setValue ( etag ( data: existingFileData) , forHTTPHeaderField: " If-None-Match " )
121
121
}
122
122
}
123
123
// TODO: Add Content-Length headers to coderd, add download progress delegate
124
- let ( tempURL, response) = try await URLSession . shared. download ( for: req)
124
+ let tempURL : URL
125
+ let response : URLResponse
126
+ do {
127
+ ( tempURL, response) = try await URLSession . shared. download ( for: req)
128
+ } catch {
129
+ throw . networkError( error)
130
+ }
125
131
defer {
126
132
if FileManager . default. fileExists ( atPath: dest. path) {
127
133
do { try FileManager . default. removeItem ( at: tempURL) } catch { }
128
134
}
129
135
}
130
136
131
137
guard let httpResponse = response as? HTTPURLResponse else {
132
- throw DownloadError . invalidResponse
138
+ throw . invalidResponse
133
139
}
134
140
guard httpResponse. statusCode != 304 else {
135
141
return
136
142
}
137
143
guard ( 200 ..< 300 ) . contains ( httpResponse. statusCode) else {
138
- throw DownloadError . unexpectedStatusCode ( httpResponse. statusCode)
144
+ throw . unexpectedStatusCode( httpResponse. statusCode)
139
145
}
140
146
141
- if FileManager . default. fileExists ( atPath: dest. path) {
142
- try FileManager . default. removeItem ( at: dest)
147
+ do {
148
+ if FileManager . default. fileExists ( atPath: dest. path) {
149
+ try FileManager . default. removeItem ( at: dest)
150
+ }
151
+ try FileManager . default. moveItem ( at: tempURL, to: dest)
152
+ } catch {
153
+ throw . fileOpError( error)
154
+ }
155
+ do throws ( ValidationError) {
156
+ try await validator. validate ( path: dest)
157
+ } catch {
158
+ throw . validationError( error)
143
159
}
144
- try FileManager . default. moveItem ( at: tempURL, to: dest)
145
- try await validator. validate ( path: dest)
146
160
}
147
161
}
148
162
@@ -152,14 +166,23 @@ func etag(data: Data) -> String {
152
166
return " \" \( etag) \" "
153
167
}
154
168
155
- enum DownloadError : LocalizedError {
169
+ public enum DownloadError : Error {
156
170
case unexpectedStatusCode( Int )
157
171
case invalidResponse
172
+ case networkError( any Error )
173
+ case fileOpError( any Error )
174
+ case validationError( ValidationError )
158
175
159
176
var localizedDescription : String {
160
177
switch self {
161
178
case let . unexpectedStatusCode( code) :
162
- return " Unexpected status code: \( code) "
179
+ return " Unexpected HTTP status code: \( code) "
180
+ case let . networkError( error) :
181
+ return " Network error: \( error. localizedDescription) "
182
+ case let . fileOpError( error) :
183
+ return " File operation error: \( error. localizedDescription) "
184
+ case let . validationError( error) :
185
+ return " Validation error: \( error. localizedDescription) "
163
186
case . invalidResponse:
164
187
return " Received non-HTTP response "
165
188
}
0 commit comments