Switch from String
to Box<str>
for metadata
This commit is contained in:
parent
c4492b9635
commit
384afcda2a
5 changed files with 40 additions and 35 deletions
|
@ -245,3 +245,9 @@
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed some rare bugs that could cause panics when trying to read an invalid ZIP file or using an incorrect password.
|
- Fixed some rare bugs that could cause panics when trying to read an invalid ZIP file or using an incorrect password.
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Now uses `Box<str>` rather than `String` for metadata.
|
32
src/read.rs
32
src/read.rs
|
@ -11,10 +11,10 @@ use crate::spec;
|
||||||
use crate::types::{AesMode, AesVendorVersion, AtomicU64, DateTime, System, ZipFileData};
|
use crate::types::{AesMode, AesVendorVersion, AtomicU64, DateTime, System, ZipFileData};
|
||||||
use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator};
|
use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator};
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use std::borrow::Cow;
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{self, prelude::*};
|
use std::io::{self, prelude::*};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
|
@ -45,7 +45,7 @@ pub(crate) mod zip_archive {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Shared {
|
pub(crate) struct Shared {
|
||||||
pub(crate) files: Vec<super::ZipFileData>,
|
pub(crate) files: Vec<super::ZipFileData>,
|
||||||
pub(crate) names_map: super::HashMap<String, usize>,
|
pub(crate) names_map: super::HashMap<Box<str>, usize>,
|
||||||
pub(super) offset: u64,
|
pub(super) offset: u64,
|
||||||
pub(super) dir_start: u64,
|
pub(super) dir_start: u64,
|
||||||
pub(super) dir_end: u64,
|
pub(super) dir_end: u64,
|
||||||
|
@ -486,7 +486,7 @@ impl<R: Read + Seek> ZipArchive<R> {
|
||||||
reader.seek(io::SeekFrom::Start(dir_info.directory_start))?;
|
reader.seek(io::SeekFrom::Start(dir_info.directory_start))?;
|
||||||
for _ in 0..dir_info.number_of_files {
|
for _ in 0..dir_info.number_of_files {
|
||||||
let file = central_header_to_zip_file(reader, dir_info.archive_offset)?;
|
let file = central_header_to_zip_file(reader, dir_info.archive_offset)?;
|
||||||
names_map.insert(file.file_name.clone(), files.len());
|
names_map.insert(file.file_name.clone().into(), files.len());
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
let dir_end = reader.seek(io::SeekFrom::Start(dir_info.directory_start))?;
|
let dir_end = reader.seek(io::SeekFrom::Start(dir_info.directory_start))?;
|
||||||
|
@ -600,7 +600,7 @@ impl<R: Read + Seek> ZipArchive<R> {
|
||||||
|
|
||||||
/// Returns an iterator over all the file and directory names in this archive.
|
/// Returns an iterator over all the file and directory names in this archive.
|
||||||
pub fn file_names(&self) -> impl Iterator<Item = &str> {
|
pub fn file_names(&self) -> impl Iterator<Item = &str> {
|
||||||
self.shared.names_map.keys().map(|s| s.as_str())
|
self.shared.names_map.keys().map(Box::borrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a file entry by name, decrypt with given password
|
/// Search for a file entry by name, decrypt with given password
|
||||||
|
@ -777,13 +777,13 @@ fn central_header_to_zip_file_inner<R: Read>(
|
||||||
let mut file_comment_raw = vec![0; file_comment_length];
|
let mut file_comment_raw = vec![0; file_comment_length];
|
||||||
reader.read_exact(&mut file_comment_raw)?;
|
reader.read_exact(&mut file_comment_raw)?;
|
||||||
|
|
||||||
let file_name = match is_utf8 {
|
let file_name: Box<str> = match is_utf8 {
|
||||||
true => String::from_utf8_lossy(&file_name_raw).into_owned(),
|
true => String::from_utf8_lossy(&file_name_raw).into(),
|
||||||
false => file_name_raw.clone().from_cp437(),
|
false => file_name_raw.clone().from_cp437().into_boxed_str(),
|
||||||
};
|
};
|
||||||
let file_comment = match is_utf8 {
|
let file_comment = match is_utf8 {
|
||||||
true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
|
true => String::from_utf8_lossy(&file_comment_raw).into(),
|
||||||
false => file_comment_raw.from_cp437(),
|
false => file_comment_raw.from_cp437().into_boxed_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Construct the result
|
// Construct the result
|
||||||
|
@ -991,7 +991,7 @@ impl<'a> ZipFile<'a> {
|
||||||
/// This will read well-formed ZIP files correctly, and is resistant
|
/// This will read well-formed ZIP files correctly, and is resistant
|
||||||
/// to path-based exploits. It is recommended over
|
/// to path-based exploits. It is recommended over
|
||||||
/// [`ZipFile::mangled_name`].
|
/// [`ZipFile::mangled_name`].
|
||||||
pub fn enclosed_name(&self) -> Option<&Path> {
|
pub fn enclosed_name(&self) -> Option<PathBuf> {
|
||||||
self.data.enclosed_name()
|
self.data.enclosed_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,9 +1145,9 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult<Opt
|
||||||
let mut extra_field = vec![0; extra_field_length];
|
let mut extra_field = vec![0; extra_field_length];
|
||||||
reader.read_exact(&mut extra_field)?;
|
reader.read_exact(&mut extra_field)?;
|
||||||
|
|
||||||
let file_name = match is_utf8 {
|
let file_name: Box<str> = match is_utf8 {
|
||||||
true => String::from_utf8_lossy(&file_name_raw).into_owned(),
|
true => String::from_utf8_lossy(&file_name_raw).into(),
|
||||||
false => file_name_raw.clone().from_cp437(),
|
false => file_name_raw.clone().from_cp437().into_boxed_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result = ZipFileData {
|
let mut result = ZipFileData {
|
||||||
|
@ -1161,11 +1161,11 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult<Opt
|
||||||
crc32,
|
crc32,
|
||||||
compressed_size: compressed_size as u64,
|
compressed_size: compressed_size as u64,
|
||||||
uncompressed_size: uncompressed_size as u64,
|
uncompressed_size: uncompressed_size as u64,
|
||||||
file_name,
|
file_name: file_name.into(),
|
||||||
file_name_raw,
|
file_name_raw,
|
||||||
extra_field: Arc::new(extra_field),
|
extra_field: Arc::new(extra_field),
|
||||||
central_extra_field: Arc::new(vec![]),
|
central_extra_field: Arc::new(vec![]),
|
||||||
file_comment: String::new(), // file comment is only available in the central directory
|
file_comment: String::with_capacity(0).into_boxed_str(), // file comment is only available in the central directory
|
||||||
// header_start and data start are not available, but also don't matter, since seeking is
|
// header_start and data start are not available, but also don't matter, since seeking is
|
||||||
// not available.
|
// not available.
|
||||||
header_start: 0,
|
header_start: 0,
|
||||||
|
|
|
@ -176,7 +176,7 @@ impl ZipStreamFileMetadata {
|
||||||
/// This will read well-formed ZIP files correctly, and is resistant
|
/// This will read well-formed ZIP files correctly, and is resistant
|
||||||
/// to path-based exploits. It is recommended over
|
/// to path-based exploits. It is recommended over
|
||||||
/// [`ZipFile::mangled_name`].
|
/// [`ZipFile::mangled_name`].
|
||||||
pub fn enclosed_name(&self) -> Option<&Path> {
|
pub fn enclosed_name(&self) -> Option<PathBuf> {
|
||||||
self.0.enclosed_name()
|
self.0.enclosed_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
src/types.rs
16
src/types.rs
|
@ -315,7 +315,7 @@ impl TryFrom<OffsetDateTime> for DateTime {
|
||||||
fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
|
fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
|
||||||
if dt.year() >= 1980 && dt.year() <= 2107 {
|
if dt.year() >= 1980 && dt.year() <= 2107 {
|
||||||
Ok(DateTime {
|
Ok(DateTime {
|
||||||
year: (dt.year()).try_into()?,
|
year: dt.year().try_into()?,
|
||||||
month: dt.month().into(),
|
month: dt.month().into(),
|
||||||
day: dt.day(),
|
day: dt.day(),
|
||||||
hour: dt.hour(),
|
hour: dt.hour(),
|
||||||
|
@ -385,7 +385,7 @@ pub struct ZipFileData {
|
||||||
/// Size of the file when extracted
|
/// Size of the file when extracted
|
||||||
pub uncompressed_size: u64,
|
pub uncompressed_size: u64,
|
||||||
/// Name of the file
|
/// Name of the file
|
||||||
pub file_name: String,
|
pub file_name: Box<str>,
|
||||||
/// Raw file name. To be used when file_name was incorrectly decoded.
|
/// Raw file name. To be used when file_name was incorrectly decoded.
|
||||||
pub file_name_raw: Vec<u8>,
|
pub file_name_raw: Vec<u8>,
|
||||||
/// Extra field usually used for storage expansion
|
/// Extra field usually used for storage expansion
|
||||||
|
@ -393,7 +393,7 @@ pub struct ZipFileData {
|
||||||
/// Extra field only written to central directory
|
/// Extra field only written to central directory
|
||||||
pub central_extra_field: Arc<Vec<u8>>,
|
pub central_extra_field: Arc<Vec<u8>>,
|
||||||
/// File comment
|
/// File comment
|
||||||
pub file_comment: String,
|
pub file_comment: Box<str>,
|
||||||
/// Specifies where the local header of the file starts
|
/// Specifies where the local header of the file starts
|
||||||
pub header_start: u64,
|
pub header_start: u64,
|
||||||
/// Specifies where the central header of the file starts
|
/// Specifies where the central header of the file starts
|
||||||
|
@ -431,18 +431,18 @@ impl ZipFileData {
|
||||||
|
|
||||||
Path::new(&filename)
|
Path::new(&filename)
|
||||||
.components()
|
.components()
|
||||||
.filter(|component| matches!(*component, path::Component::Normal(..)))
|
.filter(|component| matches!(*component, Component::Normal(..)))
|
||||||
.fold(PathBuf::new(), |mut path, ref cur| {
|
.fold(PathBuf::new(), |mut path, ref cur| {
|
||||||
path.push(cur.as_os_str());
|
path.push(cur.as_os_str());
|
||||||
path
|
path
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn enclosed_name(&self) -> Option<&Path> {
|
pub(crate) fn enclosed_name(&self) -> Option<PathBuf> {
|
||||||
if self.file_name.contains('\0') {
|
if self.file_name.contains('\0') {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let path = Path::new(&self.file_name);
|
let path = PathBuf::from(self.file_name.to_string());
|
||||||
let mut depth = 0usize;
|
let mut depth = 0usize;
|
||||||
for component in path.components() {
|
for component in path.components() {
|
||||||
match component {
|
match component {
|
||||||
|
@ -556,11 +556,11 @@ mod test {
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
compressed_size: 0,
|
compressed_size: 0,
|
||||||
uncompressed_size: 0,
|
uncompressed_size: 0,
|
||||||
file_name: file_name.clone(),
|
file_name: file_name.clone().into_boxed_str(),
|
||||||
file_name_raw: file_name.into_bytes(),
|
file_name_raw: file_name.into_bytes(),
|
||||||
extra_field: Arc::new(vec![]),
|
extra_field: Arc::new(vec![]),
|
||||||
central_extra_field: Arc::new(vec![]),
|
central_extra_field: Arc::new(vec![]),
|
||||||
file_comment: String::new(),
|
file_comment: Box::new("".into()),
|
||||||
header_start: 0,
|
header_start: 0,
|
||||||
data_start: AtomicU64::new(0),
|
data_start: AtomicU64::new(0),
|
||||||
central_header_start: 0,
|
central_header_start: 0,
|
||||||
|
|
19
src/write.rs
19
src/write.rs
|
@ -121,7 +121,7 @@ pub(crate) mod zip_writer {
|
||||||
pub struct ZipWriter<W: Write + Seek> {
|
pub struct ZipWriter<W: Write + Seek> {
|
||||||
pub(super) inner: GenericZipWriter<W>,
|
pub(super) inner: GenericZipWriter<W>,
|
||||||
pub(super) files: Vec<ZipFileData>,
|
pub(super) files: Vec<ZipFileData>,
|
||||||
pub(super) files_by_name: HashMap<String, usize>,
|
pub(super) files_by_name: HashMap<Box<str>, usize>,
|
||||||
pub(super) stats: ZipWriterStats,
|
pub(super) stats: ZipWriterStats,
|
||||||
pub(super) writing_to_file: bool,
|
pub(super) writing_to_file: bool,
|
||||||
pub(super) writing_raw: bool,
|
pub(super) writing_raw: bool,
|
||||||
|
@ -583,7 +583,7 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
raw_values: Option<ZipRawValues>,
|
raw_values: Option<ZipRawValues>,
|
||||||
) -> ZipResult<()>
|
) -> ZipResult<()>
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<Box<str>>,
|
||||||
{
|
{
|
||||||
self.finish_file()?;
|
self.finish_file()?;
|
||||||
|
|
||||||
|
@ -595,7 +595,6 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
|
|
||||||
{
|
{
|
||||||
let header_start = self.inner.get_plain().stream_position()?;
|
let header_start = self.inner.get_plain().stream_position()?;
|
||||||
let name = name.into();
|
|
||||||
|
|
||||||
let permissions = options.permissions.unwrap_or(0o100644);
|
let permissions = options.permissions.unwrap_or(0o100644);
|
||||||
let file = ZipFileData {
|
let file = ZipFileData {
|
||||||
|
@ -609,11 +608,11 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
crc32: raw_values.crc32,
|
crc32: raw_values.crc32,
|
||||||
compressed_size: raw_values.compressed_size,
|
compressed_size: raw_values.compressed_size,
|
||||||
uncompressed_size: raw_values.uncompressed_size,
|
uncompressed_size: raw_values.uncompressed_size,
|
||||||
file_name: name,
|
file_name: name.into(),
|
||||||
file_name_raw: Vec::new(), // Never used for saving
|
file_name_raw: Vec::new(), // Never used for saving
|
||||||
extra_field: options.extra_data,
|
extra_field: options.extra_data,
|
||||||
central_extra_field: options.central_extra_data,
|
central_extra_field: options.central_extra_data,
|
||||||
file_comment: String::new(),
|
file_comment: String::with_capacity(0).into_boxed_str(),
|
||||||
header_start,
|
header_start,
|
||||||
data_start: AtomicU64::new(0),
|
data_start: AtomicU64::new(0),
|
||||||
central_header_start: 0,
|
central_header_start: 0,
|
||||||
|
@ -818,7 +817,7 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
/// The data should be written using the [`Write`] implementation on this [`ZipWriter`]
|
/// The data should be written using the [`Write`] implementation on this [`ZipWriter`]
|
||||||
pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
|
pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<Box<str>>,
|
||||||
{
|
{
|
||||||
Self::normalize_options(&mut options);
|
Self::normalize_options(&mut options);
|
||||||
let make_new_self = self.inner.prepare_next_writer(
|
let make_new_self = self.inner.prepare_next_writer(
|
||||||
|
@ -890,7 +889,7 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn raw_copy_file_rename<S>(&mut self, mut file: ZipFile, name: S) -> ZipResult<()>
|
pub fn raw_copy_file_rename<S>(&mut self, mut file: ZipFile, name: S) -> ZipResult<()>
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<Box<str>>,
|
||||||
{
|
{
|
||||||
let mut options = FileOptions::default()
|
let mut options = FileOptions::default()
|
||||||
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
|
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
|
||||||
|
@ -1015,8 +1014,8 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
mut options: FileOptions,
|
mut options: FileOptions,
|
||||||
) -> ZipResult<()>
|
) -> ZipResult<()>
|
||||||
where
|
where
|
||||||
N: Into<String>,
|
N: Into<Box<str>>,
|
||||||
T: Into<String>,
|
T: Into<Box<str>>,
|
||||||
{
|
{
|
||||||
if options.permissions.is_none() {
|
if options.permissions.is_none() {
|
||||||
options.permissions = Some(0o777);
|
options.permissions = Some(0o777);
|
||||||
|
@ -1303,7 +1302,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*self = (make_new_self)(bare);
|
*self = make_new_self(bare);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue