buup/transformers/
base64_decode.rs

1use crate::{Transform, TransformError, TransformerCategory};
2
3/// Base64 decode transformer
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Base64Decode;
6
7impl Transform for Base64Decode {
8    fn name(&self) -> &'static str {
9        "Base64 Decode"
10    }
11
12    fn id(&self) -> &'static str {
13        "base64decode"
14    }
15
16    fn description(&self) -> &'static str {
17        "Decode Base64 text to plain text"
18    }
19
20    fn category(&self) -> TransformerCategory {
21        TransformerCategory::Decoder
22    }
23
24    fn transform(&self, input: &str) -> Result<String, TransformError> {
25        let decoded = base64_decode(input).map_err(|_| TransformError::Base64DecodeError)?;
26        String::from_utf8(decoded).map_err(|_| TransformError::Utf8Error)
27    }
28
29    fn default_test_input(&self) -> &'static str {
30        "SGVsbG8sIFdvcmxkIQ=="
31    }
32}
33
34/// Decodes base64 string to bytes without external dependencies
35pub(crate) fn base64_decode(input: &str) -> Result<Vec<u8>, &'static str> {
36    // Creates a mapping from each base64 character to its 6-bit value
37    fn create_lookup_table() -> [i8; 256] {
38        let mut table = [-1i8; 256];
39        b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
40            .iter()
41            .enumerate()
42            .for_each(|(i, &c)| table[c as usize] = i as i8);
43        table
44    }
45
46    let lookup = create_lookup_table();
47    let input = input.trim().as_bytes();
48
49    // Calculate output length (removing padding)
50    let padding = input.iter().rev().take_while(|&&c| c == b'=').count();
51    let output_len = input.len() * 3 / 4 - padding;
52
53    let mut output = vec![0u8; output_len];
54    let mut output_index = 0;
55
56    // Process 4 input bytes at a time
57    for chunk in input.chunks(4) {
58        if chunk.len() < 2 {
59            return Err("Invalid base64 length");
60        }
61
62        // Convert each character to its 6-bit value
63        let b0 = lookup[chunk[0] as usize];
64        let b1 = lookup[chunk[1] as usize];
65
66        // Check for invalid characters
67        if b0 < 0 || b1 < 0 {
68            return Err("Invalid base64 character");
69        }
70
71        // Handle the first byte
72        if output_index < output_len {
73            output[output_index] = ((b0 << 2) | (b1 >> 4)) as u8;
74            output_index += 1;
75        }
76
77        if chunk.len() > 2 {
78            // Handle padding or valid character
79            if chunk[2] == b'=' {
80                if chunk.len() < 4 || chunk[3] != b'=' {
81                    return Err("Invalid base64 padding");
82                }
83                continue; // Done with this chunk
84            }
85
86            let b2 = lookup[chunk[2] as usize];
87            if b2 < 0 {
88                return Err("Invalid base64 character");
89            }
90
91            if output_index < output_len {
92                output[output_index] = (((b1 & 0xF) << 4) | (b2 >> 2)) as u8;
93                output_index += 1;
94            }
95
96            if chunk.len() > 3 {
97                if chunk[3] == b'=' {
98                    continue; // Done with this chunk
99                }
100
101                let b3 = lookup[chunk[3] as usize];
102                if b3 < 0 {
103                    return Err("Invalid base64 character");
104                }
105
106                if output_index < output_len {
107                    output[output_index] = (((b2 & 0x3) << 6) | b3) as u8;
108                    output_index += 1;
109                }
110            }
111        }
112    }
113
114    Ok(output)
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_base64_decode() {
123        let transformer = Base64Decode;
124        assert_eq!(
125            transformer
126                .transform(transformer.default_test_input())
127                .unwrap(),
128            "Hello, World!"
129        );
130        assert_eq!(transformer.transform("").unwrap(), "");
131        assert_eq!(transformer.transform("YQ==").unwrap(), "a");
132    }
133}