Skip to main content

tectonic_bundles/
zip.rs

1// Copyright 2016-2021 the Tectonic Project
2// Licensed under the MIT License.
3
4//! ZIP files as Tectonic bundles.
5
6use crate::Bundle;
7use std::{
8    fs::File,
9    io::{Cursor, Read, Seek},
10    path::Path,
11    str::FromStr,
12};
13use tectonic_errors::prelude::*;
14use tectonic_io_base::{digest, InputHandle, InputOrigin, IoProvider, OpenResult};
15use tectonic_status_base::{NoopStatusBackend, StatusBackend};
16use zip::{result::ZipError, ZipArchive};
17
18/// A bundle backed by a ZIP file.
19pub struct ZipBundle<R: Read + Seek> {
20    zip: ZipArchive<R>,
21}
22
23impl<R: Read + Seek> ZipBundle<R> {
24    /// Create a new ZIP bundle for a generic readable and seekable stream.
25    pub fn new(reader: R) -> Result<ZipBundle<R>> {
26        Ok(ZipBundle {
27            zip: ZipArchive::new(reader)?,
28        })
29    }
30}
31
32impl ZipBundle<File> {
33    /// Open a file on the filesystem as a ZIP bundle.
34    pub fn open<P: AsRef<Path>>(path: P) -> Result<ZipBundle<File>> {
35        Self::new(File::open(path)?)
36    }
37}
38
39impl<R: Read + Seek> IoProvider for ZipBundle<R> {
40    fn input_open_name(
41        &mut self,
42        name: &str,
43        _status: &mut dyn StatusBackend,
44    ) -> OpenResult<InputHandle> {
45        // We need to be able to look at other items in the Zip file while
46        // reading this one, so the only path forward is to read the entire
47        // contents into a buffer right now. RAM is cheap these days.
48
49        let mut zipitem = match self.zip.by_name(name) {
50            Ok(f) => f,
51            Err(e) => {
52                return match e {
53                    ZipError::Io(sube) => OpenResult::Err(sube.into()),
54                    ZipError::FileNotFound => OpenResult::NotAvailable,
55                    _ => OpenResult::Err(e.into()),
56                };
57            }
58        };
59
60        let s = zipitem.size();
61        if s >= u32::MAX as u64 {
62            return OpenResult::Err(anyhow!("Zip item too large."));
63        }
64        let mut buf = Vec::with_capacity(s as usize);
65
66        if let Err(e) = zipitem.read_to_end(&mut buf) {
67            return OpenResult::Err(e.into());
68        }
69
70        OpenResult::Ok(InputHandle::new_read_only(
71            name,
72            Cursor::new(buf),
73            InputOrigin::Other,
74        ))
75    }
76}
77
78impl<R: Read + Seek> Bundle for ZipBundle<R> {
79    fn all_files(&self) -> Vec<String> {
80        self.zip.file_names().map(|x| x.to_owned()).collect()
81    }
82
83    fn get_digest(&mut self) -> Result<tectonic_io_base::digest::DigestData> {
84        let digest_text = match self.input_open_name(digest::DIGEST_NAME, &mut NoopStatusBackend {})
85        {
86            OpenResult::Ok(h) => {
87                let mut text = String::new();
88                h.take(64).read_to_string(&mut text)?;
89                text
90            }
91
92            OpenResult::NotAvailable => {
93                bail!("bundle does not provide needed SHA256SUM file");
94            }
95
96            OpenResult::Err(e) => {
97                return Err(e);
98            }
99        };
100
101        Ok(atry!(digest::DigestData::from_str(&digest_text); ["corrupted SHA256 digest data"]))
102    }
103}