NewHome/assets/js/codec.js

241 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* codec.js —— 编码/加解密纯前端算法库
* 提供UTF-8、Base64、Base32、URL、Unicode、SHA 系列Web Crypto 原生)
* MD5 / SHA-224 依赖 CryptoJS由页面懒加载双 CDN fallback
*/
(function (global) {
'use strict';
/* ---------- UTF-8 工具 ---------- */
function utf8Encode(str) {
if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(str);
// 降级实现
var out = [], ch, i, code;
for (i = 0; i < str.length; i++) {
code = str.charCodeAt(i);
if (code < 0x80) out.push(code);
else if (code < 0x800) {
out.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
} else if (code < 0xd800 || code >= 0xe000) {
out.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
} else {
// surrogate pair
ch = (code - 0xd800) * 0x400 + (str.charCodeAt(++i) - 0xdc00) + 0x10000;
out.push(0xf0 | (ch >> 18), 0x80 | ((ch >> 12) & 0x3f), 0x80 | ((ch >> 6) & 0x3f), 0x80 | (ch & 0x3f));
}
}
return new Uint8Array(out);
}
function utf8Decode(bytes) {
if (typeof TextDecoder !== 'undefined') return new TextDecoder('utf-8').decode(bytes);
var out = '', i = 0, b1, b2, b3, b4, code;
while (i < bytes.length) {
b1 = bytes[i++];
if (b1 < 0x80) { out += String.fromCharCode(b1); }
else if (b1 < 0xe0) { b2 = bytes[i++]; out += String.fromCharCode(((b1 & 0x1f) << 6) | (b2 & 0x3f)); }
else if (b1 < 0xf0) {
b2 = bytes[i++]; b3 = bytes[i++];
out += String.fromCharCode(((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f));
} else {
b2 = bytes[i++]; b3 = bytes[i++]; b4 = bytes[i++];
code = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
code -= 0x10000;
out += String.fromCharCode(0xd800 + (code >> 10), 0xdc00 + (code & 0x3ff));
}
}
return out;
}
function bytesToBin(bytes) {
var bin = '', i;
for (i = 0; i < bytes.length; i++) {
bin += String.fromCharCode(bytes[i]);
}
return bin;
}
function binToBytes(bin) {
var out = new Uint8Array(bin.length), i;
for (i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
return out;
}
/* ---------- Base64UTF-8 安全) ---------- */
function b64Encode(str) {
var bytes = utf8Encode(str);
// 浏览器环境用 btoa
if (typeof btoa !== 'undefined') {
var bin = '';
for (var i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
return btoa(bin);
}
return global.Base64 && global.Base64.encode(bytesToBin(bytes)) ? global.Base64.encode(bytesToBin(bytes)) : '';
}
function b64Decode(b64) {
b64 = String(b64 || '').replace(/\s+/g, '');
var bin;
if (typeof atob !== 'undefined') {
bin = atob(b64);
} else if (global.Base64) {
bin = global.Base64.decode(b64);
} else {
throw new Error('当前环境不支持 Base64 解码');
}
return utf8Decode(binToBytes(bin));
}
/* ---------- Base32RFC 4648UTF-8 安全) ---------- */
var B32_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
function base32Encode(str) {
var bytes = utf8Encode(str), out = '', bits = 0, value = 0, i;
for (i = 0; i < bytes.length; i++) {
value = (value << 8) | bytes[i];
bits += 8;
while (bits >= 5) {
out += B32_ALPHA[(value >>> (bits - 5)) & 31];
bits -= 5;
}
}
if (bits > 0) out += B32_ALPHA[(value << (5 - bits)) & 31];
while (out.length % 8 !== 0) out += '=';
return out;
}
function base32Decode(b32) {
b32 = String(b32 || '').toUpperCase().replace(/[=\s]/g, '');
if (!b32) return '';
var bits = 0, value = 0, out = [], i, idx, b;
for (i = 0; i < b32.length; i++) {
idx = B32_ALPHA.indexOf(b32[i]);
if (idx === -1) throw new Error('Base32 字符串包含非法字符: ' + b32[i]);
value = (value << 5) | idx;
bits += 5;
if (bits >= 8) {
b = (value >>> (bits - 8)) & 0xff;
out.push(b);
bits -= 8;
}
}
return utf8Decode(new Uint8Array(out));
}
/* ---------- URL 编解码 ---------- */
function urlEncode(str) { return encodeURIComponent(String(str)); }
function urlDecode(str) { return decodeURIComponent(String(str).replace(/\+/g, ' ')); }
/* ---------- Unicode 编解码支持中文、emoji ---------- */
function unicodeEncode(str) {
str = String(str);
var out = [], i, code;
for (i = 0; i < str.length; i++) {
code = str.charCodeAt(i);
if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
// 代理对:按两个 \uXXXX 输出JS 自动组合
out.push('\\u' + code.toString(16).toUpperCase().padStart(4, '0'));
out.push('\\u' + str.charCodeAt(++i).toString(16).toUpperCase().padStart(4, '0'));
} else {
out.push('\\u' + code.toString(16).toUpperCase().padStart(4, '0'));
}
}
return out.join('');
}
function unicodeDecode(str) {
str = String(str);
// 兼容 \u{1F600} 与 \uXXXX 两种写法
str = str.replace(/\\u\{([0-9a-fA-F]{1,6})\}/g, function (m, h) {
var cp = parseInt(h, 16);
if (cp > 0xffff) {
cp -= 0x10000;
return String.fromCharCode(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff));
}
return String.fromCharCode(cp);
});
str = str.replace(/\\u([0-9a-fA-F]{4})/g, function (m, h) {
return String.fromCharCode(parseInt(h, 16));
});
return str;
}
/* ---------- SHA 系列Web Crypto 原生) ---------- */
var HASHES = { 'SHA-1': 'SHA-1', 'SHA-256': 'SHA-256', 'SHA-384': 'SHA-384', 'SHA-512': 'SHA-512' };
function hexFromBuffer(buf) {
var bytes = new Uint8Array(buf), out = '', i;
for (i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, '0');
return out;
}
function shaDigest(algorithm, text) {
var bytes = utf8Encode(text);
if (typeof crypto !== 'undefined' && crypto.subtle) {
return crypto.subtle.digest(algorithm, bytes).then(hexFromBuffer);
}
// 非安全上下文(如普通 http无 Web Crypto回退 crypto-js
return cryptoJsDigest(algorithm, text);
}
/* ---------- CryptoJS 懒加载MD5 / SHA-224 等需要) ---------- */
var CDN_CRYPTO_JS = [
'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js',
'https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js'
];
var cryptoJSPromise = null;
function loadCryptoJS() {
if (global.CryptoJS) return Promise.resolve(global.CryptoJS);
if (cryptoJSPromise) return cryptoJSPromise;
cryptoJSPromise = new Promise(function (resolve) {
var load = function (idx) {
if (global.CryptoJS) { resolve(global.CryptoJS); return; }
if (idx >= CDN_CRYPTO_JS.length) { resolve(null); return; }
var s = document.createElement('script');
s.src = CDN_CRYPTO_JS[idx];
s.onload = function () { resolve(global.CryptoJS || null); };
s.onerror = function () { load(idx + 1); };
document.head.appendChild(s);
};
load(0);
});
return cryptoJSPromise;
}
var CRYPTO_JS_METHODS = {
'MD5': 'MD5', 'SHA-1': 'SHA1', 'SHA-224': 'SHA224',
'SHA-256': 'SHA256', 'SHA-384': 'SHA384', 'SHA-512': 'SHA512'
};
function cryptoJsDigest(algo, text) {
return loadCryptoJS().then(function (C) {
if (!C) throw new Error('计算 ' + algo + ' 需要联网加载加密组件crypto-js失败请检查网络');
var method = CRYPTO_JS_METHODS[algo];
if (!method || typeof C[method] !== 'function') throw new Error('不支持的算法: ' + algo);
return C[method](text).toString(C.enc.Hex);
});
}
function hashText(algo, text) {
if (HASHES[algo]) return shaDigest(HASHES[algo], text);
if (algo === 'MD5' || algo === 'SHA-224') return cryptoJsDigest(algo, text);
return Promise.reject(new Error('未知算法: ' + algo));
}
/* ---------- 导出 ---------- */
global.CodecLib = {
utf8Encode: utf8Encode,
utf8Decode: utf8Decode,
b64Encode: b64Encode,
b64Decode: b64Decode,
base32Encode: base32Encode,
base32Decode: base32Decode,
urlEncode: urlEncode,
urlDecode: urlDecode,
unicodeEncode: unicodeEncode,
unicodeDecode: unicodeDecode,
hashText: hashText,
hashes: ['MD5', 'SHA-1', 'SHA-224', 'SHA-256', 'SHA-384', 'SHA-512']
};
})(window);