use std::fmt;
use std::io::{self, Write};
use std::sync::Arc;
use crate::account::model::CommonPaths;
use crate::mime::content_encoding::ContentDecoder;
use crate::mime::grovel::Visitor;
use crate::mime::header;
use crate::support::buffer::*;
use crate::support::error::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LeafType {
Full,
Headers,
Mime,
Content,
Text,
}
impl LeafType {
fn include_headers(self) -> bool {
match self {
Self::Full | Self::Headers | Self::Mime => true,
Self::Content | Self::Text => false,
}
}
fn include_content(self) -> bool {
match self {
Self::Full | Self::Content | Self::Text => true,
Self::Headers | Self::Mime => false,
}
}
fn act_on_first_child(self, is_top_level: bool) -> bool {
match self {
Self::Full | Self::Mime | Self::Content => false,
Self::Headers | Self::Text => !is_top_level,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct BodySection {
pub subscripts: Vec<u32>,
pub leaf_type: LeafType,
pub header_filter: Vec<String>,
pub discard_matching_headers: bool,
pub partial: Option<(u64, u64)>,
pub decode_cte: bool,
pub decode_charset: bool,
pub report_as_legacy: Option<Imap2Section>,
pub report_as_binary: bool,
pub size_only: bool,
}
impl Default for BodySection {
fn default() -> Self {
BodySection {
subscripts: vec![],
leaf_type: LeafType::Full,
header_filter: vec![],
discard_matching_headers: false,
partial: None,
decode_cte: false,
decode_charset: false,
report_as_legacy: None,
report_as_binary: false,
size_only: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Imap2Section {
Rfc822,
Rfc822Header,
Rfc822Text,
}
pub type Fetcher = Box<dyn Visitor<Output = Output>>;
pub type Output = (BodySection, Result<FetchedBodySection, Error>);
impl BodySection {
pub fn fetcher(self, common_paths: Arc<CommonPaths>) -> Fetcher {
if self.subscripts.is_empty() {
let decode_cte = self.decode_cte;
let decode_charset = self.decode_charset;
let leaf: Fetcher =
Box::new(SectionFetcher::new(self, common_paths));
if decode_cte {
Box::new(ContentDecoder::new(leaf, decode_charset))
} else {
leaf
}
} else {
Box::new(SectionLocator {
target: Some(self),
level: 0,
curr_part_number: 0,
content_transfer_encoding:
header::ContentTransferEncoding::SevenBit,
common_paths,
is_message_rfc822: false,
})
}
}
}
pub struct FetchedBodySection {
pub buffer: BufferReader,
pub contains_nul: bool,
}
impl fmt::Debug for FetchedBodySection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FetchedBodySection")
.field("buffer", &self.buffer.len())
.field("contains_nul", &self.contains_nul)
.finish()
}
}
#[derive(Debug)]
struct SectionLocator {
target: Option<BodySection>,
level: usize,
curr_part_number: u32,
content_transfer_encoding: header::ContentTransferEncoding,
common_paths: Arc<CommonPaths>,
is_message_rfc822: bool,
}
impl Visitor for SectionLocator {
type Output = Output;
fn content_type(
&mut self,
ct: &header::ContentType<'_>,
) -> Result<(), Output> {
self.is_message_rfc822 =
ct.is_type("message") && ct.is_subtype("rfc822");
Ok(())
}
fn header(
&mut self,
_raw: &[u8],
name: &str,
value: &[u8],
) -> Result<(), Output> {
if self.target.as_ref().map_or(false, |t| t.decode_cte) {
if "Content-Transfer-Encoding".eq_ignore_ascii_case(name) {
if let Some(cte) =
header::parse_content_transfer_encoding(value)
{
self.content_transfer_encoding = cte;
} else {
return Err((
self.target.take().unwrap(),
Err(Error::UnknownCte),
));
}
}
}
Ok(())
}
fn leaf_section(&mut self) -> Option<Fetcher> {
match self.target.as_ref().map(|t| t.leaf_type) {
Some(LeafType::Content) | Some(LeafType::Text) => {
let mut leaf = self.make_section_fetcher();
leaf.header(
b"",
"Content-Transfer-Encoding",
self.content_transfer_encoding.name().as_bytes(),
)
.expect("leaf.header() returned early");
Some(leaf)
}
_ => None,
}
}
fn start_part(&mut self) -> Option<Fetcher> {
enum NextLevel {
NoMatch,
Final,
Recurse(usize),
}
let next_level = if let Some(target) = self.target.as_ref() {
self.curr_part_number += 1;
#[allow(clippy::if_same_then_else)]
if self.level >= target.subscripts.len() {
NextLevel::Final
} else if self.is_message_rfc822 {
NextLevel::Recurse(self.level)
} else if self.curr_part_number != target.subscripts[self.level] {
NextLevel::NoMatch
} else if self.level + 1 < target.subscripts.len() {
NextLevel::Recurse(self.level + 1)
} else if target.leaf_type.act_on_first_child(false) {
NextLevel::Recurse(self.level + 1)
} else {
NextLevel::Final
}
} else {
NextLevel::NoMatch
};
match next_level {
NextLevel::NoMatch => None,
NextLevel::Final => Some(self.make_section_fetcher()),
NextLevel::Recurse(next_level) => Some(Box::new(SectionLocator {
target: self.target.take(),
level: next_level,
curr_part_number: 0,
content_transfer_encoding:
header::ContentTransferEncoding::SevenBit,
common_paths: Arc::clone(&self.common_paths),
is_message_rfc822: false,
})),
}
}
fn child_result(&mut self, result: Output) -> Result<(), Output> {
Err(result)
}
fn end(&mut self) -> Output {
(
self.target.take().unwrap(),
Ok(FetchedBodySection {
buffer: BufferReader::new(vec![]),
contains_nul: false,
}),
)
}
}
impl SectionLocator {
fn make_section_fetcher(&mut self) -> Fetcher {
let target = self.target.take().unwrap();
let decode_cte = target.decode_cte;
let decode_charset = target.decode_charset;
let leaf: Fetcher = Box::new(SectionFetcher::new(
target,
Arc::clone(&self.common_paths),
));
if decode_cte {
Box::new(ContentDecoder::new(leaf, decode_charset))
} else {
leaf
}
}
}
struct SectionFetcher {
target: Option<BodySection>,
buffer: Option<io::BufWriter<BufferWriter>>,
in_headers: bool,
contains_nul: bool,
skipped: u64,
processed: u64,
desired_range: (u64, u64),
leaf_type: LeafType,
}
impl fmt::Debug for SectionFetcher {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SectionFetcher")
.field("target", &self.target)
.field("buffer", &"BufferWriter")
.field("in_headers", &self.in_headers)
.field("contains_nul", &self.contains_nul)
.field("skipped", &self.skipped)
.field("processed", &self.processed)
.field("desired_range", &self.desired_range)
.field("leaf_type", &self.leaf_type)
.finish()
}
}
impl SectionFetcher {
fn new(target: BodySection, common_paths: Arc<CommonPaths>) -> Self {
SectionFetcher {
in_headers: true,
contains_nul: false,
skipped: 0,
processed: 0,
desired_range: target.partial.unwrap_or((0, u64::MAX)),
leaf_type: target.leaf_type,
buffer: Some(io::BufWriter::new(BufferWriter::new(common_paths))),
target: Some(target),
}
}
fn write(&mut self, data: &[u8]) -> Result<(), Output> {
let to_process =
(self.desired_range.1 - self.processed).min(data.len() as u64);
let data = &data[..to_process as usize];
self.processed += to_process;
let to_skip =
(self.desired_range.0 - self.skipped).min(data.len() as u64);
let data = &data[to_skip as usize..];
self.skipped += to_skip;
if !data.is_empty() {
self.contains_nul |= memchr::memchr(0, data).is_some();
if let Err(e) = self
.buffer
.as_mut()
.expect("Write to SectionFetcher after end")
.write_all(data)
{
return Err((self.target.take().unwrap(), Err(e.into())));
}
}
if self.processed < self.desired_range.1 {
Ok(())
} else {
Err(self.end())
}
}
fn end_buffer(&mut self) -> Result<FetchedBodySection, Error> {
let buffer = self.buffer.take().unwrap();
let buffer = buffer.into_inner().map_err(io::Error::from)?.flip()?;
Ok(FetchedBodySection {
buffer,
contains_nul: self.contains_nul,
})
}
}
impl Visitor for SectionFetcher {
type Output = Output;
fn raw_line(&mut self, raw: &[u8]) -> Result<(), Output> {
if self.leaf_type.include_headers()
&& self
.target
.as_ref()
.map_or(false, |t| t.header_filter.is_empty())
&& self.in_headers
{
self.write(raw)
} else {
Ok(())
}
}
fn header(
&mut self,
raw: &[u8],
name: &str,
value: &[u8],
) -> Result<(), Output> {
if self.target.as_ref().map_or(false, |t| t.decode_cte)
&& "Content-Transfer-Encoding".eq_ignore_ascii_case(name)
&& header::parse_content_transfer_encoding(value).is_none()
{
return Err((self.target.take().unwrap(), Err(Error::UnknownCte)));
}
if !self.leaf_type.include_headers()
|| self
.target
.as_ref()
.map_or(false, |t| t.header_filter.is_empty())
{
return Ok(());
}
{
let target = self
.target
.as_ref()
.expect("SectionFetcher::header after end");
let matches_filter = target
.header_filter
.iter()
.any(|h| name.eq_ignore_ascii_case(h));
if matches_filter == target.discard_matching_headers {
return Ok(());
}
}
self.write(raw)?;
if !raw.ends_with(b"\n") {
if raw.ends_with(b"\r") {
self.write(b"\n")?;
} else {
self.write(b"\r\n")?;
}
}
Ok(())
}
fn start_content(&mut self) -> Result<(), Output> {
self.in_headers = false;
if self.leaf_type.include_headers()
&& !self
.target
.as_ref()
.map_or(false, |t| t.header_filter.is_empty())
{
self.write(b"\r\n")?;
}
if !self.leaf_type.include_content() {
Err(self.end())
} else {
Ok(())
}
}
fn content(&mut self, data: &[u8]) -> Result<(), Output> {
self.write(data)
}
fn end(&mut self) -> Output {
let target = self.target.take().unwrap();
let fetched = self.end_buffer();
(target, fetched)
}
}
#[cfg(test)]
mod test {
use std::io::Read;
use std::str;
use proptest::prelude::*;
use super::*;
use crate::mime::grovel;
fn do_fetch(message: &str, section: BodySection) -> String {
let message = message.replace('\n', "\r\n");
do_fetch_bytes(message.into(), section)
}
fn do_fetch_sample(section: BodySection) -> String {
do_fetch_bytes(crate::test_data::RFC3501_P56.to_owned(), section)
}
fn do_fetch_bytes(message: Vec<u8>, section: BodySection) -> String {
let (_, result) = grovel::grovel(
&grovel::SimpleAccessor {
data: message.into(),
..grovel::SimpleAccessor::default()
},
section.fetcher(Arc::new(CommonPaths {
tmp: std::env::temp_dir(),
garbage: std::env::temp_dir(),
})),
)
.unwrap();
let mut result = result.unwrap();
let mut ret = String::new();
result.buffer.read_to_string(&mut ret).unwrap();
assert_eq!(result.buffer.len() as usize, ret.len());
assert_eq!(ret.contains('\0'), result.contains_nul);
ret
}
#[test]
fn fetch_full() {
let fetched = do_fetch_sample(BodySection::default());
assert!(fetched.starts_with("Remark:"));
assert!(fetched.ends_with("--toplevel--\r\n"));
}
#[test]
fn fetch_toplevel_header() {
let fetched = do_fetch_sample(BodySection {
leaf_type: LeafType::Headers,
..BodySection::default()
});
assert!(fetched.starts_with("Remark:"));
assert!(fetched.ends_with("boundary=toplevel\r\n\r\n"));
}
#[test]
fn fetch_toplevel_text() {
let fetched = do_fetch_sample(BodySection {
leaf_type: LeafType::Text,
..BodySection::default()
});
assert!(fetched.starts_with("--toplevel\r\n"));
assert!(fetched.ends_with("--toplevel--\r\n"));
}
#[test]
fn fetch_1_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 1\r\n", fetched);
}
#[test]
fn fetch_2_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![2],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 2\r\n", fetched);
}
#[test]
fn fetch_3_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![3],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert!(fetched.starts_with("Subject: Part 3\r\n"));
assert!(fetched.ends_with("--part3--"));
}
#[test]
fn fetch_3_header() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![3],
leaf_type: LeafType::Headers,
..BodySection::default()
});
assert!(fetched.starts_with("Subject: Part 3\r\n"));
assert!(fetched.ends_with("boundary=part3\r\n\r\n"));
}
#[test]
fn fetch_3_text() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![3],
leaf_type: LeafType::Text,
..BodySection::default()
});
assert!(fetched.starts_with("--part3\r\n"));
assert!(fetched.ends_with("--part3--"));
}
#[test]
fn fetch_3_1_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![3, 1],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 3.1\r\n", fetched);
}
#[test]
fn fetch_3_2_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![3, 2],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 3.2\r\n", fetched);
}
#[test]
fn fetch_4_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert!(fetched.starts_with("--part4\r\n"));
assert!(fetched.ends_with("--part4--"));
}
#[test]
fn fetch_4_1_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 1],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 4.1\r\n", fetched);
}
#[test]
fn fetch_4_1_mime() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 1],
leaf_type: LeafType::Mime,
..BodySection::default()
});
assert_eq!(
"Content-Id: 4.1\r\nContent-Type: image/gif\r\n\r\n",
fetched
);
}
#[test]
fn fetch_4_2_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert!(fetched.starts_with("Subject: Part 4.2\r\n"));
assert!(fetched.ends_with("--subpart42--"));
}
#[test]
fn fetch_4_2_header() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2],
leaf_type: LeafType::Headers,
..BodySection::default()
});
assert!(fetched.starts_with("Subject: Part 4.2\r\n"));
assert!(fetched.ends_with("boundary=subpart42\r\n\r\n"));
}
#[test]
fn fetch_4_2_text() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2],
leaf_type: LeafType::Text,
..BodySection::default()
});
assert!(fetched.starts_with("--subpart42\r\n"));
assert!(fetched.ends_with("--subpart42--"));
}
#[test]
fn fetch_4_2_1_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2, 1],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 4.2.1\r\n", fetched);
}
#[test]
fn fetch_4_2_2_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2, 2],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert!(fetched.starts_with("--subsubpart422\r\n"));
assert!(fetched.ends_with("--subsubpart422--"));
}
#[test]
fn fetch_4_2_2_1_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2, 2, 1],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 4.2.2.1\r\n", fetched);
}
#[test]
fn fetch_4_2_2_2_content() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 2, 2, 2],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("Part 4.2.2.2\r\n", fetched);
}
#[test]
fn fetch_out_of_bounds_section() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![10],
leaf_type: LeafType::Content,
..BodySection::default()
});
assert_eq!("", fetched);
}
#[test]
fn fetch_part_1_of_non_multipart() {
let fetched = do_fetch(
"Content-Type: text/plain\n\
\n\
foo\n",
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
..BodySection::default()
},
);
assert_eq!("foo\r\n", fetched);
}
#[test]
fn header_filter_retain() {
let fetched = do_fetch(
"fOo : foo\nBar: bar\nBaz: baz\n\nContent",
BodySection {
leaf_type: LeafType::Headers,
header_filter: vec!["Foo".to_owned(), "Baz".to_owned()],
discard_matching_headers: false,
..BodySection::default()
},
);
assert_eq!("fOo : foo\r\nBaz: baz\r\n\r\n", fetched);
}
#[test]
fn header_filter_remove() {
let fetched = do_fetch(
"fOo : foo\nBar: bar\nBaz: baz\n\nContent",
BodySection {
leaf_type: LeafType::Headers,
header_filter: vec!["Foo".to_owned(), "Baz".to_owned()],
discard_matching_headers: true,
..BodySection::default()
},
);
assert_eq!("Bar: bar\r\n\r\n", fetched);
}
#[test]
fn header_incomplete_line() {
let fetched = do_fetch(
"Foo: bar",
BodySection {
leaf_type: LeafType::Headers,
..BodySection::default()
},
);
assert_eq!("Foo: bar", fetched);
}
#[test]
fn header_incomplete_line_cr() {
let fetched = do_fetch(
"Foo: bar\r",
BodySection {
leaf_type: LeafType::Headers,
..BodySection::default()
},
);
assert_eq!("Foo: bar\r", fetched);
}
#[test]
fn header_incomplete_line_filtered() {
let fetched = do_fetch(
"Foo: bar",
BodySection {
leaf_type: LeafType::Headers,
header_filter: vec!["Foo".to_owned()],
discard_matching_headers: false,
..BodySection::default()
},
);
assert_eq!("Foo: bar\r\n", fetched);
}
#[test]
fn header_incomplete_line_cr_filtered() {
let fetched = do_fetch(
"Foo: bar\r",
BodySection {
leaf_type: LeafType::Headers,
header_filter: vec!["Foo".to_owned()],
discard_matching_headers: false,
..BodySection::default()
},
);
assert_eq!("Foo: bar\r\n", fetched);
}
#[test]
fn header_corrupt_line() {
let fetched = do_fetch(
"Foo\n\n",
BodySection {
leaf_type: LeafType::Headers,
..BodySection::default()
},
);
assert_eq!("Foo\r\n\r\n", fetched);
}
#[test]
fn nested_header_corrupt_line() {
let fetched = do_fetch(
"\
Content-Type: multipart/mixed; boundary=bound
--bound
Foo
--bound--
",
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Mime,
..BodySection::default()
},
);
assert_eq!("Foo\r\n\r\n", fetched);
}
#[test]
fn simple_partial() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 1],
leaf_type: LeafType::Content,
partial: Some((1, 8)),
..BodySection::default()
});
assert_eq!("art 4.1", fetched);
}
#[test]
fn overlength_partial() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 1],
leaf_type: LeafType::Content,
partial: Some((1, 800)),
..BodySection::default()
});
assert_eq!("art 4.1\r\n", fetched);
}
#[test]
fn empty_partial() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 1],
leaf_type: LeafType::Content,
partial: Some((1, 1)),
..BodySection::default()
});
assert_eq!("", fetched);
}
#[test]
fn inverted_partial() {
let fetched = do_fetch_sample(BodySection {
subscripts: vec![4, 1],
leaf_type: LeafType::Content,
partial: Some((8, 1)),
..BodySection::default()
});
assert_eq!("", fetched);
}
#[test]
fn single_part_with_subscript_1() {
let fetched = do_fetch_bytes(
b"Content-Type: text/plain\r\n\r\nhello".to_vec(),
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
..BodySection::default()
},
);
assert_eq!("hello", fetched);
}
#[test]
fn single_part_with_subscript_1_and_bad_leaf() {
let fetched = do_fetch_bytes(
b"Content-Type: text/plain\r\n\r\nhello".to_vec(),
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Headers,
..BodySection::default()
},
);
assert_eq!("", fetched);
}
#[test]
fn decode_cte_of_multipart() {
let fetched = do_fetch(
"\
Content-Type: multipart/mixed; boundary=bound
--bound
Content-Transfer-Encoding: base64
Zm9v
--bound--
",
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
decode_cte: true,
..BodySection::default()
},
);
assert_eq!("foo", fetched);
}
#[test]
fn decode_cte_with_nul() {
let fetched = do_fetch(
"\
Content-Type: multipart/mixed; boundary=bound
--bound
Content-Transfer-Encoding: base64
ZgBv
--bound--
",
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
decode_cte: true,
..BodySection::default()
},
);
assert_eq!("f\0o", fetched);
}
#[test]
fn decode_cte_of_single_part_with_subscript() {
let fetched = do_fetch(
"\
Content-Transfer-Encoding: base64
Zm9v
",
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
decode_cte: true,
..BodySection::default()
},
);
assert_eq!("foo", fetched);
}
#[test]
fn decode_unknown_cte_of_multipart() {
let (_, result) = grovel::grovel(
&grovel::SimpleAccessor {
data: b"\
Content-Type: multipart/mixed; boundary=bound
--bound
Content-Transfer-Encoding: chunked
5
hello
0
--bound--
"
.to_vec(),
..grovel::SimpleAccessor::default()
},
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
decode_cte: true,
..BodySection::default()
}
.fetcher(Arc::new(CommonPaths {
tmp: std::env::temp_dir(),
garbage: std::env::temp_dir(),
})),
)
.unwrap();
assert_matches!(Err(Error::UnknownCte), result);
}
#[test]
fn decode_unknown_cte_of_single_part_with_subscript() {
let (_, result) = grovel::grovel(
&grovel::SimpleAccessor {
data: b"\
Content-Transfer-Encoding: chunked
5
hello
0
"
.to_vec(),
..grovel::SimpleAccessor::default()
},
BodySection {
subscripts: vec![1],
leaf_type: LeafType::Content,
decode_cte: true,
..BodySection::default()
}
.fetcher(Arc::new(CommonPaths {
tmp: std::env::temp_dir(),
garbage: std::env::temp_dir(),
})),
)
.unwrap();
assert_matches!(Err(Error::UnknownCte), result);
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 16384,
..ProptestConfig::default()
})]
#[test]
fn whole_body_fetch_is_always_verbatim(
message in "[x: \t\r\n\"]*"
) {
let fetched = do_fetch_bytes(
message.as_bytes().to_vec(),
BodySection::default());
assert_eq!(message, fetched);
}
}
}