diff --git a/code/cli/munki/shared/network/fetch.swift b/code/cli/munki/shared/network/fetch.swift
index e129f1e0..341a772d 100644
--- a/code/cli/munki/shared/network/fetch.swift
+++ b/code/cli/munki/shared/network/fetch.swift
@@ -57,29 +57,29 @@ func storeCachedChecksum(toPath path: String, hash: String? = nil) -> String? {
return nil
}
+/// Verifies the integrity of the given software package.
+///
+/// The feature is controlled through the PackageVerificationMode key in
+/// Munki's preferences. Following modes currently exist:
+/// none: No integrity check is performed.
+/// hash: Integrity check is performed by calculating a SHA-256 hash of
+/// the given file and comparing it against the reference value in
+/// catalog. Only applies for package plists that contain the
+/// item_key; for packages without the item_key, verification always
+/// returns true.
+/// hash_strict: Same as hash, but returns false for package plists that
+/// do not contain the item_key.
+///
+/// Args:
+/// path: The file to check integrity on.
+/// expectedHash: the sha256 hash expected.
+/// alwaysHash: Boolean. Always check and return the hash even if not
+/// necessary for this function.
+///
+/// Returns:
+/// (true/false, sha256-hash)
+/// true if the package integrity could be validated. Otherwise, false.
func verifySoftwarePackageIntegrity(_ path: String, expectedHash: String, alwaysHash: Bool = false) -> (Bool, String) {
- /// Verifies the integrity of the given software package.
-
- /// The feature is controlled through the PackageVerificationMode key in
- /// Munki's preferences. Following modes currently exist:
- /// none: No integrity check is performed.
- /// hash: Integrity check is performed by calculating a SHA-256 hash of
- /// the given file and comparing it against the reference value in
- /// catalog. Only applies for package plists that contain the
- /// item_key; for packages without the item_key, verification always
- /// returns true.
- /// hash_strict: Same as hash, but returns false for package plists that
- /// do not contain the item_key.
- ///
- /// Args:
- /// path: The file to check integrity on.
- /// expectedHash: the sha256 hash expected.
- /// alwaysHash: Boolean. Always check and return the hash even if not
- /// necessary for this function.
- ///
- /// Returns:
- /// (true/false, sha256-hash)
- /// true if the package integrity could be validated. Otherwise, false.
let mode = pref("PackageVerificationMode") as? String ?? "hash"
let itemName = (path as NSString).lastPathComponent
var calculatedHash = ""
@@ -119,10 +119,10 @@ func verifySoftwarePackageIntegrity(_ path: String, expectedHash: String, always
return (false, calculatedHash)
}
+/// Given a list of strings in http header format, return a dict.
+/// A User-Agent header is added if none is present in the list.
+/// If strList is nil, returns a dict with only the User-Agent header.
func headerDictFromList(_ strList: [String]?) -> [String: String] {
- /// Given a list of strings in http header format, return a dict.
- /// A User-Agent header is added if none is present in the list.
- /// If strList is nil, returns a dict with only the User-Agent header.
var headerDict = [String: String]()
headerDict["User-Agent"] = DEFAULT_USER_AGENT
@@ -144,6 +144,17 @@ func runMiddleware(options: GurlOptions, pkginfo _: PlistDict?) -> GurlOptions {
return options
}
+/// Gets an HTTP or HTTPS URL and stores it in
+/// destination path. Returns a dictionary of headers, which includes
+/// http_result_code and http_result_description.
+/// Will throw FetchError.connection if Gurl has a connection error.
+/// Will throw FetchError.http if HTTP Result code is not 2xx or 304.
+/// Will throw FetchError.fileSystem if Gurl has a filesystem error.
+/// If destinationpath already exists, you can set 'onlyifnewer' to true to
+/// indicate you only want to download the file only if it's newer on the
+/// server.
+/// If you set resume to true, Gurl will attempt to resume an
+/// interrupted download.
func getURL(
_ url: String,
destinationPath: String,
@@ -154,17 +165,6 @@ func getURL(
followRedirects: String = "none",
pkginfo: PlistDict? = nil
) throws -> [String: String] {
- /// Gets an HTTP or HTTPS URL and stores it in
- /// destination path. Returns a dictionary of headers, which includes
- /// http_result_code and http_result_description.
- /// Will throw FetchError.connection if Gurl has a connection error.
- /// Will throw FetchError.http if HTTP Result code is not 2xx or 304.
- /// Will throw FetchError.fileSystem if Gurl has a filesystem error.
- /// If destinationpath already exists, you can set 'onlyifnewer' to true to
- /// indicate you only want to download the file only if it's newer on the
- /// server.
- /// If you set resume to true, Gurl will attempt to resume an
- /// interrupted download.
let tempDownloadPath = destinationPath + ".download"
if pathExists(tempDownloadPath), !resume {
try? FileManager.default.removeItem(atPath: tempDownloadPath)
@@ -286,6 +286,13 @@ func getURL(
throw FetchError.http(errorCode: session.status, description: statusDescription)
}
+/// Gets file from HTTP URL, checking first to see if it has changed on the
+/// server.
+///
+/// Returns True if a new download was required; False if the
+/// item is already in the local cache.
+///
+/// Throws a FetchError if there is an error (.connection or .download)
func getHTTPfileIfChangedAtomically(
_ url: String,
destinationPath: String,
@@ -295,13 +302,6 @@ func getHTTPfileIfChangedAtomically(
followRedirects: String = "none",
pkginfo: PlistDict? = nil
) throws -> Bool {
- /// Gets file from HTTP URL, checking first to see if it has changed on the
- /// server.
- ///
- /// Returns True if a new download was required; False if the
- /// item is already in the local cache.
- ///
- /// Throws a FetchError if there is an error (.connection or .download)
// TODO: etag support
// var eTag = ""
var getOnlyIfNewer = false
@@ -381,14 +381,14 @@ func getHTTPfileIfChangedAtomically(
return true
}
+/// Gets file from path, checking first to see if it has changed on the
+/// source.
+///
+/// Returns true if a new copy was required; false if the
+/// item is already in the local cache.
+///
+/// Throws FetchError.fileSystem if there is an error.
func getFileIfChangedAtomically(_ path: String, destinationPath: String) throws -> Bool {
- /// Gets file from path, checking first to see if it has changed on the
- /// source.
- ///
- /// Returns true if a new copy was required; false if the
- /// item is already in the local cache.
- ///
- /// Throws FetchError.fileSystem if there is an error.
let filemanager = FileManager.default
if !pathExists(path) {
throw FetchError.fileSystem("Source does not exist: \(path)")
@@ -445,6 +445,19 @@ func getFileIfChangedAtomically(_ path: String, destinationPath: String) throws
return true
}
+/// Gets file from a URL.
+/// Checks first if there is already a file with the necessary checksum.
+/// Then checks if the file has changed on the server, resuming or
+/// re-downloading as necessary.
+///
+/// If the file has changed verify the pkg hash if so configured.
+///
+/// Supported schemes are http, https, file.
+///
+/// Returns true if a new download was required; False if the
+/// item is already in the local cache.
+///
+/// Throws a FetchError if there is an error.
func getResourceIfChangedAtomically(
_ url: String,
destinationPath: String,
@@ -456,20 +469,6 @@ func getResourceIfChangedAtomically(
followRedirects: String? = nil,
pkginfo: PlistDict? = nil
) throws -> Bool {
- /// Gets file from a URL.
- /// Checks first if there is already a file with the necessary checksum.
- /// Then checks if the file has changed on the server, resuming or
- /// re-downloading as necessary.
- ///
- /// If the file has changed verify the pkg hash if so configured.
- ///
- /// Supported schemes are http, https, file.
- ///
- /// Returns true if a new download was required; False if the
- /// item is already in the local cache.
- ///
- /// Throws a FetchError if there is an error.
-
guard let resolvedURL = URL(string: url) else {
throw FetchError.connection(errorCode: -1, description: "Invalid URL: \(url)")
}
@@ -549,6 +548,19 @@ func getResourceIfChangedAtomically(
return changed
}
+/// A high-level function for getting resources from the Munki repo.
+/// Gets a given URL from the Munki server.
+/// Adds any additional headers to the request if present
+/// Throws a FetchError if there's an error
+///
+/// Add any additional headers specified in ManagedInstalls.plist.
+/// AdditionalHttpHeaders must be an array of strings with valid HTTP
+/// header format. For example:
+/// AdditionalHttpHeaders
+///
+/// Key-With-Optional-Dashes: Foo Value
+/// another-custom-header: bar value
+///
func fetchMunkiResourceByURL(
_ url: String,
destinationPath: String,
@@ -558,19 +570,6 @@ func fetchMunkiResourceByURL(
verify: Bool = false,
pkginfo: PlistDict? = nil
) throws -> Bool {
- /// A high-level function for getting resources from the Munki repo.
- /// Gets a given URL from the Munki server.
- /// Adds any additional headers to the request if present
- /// Throws a FetchError if there's an error
- ///
- /// Add any additional headers specified in ManagedInstalls.plist.
- /// AdditionalHttpHeaders must be an array of strings with valid HTTP
- /// header format. For example:
- /// AdditionalHttpHeaders
- ///
- /// Key-With-Optional-Dashes: Foo Value
- /// another-custom-header: bar value
- ///
let customHeaders = pref(ADDITIONAL_HTTP_HEADERS_KEY) as? [String]
return try getResourceIfChangedAtomically(
@@ -593,6 +592,7 @@ enum MunkiResourceType: String {
case package = "pkgs"
}
+/// An even higher-level function for getting resources from the Munki repo.
func fetchMunkiResource(
kind: MunkiResourceType,
name: String,
@@ -603,7 +603,6 @@ func fetchMunkiResource(
verify: Bool = false,
pkginfo: PlistDict? = nil
) throws -> Bool {
- /// An even higher-level function for getting resources from the Munki repo.
guard let url = munkiRepoURL(kind.rawValue, resource: name) else {
throw FetchError.connection(
errorCode: -1,
@@ -621,13 +620,12 @@ func fetchMunkiResource(
)
}
+/// Returns data from URL.
+/// We use the existing fetchMunkiResource function so any custom
+/// authentication/authorization headers are used
+/// (including, eventually, middleware-generated headers)
+/// May throw a FetchError
func getDataFromURL(_ url: String) throws -> Data? {
- /// Returns data from URL.
- /// We use the existing fetchMunkiResource function so any custom
- /// authentication/authorization headers are used
- /// (including, eventually, middleware-generated headers)
- /// May throw a FetchError
-
guard let tmpDir = TempDir.shared.makeTempDir() else {
displayError("Could not create temporary directory")
return nil
@@ -640,12 +638,11 @@ func getDataFromURL(_ url: String) throws -> Data? {
return FileManager.default.contents(atPath: tempDataPath)
}
+/// A function we can call to check to see if the server is
+/// available before we kick off a full run. This can be fooled by
+/// ISPs that return results for non-existent web servers...
+/// Returns a tuple (exitCode, exitDescription)
func checkServer(_ urlString: String = "") -> (Int, String) {
- /// A function we can call to check to see if the server is
- /// available before we kick off a full run. This can be fooled by
- /// ISPs that return results for non-existent web servers...
- /// Returns a tuple (exitCode, exitDescription)
-
let serverURL: String = if !urlString.isEmpty {
urlString
} else {
diff --git a/code/cli/munki/shared/network/gurl.swift b/code/cli/munki/shared/network/gurl.swift
index adcc8578..47619ebe 100644
--- a/code/cli/munki/shared/network/gurl.swift
+++ b/code/cli/munki/shared/network/gurl.swift
@@ -25,6 +25,7 @@ func defaultLogger(_ message: String) {
print(message)
}
+/// Some options used by Gurl
struct GurlOptions {
var url: String
var destinationPath: String
@@ -41,10 +42,9 @@ struct GurlOptions {
var log: (String) -> Void = defaultLogger // logging function
}
+/// A class for getting content from a URL
+/// using NSURLSession and friends
class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate {
- /// A class for getting content from a URL
- /// using NSURLSession and friends
-
let GURL_XATTR = "com.googlecode.munki.downloadData"
var options: GurlOptions
@@ -67,8 +67,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
super.init()
}
+ /// Start the connection
func start() {
- /// Start the connection
guard !options.destinationPath.isEmpty else {
options.log("No output file specified")
done = true
@@ -139,17 +139,17 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
}
+ /// Cancel the session
func cancel() {
- /// Cancel the session
if let session {
session.invalidateAndCancel()
}
done = true
}
+ /// Check if the connection request is complete. As a side effect,
+ /// allow the delegates to work by letting the run loop run for a bit
func isDone() -> Bool {
- /// Check if the connection request is complete. As a side effect,
- /// allow the delegates to work by letting the run loop run for a bit
if done {
return true
}
@@ -158,8 +158,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
return done
}
+ /// Returns any stored headers for destinationPath
func getStoredHeaders() -> [String: String]? {
- /// Returns any stored headers for destinationPath
if options.destinationPath.isEmpty {
displayDebug1("destination path is not defined")
return nil
@@ -182,8 +182,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
}
+ /// Store headers dictionary as an xattr for options.destinationPath
func storeHeaders(_ headers: [String: String]) {
- /// Store headers dictionary as an xattr for options.destinationPath
guard let data = try? plistToData(headers) else {
options.log("header convert to plist data failure")
return
@@ -199,10 +199,10 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
}
+ /// Since HTTP header names are not case-sensitive, we normalize a
+ /// dictionary of HTTP headers by converting all the key names to
+ /// lower case
func normalizeHeaderDict(_ headers: [String: String]) -> [String: String] {
- /// Since HTTP header names are not case-sensitive, we normalize a
- /// dictionary of HTTP headers by converting all the key names to
- /// lower case
var normalizedHeaders = [String: String]()
for (key, value) in headers {
normalizedHeaders[key.lowercased()] = value
@@ -210,8 +210,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
return normalizedHeaders
}
+ /// Record any error info from completed session
func recordError(_ error: NSError) {
- /// Record any error info from completed session
self.error = error
// if this was an SSL error, try to extract the SSL error code
if let underlyingError = error.userInfo["NSUnderlyingError"] as? NSError,
@@ -224,8 +224,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
func removeExpectedSizeFromStoredHeaders() {
- /// If a successful transfer, clear the expected size so we
- /// don't attempt to resume the download next time
+ // If a successful transfer, clear the expected size so we
+ // don't attempt to resume the download next time
if String(status).hasPrefix("2"),
var headers = getStoredHeaders(),
headers.keys.contains("expected-length")
@@ -235,8 +235,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
}
+ /// URLSessionTaskDelegate method
@objc func urlSession(_: URLSession, task _: URLSessionTask, didCompleteWithError error: (any Error)?) {
- /// URLSessionTaskDelegate method
if task != task {
return
}
@@ -261,7 +261,7 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
func okToResume(downloadData: [String: String]) -> Bool {
- /// returns a boolean
+ // returns a boolean
guard let storedData = getStoredHeaders() else {
options.log("No stored headers")
return false
@@ -297,13 +297,13 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
return true
}
+ /// URLSessionDataDelegate method
@objc func urlSession(
_: URLSession,
dataTask _: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping @Sendable (URLSession.ResponseDisposition) -> Void
) {
- /// URLSessionDataDelegate method
// self.response = response // doesn't appear to be actually used
bytesReceived = 0
percentComplete = -1
@@ -372,6 +372,7 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
completionHandler(.allow)
}
+ /// URLSessionTaskDelegate method
@objc func urlSession(
_: URLSession,
task _: URLSessionTask,
@@ -379,7 +380,6 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
newRequest request: URLRequest,
completionHandler: @escaping @Sendable (URLRequest?) -> Void
) {
- /// URLSessionTaskDelegate method
guard let newURL = request.url else {
// deny the redirect
completionHandler(nil)
@@ -403,13 +403,13 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
completionHandler(nil)
}
+ /// URLSessionTaskDelegate method
@objc func urlSession(
_: URLSession,
task _: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
- /// URLSessionTaskDelegate method
// Handle an authentication challenge
let supportedAuthMethods = [
NSURLAuthenticationMethodDefault,
@@ -453,8 +453,8 @@ class Gurl: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionData
}
}
+ /// URLSessionDataDelegate method
@objc func urlSession(_: URLSession, dataTask _: URLSessionDataTask, didReceive data: Data) {
- /// URLSessionDataDelegate method
// Handle received data
if let destination {
destination.write(data)