import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif public enum AlibabaTokenPlanUsageError: LocalizedError, Sendable, Equatable { case loginRequired case invalidCredentials case apiError(String) case networkError(String) case parseFailed(String) public var errorDescription: String? { switch self { case .loginRequired: "Alibaba Token Plan login required." case .invalidCredentials: "Alibaba Token Plan credentials are invalid." case let .apiError(message): "Alibaba Token Plan API error: \(message)" case let .networkError(message): "Alibaba Token Plan network error: \(message)" case let .parseFailed(message): "Could not parse Alibaba Token Plan usage: \(message)" } } } // swiftlint:disable:next type_body_length public struct AlibabaTokenPlanUsageFetcher: Sendable { private static let log = CodexBarLog.logger("alibaba-token-plan") private static let bssServiceCode = "BssOpenAPI-V3" private static let subscriptionSummaryAction = "GetSubscriptionSummary" private static let browserLikeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" private static let safariLikeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " + "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15" public static var dashboardURL: URL { Self.dashboardURL(region: .international, environment: ProcessInfo.processInfo.environment) } public static func dashboardURL( region: AlibabaTokenPlanAPIRegion, environment: [String: String] = ProcessInfo.processInfo.environment) -> URL { if let host = AlibabaTokenPlanSettingsReader.hostOverride(environment: environment), let base = ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: host), var components = URLComponents(url: base, resolvingAgainstBaseURL: false), let dashboardComponents = URLComponents(url: region.dashboardURL, resolvingAgainstBaseURL: false) { components.path = dashboardComponents.path components.percentEncodedQuery = dashboardComponents.percentEncodedQuery components.fragment = dashboardComponents.fragment return components.url ?? region.dashboardURL } return region.dashboardURL } public static func fetchUsage( cookieHeader: String, region: AlibabaTokenPlanAPIRegion = .international, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date()) async throws -> AlibabaTokenPlanUsageSnapshot { guard let headers = AlibabaTokenPlanCookieHeaders(singleHeader: cookieHeader) else { throw AlibabaTokenPlanSettingsError.invalidCookie } return try await self.fetchUsage( apiCookieHeader: headers.apiCookieHeader, dashboardCookieHeader: headers.dashboardCookieHeader, region: region, environment: environment, now: now) } static func fetchUsage( apiCookieHeader: String, dashboardCookieHeader: String, region: AlibabaTokenPlanAPIRegion = .international, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), session overrideSession: URLSession? = nil) async throws -> AlibabaTokenPlanUsageSnapshot { guard let normalizedAPIHeader = CookieHeaderNormalizer.normalize(apiCookieHeader), let normalizedDashboardHeader = CookieHeaderNormalizer.normalize(dashboardCookieHeader) else { throw AlibabaTokenPlanSettingsError.invalidCookie } let url = self.resolveQuotaURL(region: region, environment: environment) let apiRedirectDiagnostics = RedirectDiagnostics(cookieHeader: normalizedAPIHeader) let dashboardRedirectDiagnostics: RedirectDiagnostics? let apiSession: URLSession let dashboardSession: URLSession if let overrideSession { apiSession = overrideSession dashboardSession = overrideSession dashboardRedirectDiagnostics = nil } else { let dashboardDiagnostics = RedirectDiagnostics(cookieHeader: normalizedDashboardHeader) apiSession = URLSession( configuration: .default, delegate: apiRedirectDiagnostics, delegateQueue: nil) dashboardSession = URLSession( configuration: .default, delegate: dashboardDiagnostics, delegateQueue: nil) dashboardRedirectDiagnostics = dashboardDiagnostics } defer { if overrideSession == nil { apiSession.invalidateAndCancel() dashboardSession.invalidateAndCancel() } } let secToken = await self.resolveSECToken( dashboardCookieHeader: normalizedDashboardHeader, apiCookieHeader: normalizedAPIHeader, region: region, environment: environment, session: dashboardSession) Self.log.info( "Fetching Alibaba Token Plan usage", metadata: [ "apiHost": url.host ?? "unknown", "region": region.rawValue, "apiCookieNames": self.cookieNamesDescription(from: normalizedAPIHeader), "dashboardCookieNames": self.cookieNamesDescription(from: normalizedDashboardHeader), "hasCSRF": self.hasCSRF(in: normalizedAPIHeader) ? "1" : "0", "secTokenSource": secToken == nil ? "missing" : "resolved", ]) var request = URLRequest(url: url) request.httpMethod = "POST" request.timeoutInterval = 20 request.httpBody = self.subscriptionSummaryRequestBody(region: region, secToken: secToken) request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.setValue("*/*", forHTTPHeaderField: "Accept") request.setValue(normalizedAPIHeader, forHTTPHeaderField: "Cookie") if let csrf = self.extractCookieValue(name: "login_aliyunid_csrf", from: normalizedAPIHeader) ?? self.extractCookieValue(name: "csrf", from: normalizedAPIHeader) { request.setValue(csrf, forHTTPHeaderField: "x-xsrf-token") request.setValue(csrf, forHTTPHeaderField: "x-csrf-token") } request.setValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With") request.setValue(Self.browserLikeUserAgent, forHTTPHeaderField: "User-Agent") request.setValue(region.dashboardOriginURLString, forHTTPHeaderField: "Origin") request.setValue( Self.dashboardURL(region: region, environment: environment).absoluteString, forHTTPHeaderField: "Referer") let data: Data let response: URLResponse do { (data, response) = try await apiSession.data(for: request) } catch { Self.log.error( "Alibaba Token Plan request failed", metadata: [ "apiHost": url.host ?? "unknown", "error": error.localizedDescription, ]) throw AlibabaTokenPlanUsageError.networkError(error.localizedDescription) } if let dashboardRedirectDiagnostics, !dashboardRedirectDiagnostics.redirects.isEmpty { Self.log.info( "Alibaba Token Plan dashboard redirects", metadata: [ "count": "\(dashboardRedirectDiagnostics.redirects.count)", "items": dashboardRedirectDiagnostics.redirects.joined(separator: " | "), ]) } if !apiRedirectDiagnostics.redirects.isEmpty { Self.log.info( "Alibaba Token Plan redirects", metadata: [ "count": "\(apiRedirectDiagnostics.redirects.count)", "items": apiRedirectDiagnostics.redirects.joined(separator: " | "), ]) } guard let httpResponse = response as? HTTPURLResponse else { Self.log.error("Alibaba Token Plan response was not HTTP") throw AlibabaTokenPlanUsageError.networkError("Invalid response") } Self.log.info( "Alibaba Token Plan HTTP response", metadata: [ "status": "\(httpResponse.statusCode)", "bodyBytes": "\(data.count)", "contentType": httpResponse.value(forHTTPHeaderField: "Content-Type") ?? "none", ]) guard httpResponse.statusCode == 200 else { if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 { throw AlibabaTokenPlanUsageError.loginRequired } Self.log.error("Alibaba Token Plan returned HTTP \(httpResponse.statusCode)") throw AlibabaTokenPlanUsageError.apiError("HTTP \(httpResponse.statusCode)") } return try self.parseUsageSnapshot(from: data, now: now) } static func resolveQuotaURL( region: AlibabaTokenPlanAPIRegion, environment: [String: String]) -> URL { if let override = AlibabaTokenPlanSettingsReader.quotaURL(environment: environment) { return override } if let host = AlibabaTokenPlanSettingsReader.hostOverride(environment: environment), let hostURL = self.quotaURL(from: host) { return hostURL } return self.defaultQuotaURL(region: region) } static var defaultQuotaURL: URL { self.defaultQuotaURL(region: .international) } static func defaultQuotaURL(region: AlibabaTokenPlanAPIRegion) -> URL { var components = URLComponents(string: region.gatewayBaseURLString)! components.path = "/data/api.json" components.queryItems = [ URLQueryItem(name: "action", value: Self.subscriptionSummaryAction), URLQueryItem(name: "product", value: Self.bssServiceCode), URLQueryItem(name: "_tag", value: ""), ] return components.url! } static func parseUsageSnapshot(from data: Data, now: Date = Date()) throws -> AlibabaTokenPlanUsageSnapshot { guard !data.isEmpty else { throw AlibabaTokenPlanUsageError.parseFailed("Empty response body") } let object: Any do { object = try JSONSerialization.jsonObject(with: data, options: []) } catch { if self.isLikelyLoginHTML(data) { throw AlibabaTokenPlanUsageError.loginRequired } throw AlibabaTokenPlanUsageError.parseFailed("Invalid JSON response") } let expanded = self.expandedJSON(object) guard let dictionary = expanded as? [String: Any] else { throw AlibabaTokenPlanUsageError.parseFailed("Unexpected payload") } try self.throwIfErrorPayload(dictionary) let summary = self.findSubscriptionSummary(in: dictionary) ?? dictionary let total = self.anyDouble(for: Self.totalQuotaKeys, in: summary) let remaining = self.anyDouble(for: Self.remainingQuotaKeys, in: summary) let used = self.anyDouble(for: Self.usedQuotaKeys, in: summary) ?? total.flatMap { total in remaining.map { max(0, total - $0) } } let resetsAt = self.findResetDate(in: summary) ?? self.findResetDate(in: dictionary) let totalCount = self.anyDouble(for: Self.subscriptionCountKeys, in: summary) let planName = self.findPlanName(in: summary) ?? ((totalCount ?? 0) > 0 || total != nil ? "TOKEN PLAN" : nil) if planName == nil, total == nil, used == nil, remaining == nil, totalCount == nil { let diagnostics = self.payloadDiagnostics(payload: dictionary) Self.log.error("Alibaba Token Plan payload missing expected fields: \(diagnostics)") throw AlibabaTokenPlanUsageError.parseFailed("Missing token plan data (\(diagnostics))") } return AlibabaTokenPlanUsageSnapshot( planName: planName, usedQuota: used, totalQuota: total, remainingQuota: remaining, resetsAt: resetsAt, updatedAt: now) } private static func subscriptionSummaryRequestBody(region: AlibabaTokenPlanAPIRegion, secToken: String?) -> Data { let paramsObject = ["ProductCode": region.tokenPlanProductCode] guard let paramsData = try? JSONSerialization.data(withJSONObject: paramsObject, options: []), let paramsString = String(data: paramsData, encoding: .utf8) else { return Data() } var components = URLComponents() var queryItems = [ URLQueryItem(name: "product", value: Self.bssServiceCode), URLQueryItem(name: "action", value: Self.subscriptionSummaryAction), URLQueryItem(name: "params", value: paramsString), URLQueryItem(name: "region", value: region.currentRegionID), ] if let secToken, !secToken.isEmpty { queryItems.append(URLQueryItem(name: "sec_token", value: secToken)) } components.queryItems = queryItems return Data((components.percentEncodedQuery ?? "").utf8) } private static func resolveSECToken( dashboardCookieHeader: String, apiCookieHeader: String, region: AlibabaTokenPlanAPIRegion, environment: [String: String], session: URLSession) async -> String? { let cookieSECToken = self.extractCookieValue(name: "sec_token", from: dashboardCookieHeader) ?? self.extractCookieValue(name: "sec_token", from: apiCookieHeader) var request = URLRequest(url: self.dashboardURL(region: region, environment: environment)) request.httpMethod = "GET" request.timeoutInterval = 10 request.setValue(dashboardCookieHeader, forHTTPHeaderField: "Cookie") request.setValue(Self.safariLikeUserAgent, forHTTPHeaderField: "User-Agent") request.setValue( "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", forHTTPHeaderField: "Accept") if let (data, response) = try? await session.data(for: request), let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let html = String(data: data, encoding: .utf8), let token = self.extractSECToken(from: html) { Self.log.info( "Resolved Alibaba Token Plan sec_token from dashboard HTML", metadata: [ "dashboardHost": request.url?.host ?? "unknown", "htmlBytes": "\(data.count)", ]) return token } if let token = await self.fetchSECTokenFromUserInfo( cookieHeader: dashboardCookieHeader, region: region, environment: environment, session: session) { return token } if let cookieSECToken, !cookieSECToken.isEmpty { Self.log.info("Resolved Alibaba Token Plan sec_token from cookies") return cookieSECToken } Self.log.info( "Alibaba Token Plan sec_token missing; continuing with cookie-only request", metadata: [ "dashboardCookieNames": self.cookieNamesDescription(from: dashboardCookieHeader), "apiCookieNames": self.cookieNamesDescription(from: apiCookieHeader), ]) return nil } private static func fetchSECTokenFromUserInfo( cookieHeader: String, region: AlibabaTokenPlanAPIRegion, environment: [String: String], session: URLSession) async -> String? { let baseURL = self.consoleBaseURL(region: region, environment: environment) let userInfoURL = baseURL.appendingPathComponent("tool/user/info.json") var request = URLRequest(url: userInfoURL) request.httpMethod = "GET" request.timeoutInterval = 10 request.setValue(cookieHeader, forHTTPHeaderField: "Cookie") request.setValue(Self.safariLikeUserAgent, forHTTPHeaderField: "User-Agent") request.setValue("application/json, text/plain, */*", forHTTPHeaderField: "Accept") let referer = baseURL.absoluteString.hasSuffix("/") ? baseURL.absoluteString : "\(baseURL.absoluteString)/" request.setValue(referer, forHTTPHeaderField: "Referer") guard let (data, response) = try? await session.data(for: request), let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let object = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil } let expanded = self.expandedJSON(object) guard let token = self.findFirstString(forKeys: ["secToken", "sec_token"], in: expanded), !token.isEmpty else { return nil } Self.log.info( "Resolved Alibaba Token Plan sec_token from user info", metadata: [ "userInfoHost": userInfoURL.host ?? "unknown", "bodyBytes": "\(data.count)", ]) return token } private static func consoleBaseURL( region: AlibabaTokenPlanAPIRegion, environment: [String: String]) -> URL { let dashboard = self.dashboardURL(region: region, environment: environment) var components = URLComponents() components.scheme = dashboard.scheme components.host = dashboard.host components.port = dashboard.port return components.url ?? URL(string: region.dashboardOriginURLString)! } private static func quotaURL(from rawHost: String) -> URL? { let cleaned = AlibabaTokenPlanSettingsReader.cleaned(rawHost) guard let cleaned else { return nil } guard let base = ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: cleaned) else { return nil } var components = URLComponents(url: base, resolvingAgainstBaseURL: false) let defaultComponents = URLComponents( url: Self.defaultQuotaURL(region: .international), resolvingAgainstBaseURL: false) components?.path = "/data/api.json" components?.queryItems = defaultComponents?.queryItems return components?.url } private final class RedirectDiagnostics: NSObject, URLSessionTaskDelegate, @unchecked Sendable { private let cookieHeader: String var redirects: [String] = [] init(cookieHeader: String) { self.cookieHeader = cookieHeader } func urlSession( _: URLSession, task _: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { let from = AlibabaTokenPlanUsageFetcher.redactedURLDescription(response.url) let to = AlibabaTokenPlanUsageFetcher.redactedURLDescription(request.url) self.redirects.append("\(response.statusCode) \(from) -> \(to)") completionHandler(AlibabaTokenPlanUsageFetcher.redirectedRequest( response: response, request: request, cookieHeader: self.cookieHeader)) } } private static func redactedURLDescription(_ url: URL?) -> String { guard let url else { return "unknown" } var components = URLComponents(url: url, resolvingAgainstBaseURL: false) components?.query = nil components?.fragment = nil return components?.string ?? "\(url.scheme ?? "unknown")://\(url.host ?? "unknown")" } static func redirectedRequest( response: HTTPURLResponse, request: URLRequest, cookieHeader: String) -> URLRequest? { guard request.url?.scheme?.lowercased() == "https" else { return nil } var updated = request if self.shouldForwardRedirectCookies(from: response.url, to: request.url) { updated.setValue(cookieHeader, forHTTPHeaderField: "Cookie") } else { updated.setValue(nil, forHTTPHeaderField: "Cookie") } return updated } private static func shouldForwardRedirectCookies(from sourceURL: URL?, to targetURL: URL?) -> Bool { guard let sourceHost = sourceURL?.host?.lowercased(), let targetHost = targetURL?.host?.lowercased() else { return false } return sourceHost == targetHost } private static func throwIfErrorPayload(_ dictionary: [String: Any]) throws { if self.parseBool(dictionary["successResponse"]) == false { if let statusCode = self.findFirstInt(forKeys: ["statusCode", "status_code", "code"], in: dictionary), statusCode == 401 || statusCode == 403 { throw AlibabaTokenPlanUsageError.invalidCredentials } let code = self.findFirstString(forKeys: ["code", "status", "statusCode"], in: dictionary) let message = self.findFirstString(forKeys: ["message", "msg", "statusMessage"], in: dictionary) ?? code ?? "request was not successful" if self.isLoginOrTokenError(code: code, message: message) { throw AlibabaTokenPlanUsageError.loginRequired } throw AlibabaTokenPlanUsageError.apiError(message) } if self.findBoolValues(forKeys: ["Success", "success"], in: dictionary).contains(false) { let code = self.findFirstString(forKeys: ["Code", "code"], in: dictionary) let message = self.findFirstString(forKeys: ["Message", "message", "msg", "Code", "code"], in: dictionary) ?? "request was not successful" if self.isLoginOrTokenError(code: code, message: message) { throw AlibabaTokenPlanUsageError.loginRequired } throw AlibabaTokenPlanUsageError.apiError(message) } if let statusCode = self.findFirstInt(forKeys: ["statusCode", "status_code", "code"], in: dictionary), statusCode != 0, statusCode != 200 { let message = self.findFirstString( forKeys: ["statusMessage", "status_msg", "message", "msg"], in: dictionary) ?? "status code \(statusCode)" if statusCode == 401 || statusCode == 403 { throw AlibabaTokenPlanUsageError.invalidCredentials } throw AlibabaTokenPlanUsageError.apiError(message) } let codeText = self.findFirstString(forKeys: ["code", "status", "statusCode"], in: dictionary)?.lowercased() let messageText = self.findFirstString(forKeys: ["message", "msg", "statusMessage"], in: dictionary)? .lowercased() if self.isLoginOrTokenError(code: codeText, message: messageText) { throw AlibabaTokenPlanUsageError.loginRequired } } private static func isLoginOrTokenError(code: String?, message: String?) -> Bool { let combined = [code, message] .compactMap { $0?.lowercased() } .joined(separator: " ") return combined.contains("needlogin") || combined.contains("login") || combined.contains("postonlyortokenerror") || combined.contains("tokenerror") || combined.contains("request has expired") || combined.contains("refresh page") || combined.contains("请求已经过期") } private static let planNameKeys = [ "planName", "plan_name", "packageName", "package_name", "commodityName", "commodity_name", "instanceName", "instance_name", "displayName", "display_name", "ProductName", "productName", "name", "title", "planType", "plan_type", ] private static let usedQuotaKeys = [ "usedQuota", "used_quota", "usedCredits", "usedCredit", "consumedCredits", "usage", "used", "usedAmount", "consumeAmount", "usedValue", "UsedValue", "consumedValue", "ConsumedValue", ] private static let totalQuotaKeys = [ "totalQuota", "total_quota", "totalCredits", "totalCredit", "quota", "creditLimit", "creditsTotal", "monthlyTotalQuota", "amount", "totalValue", "TotalValue", ] private static let remainingQuotaKeys = [ "remainingQuota", "remainQuota", "remainingCredits", "remainingCredit", "availableCredits", "balance", "remaining", "availableAmount", "remainAmount", "totalSurplusValue", "TotalSurplusValue", "surplusValue", "SurplusValue", ] private static let subscriptionCountKeys = [ "totalCount", "TotalCount", "subscriptionTotalNumber", "SubscriptionTotalNumber", ] private static let resetDateKeys = [ "nextRefreshTime", "resetTime", "periodEndTime", "billingCycleEnd", "billCycleEndTime", "expireTime", "expirationTime", "endTime", "validEndTime", "instanceEndTime", "nearestExpireDate", "NearestExpireDate", ] private static func findSubscriptionSummary(in payload: [String: Any]) -> [String: Any]? { if let data = self.findFirstDictionary( forKeys: ["Data", "data", "successResponse", "success_response"], in: payload), self.containsSubscriptionSummaryFields(data) { return data } return self.findFirstDictionary( matchingAnyKey: Self.usedQuotaKeys + Self.totalQuotaKeys + Self.remainingQuotaKeys + Self.subscriptionCountKeys, in: payload) } private static func containsSubscriptionSummaryFields(_ payload: [String: Any]) -> Bool { let keys = self.usedQuotaKeys + self.totalQuotaKeys + self.remainingQuotaKeys + self.subscriptionCountKeys return keys.contains { payload[$0] != nil } } private static func findPlanName(in payload: [String: Any]) -> String? { self.anyString(for: self.planNameKeys, in: payload) ?? self.findFirstString(forKeys: self.planNameKeys, in: payload) } private static func findResetDate(in payload: [String: Any]) -> Date? { self.anyDate(for: self.resetDateKeys, in: payload) ?? self.findFirstDate(forKeys: self.resetDateKeys, in: payload) } private static func payloadDiagnostics(payload: [String: Any]) -> String { let topKeys = payload.keys.sorted() let dataDict = self.findFirstDictionary( forKeys: ["Data", "data", "successResponse", "success_response"], in: payload) let dataKeys = dataDict?.keys.sorted() ?? [] return "topKeys=\(topKeys.joined(separator: ",")) dataKeys=\(dataKeys.joined(separator: ","))" } private static func isLikelyLoginHTML(_ data: Data) -> Bool { guard let text = String(data: data, encoding: .utf8)?.lowercased() else { return false } return text.contains(" [String: Any]? { if let dict = value as? [String: Any] { for key in keys { if let nested = dict[key] as? [String: Any] { return nested } } for nestedValue in dict.values { if let nested = self.findFirstDictionary(forKeys: keys, in: nestedValue) { return nested } } return nil } if let array = value as? [Any] { for item in array { if let nested = self.findFirstDictionary(forKeys: keys, in: item) { return nested } } } return nil } private static func findFirstDictionary(matchingAnyKey keys: [String], in value: Any) -> [String: Any]? { if let dict = value as? [String: Any] { if keys.contains(where: { dict[$0] != nil }) { return dict } for nestedValue in dict.values { if let nested = self.findFirstDictionary(matchingAnyKey: keys, in: nestedValue) { return nested } } return nil } if let array = value as? [Any] { for item in array { if let nested = self.findFirstDictionary(matchingAnyKey: keys, in: item) { return nested } } } return nil } private static func findFirstString(forKeys keys: [String], in value: Any) -> String? { if let dict = value as? [String: Any] { for key in keys { if let parsed = self.parseString(dict[key]) { return parsed } } for nestedValue in dict.values { if let parsed = self.findFirstString(forKeys: keys, in: nestedValue) { return parsed } } return nil } if let array = value as? [Any] { for item in array { if let parsed = self.findFirstString(forKeys: keys, in: item) { return parsed } } } return nil } private static func findBoolValues(forKeys keys: [String], in value: Any) -> [Bool] { if let dict = value as? [String: Any] { let directValues = keys.compactMap { self.parseBool(dict[$0]) } let nestedValues = dict.values.flatMap { self.findBoolValues(forKeys: keys, in: $0) } return directValues + nestedValues } if let array = value as? [Any] { return array.flatMap { self.findBoolValues(forKeys: keys, in: $0) } } return [] } private static func findFirstInt(forKeys keys: [String], in value: Any) -> Int? { if let dict = value as? [String: Any] { for key in keys { if let parsed = self.parseInt(dict[key]) { return parsed } } for nestedValue in dict.values { if let parsed = self.findFirstInt(forKeys: keys, in: nestedValue) { return parsed } } return nil } if let array = value as? [Any] { for item in array { if let parsed = self.findFirstInt(forKeys: keys, in: item) { return parsed } } } return nil } private static func findFirstDate(forKeys keys: [String], in value: Any) -> Date? { if let dict = value as? [String: Any] { for key in keys { if let parsed = self.parseDate(dict[key]) { return parsed } } for nestedValue in dict.values { if let parsed = self.findFirstDate(forKeys: keys, in: nestedValue) { return parsed } } return nil } if let array = value as? [Any] { for item in array { if let parsed = self.findFirstDate(forKeys: keys, in: item) { return parsed } } } return nil } private static func expandedJSON(_ value: Any) -> Any { if let dict = value as? [String: Any] { var expanded: [String: Any] = [:] expanded.reserveCapacity(dict.count) for (key, nested) in dict { expanded[key] = self.expandedJSON(nested) } return expanded } if let array = value as? [Any] { return array.map { self.expandedJSON($0) } } if let string = value as? String, let data = string.data(using: .utf8), let nested = try? JSONSerialization.jsonObject(with: data, options: []), nested is [String: Any] || nested is [Any] { return self.expandedJSON(nested) } return value } private static func anyString(for keys: [String], in dict: [String: Any]) -> String? { for key in keys { if let value = self.parseString(dict[key]) { return value } } return nil } private static func anyDouble(for keys: [String], in dict: [String: Any]) -> Double? { for key in keys { if let value = self.parseDouble(dict[key]) { return value } } return nil } private static func anyDate(for keys: [String], in dict: [String: Any]) -> Date? { for key in keys { if let value = self.parseDate(dict[key]) { return value } } return nil } private static func anyBool(for keys: [String], in dict: [String: Any]) -> Bool? { for key in keys { if let value = self.parseBool(dict[key]) { return value } } return nil } private static func parseInt(_ raw: Any?) -> Int? { if let value = raw as? Int { return value } if let value = raw as? Int64 { return Int(value) } if let value = raw as? Double { return Int(value) } if let value = raw as? NSNumber { return value.intValue } if let value = self.parseString(raw) { return Int(value) } return nil } private static func parseDouble(_ raw: Any?) -> Double? { if let value = raw as? Double { return value } if let value = raw as? Int { return Double(value) } if let value = raw as? Int64 { return Double(value) } if let value = raw as? NSNumber { return value.doubleValue } if let value = self.parseString(raw) { let cleaned = value.replacingOccurrences(of: ",", with: "") return Double(cleaned) } return nil } private static func parseString(_ raw: Any?) -> String? { guard let value = raw as? String else { return nil } let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? nil : trimmed } private static func parseDate(_ raw: Any?) -> Date? { if let intValue = self.parseInt(raw) { if intValue > 1_000_000_000_000 { return Date(timeIntervalSince1970: TimeInterval(intValue) / 1000) } if intValue > 1_000_000_000 { return Date(timeIntervalSince1970: TimeInterval(intValue)) } } if let string = self.parseString(raw) { let formatter = ISO8601DateFormatter() if let date = formatter.date(from: string) { return date } let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") for format in ["yyyy-MM-dd", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss"] { dateFormatter.dateFormat = format if let date = dateFormatter.date(from: string) { return date } } } return nil } private static func parseBool(_ raw: Any?) -> Bool? { if let value = raw as? Bool { return value } if let number = raw as? NSNumber { return number.boolValue } guard let string = self.parseString(raw)?.lowercased() else { return nil } switch string { case "true", "1", "yes", "active", "valid", "normal": return true case "false", "0", "no", "inactive", "invalid", "expired": return false default: return nil } } private static func extractCookieValue(name: String, from cookieHeader: String) -> String? { cookieHeader .split(separator: ";") .compactMap { part -> (String, String)? in let pieces = part.split(separator: "=", maxSplits: 1).map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } guard pieces.count == 2 else { return nil } return (pieces[0], pieces[1]) } .first { $0.0 == name }? .1 } private static func hasCSRF(in cookieHeader: String) -> Bool { self.extractCookieValue(name: "login_aliyunid_csrf", from: cookieHeader) != nil || self.extractCookieValue(name: "csrf", from: cookieHeader) != nil } static func cookieNames(from cookieHeader: String) -> [String] { CookieHeaderNormalizer.pairs(from: cookieHeader) .map(\.name) .filter { !$0.isEmpty } .uniquedSorted() } static func cookieNamesDescription(from cookieHeader: String) -> String { let names = self.cookieNames(from: cookieHeader) return names.isEmpty ? "none" : names.joined(separator: ",") } private static func extractSECToken(from html: String) -> String? { let patterns = [ #""secToken"\s*:\s*"([^"]+)""#, #""sec_token"\s*:\s*"([^"]+)""#, #"secToken['"]?\s*[:=]\s*['"]([^'"]+)['"]"#, #"sec_token['"]?\s*[:=]\s*['"]([^'"]+)['"]"#, ] for pattern in patterns { if let token = self.matchFirstGroup(pattern: pattern, in: html), !token.isEmpty { return token } } return nil } private static func matchFirstGroup(pattern: String, in text: String) -> String? { guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil } let range = NSRange(text.startIndex.. 1, let valueRange = Range(match.range(at: 1), in: text) else { return nil } let value = text[valueRange].trimmingCharacters(in: .whitespacesAndNewlines) return value.isEmpty ? nil : String(value) } } extension [String] { fileprivate func uniquedSorted() -> [String] { Array(Set(self)).sorted() } }