Skip to main content

zip/
unstable.rs

1#![doc = "Unstable APIs\n\
2\
3All APIs accessible by importing this module are unstable; They may be changed in patch \
4releases. You MUST use an exact version specifier in `Cargo.toml`, to indicate the version of this \
5API you're using:\n\
6\
7```toml\n
8[dependencies]\n
9zip = \"="]
10#![doc=env!("CARGO_PKG_VERSION")]
11#![doc = "\"\n\
12```"]
13
14use std::borrow::Cow;
15use std::io;
16use std::io::Read;
17use std::path::{Component, MAIN_SEPARATOR, Path};
18
19/// Provides high level API for reading from a stream.
20pub mod stream {
21    pub use crate::read::stream::{ZipStreamReader, ZipStreamVisitor};
22}
23
24/// Zip format
25pub mod format {
26    pub use crate::format::*;
27}
28/// Types for creating ZIP archives.
29pub mod write {
30    use crate::result::{ZipError, ZipResult};
31    use crate::write::{FileOptionExtension, FileOptions};
32    /// Unstable methods for [`FileOptions`].
33    pub trait FileOptionsExt<'k> {
34        /// Write the file with the given password using the deprecated `ZipCrypto` algorithm.
35        ///
36        /// <div class="warning">This is not recommended for new archives, as `ZipCrypto` is not
37        /// secure (it can be cracked given 12 bytes of known plaintext after compression). It is
38        /// provided only for backward compatibility with older software that doesn't support
39        /// AES-encrypted archives.</div>
40        fn with_deprecated_encryption(self, password: &'k [u8]) -> ZipResult<Self>
41        where
42            Self: Sized;
43    }
44    impl<'k, 'n, T: FileOptionExtension> FileOptionsExt<'k> for FileOptions<'k, 'n, T> {
45        fn with_deprecated_encryption(
46            self,
47            password: &'k [u8],
48        ) -> ZipResult<FileOptions<'k, 'n, T>> {
49            if password.is_empty() {
50                // Forbid empty passwords to avoid deriving a predictable key from a
51                // fixed, public value. Callers must ensure passwords are non-empty.
52                // This check isn't in with_deprecated_encryption itself because that would also
53                // affect reading.
54                Err(ZipError::InvalidPassword)
55            } else {
56                Ok(self.with_deprecated_encryption(password))
57            }
58        }
59    }
60}
61
62/// Helper methods for reading unsigned integers in little-endian form.
63pub trait LittleEndianReadExt: Read {
64    /// Read a u16 as little endian
65    fn read_u16_le(&mut self) -> io::Result<u16> {
66        let mut out = [0u8; 2];
67        self.read_exact(&mut out)?;
68        Ok(u16::from_le_bytes(out))
69    }
70
71    /// Read a u32 as little endian
72    fn read_u32_le(&mut self) -> io::Result<u32> {
73        let mut out = [0u8; 4];
74        self.read_exact(&mut out)?;
75        Ok(u32::from_le_bytes(out))
76    }
77
78    /// Read a u64 as little endian
79    fn read_u64_le(&mut self) -> io::Result<u64> {
80        let mut out = [0u8; 8];
81        self.read_exact(&mut out)?;
82        Ok(u64::from_le_bytes(out))
83    }
84}
85
86impl<R: Read> LittleEndianReadExt for R {}
87
88/// Converts a path to the ZIP format (forward-slash-delimited and normalized).
89pub fn path_to_string<T: AsRef<Path>>(path: T) -> Result<Box<str>, std::io::Error> {
90    let mut maybe_original = None;
91    if let Some(original) = path.as_ref().to_str() {
92        if original.is_empty() || original == "." || original == ".." {
93            return Ok(String::new().into_boxed_str());
94        }
95        if original.starts_with(MAIN_SEPARATOR) {
96            if original.len() == 1 {
97                return Ok(MAIN_SEPARATOR.to_string().into_boxed_str());
98            } else if (MAIN_SEPARATOR == '/' || !original[1..].contains(MAIN_SEPARATOR))
99                && !original.ends_with('.')
100                && !original.contains([MAIN_SEPARATOR, MAIN_SEPARATOR])
101                && !original.contains([MAIN_SEPARATOR, '.', MAIN_SEPARATOR])
102                && !original.contains([MAIN_SEPARATOR, '.', '.', MAIN_SEPARATOR])
103            {
104                maybe_original = Some(&original[1..]);
105            }
106        } else if !original.contains(MAIN_SEPARATOR) {
107            return Ok(original.into());
108        }
109    }
110    let mut recreate = maybe_original.is_none();
111    let mut normalized_components = Vec::new();
112
113    for component in path.as_ref().components() {
114        match component {
115            Component::Normal(os_str) => {
116                if let Some(valid_str) = os_str.to_str() {
117                    normalized_components.push(Cow::Borrowed(valid_str));
118                } else {
119                    recreate = true;
120                    normalized_components.push(os_str.to_string_lossy());
121                }
122            }
123            Component::ParentDir => {
124                recreate = true;
125                normalized_components.pop();
126            }
127            _ => {
128                recreate = true;
129            }
130        }
131    }
132    if recreate {
133        Ok(normalized_components.join("/").into())
134    } else {
135        Ok(maybe_original
136            .ok_or_else(|| std::io::Error::other("Original path is empty"))?
137            .into())
138    }
139}