1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#[deny(missing_docs)]
pub fn uuencode(filename: &str, input: &[u8]) -> String {
let mut output : Vec<u8> = Vec::new();
output.extend(b"begin 644 ");
output.extend(filename.as_bytes());
output.extend(b"\n");
for line in input.chunks(45) {
let line_length = line.len() as u8 + 32;
output.push(line_length);
for c in line.chunks(3) {
output.extend(uuencode_chuck(c).into_iter());
}
output.push(b'\n');
}
output.extend(b"`\nend");
String::from_utf8(output).unwrap()
}
pub fn uudecode(encoded: &str) -> Option<(Vec<u8>, String)> {
let mut lines = encoded.lines();
let name = lines.next().expect("No next lines!").split(" ").collect::<Vec<_>>()[2].to_string();
let mut output: Vec<u8> = Vec::new();
for line in lines {
let padded_line = maybe_pad_line(line);
if let Some(chr) = padded_line.chars().nth(0) {
match chr {
'`' => break,
' '..='_' => {
for dc in padded_line[1..].as_bytes().chunks(4) {
output.extend( uudecode_chunk(dc) );
}
},
_ => break
}
}
}
Some((output, name))
}
fn uuencode_chuck(input: &[u8]) -> [u8;4] {
let i = [ input[0],
*input.get(1).unwrap_or(&0),
*input.get(2).unwrap_or(&0) ];
[ 32 + (i[0]>>2),
32 + ((i[0]<<6 | i[1]>>2) >> 2),
32 + ((i[1]<<4 | i[2]>>4) >> 2),
32 + ((i[2]<<2) >> 2) ]
}
fn uudecode_chunk(input: &[u8]) -> impl Iterator<Item=u8> {
let combined: u32 = input.iter().enumerate()
.fold(0, | acc, (index, &val) | {
acc + (((val as u32) - 32) << 6 * (3 - index))
});
(0..3).rev().map(move |val| {
let val = (combined >> (8 * val)) & 255;
val as u8
})
}
fn maybe_pad_line(line: &str) -> String {
const REQUIRED_LENGTH: usize = 61;
let actual_length = line.len();
let diff = REQUIRED_LENGTH - actual_length;
match diff {
d if d <= 0 => String::from(line),
_ => {
let mut padded = String::from(line);
for _i in 1..=diff {
padded.push(' ');
}
return padded;
},
}
}
mod test {
use crate::*;
#[test]
fn test_cat() {
let filename = "wow.jpg";
let original_encoded = "begin 644 wow.jpg\nM0V%T \n`\nend";
let decoded = uudecode(original_encoded).unwrap();
let encoded = uuencode(filename, decoded.0.as_slice());
assert_eq!(original_encoded, encoded);
}
#[test]
fn test_logo() {
let filename = "amglogoa09.jpg";
let original_encoded = include_str!("../images/logo_encoded_padded").trim();
let decoded = uudecode(original_encoded).unwrap();
let encoded = uuencode(filename, decoded.0.as_slice());
assert_eq!(original_encoded, encoded);
}
#[test]
fn test_piechart() {
let filename = "aumpiechartscombinded5217v4.jpg";
let original_encoded = include_str!("../images/piechart_encoded_padded").trim();
let decoded = uudecode(original_encoded).unwrap();
let encoded = uuencode(filename, decoded.0.as_slice());
assert_eq!(original_encoded, encoded);
}
#[test]
fn test_pad_line() {
let unpadded = r#"=HHH **** "BBB@ HHHH **** "BBB@ HHHH _]D!"#;
let padded = r#"=HHH **** "BBB@ HHHH **** "BBB@ HHHH _]D! "#;
let r = maybe_pad_line(unpadded);
assert_eq!(padded, r);
}
}