use base64;
use buffered_reader::BufferedReader;
use std::fmt;
use std::io::{Cursor, Read, Write};
use std::io::{Result, Error, ErrorKind};
use std::path::Path;
use std::cmp;
use std::str;
use std::borrow::Cow;
#[cfg(test)]
use quickcheck::{Arbitrary, Gen};
use crate::vec_truncate;
use crate::packet::prelude::*;
use crate::packet::header::{BodyLength, CTBNew, CTBOld};
use crate::parse::Cookie;
use crate::serialize::MarshalInto;
pub(crate) const LINE_LENGTH: usize = 64;
const LINE_ENDING: &str = "\n";
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Kind {
Message,
PublicKey,
SecretKey,
Signature,
File,
}
assert_send_and_sync!(Kind);
#[cfg(test)]
impl Arbitrary for Kind {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
use self::Kind::*;
match u8::arbitrary(g) % 5 {
0 => Message,
1 => PublicKey,
2 => SecretKey,
3 => Signature,
4 => File,
_ => unreachable!(),
}
}
}
impl Kind {
fn detect_header(blurb: &[u8]) -> Option<(Self, usize)> {
let (leading_dashes, rest) = dash_prefix(blurb);
if ! rest.starts_with(b"BEGIN PGP ") {
return None;
}
let rest = &rest[b"BEGIN PGP ".len()..];
let kind = if rest.starts_with(b"MESSAGE") {
Kind::Message
} else if rest.starts_with(b"PUBLIC KEY BLOCK") {
Kind::PublicKey
} else if rest.starts_with(b"PRIVATE KEY BLOCK") {
Kind::SecretKey
} else if rest.starts_with(b"SIGNATURE") {
Kind::Signature
} else if rest.starts_with(b"ARMORED FILE") {
Kind::File
} else {
return None;
};
let (trailing_dashes, _) = dash_prefix(&rest[kind.blurb().len()..]);
Some((kind,
leading_dashes.len()
+ b"BEGIN PGP ".len() + kind.blurb().len()
+ trailing_dashes.len()))
}
fn detect_footer(&self, blurb: &[u8]) -> Option<usize> {
let (leading_dashes, rest) = dash_prefix(blurb);
if ! rest.starts_with(b"END PGP ") {
return None;
}
let rest = &rest[b"END PGP ".len()..];
let ident = self.blurb().as_bytes();
if ! rest.starts_with(ident) {
return None;
}
let (trailing_dashes, _) = dash_prefix(&rest[ident.len()..]);
Some(leading_dashes.len()
+ b"END PGP ".len() + ident.len()
+ trailing_dashes.len())
}
fn blurb(&self) -> &str {
match self {
&Kind::Message => "MESSAGE",
&Kind::PublicKey => "PUBLIC KEY BLOCK",
&Kind::SecretKey => "PRIVATE KEY BLOCK",
&Kind::Signature => "SIGNATURE",
&Kind::File => "ARMORED FILE",
}
}
fn begin(&self) -> String {
format!("-----BEGIN PGP {}-----", self.blurb())
}
fn end(&self) -> String {
format!("-----END PGP {}-----", self.blurb())
}
}
pub struct Writer<W: Write> {
sink: W,
kind: Kind,
stash: Vec<u8>,
column: usize,
crc: CRC,
header: Vec<u8>,
dirty: bool,
}
assert_send_and_sync!(Writer<W> where W: Write);
impl<W: Write> Writer<W> {
pub fn new(inner: W, kind: Kind) -> Result<Self> {
Self::with_headers(inner, kind, Option::<(&str, &str)>::None)
}
pub fn with_headers<I, K, V>(inner: W, kind: Kind, headers: I)
-> Result<Self>
where I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
let mut w = Writer {
sink: inner,
kind,
stash: Vec::<u8>::with_capacity(2),
column: 0,
crc: CRC::new(),
header: Vec::with_capacity(128),
dirty: false,
};
{
let mut cur = Cursor::new(&mut w.header);
write!(&mut cur, "{}{}", kind.begin(), LINE_ENDING)?;
for h in headers {
write!(&mut cur, "{}: {}{}", h.0.as_ref(), h.1.as_ref(),
LINE_ENDING)?;
}
write!(&mut cur, "{}", LINE_ENDING)?;
}
Ok(w)
}
pub fn get_ref(&self) -> &W {
&self.sink
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.sink
}
fn finalize_headers(&mut self) -> Result<()> {
if ! self.dirty {
self.dirty = true;
self.sink.write_all(&self.header)?;
crate::vec_truncate(&mut self.header, 0);
self.header.shrink_to_fit();
}
Ok(())
}
pub fn finalize(mut self) -> Result<W> {
if ! self.dirty {
return Ok(self.sink);
}
self.finalize_armor()?;
Ok(self.sink)
}
fn finalize_armor(&mut self) -> Result<()> {
if ! self.dirty {
return Ok(());
}
self.finalize_headers()?;
if self.stash.len() > 0 {
self.sink.write_all(base64::encode_config(
&self.stash, base64::STANDARD).as_bytes())?;
self.column += 4;
}
assert!(self.column <= LINE_LENGTH);
if self.column == LINE_LENGTH {
write!(self.sink, "{}", LINE_ENDING)?;
self.column = 0;
}
if self.column > 0 {
write!(self.sink, "{}", LINE_ENDING)?;
}
let crc = self.crc.finalize();
let bytes = &crc.to_be_bytes()[1..4];
write!(self.sink, "={}{}{}{}",
base64::encode_config(&bytes, base64::STANDARD_NO_PAD),
LINE_ENDING, self.kind.end(), LINE_ENDING)?;
self.dirty = false;
Ok(())
}
fn linebreak(&mut self) -> Result<()> {
assert!(self.column <= LINE_LENGTH);
if self.column == LINE_LENGTH {
write!(self.sink, "{}", LINE_ENDING)?;
self.column = 0;
}
Ok(())
}
}
impl<W: Write> Write for Writer<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.finalize_headers()?;
self.crc.update(buf);
let mut input = buf;
let mut written = 0;
assert!(self.stash.len() <= 3);
if self.stash.len() > 0 {
while self.stash.len() < 3 {
if input.len() == 0 {
return Ok(written);
}
self.stash.push(input[0]);
input = &input[1..];
written += 1;
}
assert_eq!(self.stash.len(), 3);
self.sink
.write_all(base64::encode_config(
&self.stash, base64::STANDARD_NO_PAD).as_bytes())?;
self.column += 4;
self.linebreak()?;
crate::vec_truncate(&mut self.stash, 0);
}
while input.len() % 3 > 0 {
self.stash.push(input[input.len()-1]);
input = &input[..input.len()-1];
written += 1;
}
self.stash.reverse();
assert!(self.stash.len() < 3);
assert!(input.len() % 3 == 0);
let encoded = base64::encode_config(input, base64::STANDARD_NO_PAD);
written += input.len();
let mut enc = encoded.as_bytes();
while enc.len() > 0 {
let n = cmp::min(LINE_LENGTH - self.column, enc.len());
self.sink
.write_all(&enc[..n])?;
enc = &enc[n..];
self.column += n;
self.linebreak()?;
}
assert_eq!(written, buf.len());
Ok(written)
}
fn flush(&mut self) -> Result<()> {
self.sink.flush()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ReaderMode {
Tolerant(Option<Kind>),
VeryTolerant,
}
assert_send_and_sync!(ReaderMode);
pub struct Reader<'a> {
reader: buffered_reader::Generic<IoReader<'a>, Cookie>,
}
assert_send_and_sync!(Reader<'_>);
impl<'a> fmt::Debug for Reader<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("armor::Reader")
.field("reader", self.reader.reader_ref())
.finish()
}
}
impl<'a> fmt::Display for Reader<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "armor::Reader")
}
}
#[derive(Debug)]
struct IoReader<'a> {
source: Box<dyn BufferedReader<Cookie> + 'a>,
kind: Option<Kind>,
mode: ReaderMode,
buffer: Vec<u8>,
crc: CRC,
expect_crc: Option<u32>,
initialized: bool,
headers: Vec<(String, String)>,
finalized: bool,
prefix_len: usize,
prefix_remaining: usize,
}
assert_send_and_sync!(IoReader<'_>);
impl Default for ReaderMode {
fn default() -> Self {
ReaderMode::Tolerant(None)
}
}
impl<'a> Reader<'a> {
pub fn new<R, M>(inner: R, mode: M) -> Self
where R: 'a + Read + Send + Sync,
M: Into<Option<ReaderMode>>
{
Self::from_buffered_reader(
Box::new(buffered_reader::Generic::with_cookie(inner, None,
Default::default())),
mode, Default::default())
}
pub fn from_reader<R, M>(reader: R, mode: M) -> Self
where R: 'a + Read + Send + Sync,
M: Into<Option<ReaderMode>>
{
Self::from_buffered_reader(
Box::new(buffered_reader::Generic::with_cookie(reader, None,
Default::default())),
mode, Default::default())
}
pub fn from_file<P, M>(path: P, mode: M) -> Result<Self>
where P: AsRef<Path>,
M: Into<Option<ReaderMode>>
{
Ok(Self::from_buffered_reader(
Box::new(buffered_reader::File::with_cookie(path,
Default::default())?),
mode, Default::default()))
}
pub fn from_bytes<M>(bytes: &'a [u8], mode: M) -> Self
where M: Into<Option<ReaderMode>>
{
Self::from_buffered_reader(
Box::new(buffered_reader::Memory::with_cookie(bytes,
Default::default())),
mode, Default::default())
}
pub(crate) fn from_buffered_reader<M>(
inner: Box<dyn BufferedReader<Cookie> + 'a>, mode: M, cookie: Cookie)
-> Self
where M: Into<Option<ReaderMode>>
{
let mode = mode.into().unwrap_or(Default::default());
let io_reader = IoReader {
source: inner,
kind: None,
mode,
buffer: Vec::<u8>::with_capacity(1024),
crc: CRC::new(),
expect_crc: None,
headers: Vec::new(),
initialized: false,
finalized: false,
prefix_len: 0,
prefix_remaining: 0,
};
Reader {
reader: buffered_reader::Generic::with_cookie(io_reader,
None,
cookie),
}
}
pub fn kind(&self) -> Option<Kind> {
self.reader.reader_ref().kind
}
pub fn headers(&mut self) -> Result<&[(String, String)]> {
self.reader.reader_mut().initialize()?;
Ok(&self.reader.reader_ref().headers[..])
}
}
impl<'a> IoReader<'a> {
fn initialize(&mut self) -> Result<()> {
if self.initialized { return Ok(()) }
lazy_static::lazy_static!{
static ref START_CHARS_VERY_TOLERANT: Vec<u8> = {
let mut valid_start = Vec::new();
for &tag in &[ Tag::PKESK, Tag::SKESK,
Tag::OnePassSig, Tag::Signature,
Tag::PublicKey, Tag::SecretKey,
Tag::CompressedData, Tag::Literal,
Tag::Marker,
] {
let mut ctb = [ 0u8; 1 ];
let mut o = [ 0u8; 4 ];
CTBNew::new(tag).serialize_into(&mut ctb[..]).unwrap();
base64::encode_config_slice(&ctb[..], base64::STANDARD, &mut o[..]);
valid_start.push(o[0]);
CTBOld::new(tag, BodyLength::Full(0)).unwrap()
.serialize_into(&mut ctb[..]).unwrap();
base64::encode_config_slice(&ctb[..], base64::STANDARD, &mut o[..]);
valid_start.push(o[0]);
}
let mut b = [0; 4];
for d in dashes() {
d.encode_utf8(&mut b);
valid_start.push(b[0]);
}
valid_start.push(b'B');
valid_start.sort();
valid_start.dedup();
valid_start
};
static ref START_CHARS_TOLERANT: Vec<u8> = {
let mut valid_start = Vec::new();
let mut b = [0; 4];
for d in dashes() {
d.encode_utf8(&mut b);
valid_start.push(b[0]);
}
valid_start.push(b'B');
valid_start.sort();
valid_start.dedup();
valid_start
};
}
let mut found_blob = false;
let start_chars = if self.mode != ReaderMode::VeryTolerant {
&START_CHARS_TOLERANT[..]
} else {
&START_CHARS_VERY_TOLERANT[..]
};
let mut lines = 0;
let mut prefix = Vec::new();
let n = 'search: loop {
if lines > 0 {
self.source.drop_through(&[b'\n'], true)?;
crate::vec_truncate(&mut prefix, 0);
}
lines += 1;
while match self.source.data_hard(1)?[0] {
b' ' | b'\t' | b'\r' | b'\n' => true,
b'>' | b'|' | b']' | b'}' => true,
_ => false,
} {
let c = self.source.data(1)?[0];
if c == b'\n' {
crate::vec_truncate(&mut prefix, 0);
} else {
prefix.push(self.source.data_hard(1)?[0]);
}
self.source.consume(1);
}
let start = self.source.data_hard(1)?[0];
if !start_chars.binary_search(&start).is_ok()
{
self.source.consume(1);
continue;
}
{
let mut input = self.source.data(128)?;
let n = input.len();
if n == 0 {
return Err(
Error::new(ErrorKind::InvalidInput,
"Reached EOF looking for Armor Header Line"));
}
if n > 128 {
input = &input[..128];
}
if let Some((kind, len)) = Kind::detect_header(&input) {
let mut expected_kind = None;
if let ReaderMode::Tolerant(Some(kind)) = self.mode {
expected_kind = Some(kind);
}
if expected_kind == None {
self.kind = Some(kind);
break 'search len;
}
if expected_kind == Some(kind) {
self.kind = Some(kind);
break 'search len;
}
}
if self.mode == ReaderMode::VeryTolerant {
if is_armored_pgp_blob(input) {
found_blob = true;
break 'search 0;
}
}
}
};
self.source.consume(n);
if found_blob {
self.initialized = true;
self.prefix_len = prefix.len();
self.prefix_remaining = prefix.len();
return Ok(());
}
let n = {
let line = self.source.read_to('\n' as u8)?;
line.iter().position(|&c| {
!c.is_ascii_whitespace()
}).unwrap_or(line.len())
};
self.source.consume(n);
let next_prefix = &self.source.data_hard(prefix.len())?[..prefix.len()];
if prefix != next_prefix {
if prefix.iter().all(|b| (*b as char).is_ascii_whitespace()) {
crate::vec_truncate(&mut prefix, 0);
} else {
return Err(
Error::new(ErrorKind::InvalidInput,
"Inconsistent quoting of armored data"));
}
}
let mut n = 0;
let mut prefix_len = None;
let mut lines = 0;
loop {
self.source.consume(
prefix_len.take().unwrap_or_else(|| prefix.len()));
self.source.consume(n);
let line = self.source.read_to('\n' as u8)?;
n = line.len();
lines += 1;
let line = str::from_utf8(line);
if line.is_err() {
let next_prefix =
&self.source.data_hard(n + prefix.len())?[n..n + prefix.len()];
if prefix != next_prefix {
return Err(
Error::new(ErrorKind::InvalidInput,
"Inconsistent quoting of armored data"));
}
continue;
}
let line = line.unwrap();
let line = if line.ends_with(&"\r\n"[..]) {
&line[..line.len() - 2]
} else if line.ends_with("\n") {
&line[..line.len() - 1]
} else {
line
};
let key_value = line.splitn(2, ": ").collect::<Vec<&str>>();
if key_value.len() == 1 {
if line.trim_start().len() == 0 {
break;
} else if lines == 1 {
n = 0;
break;
}
} else {
let key = key_value[0].trim_start();
let value = key_value[1];
self.headers.push((key.into(), value.into()));
}
let next_prefix =
&self.source.data_hard(n + prefix.len())?[n..n + prefix.len()];
let l = common_prefix(&prefix, next_prefix);
let full_prefix = l == prefix.len();
if ! (full_prefix
|| prefix[l..].iter().all(|c| c.is_ascii_whitespace()))
{
return Err(
Error::new(ErrorKind::InvalidInput,
"Inconsistent quoting of armored data"));
}
if ! full_prefix {
prefix_len = Some(l);
}
}
self.source.consume(n);
self.initialized = true;
self.prefix_len = prefix.len();
self.prefix_remaining = prefix.len();
Ok(())
}
}
fn common_prefix<A: AsRef<[u8]>, B: AsRef<[u8]>>(a: A, b: B) -> usize {
a.as_ref().iter().zip(b.as_ref().iter()).take_while(|(a, b)| a == b).count()
}
fn base64_filter(mut bytes: Cow<[u8]>, base64_data_max: usize,
mut prefix_remaining: usize, prefix_len: usize)
-> (Cow<[u8]>, usize, usize)
{
let mut leading_whitespace = 0;
let base64_data_max = base64_data_max / 4 * 4;
let mut base64_len = 0;
let mut unfiltered_offset = 0;
let mut unfiltered_complete_len = 0;
let mut padding = 0;
while unfiltered_offset < bytes.len()
&& base64_len < base64_data_max
&& ! (padding > 0 && base64_len % 4 == 0)
{
if prefix_remaining > 0 {
prefix_remaining -= 1;
if unfiltered_offset == 0 {
match bytes {
Cow::Borrowed(s) => {
bytes = Cow::Borrowed(&s[1..]);
leading_whitespace += 1;
continue;
}
Cow::Owned(_) => (),
}
}
unfiltered_offset += 1;
continue;
}
match bytes[unfiltered_offset] {
c if c.is_ascii_whitespace() => {
if c == b'\n' {
prefix_remaining = prefix_len;
}
if unfiltered_offset == 0 {
match bytes {
Cow::Borrowed(s) => {
bytes = Cow::Borrowed(&s[1..]);
leading_whitespace += 1;
continue;
}
Cow::Owned(_) => (),
}
}
}
b'=' => {
if padding == 2 {
break;
}
if base64_len % 4 == 0 {
break;
}
if unfiltered_offset != base64_len {
bytes.to_mut()[base64_len] = b'=';
}
base64_len += 1;
if base64_len % 4 == 0 {
unfiltered_complete_len = unfiltered_offset + 1;
}
padding += 1;
}
_ if padding > 0 => break,
b if is_base64_char(&b) => {
if unfiltered_offset != base64_len {
bytes.to_mut()[base64_len] = b;
}
base64_len += 1;
if base64_len % 4 == 0 {
unfiltered_complete_len = unfiltered_offset + 1;
}
}
_ => break,
}
unfiltered_offset += 1;
}
let base64_len = base64_len - (base64_len % 4);
unfiltered_complete_len += leading_whitespace;
match bytes {
Cow::Borrowed(s) =>
(Cow::Borrowed(&s[..base64_len]), unfiltered_complete_len,
prefix_remaining),
Cow::Owned(mut v) => {
vec_truncate(&mut v, base64_len);
(Cow::Owned(v), unfiltered_complete_len, prefix_remaining)
}
}
}
fn is_armored_pgp_blob(bytes: &[u8]) -> bool {
let (bytes, _, _) = base64_filter(Cow::Borrowed(bytes), 32, 0, 0);
match base64::decode_config(&bytes, base64::STANDARD) {
Ok(d) => {
if d.len() == 0 {
false
} else {
let mut br = buffered_reader::Memory::new(&d);
if let Ok(header) = Header::parse(&mut br) {
header.ctb().tag().valid_start_of_message()
&& header.valid(false).is_ok()
} else {
false
}
}
},
Err(_err) => false,
}
}
fn is_base64_char(b: &u8) -> bool {
b.is_ascii_alphanumeric() || *b == '+' as u8 || *b == '/' as u8
}
fn base64_size(s: usize) -> usize {
(s + 3 - 1) / 3 * 4
}
#[test]
fn base64_size_test() {
assert_eq!(base64_size(0), 0);
assert_eq!(base64_size(1), 4);
assert_eq!(base64_size(2), 4);
assert_eq!(base64_size(3), 4);
assert_eq!(base64_size(4), 8);
assert_eq!(base64_size(5), 8);
assert_eq!(base64_size(6), 8);
assert_eq!(base64_size(7), 12);
}
impl<'a> Read for IoReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if ! self.initialized {
self.initialize()?;
}
if buf.len() == 0 {
return Ok(0);
}
if self.finalized {
assert_eq!(self.buffer.len(), 0);
return Ok(0);
}
let (consumed, decoded) = if self.buffer.len() > 0 {
let amount = cmp::min(buf.len(), self.buffer.len());
buf[..amount].copy_from_slice(&self.buffer[..amount]);
crate::vec_drain_prefix(&mut self.buffer, amount);
(0, amount)
} else {
const THRESHOLD : usize = 64;
let to_read =
cmp::max(
THRESHOLD + 2,
base64_size(buf.len())
+ 2 * ((buf.len() + 63) / 64));
let base64data = self.source.data(to_read)?;
let base64data = if base64data.len() > to_read {
&base64data[..to_read]
} else {
base64data
};
let (base64data, consumed, prefix_remaining)
= base64_filter(Cow::Borrowed(base64data),
cmp::max(THRESHOLD, buf.len() / 3 * 4),
self.prefix_remaining,
self.prefix_len);
assert_eq!(base64data.len() % 4, 0);
let decoded = if base64data.len() / 4 * 3 > buf.len() {
self.buffer = base64::decode_config(
&base64data, base64::STANDARD)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
self.crc.update(&self.buffer);
let copied = cmp::min(buf.len(), self.buffer.len());
buf[..copied].copy_from_slice(&self.buffer[..copied]);
crate::vec_drain_prefix(&mut self.buffer, copied);
copied
} else {
let decoded = base64::decode_config_slice(
&base64data, base64::STANDARD, buf)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
self.crc.update(&buf[..decoded]);
decoded
};
self.prefix_remaining = prefix_remaining;
(consumed, decoded)
};
self.source.consume(consumed);
if decoded == 0 {
self.finalized = true;
let consumed = {
while self.source.data(1)?.len() > 0
&& self.source.buffer()[0].is_ascii_whitespace()
{
self.source.consume(1);
}
let data = self.source.data(5)?;
let data = if data.len() > 5 {
&data[..5]
} else {
data
};
if data.len() == 5
&& data[0] == '=' as u8
&& data[1..5].iter().all(is_base64_char)
{
let crc = match base64::decode_config(
&data[1..5], base64::STANDARD)
{
Ok(d) => d,
Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
};
assert_eq!(crc.len(), 3);
let crc =
(crc[0] as u32) << 16
| (crc[1] as u32) << 8
| crc[2] as u32;
self.expect_crc = Some(crc);
5
} else {
0
}
};
self.source.consume(consumed);
self.source.data_consume_hard(self.prefix_len)?;
let consumed = {
while self.source.data(1)?.len() > 0
&& self.source.buffer()[0].is_ascii_whitespace()
{
self.source.consume(1);
}
if let Some(kind) = self.kind {
let footer_lookahead = 128;
let got = self.source.data(footer_lookahead)?;
let got = if got.len() > footer_lookahead {
&got[..footer_lookahead]
} else {
got
};
if let Some(footer_len) = kind.detect_footer(got) {
footer_len
} else {
return Err(Error::new(ErrorKind::InvalidInput,
"Invalid ASCII Armor footer."));
}
} else {
0
}
};
self.source.consume(consumed);
if let Some(crc) = self.expect_crc {
if self.crc.finalize() != crc {
return Err(Error::new(ErrorKind::InvalidInput,
"Bad CRC sum."));
}
}
}
Ok(decoded)
}
}
impl<'a> Read for Reader<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.reader.read(buf)
}
}
impl<'a> BufferedReader<Cookie> for Reader<'a> {
fn buffer(&self) -> &[u8] {
self.reader.buffer()
}
fn data(&mut self, amount: usize) -> Result<&[u8]> {
self.reader.data(amount)
}
fn consume(&mut self, amount: usize) -> &[u8] {
self.reader.consume(amount)
}
fn data_consume(&mut self, amount: usize) -> Result<&[u8]> {
self.reader.data_consume(amount)
}
fn data_consume_hard(&mut self, amount: usize) -> Result<&[u8]> {
self.reader.data_consume_hard(amount)
}
fn consummated(&mut self) -> bool {
self.reader.consummated()
}
fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> {
Some(&mut self.reader.reader_mut().source)
}
fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> {
Some(&self.reader.reader_ref().source)
}
fn into_inner<'b>(self: Box<Self>)
-> Option<Box<dyn BufferedReader<Cookie> + 'b>>
where Self: 'b {
Some(self.reader.into_reader().source)
}
fn cookie_set(&mut self, cookie: Cookie) -> Cookie {
self.reader.cookie_set(cookie)
}
fn cookie_ref(&self) -> &Cookie {
self.reader.cookie_ref()
}
fn cookie_mut(&mut self) -> &mut Cookie {
self.reader.cookie_mut()
}
}
const CRC24_INIT: u32 = 0xB704CE;
const CRC24_POLY: u32 = 0x1864CFB;
#[derive(Debug)]
struct CRC {
n: u32,
}
impl CRC {
fn new() -> Self {
CRC { n: CRC24_INIT }
}
fn update(&mut self, buf: &[u8]) -> &Self {
for octet in buf {
self.n ^= (*octet as u32) << 16;
for _ in 0..8 {
self.n <<= 1;
if self.n & 0x1000000 > 0 {
self.n ^= CRC24_POLY;
}
}
}
self
}
fn finalize(&self) -> u32 {
self.n & 0xFFFFFF
}
}
fn dashes() -> impl Iterator<Item = char> {
['\u{002D}',
'\u{058A}',
'\u{05BE}',
'\u{1400}',
'\u{1806}',
'\u{2010}',
'\u{2011}',
'\u{2012}',
'\u{2013}',
'\u{2014}',
'\u{2015}',
'\u{2E17}',
'\u{2E1A}',
'\u{2E3A}',
'\u{2E3B}',
'\u{2E40}',
'\u{301C}',
'\u{3030}',
'\u{30A0}',
'\u{FE31}',
'\u{FE32}',
'\u{FE58}',
'\u{FE63}',
'\u{FF0D}',
].iter().cloned()
}
fn dash_prefix(d: &[u8]) -> (&[u8], &[u8]) {
let p = match std::str::from_utf8(d) {
Ok(u) => u,
Err(e) => std::str::from_utf8(&d[..e.valid_up_to()])
.expect("valid up to this point"),
};
let mut prefix_len = 0;
for c in p.chars() {
match c {
'\u{002D}'
| '\u{058A}'
| '\u{05BE}'
| '\u{1400}'
| '\u{1806}'
| '\u{2010}'
| '\u{2011}'
| '\u{2012}'
| '\u{2013}'
| '\u{2014}'
| '\u{2015}'
| '\u{2E17}'
| '\u{2E1A}'
| '\u{2E3A}'
| '\u{2E3B}'
| '\u{2E40}'
| '\u{301C}'
| '\u{3030}'
| '\u{30A0}'
| '\u{FE31}'
| '\u{FE32}'
| '\u{FE58}'
| '\u{FE63}'
| '\u{FF0D}'
=> prefix_len += c.len_utf8(),
_ => break,
}
}
(&d[..prefix_len], &d[prefix_len..])
}
#[cfg(test)]
mod test {
use std::io::{Cursor, Read, Write};
use super::CRC;
use super::Kind;
use super::Writer;
#[test]
fn crc() {
let b = b"foobarbaz";
let crcs = [
0xb704ce,
0x6d2804,
0xa2d10d,
0x4fc255,
0x7aafca,
0xc79c46,
0x7334de,
0x77dc72,
0x000f65,
0xf40d86,
];
for len in 0..b.len() + 1 {
assert_eq!(CRC::new().update(&b[..len]).finalize(), crcs[len]);
}
}
macro_rules! t {
( $path: expr ) => {
include_bytes!(concat!("../tests/data/armor/", $path))
}
}
macro_rules! vectors {
( $prefix: expr, $suffix: expr ) => {
&[t!(concat!($prefix, "-0", $suffix)),
t!(concat!($prefix, "-1", $suffix)),
t!(concat!($prefix, "-2", $suffix)),
t!(concat!($prefix, "-3", $suffix)),
t!(concat!($prefix, "-47", $suffix)),
t!(concat!($prefix, "-48", $suffix)),
t!(concat!($prefix, "-49", $suffix)),
t!(concat!($prefix, "-50", $suffix)),
t!(concat!($prefix, "-51", $suffix))]
}
}
const TEST_BIN: &[&[u8]] = vectors!("test", ".bin");
const TEST_ASC: &[&[u8]] = vectors!("test", ".asc");
const LITERAL_BIN: &[&[u8]] = vectors!("literal", ".bin");
const LITERAL_ASC: &[&[u8]] = vectors!("literal", ".asc");
const LITERAL_NO_HEADER_ASC: &[&[u8]] =
vectors!("literal", "-no-header.asc");
const LITERAL_NO_HEADER_WITH_CHKSUM_ASC: &[&[u8]] =
vectors!("literal", "-no-header-with-chksum.asc");
const LITERAL_NO_NEWLINES_ASC: &[&[u8]] =
vectors!("literal", "-no-newlines.asc");
#[test]
fn enarmor() {
for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) {
let mut w =
Writer::new(Vec::new(), Kind::File).unwrap();
w.write(&[]).unwrap();
w.write_all(bin).unwrap();
let buf = w.finalize().unwrap();
assert_eq!(String::from_utf8_lossy(&buf),
String::from_utf8_lossy(asc));
}
}
#[test]
fn enarmor_bytewise() {
for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) {
let mut w = Writer::new(Vec::new(), Kind::File).unwrap();
w.write(&[]).unwrap();
for b in bin.iter() {
w.write(&[*b]).unwrap();
}
let buf = w.finalize().unwrap();
assert_eq!(String::from_utf8_lossy(&buf),
String::from_utf8_lossy(asc));
}
}
#[test]
fn drop_writer() {
assert!(Writer::new(Vec::new(), Kind::File).unwrap()
.finalize().unwrap().is_empty());
let mut w = Writer::new(Vec::new(), Kind::File).unwrap();
w.write(&[]).unwrap();
let buf = w.finalize().unwrap();
assert_eq!(
&buf[..],
&b"-----BEGIN PGP ARMORED FILE-----\n\
\n\
=twTO\n\
-----END PGP ARMORED FILE-----\n"[..]);
}
use super::{Reader, ReaderMode};
#[test]
fn dearmor_robust() {
for (i, reference) in LITERAL_BIN.iter().enumerate() {
for test in &[LITERAL_ASC[i],
LITERAL_NO_HEADER_WITH_CHKSUM_ASC[i],
LITERAL_NO_HEADER_ASC[i],
LITERAL_NO_NEWLINES_ASC[i]] {
let mut r = Reader::new(Cursor::new(test),
ReaderMode::VeryTolerant);
let mut dearmored = Vec::<u8>::new();
r.read_to_end(&mut dearmored).unwrap();
assert_eq!(&dearmored, reference);
}
}
}
#[test]
fn dearmor_binary() {
for bin in TEST_BIN.iter() {
let mut r = Reader::new(
Cursor::new(bin), ReaderMode::Tolerant(Some(Kind::Message)));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
}
#[test]
fn dearmor_wrong_kind() {
let mut r = Reader::new(
Cursor::new(&include_bytes!("../tests/data/armor/test-0.asc")[..]),
ReaderMode::Tolerant(Some(Kind::Message)));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor_wrong_crc() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-0.bad-crc.asc")[..]),
ReaderMode::Tolerant(Some(Kind::File)));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor_wrong_footer() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-2.bad-footer.asc")[..]
),
ReaderMode::Tolerant(Some(Kind::File)));
let mut read = 0;
loop {
let mut buf = [0; 5];
match r.read(&mut buf) {
Ok(0) => panic!("Reached EOF, but expected an error!"),
Ok(r) => read += r,
Err(_) => break,
}
}
assert!(read <= 2);
}
#[test]
fn dearmor_no_crc() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-1.no-crc.asc")[..]),
ReaderMode::Tolerant(Some(Kind::File)));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.unwrap() == 1 && buf[0] == 0xde);
}
#[test]
fn dearmor_with_header() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-3.with-headers.asc")[..]
),
ReaderMode::Tolerant(Some(Kind::File)));
assert_eq!(r.headers().unwrap(),
&[("Comment".into(), "Some Header".into()),
("Comment".into(), "Another one".into())]);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_ok());
assert_eq!(e.unwrap(), 3);
assert_eq!(&buf[..3], TEST_BIN[3]);
}
#[test]
fn dearmor_any() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-3.with-headers.asc")[..]
),
ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert_eq!(r.kind(), Some(Kind::File));
assert!(e.is_ok());
assert_eq!(e.unwrap(), 3);
assert_eq!(&buf[..3], TEST_BIN[3]);
}
#[test]
fn dearmor_with_garbage() {
let armored =
include_bytes!("../tests/data/armor/test-3.with-headers.asc");
let mut b: Vec<u8> = "Some\ngarbage\nlines\n\t\r ".into();
b.extend_from_slice(armored);
let mut r = Reader::new(Cursor::new(b), ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert_eq!(r.kind(), Some(Kind::File));
assert!(e.is_ok());
assert_eq!(e.unwrap(), 3);
assert_eq!(&buf[..3], TEST_BIN[3]);
let mut b: Vec<u8> = "Some\ngarbage\nlines\n\t.\r ".into();
b.extend_from_slice(armored);
let mut r = Reader::new(Cursor::new(b), ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor() {
for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) {
let mut r = Reader::new(
Cursor::new(asc),
ReaderMode::Tolerant(Some(Kind::File)));
let mut dearmored = Vec::<u8>::new();
r.read_to_end(&mut dearmored).unwrap();
assert_eq!(&dearmored, bin);
}
}
#[test]
fn dearmor_bytewise() {
for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) {
let r = Reader::new(
Cursor::new(asc),
ReaderMode::Tolerant(Some(Kind::File)));
let mut dearmored = Vec::<u8>::new();
for c in r.bytes() {
dearmored.push(c.unwrap());
}
assert_eq!(&dearmored, bin);
}
}
#[test]
fn dearmor_yuge() {
let yuge_key = crate::tests::key("yuge-key-so-yuge-the-yugest.asc");
let mut r = Reader::new(Cursor::new(&yuge_key[..]),
ReaderMode::VeryTolerant);
let mut dearmored = Vec::<u8>::new();
r.read_to_end(&mut dearmored).unwrap();
let r = Reader::new(Cursor::new(&yuge_key[..]),
ReaderMode::VeryTolerant);
let mut dearmored = Vec::<u8>::new();
for c in r.bytes() {
dearmored.push(c.unwrap());
}
}
#[test]
fn dearmor_quoted() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-3.with-headers-quoted.asc")[..]
),
ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert_eq!(r.kind(), Some(Kind::File));
assert!(e.is_ok());
assert_eq!(e.unwrap(), 3);
assert_eq!(&buf[..3], TEST_BIN[3]);
}
#[test]
fn dearmor_quoted_stripped() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-3.with-headers-quoted-stripped.asc")[..]
),
ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert_eq!(r.kind(), Some(Kind::File));
assert!(e.is_ok());
assert_eq!(e.unwrap(), 3);
assert_eq!(&buf[..3], TEST_BIN[3]);
}
#[test]
fn dearmor_quoted_a_lot() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-3.with-headers-quoted-a-lot.asc")[..]
),
ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert_eq!(r.kind(), Some(Kind::File));
assert!(e.is_ok());
assert_eq!(e.unwrap(), 3);
assert_eq!(&buf[..3], TEST_BIN[3]);
}
#[test]
fn dearmor_quoted_badly() {
let mut r = Reader::new(
Cursor::new(
&include_bytes!("../tests/data/armor/test-3.with-headers-quoted-badly.asc")[..]
),
ReaderMode::VeryTolerant);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
quickcheck! {
fn roundtrip(kind: Kind, payload: Vec<u8>) -> bool {
if payload.is_empty() {
return true;
}
let mut w = Writer::new(Vec::new(), kind).unwrap();
w.write_all(&payload).unwrap();
let encoded = w.finalize().unwrap();
let mut recovered = Vec::new();
Reader::new(Cursor::new(&encoded),
ReaderMode::Tolerant(Some(kind)))
.read_to_end(&mut recovered)
.unwrap();
let mut recovered_any = Vec::new();
Reader::new(Cursor::new(&encoded), ReaderMode::VeryTolerant)
.read_to_end(&mut recovered_any)
.unwrap();
payload == recovered && payload == recovered_any
}
}
#[test]
fn zero_sized_read() {
let mut r = Reader::from_bytes(crate::tests::file("armor/test-1.asc"),
None);
let mut buf = Vec::new();
r.read(&mut buf).unwrap();
r.read(&mut buf).unwrap();
}
#[test]
fn issue_515() {
let data = [63, 9, 45, 10, 45, 10, 45, 45, 45, 45, 45, 66, 69,
71, 73, 78, 32, 80, 71, 80, 32, 77, 69, 83, 83,
65, 71, 69, 45, 45, 45, 45, 45, 45, 152, 152, 152,
152, 152, 152, 255, 29, 152, 152, 152, 152, 152,
152, 152, 152, 152, 152, 10, 91, 45, 10, 45, 14,
0, 36, 0, 0, 30, 122, 4, 2, 204, 152];
let mut reader = Reader::from_bytes(&data[..], None);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).unwrap_err();
}
#[test]
fn issue_516() {
let data = [
144, 32, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 125, 13, 125,
125, 93, 125, 125, 93, 125, 13, 13, 125, 125, 45, 45, 45,
45, 45, 66, 69, 71, 73, 78, 32, 80, 71, 80, 32, 77, 69,
83, 83, 65, 71, 69, 45, 45, 45, 45, 45, 125, 13, 125,
125, 93, 125, 125, 93, 125, 13, 13, 125, 125, 45, 0, 0,
0, 0, 0, 0, 0, 0, 125, 205, 21, 1, 21, 21, 21, 1, 1, 1,
1, 21, 149, 21, 21, 21, 21, 32, 4, 141, 141, 141, 141,
202, 74, 11, 125, 8, 21, 50, 50, 194, 48, 147, 93, 174,
23, 23, 23, 23, 23, 23, 147, 147, 147, 23, 23, 23, 23,
23, 23, 48, 125, 125, 93, 125, 13, 125, 125, 125, 93,
125, 125, 13, 13, 125, 125, 13, 13, 93, 125, 13, 125, 45,
125, 125, 45, 45, 66, 69, 71, 73, 78, 32, 80, 71, 45, 45,
125, 10, 45, 45, 0, 0, 10, 45, 45, 210, 10, 0, 0, 87, 0,
0, 0, 150, 10, 0, 0, 241, 87, 45, 0, 0, 121, 121, 10, 10,
21, 58];
let mut reader = Reader::from_bytes(&data[..], None);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).unwrap_err();
}
#[test]
fn issue_517() {
let data = [13, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80,
71, 80, 32, 77, 69, 83, 83, 65, 71, 69, 45, 45, 45,
45, 45, 10, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 139];
let mut reader = Reader::from_bytes(&data[..], None);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).unwrap_err();
}
#[test]
fn common_prefix() {
use super::common_prefix as cp;
assert_eq!(cp("", ""), 0);
assert_eq!(cp("a", ""), 0);
assert_eq!(cp("", "a"), 0);
assert_eq!(cp("a", "a"), 1);
assert_eq!(cp("aa", "a"), 1);
assert_eq!(cp("a", "aa"), 1);
assert_eq!(cp("ac", "ab"), 1);
}
#[test]
fn issue_610() {
let mut buf = Vec::new();
let mut reader = Reader::from_bytes(
crate::tests::file("armor/test-3.unicode-dashes.asc"), None);
reader.read_to_end(&mut buf).unwrap();
let mut reader = Reader::from_bytes(
crate::tests::file("armor/test-3.unbalanced-dashes.asc"), None);
reader.read_to_end(&mut buf).unwrap();
let mut reader = Reader::from_bytes(
crate::tests::file("armor/test-3.no-dashes.asc"), None);
reader.read_to_end(&mut buf).unwrap();
}
}