XuqmGroup-RNSDK/packages/common/security/base64.js

86 行
3.3 KiB
JavaScript

'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 }