mirror of
https://github.com/munki/munki.git
synced 2026-02-09 08:40:40 -06:00
Finally getting the hand of how documentation comments work
This commit is contained in:
@@ -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:
|
||||
/// <key>AdditionalHttpHeaders</key>
|
||||
/// <array>
|
||||
/// <string>Key-With-Optional-Dashes: Foo Value</string>
|
||||
/// <string>another-custom-header: bar value</string>
|
||||
/// </array>
|
||||
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:
|
||||
/// <key>AdditionalHttpHeaders</key>
|
||||
/// <array>
|
||||
/// <string>Key-With-Optional-Dashes: Foo Value</string>
|
||||
/// <string>another-custom-header: bar value</string>
|
||||
/// </array>
|
||||
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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user