use flate2::read::GzDecoder;
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::str::FromStr;
use crate::ctry;
use crate::digest::{self, Digest, DigestData};
use crate::errors::{Error, ErrorKind, Result};
use crate::status::StatusBackend;
pub mod cached_itarbundle;
pub mod dirbundle;
pub mod filesystem;
pub mod format_cache;
pub mod memory;
pub mod setup;
pub mod stack;
pub mod stdstreams;
pub mod zipbundle;
pub trait InputFeatures: Read {
fn get_size(&mut self) -> Result<usize>;
fn try_seek(&mut self, pos: SeekFrom) -> Result<u64>;
fn get_unix_mtime(&mut self) -> Result<Option<i64>> {
Ok(None)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InputOrigin {
Filesystem,
NotInput,
Other,
}
pub struct InputHandle {
name: OsString,
inner: Box<dyn InputFeatures>,
read_only: bool,
digest: digest::DigestComputer,
origin: InputOrigin,
ever_read: bool,
did_unhandled_seek: bool,
ungetc_char: Option<u8>,
}
impl InputHandle {
pub fn new<T: 'static + InputFeatures>(
name: &OsStr,
inner: T,
origin: InputOrigin,
) -> InputHandle {
InputHandle {
name: name.to_os_string(),
inner: Box::new(inner),
read_only: false,
digest: Default::default(),
origin,
ever_read: false,
did_unhandled_seek: false,
ungetc_char: None,
}
}
pub fn new_read_only<T: 'static + InputFeatures>(
name: &OsStr,
inner: T,
origin: InputOrigin,
) -> InputHandle {
InputHandle {
name: name.to_os_string(),
inner: Box::new(inner),
read_only: true,
digest: Default::default(),
origin,
ever_read: false,
did_unhandled_seek: false,
ungetc_char: None,
}
}
pub fn name(&self) -> &OsStr {
self.name.as_os_str()
}
pub fn origin(&self) -> InputOrigin {
self.origin
}
pub fn into_inner(self) -> Box<dyn InputFeatures> {
self.inner
}
pub fn scan_remainder(&mut self) -> Result<()> {
const BUFSIZE: usize = 1024;
let mut buf: [u8; BUFSIZE] = [0; BUFSIZE];
loop {
let n = match self.inner.read(&mut buf[..]) {
Ok(n) => n,
Err(ref ioe) if ioe.raw_os_error() == Some(libc::EISDIR) => return Ok(()),
Err(e) => return Err(e.into()),
};
if n == 0 {
break;
}
self.digest.update(&buf[..n]);
}
Ok(())
}
pub fn into_name_digest(self) -> (OsString, Option<DigestData>) {
if self.did_unhandled_seek || !self.ever_read || self.read_only {
(self.name, None)
} else {
(self.name, Some(DigestData::from(self.digest)))
}
}
pub fn getc(&mut self) -> Result<u8> {
if let Some(c) = self.ungetc_char {
self.ungetc_char = None;
return Ok(c);
}
let mut byte = [0u8; 1];
if self.read(&mut byte[..1])? == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF in getc").into());
}
Ok(byte[0])
}
pub fn ungetc(&mut self, byte: u8) -> Result<()> {
if self.ungetc_char.is_some() {
return Err(ErrorKind::Msg(
"internal problem: cannot ungetc() more than once in a row".into(),
)
.into());
}
self.ungetc_char = Some(byte);
Ok(())
}
}
impl Read for InputHandle {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if !buf.is_empty() {
if let Some(c) = self.ungetc_char {
buf[0] = c;
self.ungetc_char = None;
return Ok(self.read(&mut buf[1..])? + 1);
}
}
self.ever_read = true;
let n = self.inner.read(buf)?;
if !self.read_only {
self.digest.update(&buf[..n]);
}
Ok(n)
}
}
impl InputFeatures for InputHandle {
fn get_size(&mut self) -> Result<usize> {
self.inner.get_size()
}
fn get_unix_mtime(&mut self) -> Result<Option<i64>> {
self.inner.get_unix_mtime()
}
fn try_seek(&mut self, pos: SeekFrom) -> Result<u64> {
match pos {
SeekFrom::Start(0) => {
self.digest = Default::default();
self.ever_read = false;
self.ungetc_char = None;
}
SeekFrom::Current(0) => {
}
_ => {
self.did_unhandled_seek = true;
self.ungetc_char = None;
}
}
let mut offset = self.inner.try_seek(pos)?;
if self.ungetc_char.is_some() {
offset -= 1;
}
Ok(offset)
}
}
pub struct OutputHandle {
name: OsString,
inner: Box<dyn Write>,
digest: digest::DigestComputer,
}
impl OutputHandle {
pub fn new<T: 'static + Write>(name: &OsStr, inner: T) -> OutputHandle {
OutputHandle {
name: name.to_os_string(),
inner: Box::new(inner),
digest: digest::create(),
}
}
pub fn name(&self) -> &OsStr {
self.name.as_os_str()
}
pub fn into_inner(self) -> Box<dyn Write> {
self.inner
}
pub fn into_name_digest(self) -> (OsString, DigestData) {
(self.name, DigestData::from(self.digest))
}
}
impl Write for OutputHandle {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.inner.write(buf)?;
self.digest.update(&buf[..n]);
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[derive(Debug)]
pub enum OpenResult<T> {
Ok(T),
NotAvailable,
Err(Error),
}
impl<T> OpenResult<T> {
pub fn unwrap(self) -> T {
match self {
OpenResult::Ok(t) => t,
_ => panic!("expected an open file"),
}
}
pub fn is_not_available(&self) -> bool {
matches!(*self, OpenResult::NotAvailable)
}
pub fn must_exist(self) -> Result<T> {
match self {
OpenResult::Ok(t) => Ok(t),
OpenResult::Err(e) => Err(e),
OpenResult::NotAvailable => {
Err(io::Error::new(io::ErrorKind::NotFound, "not found").into())
}
}
}
}
pub trait AsIoProviderMut {
fn as_ioprovider_mut(&mut self) -> &mut dyn IoProvider;
}
impl<T: IoProvider> AsIoProviderMut for T {
fn as_ioprovider_mut(&mut self) -> &mut dyn IoProvider {
self
}
}
pub trait IoProvider: AsIoProviderMut {
fn output_open_name(&mut self, _name: &OsStr) -> OpenResult<OutputHandle> {
OpenResult::NotAvailable
}
fn output_open_stdout(&mut self) -> OpenResult<OutputHandle> {
OpenResult::NotAvailable
}
fn input_open_name(
&mut self,
_name: &OsStr,
_status: &mut dyn StatusBackend,
) -> OpenResult<InputHandle> {
OpenResult::NotAvailable
}
fn input_open_primary(&mut self, _status: &mut dyn StatusBackend) -> OpenResult<InputHandle> {
OpenResult::NotAvailable
}
fn input_open_format(
&mut self,
name: &OsStr,
status: &mut dyn StatusBackend,
) -> OpenResult<InputHandle> {
self.input_open_name(name, status)
}
fn write_format(
&mut self,
_name: &str,
_data: &[u8],
_status: &mut dyn StatusBackend,
) -> Result<()> {
Err(ErrorKind::Msg("this I/O layer cannot save format files".to_owned()).into())
}
}
impl<P: IoProvider + ?Sized> IoProvider for Box<P> {
fn output_open_name(&mut self, name: &OsStr) -> OpenResult<OutputHandle> {
(**self).output_open_name(name)
}
fn output_open_stdout(&mut self) -> OpenResult<OutputHandle> {
(**self).output_open_stdout()
}
fn input_open_name(
&mut self,
name: &OsStr,
status: &mut dyn StatusBackend,
) -> OpenResult<InputHandle> {
(**self).input_open_name(name, status)
}
fn input_open_primary(&mut self, status: &mut dyn StatusBackend) -> OpenResult<InputHandle> {
(**self).input_open_primary(status)
}
fn input_open_format(
&mut self,
name: &OsStr,
status: &mut dyn StatusBackend,
) -> OpenResult<InputHandle> {
(**self).input_open_format(name, status)
}
fn write_format(
&mut self,
name: &str,
data: &[u8],
status: &mut dyn StatusBackend,
) -> Result<()> {
(**self).write_format(name, data, status)
}
}
pub trait Bundle: IoProvider {
fn get_digest(&mut self, status: &mut dyn StatusBackend) -> Result<DigestData> {
let digest_text = match self.input_open_name(OsStr::new(digest::DIGEST_NAME), status) {
OpenResult::Ok(h) => {
let mut text = String::new();
h.take(64).read_to_string(&mut text)?;
text
}
OpenResult::NotAvailable => {
return Err(ErrorKind::Msg(
"bundle does not provide needed SHA256SUM file".to_owned(),
)
.into());
}
OpenResult::Err(e) => {
return Err(e);
}
};
Ok(ctry!(DigestData::from_str(&digest_text); "corrupted SHA256 digest data"))
}
}
impl<B: Bundle + ?Sized> Bundle for Box<B> {
fn get_digest(&mut self, status: &mut dyn StatusBackend) -> Result<DigestData> {
(**self).get_digest(status)
}
}
impl<R: Read> InputFeatures for GzDecoder<R> {
fn get_size(&mut self) -> Result<usize> {
Err(ErrorKind::NotSizeable.into())
}
fn get_unix_mtime(&mut self) -> Result<Option<i64>> {
Ok(None)
}
fn try_seek(&mut self, _: SeekFrom) -> Result<u64> {
Err(ErrorKind::NotSeekable.into())
}
}
impl InputFeatures for Cursor<Vec<u8>> {
fn get_size(&mut self) -> Result<usize> {
Ok(self.get_ref().len())
}
fn get_unix_mtime(&mut self) -> Result<Option<i64>> {
Ok(None)
}
fn try_seek(&mut self, pos: SeekFrom) -> Result<u64> {
Ok(self.seek(pos)?)
}
}
pub use self::filesystem::{FilesystemIo, FilesystemPrimaryInputIo};
pub use self::memory::MemoryIo;
pub use self::setup::{IoSetup, IoSetupBuilder};
pub use self::stack::IoStack;
pub use self::stdstreams::GenuineStdoutIo;
pub fn try_open_file<P: AsRef<Path>>(path: P) -> OpenResult<File> {
use std::io::ErrorKind::NotFound;
match File::open(path) {
Ok(f) => OpenResult::Ok(f),
Err(e) => {
if e.kind() == NotFound {
OpenResult::NotAvailable
} else {
OpenResult::Err(e.into())
}
}
}
}
fn try_normalize_tex_path(path: &str) -> Option<String> {
use std::iter::repeat;
if path.is_empty() {
return Some("".into());
}
let mut r = Vec::new();
let mut parent_level = 0;
let mut has_root = false;
for (i, c) in path.split('/').enumerate() {
match c {
"" if i == 0 => {
has_root = true;
r.push("");
}
"" | "." => {}
".." => {
match r.pop() {
Some("") => return None,
None => parent_level += 1,
_ => {}
}
}
_ => r.push(c),
}
}
let r = repeat("..")
.take(parent_level)
.chain(r.into_iter())
.collect::<Vec<_>>()
.join("/");
if r.is_empty() {
if has_root {
Some("/".into())
} else {
Some(".".into())
}
} else {
Some(r)
}
}
fn normalize_tex_path(path: &OsStr) -> Cow<OsStr> {
if let Some(t) = path
.to_str()
.and_then(try_normalize_tex_path)
.map(OsString::from)
{
Cow::Owned(t)
} else {
Cow::Borrowed(path)
}
}
pub mod testing {
use super::*;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::path::{Path, PathBuf};
pub struct SingleInputFileIo {
name: OsString,
full_path: PathBuf,
}
impl SingleInputFileIo {
pub fn new(path: &Path) -> SingleInputFileIo {
let p = path.to_path_buf();
SingleInputFileIo {
name: p.file_name().unwrap().to_os_string(),
full_path: p,
}
}
}
impl IoProvider for SingleInputFileIo {
fn output_open_name(&mut self, _: &OsStr) -> OpenResult<OutputHandle> {
OpenResult::NotAvailable
}
fn output_open_stdout(&mut self) -> OpenResult<OutputHandle> {
OpenResult::NotAvailable
}
fn input_open_name(
&mut self,
name: &OsStr,
_status: &mut dyn StatusBackend,
) -> OpenResult<InputHandle> {
if name == self.name {
OpenResult::Ok(InputHandle::new(
name,
File::open(&self.full_path).unwrap(),
InputOrigin::Filesystem,
))
} else {
OpenResult::NotAvailable
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_normalize_tex_path() {
assert_eq!(try_normalize_tex_path(""), Some("".into()));
assert_eq!(try_normalize_tex_path("/"), Some("/".into()));
assert_eq!(try_normalize_tex_path("//"), Some("/".into()));
assert_eq!(try_normalize_tex_path("."), Some(".".into()));
assert_eq!(try_normalize_tex_path("./"), Some(".".into()));
assert_eq!(try_normalize_tex_path(".."), Some("..".into()));
assert_eq!(try_normalize_tex_path("././/./"), Some(".".into()));
assert_eq!(try_normalize_tex_path("/././/."), Some("/".into()));
assert_eq!(
try_normalize_tex_path("my/path/file.txt"),
Some("my/path/file.txt".into())
);
assert_eq!(
try_normalize_tex_path(" my/pa th/file .txt "),
Some(" my/pa th/file .txt ".into())
);
assert_eq!(
try_normalize_tex_path("/my/path/file.txt"),
Some("/my/path/file.txt".into())
);
assert_eq!(
try_normalize_tex_path("./my///path/././file.txt"),
Some("my/path/file.txt".into())
);
assert_eq!(
try_normalize_tex_path("./../my/../../../file.txt"),
Some("../../../file.txt".into())
);
assert_eq!(
try_normalize_tex_path("././my//../path/../here/file.txt"),
Some("here/file.txt".into())
);
assert_eq!(
try_normalize_tex_path("./my/.././/path/../../here//file.txt"),
Some("../here/file.txt".into())
);
assert_eq!(try_normalize_tex_path("/my/../../file.txt"), None);
assert_eq!(
try_normalize_tex_path("/my/./.././path//../../file.txt"),
None
);
}
}