rust_auth_utils/
base64.rs

1// based on https://github.com/better-auth/utils/blob/main/src/base64.ts
2
3use std::collections::HashMap;
4
5/**
6 * Returns the Base64 alphabet based on the encoding type.
7 */
8fn get_alphabet(url_safe: bool) -> &'static str {
9    if url_safe {
10        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
11    } else {
12        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
13    }
14}
15
16/**
17 * Creates a decode map for the given alphabet.
18 */
19fn create_decode_map(alphabet: &str) -> HashMap<char, u8> {
20    alphabet
21        .chars()
22        .enumerate()
23        .map(|(i, c)| (c, i as u8))
24        .collect()
25}
26
27/**
28 * Encodes a slice of bytes into a Base64 string.
29 */
30fn base64_encode(data: &[u8], alphabet: &str, padding: bool) -> String {
31    let mut result = String::new();
32    let mut buffer = 0u32;
33    let mut shift = 0;
34
35    for &byte in data {
36        buffer = (buffer << 8) | (byte as u32);
37        shift += 8;
38        while shift >= 6 {
39            shift -= 6;
40            result.push(
41                alphabet
42                    .chars()
43                    .nth(((buffer >> shift) & 0x3f) as usize)
44                    .unwrap(),
45            );
46        }
47    }
48
49    if shift > 0 {
50        result.push(
51            alphabet
52                .chars()
53                .nth(((buffer << (6 - shift)) & 0x3f) as usize)
54                .unwrap(),
55        );
56    }
57
58    if padding {
59        let pad_count = (4 - (result.len() % 4)) % 4;
60        result.extend(std::iter::repeat('=').take(pad_count));
61    }
62
63    result
64}
65
66/**
67 * Decodes a Base64 string into a Vec<u8>.
68 */
69fn base64_decode(data: &str, alphabet: &str) -> Result<Vec<u8>, String> {
70    let decode_map = create_decode_map(alphabet);
71    let mut result = Vec::new();
72    let mut buffer = 0u32;
73    let mut bits_collected = 0;
74
75    for c in data.chars().take_while(|&c| c != '=') {
76        let value = decode_map
77            .get(&c)
78            .ok_or_else(|| format!("Invalid Base64 character: {}", c))?;
79
80        buffer = (buffer << 6) | (*value as u32);
81        bits_collected += 6;
82
83        if bits_collected >= 8 {
84            bits_collected -= 8;
85            result.push(((buffer >> bits_collected) & 0xff) as u8);
86        }
87    }
88
89    Ok(result)
90}
91
92#[derive(Default)]
93pub struct Base64;
94
95impl Base64 {
96    /**
97     * Encodes data into a Base64 string.
98     */
99    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
100        let alphabet = get_alphabet(false);
101        base64_encode(data, alphabet, padding.unwrap_or(true))
102    }
103
104    /**
105     * Decodes a Base64 string into a Vec<u8>.
106     */
107    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
108        let url_safe = data.contains('-') || data.contains('_');
109        let alphabet = get_alphabet(url_safe);
110        base64_decode(data, alphabet)
111    }
112
113    /**
114     * Encodes a string into a Base64 string.
115     */
116    pub fn encode_string(data: &str, padding: Option<bool>) -> String {
117        Self::encode(data.as_bytes(), padding)
118    }
119
120    /**
121     * Decodes a Base64 string into a UTF-8 string.
122     */
123    pub fn decode_string(data: &str) -> Result<String, String> {
124        let bytes = Self::decode(data)?;
125        String::from_utf8(bytes).map_err(|e| format!("Invalid UTF-8 sequence: {}", e))
126    }
127}
128
129#[derive(Default)]
130pub struct Base64Url;
131
132impl Base64Url {
133    /**
134     * Encodes data into a Base64URL string.
135     */
136    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
137        let alphabet = get_alphabet(true);
138        base64_encode(data, alphabet, padding.unwrap_or(true))
139    }
140
141    /**
142     * Decodes a Base64URL string into a Vec<u8>.
143     */
144    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
145        let alphabet = get_alphabet(true);
146        base64_decode(data, alphabet)
147    }
148
149    /**
150     * Encodes a string into a Base64URL string.
151     */
152    pub fn encode_string(data: &str, padding: Option<bool>) -> String {
153        Self::encode(data.as_bytes(), padding)
154    }
155
156    /**
157     * Decodes a Base64URL string into a UTF-8 string.
158     */
159    pub fn decode_string(data: &str) -> Result<String, String> {
160        let bytes = Self::decode(data)?;
161        String::from_utf8(bytes).map_err(|e| format!("Invalid UTF-8 sequence: {}", e))
162    }
163}