977 lines · 38.0 KB · Swift
RawDownload
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_lengthpublic struct AlibabaTokenPlanUsageFetcher: Sendable {    private static let log = CodexBarLog.logger("alibaba-token-plan")    private static let gatewayBaseURLString = "https://bailian.console.aliyun.com"    private static let dashboardOriginURLString = "https://bailian.console.aliyun.com"    private static let currentRegionID = "cn-beijing"    private static let bssServiceCode = "BssOpenAPI-V3"    private static let subscriptionSummaryAction = "GetSubscriptionSummary"    private static let tokenPlanProductCode = "sfm_tokenplanteams_dp_cn"    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 {        URL(string: "https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/token-plan")!    }     public static func fetchUsage(        cookieHeader: String,        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,            environment: environment,            now: now)    }     static func fetchUsage(        apiCookieHeader: String,        dashboardCookieHeader: String,        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(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,            environment: environment,            session: dashboardSession)        Self.log.info(            "Fetching Alibaba Token Plan usage",            metadata: [                "apiHost": url.host ?? "unknown",                "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(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(Self.dashboardOriginURLString, forHTTPHeaderField: "Origin")        request.setValue(Self.dashboardURL.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(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    }     static var defaultQuotaURL: URL {        var components = URLComponents(string: Self.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(secToken: String?) -> Data {        let paramsObject = ["ProductCode": Self.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: Self.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,        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(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,            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,        environment: [String: String],        session: URLSession) async -> String?    {        let baseURL = self.consoleBaseURL(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(environment: [String: String]) -> URL {        let dashboard = self.dashboardURL(environment: environment)        var components = URLComponents()        components.scheme = dashboard.scheme        components.host = dashboard.host        components.port = dashboard.port        return components.url ?? URL(string: Self.dashboardOriginURLString)!    }     private static func quotaURL(from rawHost: String) -> URL? {        let cleaned = AlibabaTokenPlanSettingsReader.cleaned(rawHost)        guard let cleaned else { return nil }        let base = URL(string: cleaned)?.scheme == nil ? URL(string: "https://\(cleaned)") : URL(string: cleaned)        guard let base else { return nil }        var components = URLComponents(url: base, resolvingAgainstBaseURL: false)        let defaultComponents = URLComponents(url: Self.defaultQuotaURL, resolvingAgainstBaseURL: false)        components?.path = "/data/api.json"        components?.queryItems = defaultComponents?.queryItems        return components?.url    }     static func dashboardURL(environment: [String: String]) -> URL {        if let host = AlibabaTokenPlanSettingsReader.hostOverride(environment: environment),           let base = URL(string: host)?.scheme == nil ? URL(string: "https://\(host)") : URL(string: host),           var components = URLComponents(url: base, resolvingAgainstBaseURL: false),           let dashboardComponents = URLComponents(url: dashboardURL, resolvingAgainstBaseURL: false)        {            components.path = dashboardComponents.path            components.percentEncodedQuery = dashboardComponents.percentEncodedQuery            components.fragment = dashboardComponents.fragment            return components.url ?? self.dashboardURL        }        return Self.dashboardURL    }     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"],

Lines 1–500 of 977.