mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
c49cf96a16
* fix(grokbuild): complete proxy integration * fix(deeplink): preview GrokBuild configs safely * test(app): stabilize provider integration suite * fix(grokbuild): address review feedback * fix(grokbuild): resolve remaining review findings * fix(grokbuild): use native sessions and harden previews
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
/**
|
||
* 把 URL-safe Base64(RFC 4648 §5)归一到 `atob` 认识的标准字母表,
|
||
* 并还原被 URL 解析吃掉的 `+`(会变成空格)。
|
||
*
|
||
* 标准 Base64 的字母表里不存在 `-` 与 `_`,因此这两条替换不会误伤标准输入。
|
||
*
|
||
* ⚠️ 这里必须与后端 `deeplink/utils.rs::decode_base64_param` 的语义保持一致。
|
||
* 后端依次尝试 STANDARD / STANDARD_NO_PAD / URL_SAFE / URL_SAFE_NO_PAD 四种引擎;
|
||
* 前端若只认标准字母表,同一条链接就会出现「确认框显示不透明 Base64 或空列表、
|
||
* 后端却解码并保存了真实内容」的错位——即用户在看不见 payload 的情况下点了确认。
|
||
*/
|
||
function toStandardBase64Alphabet(value: string): string {
|
||
return value.replace(/ /g, "+").replace(/-/g, "+").replace(/_/g, "/");
|
||
}
|
||
|
||
function trimOuterLineBreaks(value: string): string {
|
||
return value.replace(/^[\r\n]+|[\r\n]+$/g, "");
|
||
}
|
||
|
||
/**
|
||
* Decode Base64 encoded UTF-8 string
|
||
*
|
||
* This function handles various Base64 edge cases that can occur when
|
||
* Base64 strings are passed through URLs:
|
||
* - Spaces (URL parsing may convert '+' to space)
|
||
* - Missing padding ('=' characters)
|
||
* - Standard and URL-safe alphabets (RFC 4648 §4 and §5)
|
||
*
|
||
* @param str - Base64 encoded string
|
||
* @returns Decoded UTF-8 string
|
||
*/
|
||
export function decodeBase64Utf8(str: string): string {
|
||
try {
|
||
// Keep spaces intact until they are restored to `+`. Using `trim()` here
|
||
// would discard a URL-decoded `+` at either edge and diverge from the
|
||
// backend decoder, which trims only CR/LF characters.
|
||
let cleaned = toStandardBase64Alphabet(trimOuterLineBreaks(str));
|
||
|
||
// Try to decode with standard Base64 first
|
||
try {
|
||
const binString = atob(cleaned);
|
||
const bytes = Uint8Array.from(binString, (m) => m.codePointAt(0)!);
|
||
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
||
} catch (e1) {
|
||
// If standard fails, try adding padding
|
||
const remainder = cleaned.length % 4;
|
||
if (remainder !== 0) {
|
||
cleaned += "=".repeat(4 - remainder);
|
||
}
|
||
const binString = atob(cleaned);
|
||
const bytes = Uint8Array.from(binString, (m) => m.codePointAt(0)!);
|
||
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
||
}
|
||
} catch (e) {
|
||
console.error("Base64 decode error:", e, "Input:", str);
|
||
// Last resort fallback using deprecated but sometimes working method
|
||
try {
|
||
return decodeURIComponent(
|
||
escape(atob(toStandardBase64Alphabet(trimOuterLineBreaks(str)))),
|
||
);
|
||
} catch {
|
||
// If all else fails, return original string
|
||
return str;
|
||
}
|
||
}
|
||
}
|