1#![type_length_limit = "109238057"]
2
3pub use nom;
4
5mod command;
6mod data;
7mod misc;
8mod reply;
9
10use misc::*;
13pub use command::{Command, ParameterName, Parameters};
16pub use data::{DataUnescapeRes, DataUnescaper, EscapedDataReader, EscapingDataWriter};
17pub use misc::{next_crlf, Email, Hostname, Localpart, MaybeUtf8, NextCrLfState, Path};
18pub use reply::{
19 EnhancedReplyCode, EnhancedReplyCodeClass, EnhancedReplyCodeSubject, Reply, ReplyCode,
20 ReplyCodeCategory, ReplyCodeKind, ReplyLine,
21};
22
23#[cfg(test)]
24use std::str;
25
26#[cfg(test)]
28pub(crate) fn show_bytes(b: &[u8]) -> String {
29 if b.len() > 128 {
30 "{too long}".into()
31 } else if let Ok(s) = str::from_utf8(b) {
32 s.into()
33 } else {
34 format!("{:?}", b)
35 }
36}
37
38#[cfg(any(test, feature = "fuzz-targets"))]
39pub mod fuzz {
40 use super::*;
41
42 use std::{cmp, io::IoSlice};
43
44 use futures::{
45 executor,
46 io::{AsyncReadExt, AsyncWriteExt, Cursor},
47 };
48 use regex_automata::RegexBuilder;
49
50 pub fn escaping_then_unescaping(
51 data: Vec<Vec<Vec<u8>>>,
52 maxread: usize,
53 initread: usize,
54 mut readlen: Vec<usize>,
55 ) {
56 if readlen.is_empty() {
57 readlen.push(1);
58 }
59 let mut wire = Vec::new();
78
79 {
81 let mut writer = EscapingDataWriter::new(Cursor::new(&mut wire));
82 for write in data.iter() {
83 let mut written = 0;
84 let total_to_write = write.iter().map(|b| b.len()).sum::<usize>();
85 while written != total_to_write {
86 let mut i = Vec::new();
87 let mut skipped = 0;
88 for s in write {
89 if skipped + s.len() <= written {
90 skipped += s.len();
91 continue;
92 }
93 if written - skipped != 0 {
94 i.push(IoSlice::new(&s[(written - skipped)..]));
95 skipped = written;
96 } else {
97 i.push(IoSlice::new(s));
98 }
99 }
100 written += executor::block_on(writer.write_vectored(&i)).unwrap();
101 }
102 }
103 executor::block_on(writer.finish()).unwrap();
104 }
105
106 {
108 assert!(wire == b".\r\n" || wire.ends_with(b"\r\n.\r\n"));
111
112 let reg = RegexBuilder::new()
114 .allow_invalid_utf8(true)
115 .build(r#"\r\n\.[^.]"#)
116 .unwrap();
117 assert!(
118 reg.find(&wire)
119 .map(|(start, _)| start == wire.len() - 5)
120 .unwrap_or(true)
121 );
122 }
123
124 let mut read = Vec::new();
126 {
127 let maxread = cmp::max(cmp::min(maxread, 16 * 1024 * 1024), 5);
131 let mut initbuf = vec![0; maxread];
132 let mut buf = vec![0; maxread];
133 let initread = cmp::min(cmp::min(initread, maxread), wire.len());
134 initbuf[..initread].copy_from_slice(&wire[..initread]);
135 wire = wire[initread..].to_owned();
136 let mut reader = EscapedDataReader::new(&mut initbuf, 0..initread, &wire[..]);
137 let mut unescaper = DataUnescaper::new(true);
138 let mut i = 0;
139 let mut start = 0;
140 loop {
141 let read_size = cmp::min(cmp::max(1, readlen[i % readlen.len()]), maxread - start);
143 assert!(read_size > 0, "read_size = 0, bug in the test harness");
144 let bytes_read =
145 executor::block_on(reader.read(&mut buf[start..start + read_size])).unwrap();
146 if bytes_read == 0 {
152 break;
153 }
154 let unesc = unescaper.unescape(&mut buf[..start + bytes_read]);
155 read.extend_from_slice(&buf[..unesc.written]);
156 buf.copy_within(unesc.unhandled_idx..start + bytes_read, 0);
161 start = start + bytes_read - unesc.unhandled_idx;
162 i += 1;
163 }
164 reader.complete();
166 assert!(reader.get_unhandled().unwrap().is_empty());
167 }
168
169 {
171 let mut expected = data
172 .iter()
173 .flat_map(|v| v.iter().flat_map(|w| w.iter().cloned()))
174 .collect::<Vec<u8>>();
175 if !expected.is_empty() && !expected.ends_with(b"\r\n") {
176 expected.extend_from_slice(b"\r\n");
177 }
178 assert_eq!(read, expected);
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 use quickcheck_macros::quickcheck;
190
191 #[quickcheck]
192 pub fn escaping_then_unescaping(
193 data: Vec<Vec<Vec<u8>>>,
194 maxread: usize,
195 initread: usize,
196 readlen: Vec<usize>,
197 ) {
198 fuzz::escaping_then_unescaping(data, maxread, initread, readlen)
199 }
200}