Skip to content

Instantly share code, notes, and snippets.

@yspreen
Created July 31, 2021 14:37
Show Gist options
  • Save yspreen/95627d72bedcd47f1c0d29271b5600ea to your computer and use it in GitHub Desktop.
Save yspreen/95627d72bedcd47f1c0d29271b5600ea to your computer and use it in GitHub Desktop.
//
// URLDigest.swift
//
// Created by Yannick Spreen on 7/31/21.
//
import Foundation
import CommonCrypto
class URLDigest {
public static func hex(from fileURL: URL) -> String? {
let digest = URLDigest()
guard (try? digest.update(url: fileURL)) != nil else {
return nil
}
let result = digest.finalize()
return hex(for: result)
}
enum InputStreamError: Error {
case createFailed(URL)
case readFailed
}
private lazy var context: CC_SHA256_CTX = {
var shaContext = CC_SHA256_CTX()
CC_SHA256_Init(&shaContext)
return shaContext
}()
private var result: Data?
init() {
}
func update(url: URL) throws {
guard let inputStream = InputStream(url: url) else {
throw InputStreamError.createFailed(url)
}
return try update(inputStream: inputStream)
}
func update(inputStream: InputStream) throws {
guard result == nil else {
return
}
inputStream.open()
defer {
inputStream.close()
}
let bufferSize = 4096
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer {
buffer.deallocate()
}
while true {
let bytesRead = inputStream.read(buffer, maxLength: bufferSize)
if bytesRead < 0 {
// Stream error occured
throw (inputStream.streamError ?? InputStreamError.readFailed)
} else if bytesRead == 0 {
// EOF
break
}
self.update(bytes: buffer, length: bytesRead)
}
}
func update(data: Data) {
guard result == nil else {
return
}
withUnsafeBytes(of: data) {
self.update(bytes: $0.baseAddress, length: data.count)
}
}
func update(bytes: UnsafeRawPointer?, length: Int) {
guard result == nil, let bytes = bytes else {
return
}
_ = CC_SHA256_Update(&self.context, bytes, CC_LONG(length))
}
func finalize() -> Data {
if let calculatedResult = result {
return calculatedResult
}
var resultBuffer = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256_Final(&resultBuffer, &self.context)
let theResult = Data(resultBuffer)
result = theResult
return theResult
}
static func hex(for data: Data) -> String {
return data.reduce(into: String()) { result, byte in
let ch1: Character = hexCharacterLookupTable[Int(byte >> 4)]
let ch2: Character = hexCharacterLookupTable[Int(byte & 0x0F)]
result.append(ch1)
result.append(ch2)
}
}
static let hexCharacterLookupTable: [Character] = {
(0..<16).map { Character(String(format: "%x", arguments: [$0])) }
}()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment