Files
OxideJS-New/crates/oxide_builtins/src/ta_codec.rs
T

552 lines
22 KiB
Rust
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.
//! Uint8Array base64/hex 编解码纯函数层(无 VM 依赖)。
//!
//! 承载 FromBase64/FromHex 状态机与编码输出,供 typed_array.rs 的
//! setFromBase64/setFromHex/toBase64/toHex/fromBase64/fromHex 六个内置共用。
//! 解码逐块产出 `(read, written)`:`read` 计全部已消费字符(含跳过的 ASCII
//! 空白),`written` 为已写入字节数;`Err(DecodeError)` 统一表示 SyntaxError。已解码
//! 块在出错前经 `write` 回调逐个写出(setFrom 族"前块已写入后抛错"语义)。
//! 解码失败统一以 `Err(DecodeError)` 表示,绑定层映射为 SyntaxError。
/// 解码失败标记(绑定层映射为 SyntaxError)。
#[derive(Debug, Clone, Copy)]
pub struct DecodeError;
/// base64 字母表:标准表(`+/`)与 URL 安全表(`-_`)。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Base64Alphabet {
Standard,
Url,
}
/// 尾块处理模式(lastChunkHandling 三取值)。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LastChunkHandling {
Loose,
Strict,
StopBeforePartial,
}
const B64_ALPHABET_STANDARD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const B64_ALPHABET_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
fn b64_table(alphabet: Base64Alphabet) -> &'static [u8; 64] {
match alphabet {
Base64Alphabet::Standard => B64_ALPHABET_STANDARD,
Base64Alphabet::Url => B64_ALPHABET_URL,
}
}
/// 判 ASCII 空白(0x09/0x0A/0x0C/0x0D/0x20 五码;VT 与所有非 ASCII 空格均不算)。
fn is_base64_whitespace(unit: u16) -> bool {
matches!(unit, 0x09 | 0x0A | 0x0C | 0x0D | 0x20)
}
/// 取 base64 数据字符的值(0-63);`=` 与任何非字母表字符返回 `None`。
fn base64_value(unit: u16, alphabet: Base64Alphabet) -> Option<u32> {
match unit {
0x41..=0x5a => Some(unit as u32 - 0x41),
0x61..=0x7a => Some(unit as u32 - 0x61 + 26),
0x30..=0x39 => Some(unit as u32 - 0x30 + 52),
0x2b if alphabet == Base64Alphabet::Standard => Some(62),
0x2f if alphabet == Base64Alphabet::Standard => Some(63),
0x2d if alphabet == Base64Alphabet::Url => Some(62),
0x5f if alphabet == Base64Alphabet::Url => Some(63),
_ => None,
}
}
/// 解码一个满 4 字符块:校验 padding 位置(`=` 只可居 3/4 位)与数据字符,
/// strict 模式另查 padding 位必须为 0。
///
/// # 边界与前提
/// - `chunk` 恰 4 字符;调用方保证只在此处解码满块。
/// - 返回 `(24 位值, 解码字节数 1-3)`:24 位值按 4 槽位左对齐(padding 槽补 0),
/// 字节数 = 3 - padding 数。
fn decode_full_chunk(
chunk: [u16; 4], alphabet: Base64Alphabet, handling: LastChunkHandling,
) -> Result<(u32, usize), DecodeError> {
let pad = match (chunk[2] == 0x3d, chunk[3] == 0x3d) {
(false, false) => 0,
(false, true) => 1,
(true, true) => 2,
// `XX=X`:padding 居第 3 位,四种模式全非法。
(true, false) => return Err(DecodeError),
};
let data = &chunk[..4 - pad];
let mut bits: u32 = 0;
for &c in data {
let v = base64_value(c, alphabet).ok_or(DecodeError)?;
bits = (bits << 6) | v;
}
bits <<= 6 * pad as u32;
if handling == LastChunkHandling::Strict && pad > 0 {
// padding 位即末数据字符的低 2/4 位(pad 1 查 2 位、pad 2 查 4 位)。
let last = base64_value(*data.last().unwrap(), alphabet).unwrap();
let mask = if pad == 1 { 0b11 } else { 0b1111 };
if last & mask != 0 {
return Err(DecodeError);
}
}
Ok((bits, 3 - pad))
}
/// 把满块解码出的 24 位值按字节数经回调写出,返回新的已写字节数。
fn write_chunk_bytes(write: &mut dyn FnMut(usize, u8), written: usize, bits: u32, count: usize) -> usize {
for i in 0..count {
write(written + i, (bits >> (16 - 8 * i)) as u8);
}
written + count
}
/// 尾块(1-3 个非空白字符,已越过全部满块)的终态裁决。
///
/// # 边界与前提
/// - 1 字符尾块:`=` 三种模式全抛;数据字符仅 stop-before-partial 停(成功);
/// - 含 `=` 尾块:唯一放行形态是 3 字符 `XX=` 且 stop-before-partial(停在不写
/// 出的块前);`X==`/`X=`/`=` 居首等其余 padding 形态三种模式全抛;
/// - 全数据 2-3 字符尾块:strict 抛、stop-before-partial 停在块前、loose 解
/// `clen - 1` 字节(2 字符 1 字节、3 字符 2 字节)并写出。
/// - 成功 `read`:解出字节的 loose 路径取块后消费位置;各"停在块前"路径
/// (sbp、余量不足)取块前位置 `chunk_start`。
#[allow(clippy::too_many_arguments)]
fn finish_partial(
chunk: [u16; 4], clen: usize, alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
write: &mut dyn FnMut(usize, u8), mut written: usize, chunk_start: usize, read: usize,
) -> Result<(usize, usize), DecodeError> {
if (0..clen).any(|i| chunk[i] == 0x3d) {
let trailing = (0..clen).rev().take_while(|&i| chunk[i] == 0x3d).count();
if clen == 3 && trailing == 1 && handling == LastChunkHandling::StopBeforePartial {
for &c in &chunk[..2] {
base64_value(c, alphabet).ok_or(DecodeError)?;
}
return Ok((chunk_start, written));
}
return Err(DecodeError);
}
for &c in &chunk[..clen] {
base64_value(c, alphabet).ok_or(DecodeError)?;
}
match handling {
LastChunkHandling::StopBeforePartial => Ok((chunk_start, written)),
LastChunkHandling::Strict => Err(DecodeError),
LastChunkHandling::Loose => {
if clen == 1 {
return Err(DecodeError);
}
// maxLength 余量不足时不消费该块直接停(不写出)。
if let Some(m) = max_len {
if written + clen - 1 > m {
return Ok((chunk_start, written));
}
}
let v = |i: usize| base64_value(chunk[i], alphabet).unwrap();
write(written, (v(0) << 2 | v(1) >> 4) as u8);
written += 1;
if clen == 3 {
write(written, ((v(1) << 4 | v(2) >> 2) & 0xFF) as u8);
written += 1;
}
Ok((read, written))
}
}
}
/// FromBase64 状态机:按块消费输入串,已解码字节经 `write` 回调逐块写出。
///
/// # 步骤
/// 1. `max_len == 0` 短路:输入整体忽略,直接成功空结果。
/// 2. 循环收集至多 4 个非空白字符(跳过五码 ASCII 空白)组成块。
/// 3. 满块先过 maxLength 余量判定(不足则块前停、不消费、不写出),再解码写出;
/// 写出后恰好补齐 maxLength 即成功返回。
/// 4. 块不足 4 字符时走终态裁决([`finish_partial`])。
///
/// # 边界与前提
/// - `units` 为输入串的 UTF-16 单元序列;非 ASCII 码元一律是非法字符。
/// - 出错时 `write` 已产生的写入保留(调用方承载"前块已写入"语义)。
/// - `Ok((read, written))` 中 `read` 为成功语义下的消费位置(含跳过空白,
/// 停止路径为块前位置)。
pub fn decode_base64(
units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
write: &mut dyn FnMut(usize, u8),
) -> Result<(usize, usize), DecodeError> {
if max_len == Some(0) {
return Ok((0, 0));
}
let n = units.len();
let mut read = 0usize;
let mut written = 0usize;
loop {
let chunk_start = read;
let mut chunk = [0u16; 4];
let mut clen = 0usize;
while clen < 4 && read < n {
let c = units[read];
read += 1;
if is_base64_whitespace(c) {
continue;
}
chunk[clen] = c;
clen += 1;
}
match clen {
4 => {
if chunk[2] == 0x3d || chunk[3] == 0x3d {
// 带 padding 的满块只可作末块:其后还有非空白字符时
// ("extra padding")在写出该块字节前抛。
if units[read..].iter().any(|&c| !is_base64_whitespace(c)) {
return Err(DecodeError);
}
}
let (bits, count) = decode_full_chunk(chunk, alphabet, handling)?;
if let Some(m) = max_len {
if written + count > m {
return Ok((chunk_start, written));
}
}
written = write_chunk_bytes(write, written, bits, count);
if let Some(m) = max_len {
if written == m {
return Ok((read, written));
}
}
}
0 => return Ok((read, written)),
_ => return finish_partial(chunk, clen, alphabet, handling, max_len, write, written, chunk_start, read),
}
}
}
/// 取单个 hex 字符值(大小写宽容);非 hex 字符返回 `None`。
fn hex_value(unit: u16) -> Option<u8> {
match unit {
0x30..=0x39 => Some(unit as u8 - 0x30),
0x61..=0x66 => Some(unit as u8 - 0x61 + 10),
0x41..=0x46 => Some(unit as u8 - 0x41 + 10),
_ => None,
}
}
/// FromHex 状态机:无空白跳过、无 padding;长度奇数在最前判定
/// (`read` 0、零写入);坏字符抛错时保留已写出的前字节。
///
/// # 边界与前提
/// - `units` 为输入串的 UTF-16 单元序列;任何非 hex 字符(含所有空白)非法。
/// - 每对解码前判 `written + 1 > max_len` 则对该对前停(不消费、不写出)。
pub fn decode_hex(
units: &[u16], max_len: Option<usize>, write: &mut dyn FnMut(usize, u8),
) -> Result<(usize, usize), DecodeError> {
if units.len() % 2 == 1 {
return Err(DecodeError);
}
let mut written = 0usize;
let mut read = 0usize;
for (i, pair) in units.chunks_exact(2).enumerate() {
let hi = hex_value(pair[0]).ok_or(DecodeError)?;
let lo = hex_value(pair[1]).ok_or(DecodeError)?;
if let Some(m) = max_len {
if written + 1 > m {
return Ok((i * 2, written));
}
}
write(written, hi * 16 + lo);
written += 1;
read = i * 2 + 2;
}
Ok((read, written))
}
/// 编码 base64:标准表或 URL 表;`omit_padding` 时剥尾部 `=`。
pub fn encode_base64(bytes: &[u8], alphabet: Base64Alphabet, omit_padding: bool) -> String {
let table = b64_table(alphabet);
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let bits = (chunk[0] as u32) << 16
| (chunk.get(1).copied().unwrap_or(0) as u32) << 8
| chunk.get(2).copied().unwrap_or(0) as u32;
out.push(table[(bits >> 18) as usize] as char);
out.push(table[((bits >> 12) & 0x3f) as usize] as char);
if chunk.len() > 1 {
out.push(table[((bits >> 6) & 0x3f) as usize] as char);
} else if !omit_padding {
out.push('=');
}
if chunk.len() > 2 {
out.push(table[(bits & 0x3f) as usize] as char);
} else if !omit_padding {
out.push('=');
}
}
out
}
/// 编码小写两位 hex。
pub fn encode_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for &b in bytes {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0f) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn units(s: &str) -> Vec<u16> {
s.chars().map(|c| c as u16).collect()
}
fn decode(
units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
) -> Result<(usize, Vec<u8>), DecodeError> {
let mut bytes = Vec::new();
decode_base64(units, alphabet, handling, max_len, &mut |_, b| bytes.push(b))
.map(|(r, w)| (r, bytes.into_iter().take(w).collect()))
}
fn decode_hex_full(units: &[u16], max_len: Option<usize>) -> Result<(usize, Vec<u8>), DecodeError> {
let mut bytes = Vec::new();
decode_hex(units, max_len, &mut |_, b| bytes.push(b)).map(|(r, w)| (r, bytes.into_iter().take(w).collect()))
}
/// RFC 4648 标准向量:编码/解码双向。
#[test]
fn rfc4648_vectors_roundtrip() {
let vectors: [(&str, &[u8]); 7] = [
("", &[]),
("Zg==", &[102]),
("Zm8=", &[102, 111]),
("Zm9v", &[102, 111, 111]),
("Zm9vYg==", &[102, 111, 111, 98]),
("Zm9vYmE=", &[102, 111, 111, 98, 97]),
("Zm9vYmFy", &[102, 111, 111, 98, 97, 114]),
];
for (s, bytes) in vectors {
assert_eq!(encode_base64(bytes, Base64Alphabet::Standard, false), s);
let (read, decoded) = decode(&units(s), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap();
assert_eq!(decoded, *bytes);
assert_eq!(read, s.chars().count());
}
}
#[test]
fn encode_base64_url_alphabet_and_omit_padding() {
assert_eq!(encode_base64(&[199, 239, 242], Base64Alphabet::Standard, false), "x+/y");
assert_eq!(encode_base64(&[199, 239, 242], Base64Alphabet::Url, false), "x-_y");
assert_eq!(encode_base64(&[199, 239], Base64Alphabet::Standard, false), "x+8=");
assert_eq!(encode_base64(&[199, 239], Base64Alphabet::Standard, true), "x+8");
assert_eq!(encode_base64(&[255], Base64Alphabet::Standard, true), "/w");
assert_eq!(encode_base64(&[255], Base64Alphabet::Url, true), "_w");
}
#[test]
fn decode_base64_whitespace_and_read_count() {
for ws in [' ', '\t', '\n', '\u{0C}', '\r'] {
let s = format!("Z{ws}g==");
let (read, bytes) = decode(&units(&s), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap();
assert_eq!(bytes, vec![102]);
assert_eq!(read, 5);
}
// VT 不是 ASCII 空白:非法字符。
assert!(decode(&units("Z\u{0B}g=="), Base64Alphabet::Standard, LastChunkHandling::Loose, None).is_err());
}
#[test]
fn decode_base64_strict_padding_bits() {
// 末数据字符低 2 位非 0(`h` = 33 = 0b100001):strict 抛,其余过。
for (h, ok) in [
(LastChunkHandling::Loose, true),
(LastChunkHandling::Strict, false),
(LastChunkHandling::StopBeforePartial, true),
] {
let r = decode(&units("ZXhhZh=="), Base64Alphabet::Standard, h, None);
assert_eq!(r.is_ok(), ok, "ZXhhZh== {h:?}");
if ok {
assert_eq!(r.unwrap().1, vec![101, 120, 97, 102]);
}
}
// 低 4 位非 0(`h` 居 pad 2 末位):strict 抛;`g` 低 4 位全 0 则过。
assert!(decode(&units("ZXhhZg=="), Base64Alphabet::Standard, LastChunkHandling::Strict, None).is_ok());
assert!(decode(&units("Zg1="), Base64Alphabet::Standard, LastChunkHandling::Strict, None).is_err());
// pad 2 块字节值:`Zg==` → 102。
assert_eq!(
decode(&units("Zg=="), Base64Alphabet::Standard, LastChunkHandling::Loose, None)
.unwrap()
.1,
vec![102]
);
// pad 1 块字节值:`Zm8=` → [102, 111]。
assert_eq!(
decode(&units("Zm8="), Base64Alphabet::Standard, LastChunkHandling::Loose, None)
.unwrap()
.1,
vec![102, 111]
);
}
#[test]
fn decode_base64_last_chunk_matrix() {
let cases: [(&str, [bool; 3]); 13] = [
// (输入, [loose, strict, sbp] 是否成功)
("A", [false, false, true]),
("ABCDA", [false, false, true]),
("AA=", [false, false, true]),
("aQ=", [false, false, true]),
("ABCDAA=", [false, false, true]),
("AAA", [true, false, true]),
("ZXhhZg", [true, false, true]),
("ZXhhZg=", [false, false, true]),
("ZXhhZg===", [false, false, false]),
("=", [false, false, false]),
("A==", [false, false, false]),
("AAAA=", [false, false, false]),
("ZXhhZgg", [true, false, true]),
];
for (s, expect) in cases {
for (h, ok) in [
(LastChunkHandling::Loose, expect[0]),
(LastChunkHandling::Strict, expect[1]),
(LastChunkHandling::StopBeforePartial, expect[2]),
] {
let r = decode(&units(s), Base64Alphabet::Standard, h, None);
assert_eq!(r.is_ok(), ok, "{s:?} {h:?}");
}
}
// sbp 停止路径:前块保留、尾块不消费。
let (read, bytes) =
decode(&units("ABCDAA="), Base64Alphabet::Standard, LastChunkHandling::StopBeforePartial, None).unwrap();
assert_eq!((read, bytes), (4, vec![0, 16, 131]));
let (read, bytes) =
decode(&units("ZXhhZg"), Base64Alphabet::Standard, LastChunkHandling::StopBeforePartial, None).unwrap();
assert_eq!((read, bytes), (4, vec![101, 120, 97]));
// loose 对 2 字符尾块解 1 字节。
let (read, bytes) = decode(&units("ZXhhZg"), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap();
assert_eq!((read, bytes), (6, vec![101, 120, 97, 102]));
// loose 对 3 字符尾块解 2 字节。
let (read, bytes) =
decode(&units("ZXhhZgg"), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap();
assert_eq!((read, bytes), (7, vec![101, 120, 97, 102, 8]));
}
#[test]
fn decode_base64_max_length_stop_timing() {
// 块前停:第 2 块 3 字节会超限,不消费(read 停在前值 4)。
let (read, bytes) =
decode(&units("Zm9vYmFy"), Base64Alphabet::Standard, LastChunkHandling::Loose, Some(5)).unwrap();
assert_eq!((read, bytes), (4, vec![102, 111, 111]));
// 恰好补齐:`YmE=` 出 2 字节补到 5。
let (read, bytes) =
decode(&units("Zm9vYmE="), Base64Alphabet::Standard, LastChunkHandling::Loose, Some(5)).unwrap();
assert_eq!((read, bytes), (8, vec![102, 111, 111, 98, 97]));
// 零长短路:垃圾输入整体忽略。
let (read, bytes) =
decode(&units("aaaa#"), Base64Alphabet::Standard, LastChunkHandling::Strict, Some(0)).unwrap();
assert_eq!((read, bytes), (0, Vec::new()));
// 尾 3 字符块余量不足:不消费。
let (read, bytes) =
decode(&units("Zm9vYmE"), Base64Alphabet::Standard, LastChunkHandling::Loose, Some(3)).unwrap();
assert_eq!((read, bytes), (4, vec![102, 111, 111]));
}
#[test]
fn decode_base64_illegal_characters() {
for s in ["Zm.9v", "Zm9v^", "Zg==&", "Z\u{2212}==", "Zg\u{00A0}==", "Zg\u{2009}==", "Zg\u{2028}=="] {
assert!(
decode(&units(s), Base64Alphabet::Standard, LastChunkHandling::Loose, None).is_err(),
"{s:?}"
);
}
}
#[test]
fn decode_base64_url_alphabet_decode() {
let (read, bytes) = decode(&units("x-_y"), Base64Alphabet::Url, LastChunkHandling::Loose, None).unwrap();
assert_eq!((read, bytes), (4, vec![199, 239, 242]));
// URL 表中标准表符号非法。
assert!(decode(&units("x+/y"), Base64Alphabet::Url, LastChunkHandling::Loose, None).is_err());
assert!(decode(&units("x-_y"), Base64Alphabet::Standard, LastChunkHandling::Loose, None).is_err());
}
#[test]
fn decode_base64_writes_up_to_error() {
// 坏字符前已写出的字节保留(setFrom 语义同)。
let mut out = Vec::new();
let r = decode_base64(
&units("MjYyZm.9v"),
Base64Alphabet::Standard,
LastChunkHandling::Loose,
None,
&mut |i, b| out.insert(i, b),
);
assert!(r.is_err());
assert_eq!(out, vec![50, 54, 50]);
// 带 padding 满块后随非空白字符:该块字节不写出(extra padding)。
let mut out = Vec::new();
let r = decode_base64(
&units("MjYyZg==="),
Base64Alphabet::Standard,
LastChunkHandling::Loose,
None,
&mut |i, b| out.insert(i, b),
);
assert!(r.is_err());
assert_eq!(out, vec![50, 54, 50]);
}
#[test]
fn decode_hex_vectors_and_case_insensitive() {
let cases: [(&str, &[u8]); 5] = [
("", &[]),
("66", &[102]),
("666f", &[102, 111]),
("666F", &[102, 111]),
("666f6f626172", &[102, 111, 111, 98, 97, 114]),
];
for (s, bytes) in cases {
let (read, decoded) = decode_hex_full(&units(s), None).unwrap();
assert_eq!(decoded, *bytes);
assert_eq!(read, s.len());
}
}
#[test]
fn decode_hex_odd_and_max_length() {
assert!(decode_hex_full(&units("a"), None).is_err());
assert!(decode_hex_full(&units("aaa"), None).is_err());
// 奇数在最前判定:零长目标也抛(零写入)。
assert!(decode_hex_full(&units("1"), Some(0)).is_err());
// 对前停:2 字节目标下 `aabbcc` 停在第 3 对前(read 4)。
let (read, bytes) = decode_hex_full(&units("aabbcc"), Some(2)).unwrap();
assert_eq!((read, bytes), (4, vec![0xaa, 0xbb]));
// 恰好补齐。
let (read, bytes) = decode_hex_full(&units("aabbcc"), Some(3)).unwrap();
assert_eq!((read, bytes), (6, vec![0xaa, 0xbb, 0xcc]));
}
#[test]
fn decode_hex_illegal_characters() {
for s in ["a.a", "aa^", "a a", "a\ta", "a\u{00A0}a", "a\u{2009}a", "a\u{2028}a"] {
assert!(decode_hex_full(&units(s), None).is_err(), "{s:?}");
}
}
#[test]
fn decode_hex_writes_up_to_error() {
// 前对写出、坏对抛错。
let mut out = Vec::new();
let r = decode_hex(&units("aaag"), None, &mut |i, b| out.insert(i, b));
assert!(r.is_err());
assert_eq!(out, vec![0xaa]);
}
#[test]
fn encode_hex_lowercase() {
assert_eq!(encode_hex(&[]), "");
assert_eq!(encode_hex(&[102]), "66");
assert_eq!(encode_hex(&[102, 111, 111, 98, 97, 114]), "666f6f626172");
assert_eq!(encode_hex(&[170, 187, 204]), "aabbcc");
}
}