'use strict' const STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' const URL_SAFE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' /** * 纯 JS Base64 编码,默认输出带填充的标准 Base64。 * 协议调用方必须显式声明 URL-safe 与是否保留填充,避免现场字符串替换。 */ function encodeBase64(value, options = {}) { if (!(value instanceof Uint8Array)) throw new TypeError('Base64 value must be Uint8Array') const alphabet = options.urlSafe ? URL_SAFE_ALPHABET : STANDARD_ALPHABET const padding = options.padding !== false let output = '' for (let offset = 0; offset < value.length; offset += 3) { const first = value[offset] const hasSecond = offset + 1 < value.length const hasThird = offset + 2 < value.length const second = hasSecond ? value[offset + 1] : 0 const third = hasThird ? value[offset + 2] : 0 output += alphabet[first >>> 2] output += alphabet[((first & 0x03) << 4) | (second >>> 4)] if (hasSecond) { output += alphabet[((second & 0x0f) << 2) | (third >>> 6)] } else if (padding) { output += '=' } if (hasThird) { output += alphabet[third & 0x3f] } else if (padding) { output += '=' } } return output } /** * 严格解码标准 Base64 或 Base64URL,不依赖浏览器/Hermes 的 atob。 * 接受有填充或无填充形式,但拒绝混合字母表、非法长度、非法填充和非零尾随位。 */ function decodeBase64(value) { if (typeof value !== 'string') throw new TypeError('Base64 value must be a string') if (value.length === 0) return new Uint8Array(0) if (/[^A-Za-z0-9+/_=-]/.test(value)) throw new Error('Base64 contains invalid characters') if (/[+\/]/.test(value) && /[-_]/.test(value)) { throw new Error('Base64 cannot mix standard and URL-safe alphabets') } const firstPadding = value.indexOf('=') const data = firstPadding === -1 ? value : value.slice(0, firstPadding) const padding = firstPadding === -1 ? '' : value.slice(firstPadding) if (padding !== '' && padding !== '=' && padding !== '==') { throw new Error('Base64 padding is invalid') } if (data.includes('=')) throw new Error('Base64 padding is invalid') const remainder = data.length % 4 if (remainder === 1) throw new Error('Base64 length is invalid') const expectedPadding = remainder === 0 ? 0 : 4 - remainder if (padding.length !== 0 && padding.length !== expectedPadding) { throw new Error('Base64 padding length is invalid') } const normalized = data.replace(/-/g, '+').replace(/_/g, '/') const outputLength = Math.floor((normalized.length * 6) / 8) const output = new Uint8Array(outputLength) let accumulator = 0 let bitCount = 0 let outputIndex = 0 for (const character of normalized) { const sextet = STANDARD_ALPHABET.indexOf(character) if (sextet < 0) throw new Error('Base64 contains invalid characters') accumulator = (accumulator << 6) | sextet bitCount += 6 if (bitCount >= 8) { bitCount -= 8 output[outputIndex] = (accumulator >>> bitCount) & 0xff outputIndex += 1 accumulator &= (1 << bitCount) - 1 } } if (accumulator !== 0) throw new Error('Base64 has non-zero trailing bits') return output } module.exports = { decodeBase64, encodeBase64 }