rspack_base64/
lib.rs

1pub mod base64 {
2  use std::{borrow::Cow, sync::LazyLock};
3
4  use base64_simd::{Base64 as Raw, Error, STANDARD};
5  use regex::Regex;
6
7  pub struct Base64(Raw);
8
9  impl Base64 {
10    pub const fn new() -> Self {
11      Self(STANDARD)
12    }
13
14    pub fn encode_to_string<D: AsRef<[u8]>>(&self, data: D) -> String {
15      self.0.encode_to_string(data)
16    }
17
18    pub fn decode_to_vec<D: AsRef<[u8]>>(&self, data: D) -> Result<Vec<u8>, Error> {
19      self.0.decode_to_vec(data)
20    }
21  }
22
23  impl Default for Base64 {
24    fn default() -> Self {
25      Self::new()
26    }
27  }
28
29  static BASE64: Base64 = Base64::new();
30
31  pub fn encode_to_string<D: AsRef<[u8]>>(data: D) -> String {
32    BASE64.0.encode_to_string(data)
33  }
34
35  pub fn decode_to_vec<D: AsRef<[u8]>>(data: D) -> Result<Vec<u8>, Error> {
36    BASE64.0.decode_to_vec(data)
37  }
38
39  static INVALID_BASE64_RE: LazyLock<Regex> =
40    LazyLock::new(|| Regex::new(r"[^+/0-9A-Za-z-_]").expect("Invalid RegExp"));
41
42  // modified from https://github.com/feross/buffer/blob/795bbb5bda1b39f1370ebd784bea6107b087e3a7/index.js#L1942
43  // Buffer.from in nodejs will clean base64 first, which causes some inconsistent behavior with base64_simd
44  // e.g. Buffer.from("abcd?#iefix", "base64").toString("base64")
45  pub fn clean_base64(value: &str) -> Option<Cow<'_, str>> {
46    let value = value.split('=').next()?;
47    let value = value.trim();
48    let value = INVALID_BASE64_RE.replace_all(value, "");
49    if value.len() < 2 {
50      return Some(Cow::from(""));
51    }
52    let value = value.into_owned();
53    let len = value.len();
54    let remainder = len % 4;
55    if remainder == 0 {
56      return Some(Cow::from(value));
57    }
58    let pad_len = 4 - remainder;
59    if pad_len == 1 {
60      return Some(Cow::from(value + "="));
61    }
62    if pad_len == 2 {
63      return Some(Cow::from(value + "=="));
64    }
65    // modify: add this case on the original base64clean js function
66    // why Buffer.from("abcd?#iefix", "base64") => "abcdiefi"?
67    //   1. base64clean("abcd?#iefix") => "abcdiefix==="
68    //   2. toByteArray("abcdiefix===") => "abcdiefi"
69    // but base64_simd::STANDARD.decode_to_vec("abcdiefix===") will return error
70    // because toByteArray and base64_simd::STANDARD.decode_to_vec are different with handling placeHoldersLen
71    // for detail checkout:
72    //   - https://github.com/beatgammit/base64-js/blob/88957c9943c7e2a0f03cdf73e71d579e433627d3/index.js#L80-L96
73    //   - https://docs.rs/base64-simd/0.8.0/src/base64_simd/decode.rs.html#70 (means placeHoldersLen === 3)
74    Some(Cow::from(value[..len - remainder].to_owned()))
75  }
76}
77
78pub use base64::{clean_base64, decode_to_vec, encode_to_string};