use std::borrow::Cow;
use std::str;
pub fn qp_decode(s: &[u8]) -> (Cow<'_, [u8]>, &[u8]) {
let mut transformed = Vec::new();
let mut soft_line_break = false;
let mut dangling: Option<&[u8]> = None;
let mut split = s.split(|&b| b'=' == b);
let mut prefix = split.next();
for element in split {
if let Some(prefix) = prefix.take() {
transformed.extend_from_slice(prefix);
}
if let Some(dangling) = dangling.take() {
transformed.push(b'=');
transformed.extend_from_slice(dangling);
}
if element.is_empty() {
dangling = Some(element);
continue;
}
if b'\n' == element[0] {
transformed.extend_from_slice(&element[1..]);
soft_line_break = true;
continue;
}
if element.len() < 2 {
dangling = Some(element);
continue;
}
let encoded = &element[..2];
let tail = &element[2..];
if b"\r\n" == encoded {
transformed.extend_from_slice(tail);
soft_line_break = true;
continue;
}
if let Some(ch) = str::from_utf8(encoded)
.ok()
.and_then(|e| u8::from_str_radix(e, 16).ok())
{
transformed.push(ch);
transformed.extend_from_slice(tail);
} else {
transformed.push(b'=');
transformed.extend_from_slice(element);
}
}
let dangling = dangling.map(|d| &s[s.len() - d.len() - 1..]).unwrap_or(&[]);
if transformed.is_empty() && !soft_line_break {
(Cow::Borrowed(&s[..s.len() - dangling.len()]), dangling)
} else {
(Cow::Owned(transformed), dangling)
}
}
#[cfg(test)]
mod test {
use proptest::prelude::*;
use super::*;
fn assert_qp(expected: &[u8], expected_dangling: &[u8], input: &[u8]) {
let (actual, actual_dangling) = qp_decode(input);
assert_eq!(expected, &actual[..], "Unexpected decode of {:?}", input);
assert_eq!(
expected_dangling, actual_dangling,
"Unexpected dangling of {:?}",
input
);
}
#[test]
fn test_qp_decode() {
assert_qp(b"hello world", b"", b"hello world");
assert_qp(b"\xabfoo", b"", b"=ABfoo");
assert_qp(b"fo\xabo", b"", b"fo=ABo");
assert_qp(b"foo\xab", b"", b"foo=AB");
assert_qp(b"foo\xab\xcd", b"", b"foo=AB=CD");
assert_qp(b"foo\xabbar\xcd", b"", b"foo=ABbar=CD");
assert_qp(b"foo", b"", b"foo=\n");
assert_qp(b"foobar", b"", b"foo=\nbar");
assert_qp(b"foo", b"", b"foo=\r\n");
assert_qp(b"foobar", b"", b"foo=\r\nbar");
assert_qp(b"foo=()bar", b"", b"foo=()bar");
assert_qp(b"foo=\xabbar", b"", b"foo==ABbar");
assert_qp(b"foo=A\xabbar", b"", b"foo=A=ABbar");
assert_qp("foo=ゑbar".as_bytes(), b"", "foo=ゑbar".as_bytes());
assert_qp(b"foo=\x80\x80bar", b"", b"foo=\x80\x80bar");
assert_qp(b"foo", b"=", b"foo=");
assert_qp(b"foo", b"=A", b"foo=A");
assert_qp(b"foo", b"=\r", b"foo=\r");
assert_qp(b"", b"=", b"=");
assert_qp(b"", b"=\r", b"=\r");
assert_qp(b"", b"", b"=\n");
assert_qp(b"", b"", b"=\r\n");
}
proptest! {
#[test]
fn qp_decode_never_fails_for_str(s in ".*") {
qp_decode(s.as_bytes());
}
#[test]
fn qp_decode_never_fails_for_bytes(
s in prop::collection::vec(prop::num::u8::ANY, 0..20)
) {
qp_decode(&s);
}
}
}