From aabbce53369c99d54a2dc48bbd38f607597875f5 Mon Sep 17 00:00:00 2001 From: Theo Date: Fri, 29 Sep 2023 13:29:56 +0200 Subject: [PATCH] Expand crypto module, improve fetch/request --- Sources/ECMASwift/API/Crypto/Crypto.swift | 62 +++++++++++++++++++++++ Sources/ECMASwift/API/Fetch/Fetch.swift | 6 ++- Sources/ECMASwift/API/Fetch/Request.swift | 32 +++++++----- 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/Sources/ECMASwift/API/Crypto/Crypto.swift b/Sources/ECMASwift/API/Crypto/Crypto.swift index 9284f35..8c255a7 100644 --- a/Sources/ECMASwift/API/Crypto/Crypto.swift +++ b/Sources/ECMASwift/API/Crypto/Crypto.swift @@ -4,9 +4,69 @@ import CommonCrypto // https://developer.mozilla.org/en-US/docs/Web/API/Crypto +@objc protocol SubtleCryptoExports: JSExport { + func digest(_ algorithm: String, _ data: [UInt8]) -> [UInt8]? + func encrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]? + func decrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]? +} + +@objc class SubtleCrypto: NSObject, SubtleCryptoExports { + func digest(_ algorithm: String, _ data: [UInt8]) -> [UInt8]? { + guard algorithm == "SHA-256" else { return nil } + var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + _ = data.withUnsafeBytes { + CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) + } + return hash + } + + func encrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]? { + guard algorithm == "AES-GCM", key.count == kCCKeySizeAES128 else { return nil } + var buffer = [UInt8](repeating: 0, count: data.count + kCCBlockSizeAES128) + var numBytesEncrypted: size_t = 0 + + let cryptStatus = CCCrypt(CCOperation(kCCEncrypt), + CCAlgorithm(kCCAlgorithmAES), + CCOptions(kCCOptionPKCS7Padding), + key, key.count, + iv, + data, data.count, + &buffer, buffer.count, + &numBytesEncrypted) + + if cryptStatus == kCCSuccess { + return Array(buffer.prefix(numBytesEncrypted)) + } + return nil + } + + func decrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]? { + guard algorithm == "AES-GCM", key.count == kCCKeySizeAES128 else { return nil } + var buffer = [UInt8](repeating: 0, count: data.count + kCCBlockSizeAES128) + var numBytesDecrypted: size_t = 0 + + let cryptStatus = CCCrypt(CCOperation(kCCDecrypt), + CCAlgorithm(kCCAlgorithmAES), + CCOptions(kCCOptionPKCS7Padding), + key, key.count, + iv, + data, data.count, + &buffer, buffer.count, + &numBytesDecrypted) + + if cryptStatus == kCCSuccess { + return Array(buffer.prefix(numBytesDecrypted)) + } + return nil + } +} + + @objc protocol CryptoExports: JSExport { func getRandomValues(_ array: [UInt]) -> [UInt] func randomUUID() -> String + + var subtle: SubtleCryptoExports { get } } @objc class Crypto: NSObject, CryptoExports { @@ -32,6 +92,8 @@ import CommonCrypto func randomUUID() -> String { return UUID().uuidString } + + lazy var subtle: SubtleCryptoExports = SubtleCrypto() } struct CryptoAPI { diff --git a/Sources/ECMASwift/API/Fetch/Fetch.swift b/Sources/ECMASwift/API/Fetch/Fetch.swift index b3eecbf..a3c82ff 100644 --- a/Sources/ECMASwift/API/Fetch/Fetch.swift +++ b/Sources/ECMASwift/API/Fetch/Fetch.swift @@ -43,7 +43,11 @@ public class FetchAPI { var request = URLRequest(url: url) if let options { - if let body = options.forProperty("body"), let body = try? Body.createFrom(body) { + let headers = options.toDictionary()["headers"] as? [String: String] + headers?.forEach { (key, value) in + request.setValue(value, forHTTPHeaderField: key) + } + if let body = options.forProperty("body"), let body = Body.createFrom(body) { request.httpBody = body.data() } if let method = options.forProperty("method").toString() { diff --git a/Sources/ECMASwift/API/Fetch/Request.swift b/Sources/ECMASwift/API/Fetch/Request.swift index 64442b1..ba25717 100644 --- a/Sources/ECMASwift/API/Fetch/Request.swift +++ b/Sources/ECMASwift/API/Fetch/Request.swift @@ -3,8 +3,14 @@ import JavaScriptCore // https://developer.mozilla.org/en-US/docs/Web/API/Request +extension JSValue { + func toType(_ type: T.Type) -> T? { + return self.toObject() as? T + } +} + // TODO: Probably eventually needs to model a ReadableStream properly -enum Body: Codable { +enum Body { case blob(Data) case arrayBuffer([Data]) case typedArray([UInt]) @@ -13,15 +19,13 @@ enum Body: Codable { case urlSearchParams(URLSearchParams) case string(String) - static func createFrom(_ value: Any) throws -> Body? { - switch value { - case let value as URLSearchParams: - return .urlSearchParams(value) - case let value as String: - return .string(value) - default: - return nil + static func createFrom(_ jsValue: JSValue) -> Body? { + if let searchParamsValue = jsValue.toType(URLSearchParams.self) { + return .urlSearchParams(searchParamsValue) + } else if let stringValue = jsValue.toString() { + return .string(stringValue) } + return nil } func data() -> Data? { @@ -89,13 +93,13 @@ enum Body: Codable { weak var context: JSContext? - init(url: String, options: [AnyHashable: Any]? = nil) { + init(url: String, options: JSValue? = nil) { self.url = url if let options { - self.method = options["method"] as? String - if let body = options["body"] { - self.body = try? .createFrom(body) + self.method = options.forProperty("method").toString() + if let body = options.forProperty("body") { + self.body = Body.createFrom(body) } } } @@ -164,7 +168,7 @@ enum Body: Codable { struct RequestAPI { func registerAPIInto(context: JSContext) { let requestClass: @convention(block) (String, JSValue?) -> Request = { url, options in - let request = Request(url: url, options: options?.toDictionary()) + let request = Request(url: url, options: options) request.context = context return request }