use std::io::{self, BufReader};
use super::{ReadError, StdinGuard, UiCallbacks};
use crate::{identity::IdentityFile, Identity};
#[cfg(feature = "armor")]
use crate::{armor::ArmoredReader, cli_common::file_io::InputReader};
pub fn read_identities(
filenames: Vec<String>,
max_work_factor: Option<u8>,
stdin_guard: &mut StdinGuard,
) -> Result<Vec<Box<dyn Identity>>, ReadError> {
let mut identities: Vec<Box<dyn Identity>> = Vec::with_capacity(filenames.len());
parse_identity_files::<_, ReadError>(
filenames,
max_work_factor,
stdin_guard,
&mut identities,
#[cfg(feature = "armor")]
|identities, identity| {
identities.push(Box::new(identity));
Ok(())
},
#[cfg(feature = "ssh")]
|identities, _, identity| {
identities.push(Box::new(identity.with_callbacks(UiCallbacks)));
Ok(())
},
|identities, identity_file| {
let new_identities = identity_file.into_identities();
#[cfg(feature = "plugin")]
let new_identities = new_identities.map_err(|e| match e {
#[cfg(feature = "plugin")]
crate::DecryptError::MissingPlugin { binary_name } => {
ReadError::MissingPlugin { binary_name }
}
_ => unreachable!(),
})?;
#[cfg(not(feature = "plugin"))]
let new_identities = new_identities.unwrap();
identities.extend(new_identities);
Ok(())
},
)?;
Ok(identities)
}
pub(super) fn parse_identity_files<Ctx, E: From<ReadError> + From<io::Error>>(
filenames: Vec<String>,
_max_work_factor: Option<u8>,
stdin_guard: &mut StdinGuard,
ctx: &mut Ctx,
#[cfg(feature = "armor")] encrypted_identity: impl Fn(
&mut Ctx,
crate::encrypted::Identity<ArmoredReader<BufReader<InputReader>>, UiCallbacks>,
) -> Result<(), E>,
#[cfg(feature = "ssh")] ssh_identity: impl Fn(&mut Ctx, &str, crate::ssh::Identity) -> Result<(), E>,
identity_file: impl Fn(&mut Ctx, crate::IdentityFile<UiCallbacks>) -> Result<(), E>,
) -> Result<(), E> {
for filename in filenames {
#[cfg_attr(not(any(feature = "armor", feature = "ssh")), allow(unused_mut))]
let mut reader =
PeekableReader::new(stdin_guard.open(filename.clone()).map_err(|e| match e {
ReadError::Io(e) if matches!(e.kind(), io::ErrorKind::NotFound) => {
ReadError::IdentityNotFound(filename.clone())
}
_ => e,
})?);
#[cfg(feature = "armor")]
if crate::encrypted::Identity::from_buffer(
ArmoredReader::new_buffered(&mut reader),
Some(filename.clone()),
UiCallbacks,
_max_work_factor,
)
.is_ok()
{
reader.reset()?;
let identity = crate::encrypted::Identity::from_buffer(
ArmoredReader::new_buffered(reader.inner),
Some(filename.clone()),
UiCallbacks,
_max_work_factor,
)
.expect("already parsed the age ciphertext header");
encrypted_identity(
ctx,
identity.ok_or(ReadError::IdentityEncryptedWithoutPassphrase(filename))?,
)?;
continue;
}
#[cfg(feature = "armor")]
reader.reset()?;
#[cfg(feature = "ssh")]
match crate::ssh::Identity::from_buffer(&mut reader, Some(filename.clone())) {
Ok(crate::ssh::Identity::Unsupported(k)) => {
return Err(ReadError::UnsupportedKey(filename, k).into())
}
Ok(identity) => {
ssh_identity(ctx, &filename, identity)?;
continue;
}
Err(_) => (),
}
#[cfg(feature = "ssh")]
reader.reset()?;
identity_file(
ctx,
IdentityFile::from_buffer(reader)?.with_callbacks(UiCallbacks),
)?;
}
Ok(())
}
const PEEKABLE_SIZE: usize = 8 * 1024;
enum PeekState {
Peeking { consumed: usize },
Reading,
}
struct PeekableReader<R: io::Read> {
inner: BufReader<R>,
state: PeekState,
}
impl<R: io::Read> PeekableReader<R> {
fn new(inner: R) -> Self {
Self {
inner: BufReader::with_capacity(PEEKABLE_SIZE, inner),
state: PeekState::Peeking { consumed: 0 },
}
}
#[cfg(any(feature = "armor", feature = "ssh"))]
fn reset(&mut self) -> io::Result<()> {
match &mut self.state {
PeekState::Peeking { consumed } => {
*consumed = 0;
Ok(())
}
PeekState::Reading => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Tried to reset after the underlying buffer was exceeded.",
)),
}
}
}
impl<R: io::Read> io::Read for PeekableReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self.state {
PeekState::Peeking { .. } => {
use std::io::BufRead;
let nread = {
let mut rem = self.fill_buf()?;
rem.read(buf)?
};
self.consume(nread);
Ok(nread)
}
PeekState::Reading => self.inner.read(buf),
}
}
}
impl<R: io::Read> io::BufRead for PeekableReader<R> {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
match self.state {
PeekState::Peeking { consumed } => {
let inner_len = self.inner.fill_buf()?.len();
if inner_len == 0 {
assert_eq!(consumed, 0);
Ok(&[])
} else if consumed < inner_len {
Ok(&self.inner.fill_buf()?[consumed..])
} else if inner_len < PEEKABLE_SIZE {
assert_eq!(consumed, inner_len);
Ok(&[])
} else {
self.inner.consume(consumed);
self.state = PeekState::Reading;
self.inner.fill_buf()
}
}
PeekState::Reading => self.inner.fill_buf(),
}
}
fn consume(&mut self, amt: usize) {
match &mut self.state {
PeekState::Peeking { consumed, .. } => *consumed += amt,
PeekState::Reading => self.inner.consume(amt),
}
}
}