Merge pull request #200 from qu1x/support-extra-field
This commit is contained in:
commit
3fd44ffd5d
6 changed files with 519 additions and 48 deletions
|
@ -27,6 +27,7 @@ walkdir = "2"
|
||||||
deflate = ["flate2/rust_backend"]
|
deflate = ["flate2/rust_backend"]
|
||||||
deflate-miniz = ["flate2/default"]
|
deflate-miniz = ["flate2/default"]
|
||||||
deflate-zlib = ["flate2/zlib"]
|
deflate-zlib = ["flate2/zlib"]
|
||||||
|
unreserved = []
|
||||||
default = ["bzip2", "deflate", "time"]
|
default = ["bzip2", "deflate", "time"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|
21
src/read.rs
21
src/read.rs
|
@ -592,14 +592,16 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
|
||||||
uncompressed_size: uncompressed_size as u64,
|
uncompressed_size: uncompressed_size as u64,
|
||||||
file_name,
|
file_name,
|
||||||
file_name_raw,
|
file_name_raw,
|
||||||
|
extra_field,
|
||||||
file_comment,
|
file_comment,
|
||||||
header_start: offset,
|
header_start: offset,
|
||||||
central_header_start,
|
central_header_start,
|
||||||
data_start: 0,
|
data_start: 0,
|
||||||
external_attributes: external_file_attributes,
|
external_attributes: external_file_attributes,
|
||||||
|
large_file: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse_extra_field(&mut result, &*extra_field) {
|
match parse_extra_field(&mut result) {
|
||||||
Ok(..) | Err(ZipError::Io(..)) => {}
|
Ok(..) | Err(ZipError::Io(..)) => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
|
@ -610,20 +612,22 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> {
|
fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
|
||||||
let mut reader = io::Cursor::new(data);
|
let mut reader = io::Cursor::new(&file.extra_field);
|
||||||
|
|
||||||
while (reader.position() as usize) < data.len() {
|
while (reader.position() as usize) < file.extra_field.len() {
|
||||||
let kind = reader.read_u16::<LittleEndian>()?;
|
let kind = reader.read_u16::<LittleEndian>()?;
|
||||||
let len = reader.read_u16::<LittleEndian>()?;
|
let len = reader.read_u16::<LittleEndian>()?;
|
||||||
let mut len_left = len as i64;
|
let mut len_left = len as i64;
|
||||||
// Zip64 extended information extra field
|
// Zip64 extended information extra field
|
||||||
if kind == 0x0001 {
|
if kind == 0x0001 {
|
||||||
if file.uncompressed_size == 0xFFFFFFFF {
|
if file.uncompressed_size == 0xFFFFFFFF {
|
||||||
|
file.large_file = true;
|
||||||
file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
|
file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
|
||||||
len_left -= 8;
|
len_left -= 8;
|
||||||
}
|
}
|
||||||
if file.compressed_size == 0xFFFFFFFF {
|
if file.compressed_size == 0xFFFFFFFF {
|
||||||
|
file.large_file = true;
|
||||||
file.compressed_size = reader.read_u64::<LittleEndian>()?;
|
file.compressed_size = reader.read_u64::<LittleEndian>()?;
|
||||||
len_left -= 8;
|
len_left -= 8;
|
||||||
}
|
}
|
||||||
|
@ -815,6 +819,11 @@ impl<'a> ZipFile<'a> {
|
||||||
self.data.crc32
|
self.data.crc32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the extra data of the zip header for this file
|
||||||
|
pub fn extra_data(&self) -> &[u8] {
|
||||||
|
&self.data.extra_field
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the starting offset of the data of the compressed file
|
/// Get the starting offset of the data of the compressed file
|
||||||
pub fn data_start(&self) -> u64 {
|
pub fn data_start(&self) -> u64 {
|
||||||
self.data.data_start
|
self.data.data_start
|
||||||
|
@ -933,6 +942,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
|
||||||
uncompressed_size: uncompressed_size as u64,
|
uncompressed_size: uncompressed_size as u64,
|
||||||
file_name,
|
file_name,
|
||||||
file_name_raw,
|
file_name_raw,
|
||||||
|
extra_field,
|
||||||
file_comment: String::new(), // file comment is only available in the central directory
|
file_comment: String::new(), // 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.
|
||||||
|
@ -943,9 +953,10 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
|
||||||
// We set this to zero, which should be valid as the docs state 'If input came
|
// We set this to zero, which should be valid as the docs state 'If input came
|
||||||
// from standard input, this field is set to zero.'
|
// from standard input, this field is set to zero.'
|
||||||
external_attributes: 0,
|
external_attributes: 0,
|
||||||
|
large_file: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse_extra_field(&mut result, &extra_field) {
|
match parse_extra_field(&mut result) {
|
||||||
Ok(..) | Err(ZipError::Io(..)) => {}
|
Ok(..) | Err(ZipError::Io(..)) => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
|
|
22
src/spec.rs
22
src/spec.rs
|
@ -117,6 +117,14 @@ impl Zip64CentralDirectoryEndLocator {
|
||||||
number_of_disks,
|
number_of_disks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
|
||||||
|
writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE)?;
|
||||||
|
writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.end_of_central_directory_offset)?;
|
||||||
|
writer.write_u32::<LittleEndian>(self.number_of_disks)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Zip64CentralDirectoryEnd {
|
pub struct Zip64CentralDirectoryEnd {
|
||||||
|
@ -179,4 +187,18 @@ impl Zip64CentralDirectoryEnd {
|
||||||
"Could not find ZIP64 central directory end",
|
"Could not find ZIP64 central directory end",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
|
||||||
|
writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE)?;
|
||||||
|
writer.write_u64::<LittleEndian>(44)?; // record size
|
||||||
|
writer.write_u16::<LittleEndian>(self.version_made_by)?;
|
||||||
|
writer.write_u16::<LittleEndian>(self.version_needed_to_extract)?;
|
||||||
|
writer.write_u32::<LittleEndian>(self.disk_number)?;
|
||||||
|
writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.number_of_files_on_this_disk)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.number_of_files)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.central_directory_size)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.central_directory_offset)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/types.rs
18
src/types.rs
|
@ -232,6 +232,8 @@ pub struct ZipFileData {
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
/// 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
|
||||||
|
pub extra_field: Vec<u8>,
|
||||||
/// File comment
|
/// File comment
|
||||||
pub file_comment: String,
|
pub file_comment: String,
|
||||||
/// Specifies where the local header of the file starts
|
/// Specifies where the local header of the file starts
|
||||||
|
@ -244,6 +246,8 @@ pub struct ZipFileData {
|
||||||
pub data_start: u64,
|
pub data_start: u64,
|
||||||
/// External file attributes
|
/// External file attributes
|
||||||
pub external_attributes: u32,
|
pub external_attributes: u32,
|
||||||
|
/// Reserve local ZIP64 extra field
|
||||||
|
pub large_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZipFileData {
|
impl ZipFileData {
|
||||||
|
@ -277,10 +281,18 @@ impl ZipFileData {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn zip64_extension(&self) -> bool {
|
||||||
|
self.uncompressed_size > 0xFFFFFFFF
|
||||||
|
|| self.compressed_size > 0xFFFFFFFF
|
||||||
|
|| self.header_start > 0xFFFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
pub fn version_needed(&self) -> u16 {
|
pub fn version_needed(&self) -> u16 {
|
||||||
match self.compression_method {
|
// higher versions matched first
|
||||||
|
match (self.zip64_extension(), self.compression_method) {
|
||||||
#[cfg(feature = "bzip2")]
|
#[cfg(feature = "bzip2")]
|
||||||
crate::compression::CompressionMethod::Bzip2 => 46,
|
(_, crate::compression::CompressionMethod::Bzip2) => 46,
|
||||||
|
(true, _) => 45,
|
||||||
_ => 20,
|
_ => 20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,11 +325,13 @@ mod test {
|
||||||
uncompressed_size: 0,
|
uncompressed_size: 0,
|
||||||
file_name: file_name.clone(),
|
file_name: file_name.clone(),
|
||||||
file_name_raw: file_name.into_bytes(),
|
file_name_raw: file_name.into_bytes(),
|
||||||
|
extra_field: Vec::new(),
|
||||||
file_comment: String::new(),
|
file_comment: String::new(),
|
||||||
header_start: 0,
|
header_start: 0,
|
||||||
data_start: 0,
|
data_start: 0,
|
||||||
central_header_start: 0,
|
central_header_start: 0,
|
||||||
external_attributes: 0,
|
external_attributes: 0,
|
||||||
|
large_file: false,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.file_name_sanitized(),
|
data.file_name_sanitized(),
|
||||||
|
|
477
src/write.rs
477
src/write.rs
|
@ -5,7 +5,7 @@ use crate::read::{central_header_to_zip_file, ZipArchive, ZipFile};
|
||||||
use crate::result::{ZipError, ZipResult};
|
use crate::result::{ZipError, ZipResult};
|
||||||
use crate::spec;
|
use crate::spec;
|
||||||
use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION};
|
use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use crc32fast::Hasher;
|
use crc32fast::Hasher;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -68,8 +68,10 @@ pub struct ZipWriter<W: Write + io::Seek> {
|
||||||
files: Vec<ZipFileData>,
|
files: Vec<ZipFileData>,
|
||||||
stats: ZipWriterStats,
|
stats: ZipWriterStats,
|
||||||
writing_to_file: bool,
|
writing_to_file: bool,
|
||||||
comment: Vec<u8>,
|
writing_to_extra_field: bool,
|
||||||
|
writing_to_central_extra_field_only: bool,
|
||||||
writing_raw: bool,
|
writing_raw: bool,
|
||||||
|
comment: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -91,6 +93,7 @@ pub struct FileOptions {
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
last_modified_time: DateTime,
|
last_modified_time: DateTime,
|
||||||
permissions: Option<u32>,
|
permissions: Option<u32>,
|
||||||
|
large_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileOptions {
|
impl FileOptions {
|
||||||
|
@ -114,6 +117,7 @@ impl FileOptions {
|
||||||
#[cfg(not(feature = "time"))]
|
#[cfg(not(feature = "time"))]
|
||||||
last_modified_time: DateTime::default(),
|
last_modified_time: DateTime::default(),
|
||||||
permissions: None,
|
permissions: None,
|
||||||
|
large_file: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +125,6 @@ impl FileOptions {
|
||||||
///
|
///
|
||||||
/// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
|
/// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
|
||||||
/// disabled, `CompressionMethod::Stored` becomes the default.
|
/// disabled, `CompressionMethod::Stored` becomes the default.
|
||||||
/// otherwise.
|
|
||||||
pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
|
pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
|
||||||
self.compression_method = method;
|
self.compression_method = method;
|
||||||
self
|
self
|
||||||
|
@ -145,6 +148,16 @@ impl FileOptions {
|
||||||
self.permissions = Some(mode & 0o777);
|
self.permissions = Some(mode & 0o777);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether the new file's compressed and uncompressed size is less than 4 GiB.
|
||||||
|
///
|
||||||
|
/// If set to `false` and the file exceeds the limit, an I/O error is thrown. If set to `true`,
|
||||||
|
/// readers will require ZIP64 support and if the file does not exceed the limit, 20 B are
|
||||||
|
/// wasted. The default is `false`.
|
||||||
|
pub fn large_file(mut self, large: bool) -> FileOptions {
|
||||||
|
self.large_file = large;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FileOptions {
|
impl Default for FileOptions {
|
||||||
|
@ -163,11 +176,24 @@ impl<W: Write + io::Seek> Write for ZipWriter<W> {
|
||||||
}
|
}
|
||||||
match self.inner.ref_mut() {
|
match self.inner.ref_mut() {
|
||||||
Some(ref mut w) => {
|
Some(ref mut w) => {
|
||||||
let write_result = w.write(buf);
|
if self.writing_to_extra_field {
|
||||||
if let Ok(count) = write_result {
|
self.files.last_mut().unwrap().extra_field.write(buf)
|
||||||
self.stats.update(&buf[0..count]);
|
} else {
|
||||||
|
let write_result = w.write(buf);
|
||||||
|
if let Ok(count) = write_result {
|
||||||
|
self.stats.update(&buf[0..count]);
|
||||||
|
if self.stats.bytes_written > 0xFFFFFFFF
|
||||||
|
&& !self.files.last_mut().unwrap().large_file
|
||||||
|
{
|
||||||
|
let _inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Large file option has not been set",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_result
|
||||||
}
|
}
|
||||||
write_result
|
|
||||||
}
|
}
|
||||||
None => Err(io::Error::new(
|
None => Err(io::Error::new(
|
||||||
io::ErrorKind::BrokenPipe,
|
io::ErrorKind::BrokenPipe,
|
||||||
|
@ -225,6 +251,8 @@ impl<A: Read + Write + io::Seek> ZipWriter<A> {
|
||||||
files,
|
files,
|
||||||
stats: Default::default(),
|
stats: Default::default(),
|
||||||
writing_to_file: false,
|
writing_to_file: false,
|
||||||
|
writing_to_extra_field: false,
|
||||||
|
writing_to_central_extra_field_only: false,
|
||||||
comment: footer.zip_file_comment,
|
comment: footer.zip_file_comment,
|
||||||
writing_raw: true, // avoid recomputing the last file's header
|
writing_raw: true, // avoid recomputing the last file's header
|
||||||
})
|
})
|
||||||
|
@ -241,8 +269,10 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
files: Vec::new(),
|
files: Vec::new(),
|
||||||
stats: Default::default(),
|
stats: Default::default(),
|
||||||
writing_to_file: false,
|
writing_to_file: false,
|
||||||
comment: Vec::new(),
|
writing_to_extra_field: false,
|
||||||
|
writing_to_central_extra_field_only: false,
|
||||||
writing_raw: false,
|
writing_raw: false,
|
||||||
|
comment: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +304,6 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
{
|
{
|
||||||
self.finish_file()?;
|
self.finish_file()?;
|
||||||
|
|
||||||
let is_raw = raw_values.is_some();
|
|
||||||
let raw_values = raw_values.unwrap_or_else(|| ZipRawValues {
|
let raw_values = raw_values.unwrap_or_else(|| ZipRawValues {
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
compressed_size: 0,
|
compressed_size: 0,
|
||||||
|
@ -298,11 +327,13 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
uncompressed_size: raw_values.uncompressed_size,
|
uncompressed_size: raw_values.uncompressed_size,
|
||||||
file_name: name.into(),
|
file_name: name.into(),
|
||||||
file_name_raw: Vec::new(), // Never used for saving
|
file_name_raw: Vec::new(), // Never used for saving
|
||||||
|
extra_field: Vec::new(),
|
||||||
file_comment: String::new(),
|
file_comment: String::new(),
|
||||||
header_start,
|
header_start,
|
||||||
data_start: 0,
|
data_start: 0,
|
||||||
central_header_start: 0,
|
central_header_start: 0,
|
||||||
external_attributes: permissions << 16,
|
external_attributes: permissions << 16,
|
||||||
|
large_file: options.large_file,
|
||||||
};
|
};
|
||||||
write_local_file_header(writer, &file)?;
|
write_local_file_header(writer, &file)?;
|
||||||
|
|
||||||
|
@ -316,17 +347,14 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
self.files.push(file);
|
self.files.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.writing_raw = is_raw;
|
|
||||||
self.inner.switch_to(if is_raw {
|
|
||||||
CompressionMethod::Stored
|
|
||||||
} else {
|
|
||||||
options.compression_method
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_file(&mut self) -> ZipResult<()> {
|
fn finish_file(&mut self) -> ZipResult<()> {
|
||||||
|
if self.writing_to_extra_field {
|
||||||
|
// Implicitly calling [`ZipWriter::end_extra_data`] for empty files.
|
||||||
|
self.end_extra_data()?;
|
||||||
|
}
|
||||||
self.inner.switch_to(CompressionMethod::Stored)?;
|
self.inner.switch_to(CompressionMethod::Stored)?;
|
||||||
let writer = self.inner.get_plain();
|
let writer = self.inner.get_plain();
|
||||||
|
|
||||||
|
@ -362,13 +390,14 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
}
|
}
|
||||||
*options.permissions.as_mut().unwrap() |= 0o100000;
|
*options.permissions.as_mut().unwrap() |= 0o100000;
|
||||||
self.start_entry(name, options, None)?;
|
self.start_entry(name, options, None)?;
|
||||||
|
self.inner.switch_to(options.compression_method)?;
|
||||||
self.writing_to_file = true;
|
self.writing_to_file = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a file, taking a Path as argument.
|
/// Starts a file, taking a Path as argument.
|
||||||
///
|
///
|
||||||
/// This function ensures that the '/' path seperator is used. It also ignores all non 'Normal'
|
/// This function ensures that the '/' path separator is used. It also ignores all non 'Normal'
|
||||||
/// Components, such as a starting '/' or '..' and '.'.
|
/// Components, such as a starting '/' or '..' and '.'.
|
||||||
#[deprecated(
|
#[deprecated(
|
||||||
since = "0.5.7",
|
since = "0.5.7",
|
||||||
|
@ -382,6 +411,168 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
self.start_file(path_to_string(path), options)
|
self.start_file(path_to_string(path), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an aligned file in the archive and start writing its' contents.
|
||||||
|
///
|
||||||
|
/// Returns the number of padding bytes required to align the file.
|
||||||
|
///
|
||||||
|
/// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
|
||||||
|
pub fn start_file_aligned<S>(
|
||||||
|
&mut self,
|
||||||
|
name: S,
|
||||||
|
options: FileOptions,
|
||||||
|
align: u16,
|
||||||
|
) -> Result<u64, ZipError>
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
let data_start = self.start_file_with_extra_data(name, options)?;
|
||||||
|
let align = align as u64;
|
||||||
|
if align > 1 && data_start % align != 0 {
|
||||||
|
let pad_length = (align - (data_start + 4) % align) % align;
|
||||||
|
let pad = vec![0; pad_length as usize];
|
||||||
|
self.write_all(b"za").map_err(ZipError::from)?; // 0x617a
|
||||||
|
self.write_u16::<LittleEndian>(pad.len() as u16)
|
||||||
|
.map_err(ZipError::from)?;
|
||||||
|
self.write_all(&pad).map_err(ZipError::from)?;
|
||||||
|
assert_eq!(self.end_local_start_central_extra_data()? % align, 0);
|
||||||
|
}
|
||||||
|
let extra_data_end = self.end_extra_data()?;
|
||||||
|
Ok(extra_data_end - data_start)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a file in the archive and start writing its extra data first.
|
||||||
|
///
|
||||||
|
/// Finish writing extra data and start writing file data with [`ZipWriter::end_extra_data`].
|
||||||
|
/// Optionally, distinguish local from central extra data with
|
||||||
|
/// [`ZipWriter::end_local_start_central_extra_data`].
|
||||||
|
///
|
||||||
|
/// Returns the preliminary starting offset of the file data without any extra data allowing to
|
||||||
|
/// align the file data by calculating a pad length to be prepended as part of the extra data.
|
||||||
|
///
|
||||||
|
/// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
|
/// use zip::{ZipArchive, ZipWriter, result::ZipResult};
|
||||||
|
/// use zip::{write::FileOptions, CompressionMethod};
|
||||||
|
/// use std::io::{Write, Cursor};
|
||||||
|
///
|
||||||
|
/// # fn main() -> ZipResult<()> {
|
||||||
|
/// let mut archive = Cursor::new(Vec::new());
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// let mut zip = ZipWriter::new(&mut archive);
|
||||||
|
/// let options = FileOptions::default()
|
||||||
|
/// .compression_method(CompressionMethod::Stored);
|
||||||
|
///
|
||||||
|
/// zip.start_file_with_extra_data("identical_extra_data.txt", options)?;
|
||||||
|
/// let extra_data = b"local and central extra data";
|
||||||
|
/// zip.write_u16::<LittleEndian>(0xbeef)?;
|
||||||
|
/// zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
|
||||||
|
/// zip.write_all(extra_data)?;
|
||||||
|
/// zip.end_extra_data()?;
|
||||||
|
/// zip.write_all(b"file data")?;
|
||||||
|
///
|
||||||
|
/// let data_start = zip.start_file_with_extra_data("different_extra_data.txt", options)?;
|
||||||
|
/// let extra_data = b"local extra data";
|
||||||
|
/// zip.write_u16::<LittleEndian>(0xbeef)?;
|
||||||
|
/// zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
|
||||||
|
/// zip.write_all(extra_data)?;
|
||||||
|
/// let data_start = data_start as usize + 4 + extra_data.len() + 4;
|
||||||
|
/// let align = 64;
|
||||||
|
/// let pad_length = (align - data_start % align) % align;
|
||||||
|
/// assert_eq!(pad_length, 19);
|
||||||
|
/// zip.write_u16::<LittleEndian>(0xdead)?;
|
||||||
|
/// zip.write_u16::<LittleEndian>(pad_length as u16)?;
|
||||||
|
/// zip.write_all(&vec![0; pad_length])?;
|
||||||
|
/// let data_start = zip.end_local_start_central_extra_data()?;
|
||||||
|
/// assert_eq!(data_start as usize % align, 0);
|
||||||
|
/// let extra_data = b"central extra data";
|
||||||
|
/// zip.write_u16::<LittleEndian>(0xbeef)?;
|
||||||
|
/// zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
|
||||||
|
/// zip.write_all(extra_data)?;
|
||||||
|
/// zip.end_extra_data()?;
|
||||||
|
/// zip.write_all(b"file data")?;
|
||||||
|
///
|
||||||
|
/// zip.finish()?;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let mut zip = ZipArchive::new(archive)?;
|
||||||
|
/// assert_eq!(&zip.by_index(0)?.extra_data()[4..], b"local and central extra data");
|
||||||
|
/// assert_eq!(&zip.by_index(1)?.extra_data()[4..], b"central extra data");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn start_file_with_extra_data<S>(
|
||||||
|
&mut self,
|
||||||
|
name: S,
|
||||||
|
mut options: FileOptions,
|
||||||
|
) -> ZipResult<u64>
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
if options.permissions.is_none() {
|
||||||
|
options.permissions = Some(0o644);
|
||||||
|
}
|
||||||
|
*options.permissions.as_mut().unwrap() |= 0o100000;
|
||||||
|
self.start_entry(name, options, None)?;
|
||||||
|
self.writing_to_file = true;
|
||||||
|
self.writing_to_extra_field = true;
|
||||||
|
Ok(self.files.last().unwrap().data_start)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End local and start central extra data. Requires [`ZipWriter::start_file_with_extra_data`].
|
||||||
|
///
|
||||||
|
/// Returns the final starting offset of the file data.
|
||||||
|
pub fn end_local_start_central_extra_data(&mut self) -> ZipResult<u64> {
|
||||||
|
let data_start = self.end_extra_data()?;
|
||||||
|
self.files.last_mut().unwrap().extra_field.clear();
|
||||||
|
self.writing_to_extra_field = true;
|
||||||
|
self.writing_to_central_extra_field_only = true;
|
||||||
|
Ok(data_start)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End extra data and start file data. Requires [`ZipWriter::start_file_with_extra_data`].
|
||||||
|
///
|
||||||
|
/// Returns the final starting offset of the file data.
|
||||||
|
pub fn end_extra_data(&mut self) -> ZipResult<u64> {
|
||||||
|
// Require `start_file_with_extra_data()`. Ensures `file` is some.
|
||||||
|
if !self.writing_to_extra_field {
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Not writing to extra field",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let file = self.files.last_mut().unwrap();
|
||||||
|
|
||||||
|
validate_extra_data(&file)?;
|
||||||
|
|
||||||
|
if !self.writing_to_central_extra_field_only {
|
||||||
|
let writer = self.inner.get_plain();
|
||||||
|
|
||||||
|
// Append extra data to local file header and keep it for central file header.
|
||||||
|
writer.write_all(&file.extra_field)?;
|
||||||
|
|
||||||
|
// Update final `data_start`.
|
||||||
|
let header_end = file.data_start + file.extra_field.len() as u64;
|
||||||
|
self.stats.start = header_end;
|
||||||
|
file.data_start = header_end;
|
||||||
|
|
||||||
|
// Update extra field length in local file header.
|
||||||
|
let extra_field_length =
|
||||||
|
if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
|
||||||
|
writer.seek(io::SeekFrom::Start(file.header_start + 28))?;
|
||||||
|
writer.write_u16::<LittleEndian>(extra_field_length)?;
|
||||||
|
writer.seek(io::SeekFrom::Start(header_end))?;
|
||||||
|
|
||||||
|
self.inner.switch_to(file.compression_method)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.writing_to_extra_field = false;
|
||||||
|
self.writing_to_central_extra_field_only = false;
|
||||||
|
Ok(file.data_start)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a new file using the already compressed data from a ZIP file being read and renames it, this
|
/// Add a new file using the already compressed data from a ZIP file being read and renames it, this
|
||||||
/// allows faster copies of the `ZipFile` since there is no need to decompress and compress it again.
|
/// allows faster copies of the `ZipFile` since there is no need to decompress and compress it again.
|
||||||
/// Any `ZipFile` metadata is copied and not checked, for example the file CRC.
|
/// Any `ZipFile` metadata is copied and not checked, for example the file CRC.
|
||||||
|
@ -427,6 +618,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
|
|
||||||
self.start_entry(name, options, Some(raw_values))?;
|
self.start_entry(name, options, Some(raw_values))?;
|
||||||
self.writing_to_file = true;
|
self.writing_to_file = true;
|
||||||
|
self.writing_raw = true;
|
||||||
|
|
||||||
io::copy(file.get_raw_reader(), self)?;
|
io::copy(file.get_raw_reader(), self)?;
|
||||||
|
|
||||||
|
@ -524,14 +716,51 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
}
|
}
|
||||||
let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start;
|
let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start;
|
||||||
|
|
||||||
|
if self.files.len() > 0xFFFF || central_size > 0xFFFFFFFF || central_start > 0xFFFFFFFF
|
||||||
|
{
|
||||||
|
let zip64_footer = spec::Zip64CentralDirectoryEnd {
|
||||||
|
version_made_by: DEFAULT_VERSION as u16,
|
||||||
|
version_needed_to_extract: DEFAULT_VERSION as u16,
|
||||||
|
disk_number: 0,
|
||||||
|
disk_with_central_directory: 0,
|
||||||
|
number_of_files_on_this_disk: self.files.len() as u64,
|
||||||
|
number_of_files: self.files.len() as u64,
|
||||||
|
central_directory_size: central_size,
|
||||||
|
central_directory_offset: central_start,
|
||||||
|
};
|
||||||
|
|
||||||
|
zip64_footer.write(writer)?;
|
||||||
|
|
||||||
|
let zip64_footer = spec::Zip64CentralDirectoryEndLocator {
|
||||||
|
disk_with_central_directory: 0,
|
||||||
|
end_of_central_directory_offset: central_start + central_size,
|
||||||
|
number_of_disks: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
zip64_footer.write(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let number_of_files = if self.files.len() > 0xFFFF {
|
||||||
|
0xFFFF
|
||||||
|
} else {
|
||||||
|
self.files.len() as u16
|
||||||
|
};
|
||||||
let footer = spec::CentralDirectoryEnd {
|
let footer = spec::CentralDirectoryEnd {
|
||||||
disk_number: 0,
|
disk_number: 0,
|
||||||
disk_with_central_directory: 0,
|
disk_with_central_directory: 0,
|
||||||
number_of_files_on_this_disk: self.files.len() as u16,
|
|
||||||
number_of_files: self.files.len() as u16,
|
|
||||||
central_directory_size: central_size as u32,
|
|
||||||
central_directory_offset: central_start as u32,
|
|
||||||
zip_file_comment: self.comment.clone(),
|
zip_file_comment: self.comment.clone(),
|
||||||
|
number_of_files_on_this_disk: number_of_files,
|
||||||
|
number_of_files,
|
||||||
|
central_directory_size: if central_size > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
central_size as u32
|
||||||
|
},
|
||||||
|
central_directory_offset: if central_start > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
central_start as u32
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
footer.write(writer)?;
|
footer.write(writer)?;
|
||||||
|
@ -683,18 +912,28 @@ fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipR
|
||||||
// crc-32
|
// crc-32
|
||||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||||
// compressed size
|
// compressed size
|
||||||
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
|
writer.write_u32::<LittleEndian>(if file.compressed_size > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
file.compressed_size as u32
|
||||||
|
})?;
|
||||||
// uncompressed size
|
// uncompressed size
|
||||||
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
|
writer.write_u32::<LittleEndian>(if file.uncompressed_size > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
file.uncompressed_size as u32
|
||||||
|
})?;
|
||||||
// file name length
|
// file name length
|
||||||
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
|
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
|
||||||
// extra field length
|
// extra field length
|
||||||
let extra_field = build_extra_field(file)?;
|
let extra_field_length = if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
|
||||||
writer.write_u16::<LittleEndian>(extra_field.len() as u16)?;
|
writer.write_u16::<LittleEndian>(extra_field_length)?;
|
||||||
// file name
|
// file name
|
||||||
writer.write_all(file.file_name.as_bytes())?;
|
writer.write_all(file.file_name.as_bytes())?;
|
||||||
// extra field
|
// zip64 extra field
|
||||||
writer.write_all(&extra_field)?;
|
if file.large_file {
|
||||||
|
write_local_zip64_extra_field(writer, &file)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -706,12 +945,37 @@ fn update_local_file_header<T: Write + io::Seek>(
|
||||||
const CRC32_OFFSET: u64 = 14;
|
const CRC32_OFFSET: u64 = 14;
|
||||||
writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
|
writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
|
||||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||||
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
|
writer.write_u32::<LittleEndian>(if file.compressed_size > 0xFFFFFFFF {
|
||||||
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
|
if file.large_file {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
// compressed size can be slightly larger than uncompressed size
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Large file option has not been set",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.compressed_size as u32
|
||||||
|
})?;
|
||||||
|
writer.write_u32::<LittleEndian>(if file.uncompressed_size > 0xFFFFFFFF {
|
||||||
|
// uncompressed size is checked on write to catch it as soon as possible
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
file.uncompressed_size as u32
|
||||||
|
})?;
|
||||||
|
if file.large_file {
|
||||||
|
update_local_zip64_extra_field(writer, file)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
||||||
|
// buffer zip64 extra field to determine its variable length
|
||||||
|
let mut zip64_extra_field = [0; 28];
|
||||||
|
let zip64_extra_field_length =
|
||||||
|
write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?;
|
||||||
|
|
||||||
// central file header signature
|
// central file header signature
|
||||||
writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
|
writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
|
||||||
// version made by
|
// version made by
|
||||||
|
@ -735,14 +999,21 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
|
||||||
// crc-32
|
// crc-32
|
||||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||||
// compressed size
|
// compressed size
|
||||||
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
|
writer.write_u32::<LittleEndian>(if file.compressed_size > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
file.compressed_size as u32
|
||||||
|
})?;
|
||||||
// uncompressed size
|
// uncompressed size
|
||||||
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
|
writer.write_u32::<LittleEndian>(if file.uncompressed_size > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
file.uncompressed_size as u32
|
||||||
|
})?;
|
||||||
// file name length
|
// file name length
|
||||||
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
|
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
|
||||||
// extra field length
|
// extra field length
|
||||||
let extra_field = build_extra_field(file)?;
|
writer.write_u16::<LittleEndian>(zip64_extra_field_length + file.extra_field.len() as u16)?;
|
||||||
writer.write_u16::<LittleEndian>(extra_field.len() as u16)?;
|
|
||||||
// file comment length
|
// file comment length
|
||||||
writer.write_u16::<LittleEndian>(0)?;
|
writer.write_u16::<LittleEndian>(0)?;
|
||||||
// disk number start
|
// disk number start
|
||||||
|
@ -752,21 +1023,139 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
|
||||||
// external file attributes
|
// external file attributes
|
||||||
writer.write_u32::<LittleEndian>(file.external_attributes)?;
|
writer.write_u32::<LittleEndian>(file.external_attributes)?;
|
||||||
// relative offset of local header
|
// relative offset of local header
|
||||||
writer.write_u32::<LittleEndian>(file.header_start as u32)?;
|
writer.write_u32::<LittleEndian>(if file.header_start > 0xFFFFFFFF {
|
||||||
|
0xFFFFFFFF
|
||||||
|
} else {
|
||||||
|
file.header_start as u32
|
||||||
|
})?;
|
||||||
// file name
|
// file name
|
||||||
writer.write_all(file.file_name.as_bytes())?;
|
writer.write_all(file.file_name.as_bytes())?;
|
||||||
|
// zip64 extra field
|
||||||
|
writer.write_all(&zip64_extra_field[..zip64_extra_field_length as usize])?;
|
||||||
// extra field
|
// extra field
|
||||||
writer.write_all(&extra_field)?;
|
writer.write_all(&file.extra_field)?;
|
||||||
// file comment
|
// file comment
|
||||||
// <none>
|
// <none>
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>> {
|
fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
|
||||||
let writer = Vec::new();
|
let mut data = file.extra_field.as_slice();
|
||||||
// Future work
|
|
||||||
Ok(writer)
|
if data.len() > 0xFFFF {
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"Extra data exceeds extra field",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
while data.len() > 0 {
|
||||||
|
let left = data.len();
|
||||||
|
if left < 4 {
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Incomplete extra data header",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let kind = data.read_u16::<LittleEndian>()?;
|
||||||
|
let size = data.read_u16::<LittleEndian>()? as usize;
|
||||||
|
let left = left - 4;
|
||||||
|
|
||||||
|
if kind == 0x0001 {
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"No custom ZIP64 extra data allowed",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unreserved"))]
|
||||||
|
{
|
||||||
|
if kind <= 31 || EXTRA_FIELD_MAPPING.iter().any(|&mapped| mapped == kind) {
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"Extra data header ID {:#06} requires crate feature \"unreserved\"",
|
||||||
|
kind,
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if size > left {
|
||||||
|
return Err(ZipError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Extra data size exceeds extra field",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
data = &data[size..];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_local_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
||||||
|
// This entry in the Local header MUST include BOTH original
|
||||||
|
// and compressed file size fields.
|
||||||
|
writer.write_u16::<LittleEndian>(0x0001)?;
|
||||||
|
writer.write_u16::<LittleEndian>(16)?;
|
||||||
|
writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
|
||||||
|
writer.write_u64::<LittleEndian>(file.compressed_size)?;
|
||||||
|
// Excluded fields:
|
||||||
|
// u32: disk start number
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_local_zip64_extra_field<T: Write + io::Seek>(
|
||||||
|
writer: &mut T,
|
||||||
|
file: &ZipFileData,
|
||||||
|
) -> ZipResult<()> {
|
||||||
|
let zip64_extra_field = file.header_start + 30 + file.file_name.as_bytes().len() as u64;
|
||||||
|
writer.seek(io::SeekFrom::Start(zip64_extra_field + 4))?;
|
||||||
|
writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
|
||||||
|
writer.write_u64::<LittleEndian>(file.compressed_size)?;
|
||||||
|
// Excluded fields:
|
||||||
|
// u32: disk start number
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_central_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<u16> {
|
||||||
|
// The order of the fields in the zip64 extended
|
||||||
|
// information record is fixed, but the fields MUST
|
||||||
|
// only appear if the corresponding Local or Central
|
||||||
|
// directory record field is set to 0xFFFF or 0xFFFFFFFF.
|
||||||
|
let mut size = 0;
|
||||||
|
let uncompressed_size = file.uncompressed_size > 0xFFFFFFFF;
|
||||||
|
let compressed_size = file.compressed_size > 0xFFFFFFFF;
|
||||||
|
let header_start = file.header_start > 0xFFFFFFFF;
|
||||||
|
if uncompressed_size {
|
||||||
|
size += 8;
|
||||||
|
}
|
||||||
|
if compressed_size {
|
||||||
|
size += 8;
|
||||||
|
}
|
||||||
|
if header_start {
|
||||||
|
size += 8;
|
||||||
|
}
|
||||||
|
if size > 0 {
|
||||||
|
writer.write_u16::<LittleEndian>(0x0001)?;
|
||||||
|
writer.write_u16::<LittleEndian>(size)?;
|
||||||
|
size += 4;
|
||||||
|
|
||||||
|
if uncompressed_size {
|
||||||
|
writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
|
||||||
|
}
|
||||||
|
if compressed_size {
|
||||||
|
writer.write_u64::<LittleEndian>(file.compressed_size)?;
|
||||||
|
}
|
||||||
|
if header_start {
|
||||||
|
writer.write_u64::<LittleEndian>(file.header_start)?;
|
||||||
|
}
|
||||||
|
// Excluded fields:
|
||||||
|
// u32: disk start number
|
||||||
|
}
|
||||||
|
Ok(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_to_string(path: &std::path::Path) -> String {
|
fn path_to_string(path: &std::path::Path) -> String {
|
||||||
|
@ -837,6 +1226,7 @@ mod test {
|
||||||
compression_method: CompressionMethod::Stored,
|
compression_method: CompressionMethod::Stored,
|
||||||
last_modified_time: DateTime::default(),
|
last_modified_time: DateTime::default(),
|
||||||
permissions: Some(33188),
|
permissions: Some(33188),
|
||||||
|
large_file: false,
|
||||||
};
|
};
|
||||||
writer.start_file("mimetype", options).unwrap();
|
writer.start_file("mimetype", options).unwrap();
|
||||||
writer
|
writer
|
||||||
|
@ -865,3 +1255,12 @@ mod test {
|
||||||
assert_eq!(path_str, "windows/system32");
|
assert_eq!(path_str, "windows/system32");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unreserved"))]
|
||||||
|
const EXTRA_FIELD_MAPPING: [u16; 49] = [
|
||||||
|
0x0001, 0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016,
|
||||||
|
0x0017, 0x0018, 0x0019, 0x0020, 0x0021, 0x0022, 0x0023, 0x0065, 0x0066, 0x4690, 0x07c8, 0x2605,
|
||||||
|
0x2705, 0x2805, 0x334d, 0x4341, 0x4453, 0x4704, 0x470f, 0x4b46, 0x4c41, 0x4d49, 0x4f4c, 0x5356,
|
||||||
|
0x5455, 0x554e, 0x5855, 0x6375, 0x6542, 0x7075, 0x756e, 0x7855, 0xa11e, 0xa220, 0xfd4a, 0x9901,
|
||||||
|
0x9902,
|
||||||
|
];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::{Cursor, Seek};
|
use std::io::{Cursor, Seek};
|
||||||
|
@ -76,6 +77,13 @@ fn write_to_zip(file: &mut Cursor<Vec<u8>>) -> zip::result::ZipResult<()> {
|
||||||
zip.start_file("test/☃.txt", options)?;
|
zip.start_file("test/☃.txt", options)?;
|
||||||
zip.write_all(b"Hello, World!\n")?;
|
zip.write_all(b"Hello, World!\n")?;
|
||||||
|
|
||||||
|
zip.start_file_with_extra_data("test_with_extra_data/🐢.txt", Default::default())?;
|
||||||
|
zip.write_u16::<LittleEndian>(0xbeef)?;
|
||||||
|
zip.write_u16::<LittleEndian>(EXTRA_DATA.len() as u16)?;
|
||||||
|
zip.write_all(EXTRA_DATA)?;
|
||||||
|
zip.end_extra_data()?;
|
||||||
|
zip.write_all(b"Hello, World! Again.\n")?;
|
||||||
|
|
||||||
zip.start_file(ENTRY_NAME, Default::default())?;
|
zip.start_file(ENTRY_NAME, Default::default())?;
|
||||||
zip.write_all(LOREM_IPSUM)?;
|
zip.write_all(LOREM_IPSUM)?;
|
||||||
|
|
||||||
|
@ -84,13 +92,27 @@ fn write_to_zip(file: &mut Cursor<Vec<u8>>) -> zip::result::ZipResult<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_zip<R: Read + Seek>(zip_file: R) -> zip::result::ZipResult<zip::ZipArchive<R>> {
|
fn read_zip<R: Read + Seek>(zip_file: R) -> zip::result::ZipResult<zip::ZipArchive<R>> {
|
||||||
let archive = zip::ZipArchive::new(zip_file).unwrap();
|
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
|
||||||
|
|
||||||
let expected_file_names = ["test/", "test/☃.txt", ENTRY_NAME];
|
let expected_file_names = [
|
||||||
|
"test/",
|
||||||
|
"test/☃.txt",
|
||||||
|
"test_with_extra_data/🐢.txt",
|
||||||
|
ENTRY_NAME,
|
||||||
|
];
|
||||||
let expected_file_names = HashSet::from_iter(expected_file_names.iter().map(|&v| v));
|
let expected_file_names = HashSet::from_iter(expected_file_names.iter().map(|&v| v));
|
||||||
let file_names = archive.file_names().collect::<HashSet<_>>();
|
let file_names = archive.file_names().collect::<HashSet<_>>();
|
||||||
assert_eq!(file_names, expected_file_names);
|
assert_eq!(file_names, expected_file_names);
|
||||||
|
|
||||||
|
{
|
||||||
|
let file_with_extra_data = archive.by_name("test_with_extra_data/🐢.txt")?;
|
||||||
|
let mut extra_data = Vec::new();
|
||||||
|
extra_data.write_u16::<LittleEndian>(0xbeef)?;
|
||||||
|
extra_data.write_u16::<LittleEndian>(EXTRA_DATA.len() as u16)?;
|
||||||
|
extra_data.write_all(EXTRA_DATA)?;
|
||||||
|
assert_eq!(file_with_extra_data.extra_data(), extra_data.as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(archive)
|
Ok(archive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +144,8 @@ vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam i
|
||||||
inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque.
|
inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque.
|
||||||
";
|
";
|
||||||
|
|
||||||
|
const EXTRA_DATA: &'static [u8] = b"Extra Data";
|
||||||
|
|
||||||
const ENTRY_NAME: &str = "test/lorem_ipsum.txt";
|
const ENTRY_NAME: &str = "test/lorem_ipsum.txt";
|
||||||
|
|
||||||
const COPY_ENTRY_NAME: &str = "test/lorem_ipsum_renamed.txt";
|
const COPY_ENTRY_NAME: &str = "test/lorem_ipsum_renamed.txt";
|
||||||
|
|
Loading…
Add table
Reference in a new issue