import Foundation #if os(macOS) import CommonCrypto import Security import SQLite3 import SweetCookieKit private let alibabaCookieImportOrder: BrowserCookieImportOrder = ProviderDefaults.metadata[.alibaba]?.browserCookieOrder ?? Browser.defaultImportOrder public enum AlibabaCodingPlanCookieImporter { private static let cookieClient = BrowserCookieClient() private static let cookieDomains = [ "bailian-singapore-cs.alibabacloud.com", "bailian-cs.console.aliyun.com", "bailian-beijing-cs.aliyuncs.com", "modelstudio.console.alibabacloud.com", "bailian.console.aliyun.com", "free.aliyun.com", "account.aliyun.com", "signin.aliyun.com", "passport.alibabacloud.com", "console.alibabacloud.com", "console.aliyun.com", "alibabacloud.com", "aliyun.com", ] public struct SessionInfo: Sendable { public let cookies: [HTTPCookie] public let sourceLabel: String public init(cookies: [HTTPCookie], sourceLabel: String) { self.cookies = cookies self.sourceLabel = sourceLabel } public var cookieHeader: String { var byName: [String: HTTPCookie] = [:] byName.reserveCapacity(self.cookies.count) for cookie in self.cookies { if let expiry = cookie.expiresDate, expiry < Date() { continue } guard !cookie.value.isEmpty else { continue } if let existing = byName[cookie.name] { let existingExpiry = existing.expiresDate ?? .distantPast let candidateExpiry = cookie.expiresDate ?? .distantPast if candidateExpiry >= existingExpiry { byName[cookie.name] = cookie } } else { byName[cookie.name] = cookie } } return byName.keys.sorted().compactMap { name in guard let cookie = byName[name] else { return nil } return "\(cookie.name)=\(cookie.value)" }.joined(separator: "; ") } } nonisolated(unsafe) static var importSessionOverrideForTesting: ((BrowserDetection, ((String) -> Void)?) throws -> SessionInfo)? public static func importSession( browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) throws -> SessionInfo { if let override = self.importSessionOverrideForTesting { return try override(browserDetection, logger) } let log: (String) -> Void = { msg in logger?("[alibaba-cookie] \(msg)") } var accessDeniedHints: [String] = [] var failureDetails: [String] = [] let installedBrowsers = self.cookieImportCandidates(browserDetection: browserDetection) log("Cookie import candidates: \(installedBrowsers.map(\.displayName).joined(separator: ", "))") for browserSource in installedBrowsers { do { log("Checking \(browserSource.displayName)") let query = BrowserCookieQuery(domains: self.cookieDomains) let sources = try Self.cookieClient.codexBarRecords( matching: query, in: browserSource, logger: log) if sources.isEmpty { log("No matching cookie records in \(browserSource.displayName)") if let fallbackSession = try Self.importChromiumFallbackSession( browser: browserSource, logger: log) { return fallbackSession } } for source in sources where !source.records.isEmpty { let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin) if self.isAuthenticatedSession(cookies: httpCookies) { log("Found \(httpCookies.count) Alibaba cookies in \(source.label)") return SessionInfo(cookies: httpCookies, sourceLabel: source.label) } let cookieNames = Set(httpCookies.map(\.name)) let hasTicket = cookieNames.contains("login_aliyunid_ticket") let hasAccount = cookieNames.contains("login_aliyunid_pk") || cookieNames.contains("login_current_pk") || cookieNames.contains("login_aliyunid") log("Skipping \(source.label): missing auth cookies (ticket=\(hasTicket), account=\(hasAccount))") } if let fallbackSession = try Self.importChromiumFallbackSession(browser: browserSource, logger: log) { return fallbackSession } } catch let error as BrowserCookieError { BrowserCookieAccessGate.recordIfNeeded(error) if let hint = error.accessDeniedHint { accessDeniedHints.append(hint) } failureDetails.append("\(browserSource.displayName): \(error.localizedDescription)") log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)") } catch { failureDetails.append("\(browserSource.displayName): \(error.localizedDescription)") log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)") } } let details = (Array(Set(accessDeniedHints)).sorted() + Array(Set(failureDetails)).sorted()) .joined(separator: " ") throw AlibabaCodingPlanSettingsError.missingCookie(details: details.isEmpty ? nil : details) } private static func isAuthenticatedSession(cookies: [HTTPCookie]) -> Bool { guard !cookies.isEmpty else { return false } let names = Set(cookies.map(\.name)) let hasTicket = names.contains("login_aliyunid_ticket") let hasAccount = names.contains("login_aliyunid_pk") || names.contains("login_current_pk") || names.contains("login_aliyunid") return hasTicket && hasAccount } public static func hasSession( browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) -> Bool { do { _ = try self.importSession(browserDetection: browserDetection, logger: logger) return true } catch { return false } } private static func importChromiumFallbackSession( browser: Browser, logger: ((String) -> Void)? = nil) throws -> SessionInfo? { guard browser.usesChromiumProfileStore else { return nil } return try AlibabaChromiumCookieFallbackImporter.importSession( browser: browser, domains: self.cookieDomains, logger: logger) } static func cookieImportCandidates( browserDetection: BrowserDetection, importOrder: BrowserCookieImportOrder = alibabaCookieImportOrder) -> [Browser] { importOrder.cookieImportCandidates(using: browserDetection) } static func matchesCookieDomain(_ domain: String, patterns: [String] = Self.cookieDomains) -> Bool { let normalized = self.normalizeCookieDomain(domain) return patterns.contains { pattern in let normalizedPattern = self.normalizeCookieDomain(pattern) return normalized == normalizedPattern || normalized.hasSuffix(".\(normalizedPattern)") } } static func normalizeCookieDomain(_ domain: String) -> String { let trimmed = domain.trimmingCharacters(in: .whitespacesAndNewlines) let normalized = trimmed.hasPrefix(".") ? String(trimmed.dropFirst()) : trimmed return normalized.lowercased() } } enum AlibabaChromiumCookieFallbackImporter { private struct ChromiumCookieRecord { let domain: String let name: String let path: String let value: String let expires: Date? let isSecure: Bool } enum ImportError: LocalizedError { case keyUnavailable(browser: Browser) case keychainDenied(browser: Browser) case sqliteFailed(label: String, details: String) var errorDescription: String? { switch self { case let .keyUnavailable(browser): "\(browser.displayName) Safe Storage key not found." case let .keychainDenied(browser): "macOS Keychain denied access to \(browser.displayName) Safe Storage." case let .sqliteFailed(label, details): "\(label) cookie fallback failed: \(details)" } } } static func importSession( browser: Browser, domains: [String], cookieClient: BrowserCookieClient = BrowserCookieClient(), logger: ((String) -> Void)? = nil) throws -> AlibabaCodingPlanCookieImporter.SessionInfo? { let stores = try cookieClient.codexBarStores(for: browser).filter { $0.databaseURL != nil } guard !stores.isEmpty else { return nil } logger?("[alibaba-cookie] Trying \(browser.displayName) Chromium fallback") let keys = try self.derivedKeys(for: browser) for store in stores { let cookies = try self.loadCookies(from: store, domains: domains, keys: keys) guard !cookies.isEmpty else { continue } if self.isAuthenticatedSession(cookies) { logger?("[alibaba-cookie] Found \(cookies.count) Alibaba cookies via \(store.label) fallback") return AlibabaCodingPlanCookieImporter.SessionInfo(cookies: cookies, sourceLabel: store.label) } } return nil } private static func isAuthenticatedSession(_ cookies: [HTTPCookie]) -> Bool { let names = Set(cookies.map(\.name)) let hasTicket = names.contains("login_aliyunid_ticket") let hasAccount = names.contains("login_aliyunid_pk") || names.contains("login_current_pk") || names.contains("login_aliyunid") return hasTicket && hasAccount } private static func loadCookies( from store: BrowserCookieStore, domains: [String], keys: [Data]) throws -> [HTTPCookie] { guard let sourceDB = store.databaseURL else { return [] } let records = try self.readCookiesFromLockedDB( sourceDB: sourceDB, domains: domains, keys: keys, label: store.label) return records.compactMap(self.makeCookie) } private static func readCookiesFromLockedDB( sourceDB: URL, domains: [String], keys: [Data], label: String) throws -> [ChromiumCookieRecord] { let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent("alibaba-chromium-cookies-\(UUID().uuidString)", isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let copiedDB = tempDir.appendingPathComponent("Cookies") try FileManager.default.copyItem(at: sourceDB, to: copiedDB) for suffix in ["-wal", "-shm"] { let src = URL(fileURLWithPath: sourceDB.path + suffix) if FileManager.default.fileExists(atPath: src.path) { let dst = URL(fileURLWithPath: copiedDB.path + suffix) try? FileManager.default.copyItem(at: src, to: dst) } } defer { try? FileManager.default.removeItem(at: tempDir) } return try self.readCookies(fromDB: copiedDB.path, domains: domains, keys: keys, label: label) } private static func readCookies( fromDB path: String, domains: [String], keys: [Data], label: String) throws -> [ChromiumCookieRecord] { var db: OpaquePointer? guard sqlite3_open_v2(path, &db, SQLITE_OPEN_READONLY, nil) == SQLITE_OK else { throw ImportError.sqliteFailed(label: label, details: String(cString: sqlite3_errmsg(db))) } defer { sqlite3_close(db) } let sql = "SELECT host_key, name, path, expires_utc, is_secure, value, encrypted_value FROM cookies" var stmt: OpaquePointer? guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else { throw ImportError.sqliteFailed(label: label, details: String(cString: sqlite3_errmsg(db))) } defer { sqlite3_finalize(stmt) } var records: [ChromiumCookieRecord] = [] while sqlite3_step(stmt) == SQLITE_ROW { guard let hostKey = self.readText(stmt, index: 0), self.matches(domain: hostKey, patterns: domains) else { continue } guard let name = self.readText(stmt, index: 1), let path = self.readText(stmt, index: 2) else { continue } let value: String? = if let plain = self.readText(stmt, index: 5), !plain.isEmpty { plain } else if let encrypted = self.readBlob(stmt, index: 6) { self.decrypt(encrypted, usingAnyOf: keys) } else { nil } guard let value, !value.isEmpty else { continue } records.append(ChromiumCookieRecord( domain: AlibabaCodingPlanCookieImporter.normalizeCookieDomain(hostKey), name: name, path: path, value: value, expires: self.chromiumExpiry(sqlite3_column_int64(stmt, 3)), isSecure: sqlite3_column_int(stmt, 4) != 0)) } return records.filter { record in guard let expires = record.expires else { return true } return expires >= Date() } } private static func derivedKeys(for browser: Browser) throws -> [Data] { var keys: [Data] = [] var sawDenied = false for label in browser.safeStorageLabels { switch KeychainAccessPreflight.checkGenericPassword(service: label.service, account: label.account) { case .interactionRequired: sawDenied = true continue case .allowed, .notFound, .failure: break } if let password = self.safeStoragePassword(service: label.service, account: label.account) { keys.append(self.deriveKey(from: password)) } } if !keys.isEmpty { return keys } if sawDenied { throw ImportError.keychainDenied(browser: browser) } throw ImportError.keyUnavailable(browser: browser) } private static func safeStoragePassword(service: String, account: String) -> String? { // The preflight classifies prompt-requiring items as .interactionRequired, but its // .notFound (gate disabled) and .failure outcomes still reach this read. Honor the // access gate and keep the read strictly non-interactive so it can never prompt. guard !KeychainAccessGate.isDisabled else { return nil } var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnData as String: true, ] KeychainNoUIQuery.apply(to: &query) var result: AnyObject? let status = KeychainSecurity.copyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } private static func deriveKey(from password: String) -> Data { let salt = Data("saltysalt".utf8) var key = Data(count: kCCKeySizeAES128) let keyLength = key.count _ = key.withUnsafeMutableBytes { keyBytes in password.utf8CString.withUnsafeBytes { passBytes in salt.withUnsafeBytes { saltBytes in CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), passBytes.bindMemory(to: Int8.self).baseAddress, passBytes.count - 1, saltBytes.bindMemory(to: UInt8.self).baseAddress, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), 1003, keyBytes.bindMemory(to: UInt8.self).baseAddress, keyLength) } } } return key } private static func decrypt(_ encryptedValue: Data, usingAnyOf keys: [Data]) -> String? { for key in keys { if let value = self.decrypt(encryptedValue, key: key) { return value } } return nil } private static func decrypt(_ encryptedValue: Data, key: Data) -> String? { guard encryptedValue.count > 3 else { return nil } let prefix = String(data: encryptedValue.prefix(3), encoding: .utf8) guard prefix == "v10" else { return nil } let payload = Data(encryptedValue.dropFirst(3)) let iv = Data(repeating: 0x20, count: kCCBlockSizeAES128) var outLength = 0 var out = Data(count: payload.count + kCCBlockSizeAES128) let outCapacity = out.count let status = out.withUnsafeMutableBytes { outBytes in payload.withUnsafeBytes { payloadBytes in key.withUnsafeBytes { keyBytes in iv.withUnsafeBytes { ivBytes in CCCrypt( CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), keyBytes.baseAddress, key.count, ivBytes.baseAddress, payloadBytes.baseAddress, payload.count, outBytes.baseAddress, outCapacity, &outLength) } } } } guard status == kCCSuccess else { return nil } out.count = outLength if let value = String(data: out, encoding: .utf8), !value.isEmpty { return value } if out.count > 32 { let trimmed = out.dropFirst(32) if let value = String(data: trimmed, encoding: .utf8), !value.isEmpty { return value } } return nil } private static func makeCookie(from record: ChromiumCookieRecord) -> HTTPCookie? { var properties: [HTTPCookiePropertyKey: Any] = [ .domain: record.domain, .path: record.path, .name: record.name, .value: record.value, ] if record.isSecure { properties[.secure] = true } if let expires = record.expires { properties[.expires] = expires } return HTTPCookie(properties: properties) } private static func readText(_ stmt: OpaquePointer?, index: Int32) -> String? { guard sqlite3_column_type(stmt, index) != SQLITE_NULL, let value = sqlite3_column_text(stmt, index) else { return nil } return String(cString: value) } private static func readBlob(_ stmt: OpaquePointer?, index: Int32) -> Data? { guard sqlite3_column_type(stmt, index) != SQLITE_NULL, let bytes = sqlite3_column_blob(stmt, index) else { return nil } return Data(bytes: bytes, count: Int(sqlite3_column_bytes(stmt, index))) } private static func matches(domain: String, patterns: [String]) -> Bool { AlibabaCodingPlanCookieImporter.matchesCookieDomain(domain, patterns: patterns) } private static func chromiumExpiry(_ expiresUTC: Int64) -> Date? { guard expiresUTC > 0 else { return nil } let seconds = (Double(expiresUTC) / 1_000_000.0) - 11_644_473_600.0 guard seconds > 0 else { return nil } return Date(timeIntervalSince1970: seconds) } } #endif