Files
2026-07-13 12:22:33 +08:00

164 lines
6.3 KiB
Swift

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
struct AlibabaTokenPlanCookieHeaders {
private static let cachedAPIHeaderName = "__codexbar_alibaba_token_plan_api"
private static let cachedDashboardHeaderName = "__codexbar_alibaba_token_plan_dashboard"
let apiCookieHeader: String
let dashboardCookieHeader: String
init(apiCookieHeader: String, dashboardCookieHeader: String) {
self.apiCookieHeader = apiCookieHeader
self.dashboardCookieHeader = dashboardCookieHeader
}
init?(singleHeader raw: String?) {
guard let normalized = CookieHeaderNormalizer.normalize(raw) else { return nil }
self.apiCookieHeader = normalized
self.dashboardCookieHeader = normalized
}
init?(cachedHeader raw: String?) {
var valuesByName: [String: String] = [:]
for pair in CookieHeaderNormalizer.pairs(from: raw ?? "") {
valuesByName[pair.name] = pair.value
}
if let encodedAPI = valuesByName[Self.cachedAPIHeaderName],
let encodedDashboard = valuesByName[Self.cachedDashboardHeaderName],
let apiHeader = Self.decodeCachedHeader(encodedAPI),
let dashboardHeader = Self.decodeCachedHeader(encodedDashboard),
let normalizedAPI = CookieHeaderNormalizer.normalize(apiHeader),
let normalizedDashboard = CookieHeaderNormalizer.normalize(dashboardHeader)
{
self.init(apiCookieHeader: normalizedAPI, dashboardCookieHeader: normalizedDashboard)
return
}
self.init(singleHeader: raw)
}
var cacheCookieHeader: String {
[
"\(Self.cachedAPIHeaderName)=\(Self.encodeCachedHeader(self.apiCookieHeader))",
"\(Self.cachedDashboardHeaderName)=\(Self.encodeCachedHeader(self.dashboardCookieHeader))",
].joined(separator: "; ")
}
var apiCookieNames: [String] {
Self.cookieNames(from: self.apiCookieHeader)
}
var dashboardCookieNames: [String] {
Self.cookieNames(from: self.dashboardCookieHeader)
}
func hasCookie(named name: String) -> Bool {
Self.cookieNames(from: self.apiCookieHeader).contains(name) ||
Self.cookieNames(from: self.dashboardCookieHeader).contains(name)
}
private static func cookieNames(from header: String) -> [String] {
CookieHeaderNormalizer.pairs(from: header)
.map(\.name)
.filter { !$0.isEmpty }
.uniquedSorted()
}
private static func encodeCachedHeader(_ header: String) -> String {
Data(header.utf8).base64EncodedString()
}
private static func decodeCachedHeader(_ encoded: String) -> String? {
guard let data = Data(base64Encoded: encoded) else { return nil }
return String(data: data, encoding: .utf8)
}
}
enum AlibabaTokenPlanCookieHeader {
static func headers(
from cookies: [HTTPCookie],
region: AlibabaTokenPlanAPIRegion = .international,
environment: [String: String] = ProcessInfo.processInfo.environment) -> AlibabaTokenPlanCookieHeaders?
{
guard let apiHeader = self.header(
from: cookies,
targetURL: AlibabaTokenPlanUsageFetcher.resolveQuotaURL(region: region, environment: environment)),
let dashboardHeader = self.header(
from: cookies,
targetURL: AlibabaTokenPlanUsageFetcher.dashboardURL(region: region, environment: environment))
else {
return nil
}
return AlibabaTokenPlanCookieHeaders(apiCookieHeader: apiHeader, dashboardCookieHeader: dashboardHeader)
}
static func header(from cookies: [HTTPCookie], targetURL: URL) -> String? {
var byName: [String: HTTPCookie] = [:]
for cookie in cookies {
guard !cookie.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { continue }
guard !cookie.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { continue }
if let expiry = cookie.expiresDate, expiry < Date() { continue }
guard self.matchesRequestURL(cookie: cookie, url: targetURL) else { continue }
if let existing = byName[cookie.name] {
if self.cookieSortKey(for: cookie) >= self.cookieSortKey(for: existing) {
byName[cookie.name] = cookie
}
} else {
byName[cookie.name] = cookie
}
}
guard !byName.isEmpty else { return nil }
return byName.keys.sorted().compactMap { name in
guard let cookie = byName[name] else { return nil }
return "\(cookie.name)=\(cookie.value)"
}.joined(separator: "; ")
}
private static func matchesRequestURL(cookie: HTTPCookie, url: URL) -> Bool {
guard let host = url.host?.lowercased() else { return false }
let normalizedDomain = cookie.domain.lowercased().trimmingCharacters(in: CharacterSet(charactersIn: "."))
guard !normalizedDomain.isEmpty else { return false }
guard host == normalizedDomain || host.hasSuffix(".\(normalizedDomain)") else { return false }
let cookiePath = cookie.path.isEmpty ? "/" : cookie.path
let requestPath = url.path.isEmpty ? "/" : url.path
if requestPath == cookiePath {
return true
}
guard requestPath.hasPrefix(cookiePath) else { return false }
guard cookiePath != "/" else { return true }
if cookiePath.hasSuffix("/") {
return true
}
guard
let boundaryIndex = requestPath.index(
requestPath.startIndex,
offsetBy: cookiePath.count,
limitedBy: requestPath.endIndex),
boundaryIndex < requestPath.endIndex
else {
return true
}
return requestPath[boundaryIndex] == "/"
}
private static func cookieSortKey(for cookie: HTTPCookie) -> (Int, Int, Date) {
let pathLength = cookie.path.count
let normalizedDomain = cookie.domain.lowercased().trimmingCharacters(in: CharacterSet(charactersIn: "."))
let domainLength = normalizedDomain.count
let expiry = cookie.expiresDate ?? .distantPast
return (pathLength, domainLength, expiry)
}
}
extension [String] {
fileprivate func uniquedSorted() -> [String] {
Array(Set(self)).sorted()
}
}