Skip to content

Commit ea58b90

Browse files
make ZipFile impl EntryData
1 parent d196afe commit ea58b90

File tree

3 files changed

+196
-211
lines changed

3 files changed

+196
-211
lines changed

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
#![warn(missing_docs)]
3333
#![allow(unexpected_cfgs)] // Needed for cfg(fuzzing) on nightly as of 2024-05-06
3434
pub use crate::compression::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS};
35-
pub use crate::read::HasZipMetadata;
3635
pub use crate::read::ZipArchive;
36+
pub use crate::read::{EntryData, HasZipMetadata};
3737
pub use crate::spec::{ZIP64_BYTES_THR, ZIP64_ENTRY_THR};
3838
pub use crate::types::{AesMode, DateTime};
3939
pub use crate::write::ZipWriter;

src/read.rs

Lines changed: 180 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use std::mem;
2626
use std::mem::size_of;
2727
use std::path::{Path, PathBuf};
2828
use std::rc::Rc;
29+
use std::slice;
2930
use std::sync::{Arc, OnceLock};
3031

3132
mod config;
@@ -90,9 +91,8 @@ pub(crate) mod zip_archive {
9091
///
9192
/// ```no_run
9293
/// use std::io::prelude::*;
93-
/// use zip::read::ArchiveEntry;
9494
/// fn list_zip_contents(reader: impl Read + Seek) -> zip::result::ZipResult<()> {
95-
/// use zip::HasZipMetadata;
95+
/// use zip::EntryData;
9696
/// let mut zip = zip::ZipArchive::new(reader)?;
9797
///
9898
/// for i in 0..zip.len() {
@@ -120,11 +120,11 @@ use crate::types::ffi::S_IFLNK;
120120
use crate::unstable::{path_to_string, LittleEndianReadExt};
121121

122122
use crate::crc32::non_crypto::Crc32Reader as NewCrc32Reader;
123+
pub use crate::unstable::read::ZipEntry;
123124
use crate::unstable::read::{
124125
construct_decompressing_reader, find_entry_content_range, CryptoEntryReader,
125126
CryptoKeyValidationSource,
126127
};
127-
pub use crate::unstable::read::{ArchiveEntry, ZipEntry};
128128

129129
pub use zip_archive::ZipArchive;
130130

@@ -1638,14 +1638,10 @@ pub trait HasZipMetadata {
16381638
fn get_metadata(&self) -> &ZipFileData;
16391639
}
16401640

1641-
/// Methods for retrieving information on zip files
1642-
impl<'a> ZipFile<'a> {
1643-
pub(crate) fn take_raw_reader(&mut self) -> io::Result<io::Take<&'a mut dyn Read>> {
1644-
mem::replace(&mut self.reader, ZipFileReader::NoReader).into_inner()
1645-
}
1646-
1641+
/// Trait to expose attributes of a single zip file entry.
1642+
pub trait EntryData: HasZipMetadata {
16471643
/// Get the version of the file
1648-
pub fn version_made_by(&self) -> (u8, u8) {
1644+
fn version_made_by(&self) -> (u8, u8) {
16491645
(
16501646
self.get_metadata().version_made_by / 10,
16511647
self.get_metadata().version_made_by % 10,
@@ -1664,17 +1660,137 @@ impl<'a> ZipFile<'a> {
16641660
///
16651661
/// You can use the [`ZipFile::enclosed_name`] method to validate the name
16661662
/// as a safe path.
1667-
pub fn name(&self) -> &str {
1663+
fn name(&self) -> &str {
16681664
&self.get_metadata().file_name
16691665
}
16701666

16711667
/// Get the name of the file, in the raw (internal) byte representation.
16721668
///
16731669
/// The encoding of this data is currently undefined.
1674-
pub fn name_raw(&self) -> &[u8] {
1670+
fn name_raw(&self) -> &[u8] {
16751671
&self.get_metadata().file_name_raw
16761672
}
16771673

1674+
/// Rewrite the path, ignoring any path components with special meaning.
1675+
///
1676+
/// - Absolute paths are made relative
1677+
/// - [`ParentDir`]s are ignored
1678+
/// - Truncates the filename at a NULL byte
1679+
///
1680+
/// This is appropriate if you need to be able to extract *something* from
1681+
/// any archive, but will easily misrepresent trivial paths like
1682+
/// `foo/../bar` as `foo/bar` (instead of `bar`). Because of this,
1683+
/// [`ZipFile::enclosed_name`] is the better option in most scenarios.
1684+
///
1685+
/// [`ParentDir`]: `Component::ParentDir`
1686+
fn mangled_name(&self) -> PathBuf {
1687+
self.get_metadata().file_name_sanitized()
1688+
}
1689+
1690+
/// Ensure the file path is safe to use as a [`Path`].
1691+
///
1692+
/// - It can't contain NULL bytes
1693+
/// - It can't resolve to a path outside the current directory
1694+
/// > `foo/../bar` is fine, `foo/../../bar` is not.
1695+
/// - It can't be an absolute path
1696+
///
1697+
/// This will read well-formed ZIP files correctly, and is resistant
1698+
/// to path-based exploits. It is recommended over
1699+
/// [`ZipFile::mangled_name`].
1700+
fn enclosed_name(&self) -> Option<PathBuf> {
1701+
self.get_metadata().enclosed_name()
1702+
}
1703+
1704+
/// Get the comment of the file
1705+
fn comment(&self) -> &str {
1706+
&self.get_metadata().file_comment
1707+
}
1708+
1709+
/// Get the compression method used to store the file
1710+
fn compression(&self) -> CompressionMethod {
1711+
self.get_metadata().compression_method
1712+
}
1713+
1714+
/// Get the size of the file, in bytes, in the archive
1715+
fn compressed_size(&self) -> u64 {
1716+
self.get_metadata().compressed_size
1717+
}
1718+
1719+
/// Get the size of the file, in bytes, when uncompressed
1720+
fn size(&self) -> u64 {
1721+
self.get_metadata().uncompressed_size
1722+
}
1723+
1724+
/// Get if the files is encrypted or not
1725+
fn encrypted(&self) -> bool {
1726+
self.get_metadata().encrypted
1727+
}
1728+
1729+
/// Get the time the file was last modified
1730+
fn last_modified(&self) -> Option<DateTime> {
1731+
self.get_metadata().last_modified_time
1732+
}
1733+
1734+
/// Returns whether the file is actually a directory
1735+
fn is_dir(&self) -> bool {
1736+
self.get_metadata().is_dir()
1737+
}
1738+
1739+
/// Returns whether the file is actually a symbolic link
1740+
fn is_symlink(&self) -> bool {
1741+
self.unix_mode()
1742+
.is_some_and(|mode| mode & S_IFLNK == S_IFLNK)
1743+
}
1744+
1745+
/// Returns whether the file is a normal file (i.e. not a directory or symlink)
1746+
fn is_file(&self) -> bool {
1747+
!self.is_dir() && !self.is_symlink()
1748+
}
1749+
1750+
/// Get unix mode for the file
1751+
fn unix_mode(&self) -> Option<u32> {
1752+
self.get_metadata().unix_mode()
1753+
}
1754+
1755+
/// Get the CRC32 hash of the original file
1756+
fn crc32(&self) -> u32 {
1757+
self.get_metadata().crc32
1758+
}
1759+
1760+
/// Get the extra data of the zip header for this file
1761+
fn extra_data(&self) -> Option<&[u8]> {
1762+
self.get_metadata()
1763+
.extra_field
1764+
.as_deref()
1765+
.map(|v| v.as_ref())
1766+
}
1767+
1768+
/// Get the starting offset of the data of the compressed file
1769+
fn data_start(&self) -> u64 {
1770+
*self.get_metadata().data_start.get().unwrap()
1771+
}
1772+
1773+
/// Get the starting offset of the zip header for this file
1774+
fn header_start(&self) -> u64 {
1775+
self.get_metadata().header_start
1776+
}
1777+
/// Get the starting offset of the zip header in the central directory for this file
1778+
fn central_header_start(&self) -> u64 {
1779+
self.get_metadata().central_header_start
1780+
}
1781+
1782+
/// iterate through all extra fields
1783+
fn extra_data_fields(&self) -> slice::Iter<'_, ExtraField> {
1784+
self.get_metadata().extra_fields.iter()
1785+
}
1786+
}
1787+
1788+
/// Methods for retrieving information on zip files
1789+
impl<'a> ZipFile<'a> {
1790+
pub(crate) fn take_raw_reader(&mut self) -> io::Result<io::Take<&'a mut dyn Read>> {
1791+
mem::replace(&mut self.reader, ZipFileReader::NoReader).into_inner()
1792+
}
1793+
16781794
/// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte,
16791795
/// removes a leading '/' and removes '..' parts.
16801796
#[deprecated(
@@ -1685,6 +1801,32 @@ impl<'a> ZipFile<'a> {
16851801
pub fn sanitized_name(&self) -> PathBuf {
16861802
self.mangled_name()
16871803
}
1804+
}
1805+
1806+
/// Duplicated reimplementation of [`EntryData`] for backwards compatibility.
1807+
impl<'a> ZipFile<'a> {
1808+
/// Get the name of the file
1809+
///
1810+
/// # Warnings
1811+
///
1812+
/// It is dangerous to use this name directly when extracting an archive.
1813+
/// It may contain an absolute path (`/etc/shadow`), or break out of the
1814+
/// current directory (`../runtime`). Carelessly writing to these paths
1815+
/// allows an attacker to craft a ZIP archive that will overwrite critical
1816+
/// files.
1817+
///
1818+
/// You can use the [`ZipFile::enclosed_name`] method to validate the name
1819+
/// as a safe path.
1820+
pub fn name(&self) -> &str {
1821+
EntryData::name(self)
1822+
}
1823+
1824+
/// Get the name of the file, in the raw (internal) byte representation.
1825+
///
1826+
/// The encoding of this data is currently undefined.
1827+
pub fn name_raw(&self) -> &[u8] {
1828+
EntryData::name_raw(self)
1829+
}
16881830

16891831
/// Rewrite the path, ignoring any path components with special meaning.
16901832
///
@@ -1697,9 +1839,9 @@ impl<'a> ZipFile<'a> {
16971839
/// `foo/../bar` as `foo/bar` (instead of `bar`). Because of this,
16981840
/// [`ZipFile::enclosed_name`] is the better option in most scenarios.
16991841
///
1700-
/// [`ParentDir`]: `PathBuf::Component::ParentDir`
1842+
/// [`ParentDir`]: `Component::ParentDir`
17011843
pub fn mangled_name(&self) -> PathBuf {
1702-
self.get_metadata().file_name_sanitized()
1844+
EntryData::mangled_name(self)
17031845
}
17041846

17051847
/// Ensure the file path is safe to use as a [`Path`].
@@ -1713,93 +1855,86 @@ impl<'a> ZipFile<'a> {
17131855
/// to path-based exploits. It is recommended over
17141856
/// [`ZipFile::mangled_name`].
17151857
pub fn enclosed_name(&self) -> Option<PathBuf> {
1716-
self.get_metadata().enclosed_name()
1858+
EntryData::enclosed_name(self)
17171859
}
17181860

17191861
/// Get the comment of the file
17201862
pub fn comment(&self) -> &str {
1721-
&self.get_metadata().file_comment
1863+
EntryData::comment(self)
17221864
}
17231865

17241866
/// Get the compression method used to store the file
17251867
pub fn compression(&self) -> CompressionMethod {
1726-
self.get_metadata().compression_method
1727-
}
1728-
1729-
/// Get if the files is encrypted or not
1730-
pub fn encrypted(&self) -> bool {
1731-
self.data.encrypted
1868+
EntryData::compression(self)
17321869
}
17331870

17341871
/// Get the size of the file, in bytes, in the archive
17351872
pub fn compressed_size(&self) -> u64 {
1736-
self.get_metadata().compressed_size
1873+
EntryData::compressed_size(self)
17371874
}
17381875

17391876
/// Get the size of the file, in bytes, when uncompressed
17401877
pub fn size(&self) -> u64 {
1741-
self.get_metadata().uncompressed_size
1878+
EntryData::size(self)
1879+
}
1880+
1881+
/// Get if the files is encrypted or not
1882+
pub fn encrypted(&self) -> bool {
1883+
EntryData::encrypted(self)
17421884
}
17431885

17441886
/// Get the time the file was last modified
17451887
pub fn last_modified(&self) -> Option<DateTime> {
1746-
self.data.last_modified_time
1888+
EntryData::last_modified(self)
17471889
}
17481890

17491891
/// Returns whether the file is actually a directory
17501892
pub fn is_dir(&self) -> bool {
1751-
self.data.is_dir()
1893+
EntryData::is_dir(self)
17521894
}
17531895

17541896
/// Returns whether the file is actually a symbolic link
17551897
pub fn is_symlink(&self) -> bool {
1756-
self.unix_mode()
1757-
.is_some_and(|mode| mode & S_IFLNK == S_IFLNK)
1898+
EntryData::is_symlink(self)
17581899
}
17591900

17601901
/// Returns whether the file is a normal file (i.e. not a directory or symlink)
17611902
pub fn is_file(&self) -> bool {
1762-
!self.is_dir() && !self.is_symlink()
1903+
EntryData::is_file(self)
17631904
}
17641905

17651906
/// Get unix mode for the file
17661907
pub fn unix_mode(&self) -> Option<u32> {
1767-
self.get_metadata().unix_mode()
1908+
EntryData::unix_mode(self)
17681909
}
17691910

17701911
/// Get the CRC32 hash of the original file
17711912
pub fn crc32(&self) -> u32 {
1772-
self.get_metadata().crc32
1913+
EntryData::crc32(self)
17731914
}
17741915

17751916
/// Get the extra data of the zip header for this file
17761917
pub fn extra_data(&self) -> Option<&[u8]> {
1777-
self.get_metadata()
1778-
.extra_field
1779-
.as_deref()
1780-
.map(|v| v.as_ref())
1918+
EntryData::extra_data(self)
17811919
}
17821920

17831921
/// Get the starting offset of the data of the compressed file
17841922
pub fn data_start(&self) -> u64 {
1785-
*self.data.data_start.get().unwrap()
1923+
EntryData::data_start(self)
17861924
}
17871925

17881926
/// Get the starting offset of the zip header for this file
17891927
pub fn header_start(&self) -> u64 {
1790-
self.get_metadata().header_start
1928+
EntryData::header_start(self)
17911929
}
17921930
/// Get the starting offset of the zip header in the central directory for this file
17931931
pub fn central_header_start(&self) -> u64 {
1794-
self.get_metadata().central_header_start
1932+
EntryData::central_header_start(self)
17951933
}
1796-
}
17971934

1798-
/// Methods for retrieving information on zip files
1799-
impl<'a> ZipFile<'a> {
18001935
/// iterate through all extra fields
1801-
pub fn extra_data_fields(&self) -> impl Iterator<Item = &ExtraField> {
1802-
self.data.extra_fields.iter()
1936+
pub fn extra_data_fields(&self) -> slice::Iter<'_, ExtraField> {
1937+
EntryData::extra_data_fields(self)
18031938
}
18041939
}
18051940

@@ -1809,6 +1944,8 @@ impl<'a> HasZipMetadata for ZipFile<'a> {
18091944
}
18101945
}
18111946

1947+
impl<'a> EntryData for ZipFile<'a> {}
1948+
18121949
impl<'a> Read for ZipFile<'a> {
18131950
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
18141951
self.reader.read(buf)

0 commit comments

Comments
 (0)