Merge improvements from master

This commit is contained in:
Benjamin Richner 2020-06-21 17:43:40 +02:00
commit bd3ed222ac
24 changed files with 821 additions and 707 deletions

57
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,57 @@
name: CI
on:
pull_request:
push:
branches:
- master
env:
RUSTFLAGS: -Dwarnings
jobs:
build_and_test:
name: Build and test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
rust: [stable]
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.rust }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: check
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
check_fmt_and_docs:
name: Checking fmt and docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: rustfmt, clippy
override: true
- name: fmt
run: cargo fmt --all -- --check
- name: Docs
run: cargo doc

View file

@ -1,20 +0,0 @@
language: rust
rust:
- stable
sudo: false
notifications:
email:
on_success: never
on_failure: always
script:
- cargo test
- cargo test --no-default-features
- cargo doc --no-deps
- rustdoc --test README.md -L target/debug
after_success:
- curl https://mvdnes.github.io/rust-docs/travis-doc-upload.sh | bash

View file

@ -1,7 +1,7 @@
[package]
name = "zip"
version = "0.5.5"
version = "0.5.6"
authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>"]
license = "MIT"
repository = "https://github.com/mvdnes/zip-rs.git"
@ -15,9 +15,10 @@ edition = "2018"
[dependencies]
flate2 = { version = "1.0", default-features = false, optional = true }
time = { version = "0.1", optional = true }
podio = "0.1"
byteorder = "1.3"
bzip2 = { version = "0.3", optional = true }
crc32fast = "1.0"
thiserror = "1.0"
[dev-dependencies]
bencher = "0.1"

View file

@ -1,5 +1,3 @@
**Unfortunately, due to a lack of time and loss of interest, this project will no longer be actively maintained.**
zip-rs
======

View file

@ -1,44 +0,0 @@
# Appveyor configuration template for Rust using rustup for Rust installation
# https://github.com/starkat99/appveyor-rust
os: Visual Studio 2015
environment:
matrix:
# Stable 64-bit MSVC
- channel: stable
target: x86_64-pc-windows-msvc
# Stable 32-bit MSVC
- channel: stable
target: i686-pc-windows-msvc
# Stable 64-bit GNU
- channel: stable
target: x86_64-pc-windows-gnu
# Stable 32-bit GNU
- channel: stable
target: i686-pc-windows-gnu
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %channel% --default-host %target%
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- ps: |
if ($env:target -like "*-gnu" -And $env:target -like "x86_64-*") {
Start-FileDownload "http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/5.1.0/threads-win32/seh/x86_64-5.1.0-release-win32-seh-rt_v4-rev0.7z/download" -FileName mingw64.7z
7z x -oC:\ mingw64.7z > $null
$env:path = "C:\mingw64\bin;" + $env:path
gcc --version
}
elseif ($env:target -like "*-gnu") {
$env:path = "C:\mingw\bin;" + $env:path
gcc --version
}
- rustc -vV
- cargo -vV
build: false
test_script:
- cargo test
- cargo test --no-default-features

View file

@ -9,8 +9,8 @@ use zip::{ZipArchive, ZipWriter};
fn generate_random_archive(size: usize) -> Vec<u8> {
let data = Vec::new();
let mut writer = ZipWriter::new(Cursor::new(data));
let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Stored);
let options =
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
writer.start_file("random.dat", options).unwrap();
let mut bytes = vec![0u8; size];

View file

@ -1,5 +1,5 @@
use std::io;
use std::fs;
use std::io;
fn main() {
std::process::exit(real_main());
@ -28,10 +28,19 @@ fn real_main() -> i32 {
}
if (&*file.name()).ends_with('/') {
println!("File {} extracted to \"{}\"", i, outpath.as_path().display());
println!(
"File {} extracted to \"{}\"",
i,
outpath.as_path().display()
);
fs::create_dir_all(&outpath).unwrap();
} else {
println!("File {} extracted to \"{}\" ({} bytes)", i, outpath.as_path().display(), file.size());
println!(
"File {} extracted to \"{}\" ({} bytes)",
i,
outpath.as_path().display(),
file.size()
);
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(&p).unwrap();

View file

@ -1,12 +1,10 @@
use std::io::prelude::*;
fn main()
{
fn main() {
std::process::exit(real_main());
}
fn real_main() -> i32
{
fn real_main() -> i32 {
let args: Vec<_> = std::env::args().collect();
if args.len() < 2 {
println!("Usage: {} <filename>", args[0]);
@ -17,10 +15,12 @@ fn real_main() -> i32
let mut archive = zip::ZipArchive::new(zipfile).unwrap();
let mut file = match archive.by_name("test/lorem_ipsum.txt")
{
let mut file = match archive.by_name("test/lorem_ipsum.txt") {
Ok(file) => file,
Err(..) => { println!("File test/lorem_ipsum.txt not found"); return 2;}
Err(..) => {
println!("File test/lorem_ipsum.txt not found");
return 2;
}
};
let mut contents = String::new();

View file

@ -29,9 +29,18 @@ fn real_main() -> i32 {
}
if (&*file.name()).ends_with('/') {
println!("Entry {} is a directory with name \"{}\"", i, outpath.as_path().display());
println!(
"Entry {} is a directory with name \"{}\"",
i,
outpath.as_path().display()
);
} else {
println!("Entry {} is a file with name \"{}\" ({} bytes)", i, outpath.as_path().display(), file.size());
println!(
"Entry {} is a file with name \"{}\" ({} bytes)",
i,
outpath.as_path().display(),
file.size()
);
}
}
return 0;

View file

@ -12,17 +12,22 @@ fn real_main() -> i32 {
loop {
match zip::read::read_zipfile_from_stream(&mut stdin_handle) {
Ok(Some(mut file)) => {
println!("{}: {} bytes ({} bytes packed)", file.name(), file.size(), file.compressed_size());
println!(
"{}: {} bytes ({} bytes packed)",
file.name(),
file.size(),
file.compressed_size()
);
match file.read(&mut buf) {
Ok(n) => println!("The first {} bytes are: {:?}", n, &buf[0..n]),
Err(e) => println!("Could not read the file: {:?}", e),
};
},
}
Ok(None) => break,
Err(e) => {
println!("Error encountered while reading zip: {:?}", e);
return 1;
},
}
}
}
return 0;

View file

@ -1,13 +1,12 @@
use std::io::prelude::*;
use std::io::{Write, Seek};
use std::io::{Seek, Write};
use std::iter::Iterator;
use zip::write::FileOptions;
use zip::result::ZipError;
use zip::write::FileOptions;
use walkdir::{WalkDir, DirEntry};
use std::path::Path;
use std::fs::File;
use std::path::Path;
use walkdir::{DirEntry, WalkDir};
fn main() {
std::process::exit(real_main());
@ -28,15 +27,19 @@ const METHOD_BZIP2 : Option<zip::CompressionMethod> = None;
fn real_main() -> i32 {
let args: Vec<_> = std::env::args().collect();
if args.len() < 3 {
println!("Usage: {} <source_directory> <destination_zipfile>",
args[0]);
println!(
"Usage: {} <source_directory> <destination_zipfile>",
args[0]
);
return 1;
}
let src_dir = &*args[1];
let dst_file = &*args[2];
for &method in [METHOD_STORED, METHOD_DEFLATED, METHOD_BZIP2].iter() {
if method.is_none() { continue }
if method.is_none() {
continue;
}
match doit(src_dir, dst_file, method.unwrap()) {
Ok(_) => println!("done: {} written to {}", src_dir, dst_file),
Err(e) => println!("Error: {:?}", e),
@ -46,9 +49,14 @@ fn real_main() -> i32 {
return 0;
}
fn zip_dir<T>(it: &mut dyn Iterator<Item=DirEntry>, prefix: &str, writer: T, method: zip::CompressionMethod)
-> zip::result::ZipResult<()>
where T: Write+Seek
fn zip_dir<T>(
it: &mut dyn Iterator<Item = DirEntry>,
prefix: &str,
writer: T,
method: zip::CompressionMethod,
) -> zip::result::ZipResult<()>
where
T: Write + Seek,
{
let mut zip = zip::ZipWriter::new(writer);
let options = FileOptions::default()
@ -81,7 +89,11 @@ fn zip_dir<T>(it: &mut dyn Iterator<Item=DirEntry>, prefix: &str, writer: T, met
Result::Ok(())
}
fn doit(src_dir: &str, dst_file: &str, method: zip::CompressionMethod) -> zip::result::ZipResult<()> {
fn doit(
src_dir: &str,
dst_file: &str,
method: zip::CompressionMethod,
) -> zip::result::ZipResult<()> {
if !Path::new(src_dir).is_dir() {
return Err(ZipError::FileNotFound);
}

View file

@ -1,13 +1,11 @@
use std::io::prelude::*;
use zip::write::FileOptions;
fn main()
{
fn main() {
std::process::exit(real_main());
}
fn real_main() -> i32
{
fn real_main() -> i32 {
let args: Vec<_> = std::env::args().collect();
if args.len() < 2 {
println!("Usage: {} <filename>", args[0]);
@ -15,8 +13,7 @@ fn real_main() -> i32
}
let filename = &*args[1];
match doit(filename)
{
match doit(filename) {
Ok(_) => println!("File written to {}", filename),
Err(e) => println!("Error: {:?}", e),
}
@ -24,8 +21,7 @@ fn real_main() -> i32
return 0;
}
fn doit(filename: &str) -> zip::result::ZipResult<()>
{
fn doit(filename: &str) -> zip::result::ZipResult<()> {
let path = std::path::Path::new(filename);
let file = std::fs::File::create(&path).unwrap();
@ -33,7 +29,9 @@ fn doit(filename: &str) -> zip::result::ZipResult<()>
zip.add_directory("test/", Default::default())?;
let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored).unix_permissions(0o755);
let options = FileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o755);
zip.start_file("test/☃.txt", options)?;
zip.write_all(b"Hello, World!\n")?;

View file

@ -4,8 +4,7 @@ use std::fmt;
/// Compression methods for the contents of a ZIP file.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum CompressionMethod
{
pub enum CompressionMethod {
/// The file is stored (no compression)
Stored,
/// Deflate in pure rust
@ -15,11 +14,19 @@ pub enum CompressionMethod
#[cfg(feature = "bzip2")]
Bzip2,
/// Unsupported compression method
#[deprecated(
since = "0.5.7",
note = "implementation details are being removed from the public API"
)]
Unsupported(u16),
}
impl CompressionMethod {
/// Converts an u16 to its corresponding CompressionMethod
#[deprecated(
since = "0.5.7",
note = "implementation details are being removed from the public API"
)]
pub fn from_u16(val: u16) -> CompressionMethod {
match val {
0 => CompressionMethod::Stored,
@ -27,11 +34,16 @@ impl CompressionMethod {
8 => CompressionMethod::Deflated,
#[cfg(feature = "bzip2")]
12 => CompressionMethod::Bzip2,
#[allow(deprecated)]
v => CompressionMethod::Unsupported(v),
}
}
/// Converts a CompressionMethod to a u16
#[deprecated(
since = "0.5.7",
note = "implementation details are being removed from the public API"
)]
pub fn to_u16(self) -> u16 {
match self {
CompressionMethod::Stored => 0,
@ -39,6 +51,7 @@ impl CompressionMethod {
CompressionMethod::Deflated => 8,
#[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => 12,
#[allow(deprecated)]
CompressionMethod::Unsupported(v) => v,
}
}
@ -57,9 +70,10 @@ mod test {
#[test]
fn from_eq_to() {
for v in 0..(::std::u16::MAX as u32 + 1)
{
for v in 0..(::std::u16::MAX as u32 + 1) {
#[allow(deprecated)]
let from = CompressionMethod::from_u16(v as u16);
#[allow(deprecated)]
let to = from.to_u16() as u32;
assert_eq!(v, to);
}
@ -68,17 +82,21 @@ mod test {
fn methods() -> Vec<CompressionMethod> {
let mut methods = Vec::new();
methods.push(CompressionMethod::Stored);
#[cfg(feature="deflate")] methods.push(CompressionMethod::Deflated);
#[cfg(feature="bzip2")] methods.push(CompressionMethod::Bzip2);
#[cfg(feature = "deflate")]
methods.push(CompressionMethod::Deflated);
#[cfg(feature = "bzip2")]
methods.push(CompressionMethod::Bzip2);
methods
}
#[test]
fn to_eq_from() {
fn check_match(method: CompressionMethod) {
#[allow(deprecated)]
let to = method.to_u16();
#[allow(deprecated)]
let from = CompressionMethod::from_u16(to);
#[allow(deprecated)]
let back = from.to_u16();
assert_eq!(to, back);
}

View file

@ -13,12 +13,10 @@ pub trait FromCp437 {
impl<'a> FromCp437 for &'a [u8] {
type Target = ::std::borrow::Cow<'a, str>;
fn from_cp437(self) -> Self::Target
{
fn from_cp437(self) -> Self::Target {
if self.iter().all(|c| *c < 0x80) {
::std::str::from_utf8(self).unwrap().into()
}
else {
} else {
self.iter().map(|c| to_char(*c)).collect::<String>().into()
}
}
@ -30,17 +28,14 @@ impl FromCp437 for Vec<u8> {
fn from_cp437(self) -> Self::Target {
if self.iter().all(|c| *c < 0x80) {
String::from_utf8(self).unwrap()
}
else {
self.into_iter().map(|c| to_char(c)).collect()
} else {
self.into_iter().map(to_char).collect()
}
}
}
fn to_char(input: u8) -> char
{
let output = match input
{
fn to_char(input: u8) -> char {
let output = match input {
0x00..=0x7f => input as u32,
0x80 => 0x00c7,
0x81 => 0x00fc,
@ -175,13 +170,10 @@ fn to_char(input: u8) -> char
}
#[cfg(test)]
mod test
{
mod test {
#[test]
fn to_char_valid()
{
for i in 0x00_u32 .. 0x100
{
fn to_char_valid() {
for i in 0x00_u32..0x100 {
super::to_char(i as u8);
}
}

View file

@ -6,28 +6,23 @@ use std::io::prelude::*;
use crc32fast::Hasher;
/// Reader that validates the CRC32 when it reaches the EOF.
pub struct Crc32Reader<R>
{
pub struct Crc32Reader<R> {
inner: R,
hasher: Hasher,
check: u32,
}
impl<R> Crc32Reader<R>
{
impl<R> Crc32Reader<R> {
/// Get a new Crc32Reader which check the inner reader against checksum.
pub fn new(inner: R, checksum: u32) -> Crc32Reader<R>
{
Crc32Reader
{
inner: inner,
pub fn new(inner: R, checksum: u32) -> Crc32Reader<R> {
Crc32Reader {
inner,
hasher: Hasher::new(),
check: checksum,
}
}
fn check_matches(&self) -> bool
{
fn check_matches(&self) -> bool {
self.check == self.hasher.clone().finalize()
}
@ -36,13 +31,12 @@ impl<R> Crc32Reader<R>
}
}
impl<R: Read> Read for Crc32Reader<R>
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>
{
let count = match self.inner.read(buf)
{
Ok(0) if !self.check_matches() => { return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum")) },
impl<R: Read> Read for Crc32Reader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let count = match self.inner.read(buf) {
Ok(0) if !buf.is_empty() && !self.check_matches() => {
return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum"))
}
Ok(n) => n,
Err(e) => return Err(e),
};
@ -50,3 +44,50 @@ impl<R: Read> Read for Crc32Reader<R>
Ok(count)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Read;
#[test]
fn test_empty_reader() {
let data: &[u8] = b"";
let mut buf = [0; 1];
let mut reader = Crc32Reader::new(data, 0);
assert_eq!(reader.read(&mut buf).unwrap(), 0);
let mut reader = Crc32Reader::new(data, 1);
assert!(reader
.read(&mut buf)
.unwrap_err()
.to_string()
.contains("Invalid checksum"));
}
#[test]
fn test_byte_by_byte() {
let data: &[u8] = b"1234";
let mut buf = [0; 1];
let mut reader = Crc32Reader::new(data, 0x9be3e0a3);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(reader.read(&mut buf).unwrap(), 0);
// Can keep reading 0 bytes after the end
assert_eq!(reader.read(&mut buf).unwrap(), 0);
}
#[test]
fn test_zero_read() {
let data: &[u8] = b"1234";
let mut buf = [0; 5];
let mut reader = Crc32Reader::new(data, 0x9be3e0a3);
assert_eq!(reader.read(&mut buf[..0]).unwrap(), 0);
assert_eq!(reader.read(&mut buf).unwrap(), 4);
}
}

View file

@ -2,17 +2,17 @@
#![warn(missing_docs)]
pub use crate::read::ZipArchive;
pub use crate::write::ZipWriter;
pub use crate::compression::CompressionMethod;
pub use crate::read::ZipArchive;
pub use crate::types::DateTime;
pub use crate::write::ZipWriter;
mod spec;
mod crc32;
mod types;
pub mod read;
mod compression;
pub mod write;
mod cp437;
mod crc32;
pub mod read;
pub mod result;
mod zipcrypto;
mod spec;
mod types;
pub mod write;

View file

@ -1,19 +1,19 @@
//! Structs for reading a ZIP archive
use crate::crc32::Crc32Reader;
use crate::compression::CompressionMethod;
use crate::zipcrypto::ZipCryptoReader;
use crate::zipcrypto::ZipCryptoReaderValid;
use crate::crc32::Crc32Reader;
use crate::result::{ZipError, ZipResult};
use crate::spec;
use crate::result::{ZipResult, ZipError};
use std::borrow::Cow;
use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::collections::HashMap;
use std::borrow::Cow;
use podio::{ReadPodExt, LittleEndian};
use crate::types::{ZipFileData, System, DateTime};
use crate::cp437::FromCp437;
use crate::types::{DateTime, System, ZipFileData};
use byteorder::{LittleEndian, ReadBytesExt};
#[cfg(feature = "deflate")]
use flate2::read::DeflateDecoder;
@ -28,10 +28,9 @@ mod ffi {
/// Wrapper for reading the contents of a ZIP file.
///
/// ```
/// fn doit() -> zip::result::ZipResult<()>
/// {
/// ```no_run
/// use std::io::prelude::*;
/// fn main() -> zip::result::ZipResult<()> {
///
/// // For demonstration purposes we read from an empty buffer.
/// // Normally a File object would be used.
@ -40,8 +39,7 @@ mod ffi {
///
/// let mut zip = zip::ZipArchive::new(reader)?;
///
/// for i in 0..zip.len()
/// {
/// for i in 0..zip.len() {
/// let mut file = zip.by_index(i).unwrap();
/// println!("Filename: {}", file.name());
/// let first_byte = file.bytes().next().unwrap()?;
@ -49,12 +47,9 @@ mod ffi {
/// }
/// Ok(())
/// }
///
/// println!("Result: {:?}", doit());
/// ```
#[derive(Clone, Debug)]
pub struct ZipArchive<R: Read + io::Seek>
{
pub struct ZipArchive<R: Read + io::Seek> {
reader: R,
files: Vec<ZipFileData>,
names_map: HashMap<String, usize>,
@ -132,12 +127,6 @@ pub struct ZipFile<'a> {
reader: ZipFileReader<'a>,
}
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>
{
Err(ZipError::UnsupportedArchive(detail))
}
fn make_reader<'a>(
compression_method: crate::compression::CompressionMethod,
crc32: u32,
@ -186,31 +175,36 @@ fn make_reader<'a>(
}
}
impl<R: Read+io::Seek> ZipArchive<R>
{
impl<R: Read + io::Seek> ZipArchive<R> {
/// Get the directory start offset and number of files. This is done in a
/// separate function to ease the control flow design.
fn get_directory_counts(reader: &mut R,
fn get_directory_counts(
reader: &mut R,
footer: &spec::CentralDirectoryEnd,
cde_start_pos: u64) -> ZipResult<(u64, u64, usize)> {
cde_start_pos: u64,
) -> ZipResult<(u64, u64, usize)> {
// See if there's a ZIP64 footer. The ZIP64 locator if present will
// have its signature 20 bytes in front of the standard footer. The
// standard footer, in turn, is 22+N bytes large, where N is the
// comment length. Therefore:
let zip64locator = if reader.seek(io::SeekFrom::End(-(20 + 22 + footer.zip_file_comment.len() as i64))).is_ok() {
let zip64locator = if reader
.seek(io::SeekFrom::End(
-(20 + 22 + footer.zip_file_comment.len() as i64),
))
.is_ok()
{
match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
Ok(loc) => Some(loc),
Err(ZipError::InvalidArchive(_)) => {
// No ZIP64 header; that's actually fine. We're done here.
None
},
}
Err(e) => {
// Yikes, a real problem
return Err(e);
}
}
}
else {
} else {
// Empty Zip files will have nothing else so this error might be fine. If
// not, we'll find out soon.
None
@ -222,19 +216,24 @@ impl<R: Read+io::Seek> ZipArchive<R>
// offsets all being too small. Get the amount of error by comparing
// the actual file position we found the CDE at with the offset
// recorded in the CDE.
let archive_offset = cde_start_pos.checked_sub(footer.central_directory_size as u64)
let archive_offset = cde_start_pos
.checked_sub(footer.central_directory_size as u64)
.and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
.ok_or(ZipError::InvalidArchive("Invalid central directory size or offset"))?;
.ok_or(ZipError::InvalidArchive(
"Invalid central directory size or offset",
))?;
let directory_start = footer.central_directory_offset as u64 + archive_offset;
let number_of_files = footer.number_of_files_on_this_disk as usize;
return Ok((archive_offset, directory_start, number_of_files));
},
Ok((archive_offset, directory_start, number_of_files))
}
Some(locator64) => {
// If we got here, this is indeed a ZIP64 file.
if footer.disk_number as u32 != locator64.disk_with_central_directory {
return unsupported_zip_error("Support for multi-disk files is not implemented")
return unsupported_zip_error(
"Support for multi-disk files is not implemented",
);
}
// We need to reassess `archive_offset`. We know where the ZIP64
@ -247,19 +246,28 @@ impl<R: Read+io::Seek> ZipArchive<R>
let search_upper_bound = cde_start_pos
.checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
.ok_or(ZipError::InvalidArchive("File cannot contain ZIP64 central directory end"))?;
.ok_or(ZipError::InvalidArchive(
"File cannot contain ZIP64 central directory end",
))?;
let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
reader,
locator64.end_of_central_directory_offset,
search_upper_bound)?;
search_upper_bound,
)?;
if footer.disk_number != footer.disk_with_central_directory {
return unsupported_zip_error("Support for multi-disk files is not implemented")
return unsupported_zip_error(
"Support for multi-disk files is not implemented",
);
}
let directory_start = footer.central_directory_offset + archive_offset;
Ok((archive_offset, directory_start, footer.number_of_files as usize))
},
Ok((
archive_offset,
directory_start,
footer.number_of_files as usize,
))
}
}
}
@ -267,9 +275,8 @@ impl<R: Read+io::Seek> ZipArchive<R>
pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
if footer.disk_number != footer.disk_with_central_directory
{
return unsupported_zip_error("Support for multi-disk files is not implemented")
if footer.disk_number != footer.disk_with_central_directory {
return unsupported_zip_error("Support for multi-disk files is not implemented");
}
let (archive_offset, directory_start, number_of_files) =
@ -279,20 +286,21 @@ impl<R: Read+io::Seek> ZipArchive<R>
let mut names_map = HashMap::new();
if let Err(_) = reader.seek(io::SeekFrom::Start(directory_start)) {
return Err(ZipError::InvalidArchive("Could not seek to start of central directory"));
return Err(ZipError::InvalidArchive(
"Could not seek to start of central directory",
));
}
for _ in 0 .. number_of_files
{
for _ in 0..number_of_files {
let file = central_header_to_zip_file(&mut reader, archive_offset)?;
names_map.insert(file.file_name.clone(), files.len());
files.push(file);
}
Ok(ZipArchive {
reader: reader,
files: files,
names_map: names_map,
reader,
files,
names_map,
offset: archive_offset,
comment: footer.zip_file_comment,
})
@ -300,21 +308,23 @@ impl<R: Read+io::Seek> ZipArchive<R>
/// Number of files contained in this zip.
///
/// ```
/// fn iter() {
/// ```no_run
/// let mut zip = zip::ZipArchive::new(std::io::Cursor::new(vec![])).unwrap();
///
/// for i in 0..zip.len() {
/// let mut file = zip.by_index(i).unwrap();
/// // Do something with file i
/// }
/// }
/// ```
pub fn len(&self) -> usize
{
pub fn len(&self) -> usize {
self.files.len()
}
/// Whether this zip archive contains no files
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes.
///
/// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size
@ -334,22 +344,21 @@ impl<R: Read+io::Seek> ZipArchive<R>
}
/// Search for a file entry by name, decrypt with given password
pub fn by_name_decrypt<'a>(&'a mut self, name: &str, password: &[u8]) -> ZipResult<ZipFile<'a>>
{
pub fn by_name_decrypt<'a>(&'a mut self, name: &str, password: &[u8]) -> ZipResult<ZipFile<'a>> {
self.by_name_internal(name, Some(password))
}
/// Search for a file entry by name
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>>
{
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
self.by_name_internal(name, None)
}
fn by_name_internal<'a>(&'a mut self, name: &str, password: Option<&[u8]>) -> ZipResult<ZipFile<'a>>
{
fn by_name_internal<'a>(&'a mut self, name: &str, password: Option<&[u8]>) -> ZipResult<ZipFile<'a>> {
let index = match self.names_map.get(name) {
Some(index) => *index,
None => { return Err(ZipError::FileNotFound); },
None => {
return Err(ZipError::FileNotFound);
}
};
self.by_index_internal(index, password)
}
@ -361,25 +370,20 @@ impl<R: Read+io::Seek> ZipArchive<R>
}
/// Get a contained file by index
pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>>
{
pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>> {
self.by_index_internal(file_number, None)
}
fn by_index_internal<'a>(&'a mut self, file_number: usize, mut password: Option<&[u8]>) -> ZipResult<ZipFile<'a>>
{
fn by_index_internal<'a>(&'a mut self, file_number: usize, mut password: Option<&[u8]>) -> ZipResult<ZipFile<'a>> {
if file_number >= self.files.len() { return Err(ZipError::FileNotFound); }
let ref mut data = self.files[file_number];
if password == None
{
if data.encrypted
{
if password == None {
if data.encrypted {
return Err(ZipError::PasswordRequired)
}
}
else if !data.encrypted
{
else if !data.encrypted {
//Password supplied, but none needed! Discard.
password = None;
}
@ -387,39 +391,46 @@ impl<R: Read+io::Seek> ZipArchive<R>
// Parse local header
self.reader.seek(io::SeekFrom::Start(data.header_start))?;
let signature = self.reader.read_u32::<LittleEndian>()?;
if signature != spec::LOCAL_FILE_HEADER_SIGNATURE
{
return Err(ZipError::InvalidArchive("Invalid local file header"))
if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
return Err(ZipError::InvalidArchive("Invalid local file header"));
}
self.reader.seek(io::SeekFrom::Current(22))?;
let file_name_length = self.reader.read_u16::<LittleEndian>()? as u64;
let extra_field_length = self.reader.read_u16::<LittleEndian>()? as u64;
let magic_and_header = 4 + 22 + 2 + 2;
data.data_start = data.header_start + magic_and_header + file_name_length + extra_field_length;
data.data_start =
data.header_start + magic_and_header + file_name_length + extra_field_length;
self.reader.seek(io::SeekFrom::Start(data.data_start))?;
let limit_reader = (self.reader.by_ref() as &mut dyn Read).take(data.compressed_size);
Ok(ZipFile { reader: make_reader(data.compression_method, data.crc32, limit_reader, password)?, data: Cow::Borrowed(data) })
Ok(ZipFile {
reader: make_reader(data.compression_method, data.crc32, limit_reader, password)?,
data: Cow::Borrowed(data),
})
}
/// Unwrap and return the inner reader object
///
/// The position of the reader is undefined.
pub fn into_inner(self) -> R
{
pub fn into_inner(self) -> R {
self.reader
}
}
fn central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R, archive_offset: u64) -> ZipResult<ZipFileData>
{
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
Err(ZipError::UnsupportedArchive(detail))
}
fn central_header_to_zip_file<R: Read + io::Seek>(
reader: &mut R,
archive_offset: u64,
) -> ZipResult<ZipFileData> {
// Parse central header
let signature = reader.read_u32::<LittleEndian>()?;
if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE
{
return Err(ZipError::InvalidArchive("Invalid Central Directory header"))
if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
return Err(ZipError::InvalidArchive("Invalid Central Directory header"));
}
let version_made_by = reader.read_u16::<LittleEndian>()?;
@ -440,43 +451,46 @@ fn central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R, archive_offset:
let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
let external_file_attributes = reader.read_u32::<LittleEndian>()?;
let offset = reader.read_u32::<LittleEndian>()? as u64;
let file_name_raw = ReadPodExt::read_exact(reader, file_name_length)?;
let extra_field = ReadPodExt::read_exact(reader, extra_field_length)?;
let file_comment_raw = ReadPodExt::read_exact(reader, file_comment_length)?;
let mut file_name_raw = vec![0; file_name_length];
reader.read_exact(&mut file_name_raw)?;
let mut extra_field = vec![0; extra_field_length];
reader.read_exact(&mut extra_field)?;
let mut file_comment_raw = vec![0; file_comment_length];
reader.read_exact(&mut file_comment_raw)?;
let file_name = match is_utf8
{
let file_name = match is_utf8 {
true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
false => file_name_raw.clone().from_cp437(),
};
let file_comment = match is_utf8
{
let file_comment = match is_utf8 {
true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
false => file_comment_raw.from_cp437(),
};
// Construct the result
let mut result = ZipFileData
{
let mut result = ZipFileData {
system: System::from_u8((version_made_by >> 8) as u8),
version_made_by: version_made_by as u8,
encrypted: encrypted,
compression_method: CompressionMethod::from_u16(compression_method),
encrypted,
compression_method: {
#[allow(deprecated)]
CompressionMethod::from_u16(compression_method)
},
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
crc32: crc32,
crc32,
compressed_size: compressed_size as u64,
uncompressed_size: uncompressed_size as u64,
file_name: file_name,
file_name_raw: file_name_raw,
file_comment: file_comment,
file_name,
file_name_raw,
file_comment,
header_start: offset,
data_start: 0,
external_attributes: external_file_attributes,
};
match parse_extra_field(&mut result, &*extra_field) {
Ok(..) | Err(ZipError::Io(..)) => {},
Err(e) => Err(e)?,
Ok(..) | Err(ZipError::Io(..)) => {}
Err(e) => return Err(e),
}
// Account for shifted zip offsets.
@ -485,19 +499,15 @@ fn central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R, archive_offset:
Ok(result)
}
fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>
{
fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> {
let mut reader = io::Cursor::new(data);
while (reader.position() as usize) < data.len()
{
while (reader.position() as usize) < data.len() {
let kind = reader.read_u16::<LittleEndian>()?;
let len = reader.read_u16::<LittleEndian>()?;
let mut len_left = len as i64;
match kind
{
// Zip64 extended information extra field
0x0001 => {
if kind == 0x0001 {
if file.uncompressed_size == 0xFFFFFFFF {
file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
len_left -= 8;
@ -512,8 +522,6 @@ fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>
}
// Unparsed fields:
// u32: disk start number
},
_ => {},
}
// We could also check for < 0 to check for errors
@ -528,49 +536,66 @@ fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>
impl<'a> ZipFile<'a> {
/// Get the version of the file
pub fn version_made_by(&self) -> (u8, u8) {
(self.data.version_made_by / 10, self.data.version_made_by % 10)
(
self.data.version_made_by / 10,
self.data.version_made_by % 10,
)
}
/// Get the name of the file
pub fn name(&self) -> &str {
&*self.data.file_name
&self.data.file_name
}
/// Get the name of the file, in the raw (internal) byte representation.
pub fn name_raw(&self) -> &[u8] {
&*self.data.file_name_raw
&self.data.file_name_raw
}
/// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte,
/// removes a leading '/' and removes '..' parts.
pub fn sanitized_name(&self) -> ::std::path::PathBuf {
self.data.file_name_sanitized()
}
/// Get the comment of the file
pub fn comment(&self) -> &str {
&*self.data.file_comment
&self.data.file_comment
}
/// Get the compression method used to store the file
pub fn compression(&self) -> CompressionMethod {
self.data.compression_method
}
/// Get the size of the file in the archive
pub fn compressed_size(&self) -> u64 {
self.data.compressed_size
}
/// Get the size of the file when uncompressed
pub fn size(&self) -> u64 {
self.data.uncompressed_size
}
/// Get the time the file was last modified
pub fn last_modified(&self) -> DateTime {
self.data.last_modified_time
}
/// Returns whether the file is actually a directory
pub fn is_dir(&self) -> bool {
self.name().chars().rev().next().map_or(false, |c| c == '/' || c == '\\')
self.name()
.chars()
.rev()
.next()
.map_or(false, |c| c == '/' || c == '\\')
}
/// Returns whether the file is a regular file
pub fn is_file(&self) -> bool {
!self.is_dir()
}
/// Get unix mode for the file
pub fn unix_mode(&self) -> Option<u32> {
if self.data.external_attributes == 0 {
@ -578,9 +603,7 @@ impl<'a> ZipFile<'a> {
}
match self.data.system {
System::Unix => {
Some(self.data.external_attributes >> 16)
},
System::Unix => Some(self.data.external_attributes >> 16),
System::Dos => {
// Interpret MSDOS directory bit
let mut mode = if 0x10 == (self.data.external_attributes & 0x10) {
@ -593,10 +616,11 @@ impl<'a> ZipFile<'a> {
mode &= 0o0555;
}
Some(mode)
},
}
_ => None,
}
}
/// Get the CRC32 hash of the original file
pub fn crc32(&self) -> u32 {
self.data.crc32
@ -606,6 +630,11 @@ impl<'a> ZipFile<'a> {
pub fn data_start(&self) -> u64 {
self.data.data_start
}
/// Get the starting offset of the zip header for this file
pub fn header_start(&self) -> u64 {
self.data.header_start
}
}
impl<'a> Read for ZipFile<'a> {
@ -630,7 +659,10 @@ impl<'a> Drop for ZipFile<'a> {
match reader.read(&mut buffer) {
Ok(0) => break,
Ok(_) => (),
Err(e) => panic!("Could not consume all of the output of the current ZipFile: {:?}", e),
Err(e) => panic!(
"Could not consume all of the output of the current ZipFile: {:?}",
e
),
}
}
}
@ -653,7 +685,9 @@ impl<'a> Drop for ZipFile<'a> {
/// * `comment`: set to an empty string
/// * `data_start`: set to 0
/// * `external_attributes`: `unix_mode()`: will return None
pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult<Option<ZipFile<'_>>> {
pub fn read_zipfile_from_stream<'a, R: io::Read>(
reader: &'a mut R,
) -> ZipResult<Option<ZipFile<'_>>> {
let signature = reader.read_u32::<LittleEndian>()?;
match signature {
@ -667,6 +701,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult
let encrypted = flags & 1 == 1;
let is_utf8 = flags & (1 << 11) != 0;
let using_data_descriptor = flags & (1 << 3) != 0;
#[allow(deprecated)]
let compression_method = CompressionMethod::from_u16(reader.read_u16::<LittleEndian>()?);
let last_mod_time = reader.read_u16::<LittleEndian>()?;
let last_mod_date = reader.read_u16::<LittleEndian>()?;
@ -676,27 +711,27 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult
let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
let file_name_raw = ReadPodExt::read_exact(reader, file_name_length)?;
let extra_field = ReadPodExt::read_exact(reader, extra_field_length)?;
let mut file_name_raw = vec![0; file_name_length];
reader.read_exact(&mut file_name_raw)?;
let mut extra_field = vec![0; extra_field_length];
reader.read_exact(&mut extra_field)?;
let file_name = match is_utf8
{
let file_name = match is_utf8 {
true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
false => file_name_raw.clone().from_cp437(),
};
let mut result = ZipFileData
{
let mut result = ZipFileData {
system: System::from_u8((version_made_by >> 8) as u8),
version_made_by: version_made_by as u8,
encrypted: encrypted,
compression_method: compression_method,
encrypted,
compression_method,
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
crc32: crc32,
crc32,
compressed_size: compressed_size as u64,
uncompressed_size: uncompressed_size as u64,
file_name: file_name,
file_name_raw: file_name_raw,
file_name,
file_name_raw,
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
// not available.
@ -709,12 +744,12 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult
};
match parse_extra_field(&mut result, &extra_field) {
Ok(..) | Err(ZipError::Io(..)) => {},
Err(e) => Err(e)?,
Ok(..) | Err(ZipError::Io(..)) => {}
Err(e) => return Err(e),
}
if encrypted {
return unsupported_zip_error("Encrypted files are not supported")
return unsupported_zip_error("Encrypted files are not supported");
}
if using_data_descriptor {
return unsupported_zip_error("The file length is not available in the local header");
@ -726,7 +761,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult
let result_compression_method = result.compression_method;
Ok(Some(ZipFile {
data: Cow::Owned(result),
reader: make_reader(result_compression_method, result_crc32, limit_reader, None)?
reader: make_reader(result_compression_method, result_crc32, limit_reader, None)?,
}))
}
@ -734,8 +769,8 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult
mod test {
#[test]
fn invalid_offset() {
use std::io;
use super::ZipArchive;
use std::io;
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip"));
@ -745,8 +780,8 @@ mod test {
#[test]
fn zip64_with_leading_junk() {
use std::io;
use super::ZipArchive;
use std::io;
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip"));
@ -756,8 +791,8 @@ mod test {
#[test]
fn zip_comment() {
use std::io;
use super::ZipArchive;
use std::io;
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
@ -767,8 +802,8 @@ mod test {
#[test]
fn zip_read_streaming() {
use std::io;
use super::read_zipfile_from_stream;
use std::io;
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
@ -783,8 +818,8 @@ mod test {
#[test]
fn zip_clone() {
use std::io::{self, Read};
use super::ZipArchive;
use std::io::{self, Read};
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
@ -795,7 +830,17 @@ mod test {
let mut file2 = reader2.by_index(0).unwrap();
let t = file1.last_modified();
assert_eq!((t.year(), t.month(), t.day(), t.hour(), t.minute(), t.second()), (1980, 1, 1, 0, 0, 0));
assert_eq!(
(
t.year(),
t.month(),
t.day(),
t.hour(),
t.minute(),
t.second()
),
(1980, 1, 1, 0, 0, 0)
);
let mut buf1 = [0; 5];
let mut buf2 = [0; 5];

View file

@ -1,102 +1,36 @@
//! Error types that can be emitted from this library
use std::convert;
use std::error;
use std::fmt;
use std::io;
use thiserror::Error;
/// Generic result type with ZipError as its error variant
pub type ZipResult<T> = Result<T, ZipError>;
/// Error type for Zip
#[derive(Debug)]
pub enum ZipError
{
#[derive(Debug, Error)]
pub enum ZipError {
/// An Error caused by I/O
Io(io::Error),
#[error(transparent)]
Io(#[from] io::Error),
/// This file is probably not a zip archive
#[error("invalid Zip archive")]
InvalidArchive(&'static str),
/// This archive is not supported
#[error("unsupported Zip archive")]
UnsupportedArchive(&'static str),
/// The requested file could not be found in the archive
#[error("specified file not found in archive")]
FileNotFound,
/// No password was given but the data is encrypted
#[error("missing password, file in archive is encrypted")]
PasswordRequired,
/// The given password is wrong
#[error("invalid password for file in archive")]
InvalidPassword,
}
impl ZipError
{
fn detail(&self) -> ::std::borrow::Cow<'_, str>
{
use std::error::Error;
match *self
{
ZipError::Io(ref io_err) => {
("Io Error: ".to_string() + (io_err as &dyn error::Error).description()).into()
},
ZipError::InvalidArchive(msg) | ZipError::UnsupportedArchive(msg) => {
(self.description().to_string() + ": " + msg).into()
},
ZipError::FileNotFound | ZipError::PasswordRequired | ZipError::InvalidPassword => {
self.description().into()
},
}
}
}
impl convert::From<io::Error> for ZipError
{
fn from(err: io::Error) -> ZipError
{
ZipError::Io(err)
}
}
impl convert::From<ZipError> for io::Error
{
fn from(err: ZipError) -> io::Error
{
io::Error::new(io::ErrorKind::Other, err)
}
}
impl fmt::Display for ZipError
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error>
{
fmt.write_str(&*self.detail())
}
}
impl error::Error for ZipError
{
fn description(&self) -> &str
{
match *self
{
ZipError::Io(ref io_err) => (io_err as &dyn error::Error).description(),
ZipError::InvalidArchive(..) => "Invalid Zip archive",
ZipError::UnsupportedArchive(..) => "Unsupported Zip archive",
ZipError::FileNotFound => "Specified file not found in archive",
ZipError::PasswordRequired => "Missing password, file in archive is encrypted",
ZipError::InvalidPassword => "Invalid password for file in archive",
}
}
fn cause(&self) -> Option<&dyn error::Error>
{
match *self
{
ZipError::Io(ref io_err) => Some(io_err as &dyn error::Error),
_ => None,
}
}
}

View file

@ -1,7 +1,7 @@
use crate::result::{ZipError, ZipResult};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io;
use std::io::prelude::*;
use crate::result::{ZipResult, ZipError};
use podio::{ReadPodExt, WritePodExt, LittleEndian};
pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
@ -9,8 +9,7 @@ const CENTRAL_DIRECTORY_END_SIGNATURE : u32 = 0x06054b50;
pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50;
const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50;
pub struct CentralDirectoryEnd
{
pub struct CentralDirectoryEnd {
pub disk_number: u16,
pub disk_with_central_directory: u16,
pub number_of_files_on_this_disk: u16,
@ -20,14 +19,11 @@ pub struct CentralDirectoryEnd
pub zip_file_comment: Vec<u8>,
}
impl CentralDirectoryEnd
{
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd>
{
impl CentralDirectoryEnd {
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> {
let magic = reader.read_u32::<LittleEndian>()?;
if magic != CENTRAL_DIRECTORY_END_SIGNATURE
{
return Err(ZipError::InvalidArchive("Invalid digital signature header"))
if magic != CENTRAL_DIRECTORY_END_SIGNATURE {
return Err(ZipError::InvalidArchive("Invalid digital signature header"));
}
let disk_number = reader.read_u16::<LittleEndian>()?;
let disk_with_central_directory = reader.read_u16::<LittleEndian>()?;
@ -36,42 +32,42 @@ impl CentralDirectoryEnd
let central_directory_size = reader.read_u32::<LittleEndian>()?;
let central_directory_offset = reader.read_u32::<LittleEndian>()?;
let zip_file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
let zip_file_comment = ReadPodExt::read_exact(reader, zip_file_comment_length)?;
let mut zip_file_comment = vec![0; zip_file_comment_length];
reader.read_exact(&mut zip_file_comment)?;
Ok(CentralDirectoryEnd
{
disk_number: disk_number,
disk_with_central_directory: disk_with_central_directory,
number_of_files_on_this_disk: number_of_files_on_this_disk,
number_of_files: number_of_files,
central_directory_size: central_directory_size,
central_directory_offset: central_directory_offset,
zip_file_comment: zip_file_comment,
Ok(CentralDirectoryEnd {
disk_number,
disk_with_central_directory,
number_of_files_on_this_disk,
number_of_files,
central_directory_size,
central_directory_offset,
zip_file_comment,
})
}
pub fn find_and_parse<T: Read+io::Seek>(reader: &mut T) -> ZipResult<(CentralDirectoryEnd, u64)>
{
pub fn find_and_parse<T: Read + io::Seek>(
reader: &mut T,
) -> ZipResult<(CentralDirectoryEnd, u64)> {
const HEADER_SIZE: u64 = 22;
const BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE: u64 = HEADER_SIZE - 6;
let file_length = reader.seek(io::SeekFrom::End(0))?;
let search_upper_bound = file_length.checked_sub(HEADER_SIZE + ::std::u16::MAX as u64).unwrap_or(0);
let search_upper_bound = file_length.saturating_sub(HEADER_SIZE + ::std::u16::MAX as u64);
if file_length < HEADER_SIZE {
return Err(ZipError::InvalidArchive("Invalid zip header"));
}
let mut pos = file_length - HEADER_SIZE;
while pos >= search_upper_bound
{
while pos >= search_upper_bound {
reader.seek(io::SeekFrom::Start(pos as u64))?;
if reader.read_u32::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE
{
reader.seek(io::SeekFrom::Current(BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64))?;
if reader.read_u32::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE {
reader.seek(io::SeekFrom::Current(
BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64,
))?;
let comment_length = reader.read_u16::<LittleEndian>()? as u64;
if file_length - pos - HEADER_SIZE == comment_length
{
if file_length - pos - HEADER_SIZE == comment_length {
let cde_start_pos = reader.seek(io::SeekFrom::Start(pos as u64))?;
return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos));
}
@ -81,11 +77,12 @@ impl CentralDirectoryEnd
None => break,
};
}
Err(ZipError::InvalidArchive("Could not find central directory end"))
Err(ZipError::InvalidArchive(
"Could not find central directory end",
))
}
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()>
{
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_END_SIGNATURE)?;
writer.write_u16::<LittleEndian>(self.disk_number)?;
writer.write_u16::<LittleEndian>(self.disk_with_central_directory)?;
@ -99,37 +96,33 @@ impl CentralDirectoryEnd
}
}
pub struct Zip64CentralDirectoryEndLocator
{
pub struct Zip64CentralDirectoryEndLocator {
pub disk_with_central_directory: u32,
pub end_of_central_directory_offset: u64,
pub number_of_disks: u32,
}
impl Zip64CentralDirectoryEndLocator
{
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator>
{
impl Zip64CentralDirectoryEndLocator {
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> {
let magic = reader.read_u32::<LittleEndian>()?;
if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE
{
return Err(ZipError::InvalidArchive("Invalid zip64 locator digital signature header"))
if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE {
return Err(ZipError::InvalidArchive(
"Invalid zip64 locator digital signature header",
));
}
let disk_with_central_directory = reader.read_u32::<LittleEndian>()?;
let end_of_central_directory_offset = reader.read_u64::<LittleEndian>()?;
let number_of_disks = reader.read_u32::<LittleEndian>()?;
Ok(Zip64CentralDirectoryEndLocator
{
disk_with_central_directory: disk_with_central_directory,
end_of_central_directory_offset: end_of_central_directory_offset,
number_of_disks: number_of_disks,
Ok(Zip64CentralDirectoryEndLocator {
disk_with_central_directory,
end_of_central_directory_offset,
number_of_disks,
})
}
}
pub struct Zip64CentralDirectoryEnd
{
pub struct Zip64CentralDirectoryEnd {
pub version_made_by: u16,
pub version_needed_to_extract: u16,
pub disk_number: u32,
@ -141,20 +134,18 @@ pub struct Zip64CentralDirectoryEnd
//pub extensible_data_sector: Vec<u8>, <-- We don't do anything with this at the moment.
}
impl Zip64CentralDirectoryEnd
{
pub fn find_and_parse<T: Read+io::Seek>(reader: &mut T,
impl Zip64CentralDirectoryEnd {
pub fn find_and_parse<T: Read + io::Seek>(
reader: &mut T,
nominal_offset: u64,
search_upper_bound: u64) -> ZipResult<(Zip64CentralDirectoryEnd, u64)>
{
search_upper_bound: u64,
) -> ZipResult<(Zip64CentralDirectoryEnd, u64)> {
let mut pos = nominal_offset;
while pos <= search_upper_bound
{
while pos <= search_upper_bound {
reader.seek(io::SeekFrom::Start(pos))?;
if reader.read_u32::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE
{
if reader.read_u32::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE {
let archive_offset = pos - nominal_offset;
let _record_size = reader.read_u64::<LittleEndian>()?;
@ -169,22 +160,26 @@ impl Zip64CentralDirectoryEnd
let central_directory_size = reader.read_u64::<LittleEndian>()?;
let central_directory_offset = reader.read_u64::<LittleEndian>()?;
return Ok((Zip64CentralDirectoryEnd
{
version_made_by: version_made_by,
version_needed_to_extract: version_needed_to_extract,
disk_number: disk_number,
disk_with_central_directory: disk_with_central_directory,
number_of_files_on_this_disk: number_of_files_on_this_disk,
number_of_files: number_of_files,
central_directory_size: central_directory_size,
central_directory_offset: central_directory_offset,
}, archive_offset));
return Ok((
Zip64CentralDirectoryEnd {
version_made_by,
version_needed_to_extract,
disk_number,
disk_with_central_directory,
number_of_files_on_this_disk,
number_of_files,
central_directory_size,
central_directory_offset,
},
archive_offset,
));
}
pos += 1;
}
Err(ZipError::InvalidArchive("Could not find ZIP64 central directory end"))
Err(ZipError::InvalidArchive(
"Could not find ZIP64 central directory end",
))
}
}

View file

@ -1,18 +1,15 @@
//! Types that specify what is contained in a ZIP.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum System
{
pub enum System {
Dos = 0,
Unix = 3,
Unknown,
#[doc(hidden)]
__Nonexhaustive,
}
impl System {
pub fn from_u8(system: u8) -> System
{
pub fn from_u8(system: u8) -> System {
use self::System::*;
match system {
@ -83,24 +80,33 @@ impl DateTime {
/// * hour: [0, 23]
/// * minute: [0, 59]
/// * second: [0, 60]
pub fn from_date_and_time(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Result<DateTime, ()> {
if year >= 1980 && year <= 2107
&& month >= 1 && month <= 12
&& day >= 1 && day <= 31
pub fn from_date_and_time(
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> Result<DateTime, ()> {
if year >= 1980
&& year <= 2107
&& month >= 1
&& month <= 12
&& day >= 1
&& day <= 31
&& hour <= 23
&& minute <= 59
&& second <= 60
{
Ok(DateTime {
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
year,
month,
day,
hour,
minute,
second,
})
}
else {
} else {
Err(())
}
}
@ -110,12 +116,18 @@ impl DateTime {
///
/// Returns `Err` when this object is out of bounds
pub fn from_time(tm: ::time::Tm) -> Result<DateTime, ()> {
if tm.tm_year >= 80 && tm.tm_year <= 207
&& tm.tm_mon >= 0 && tm.tm_mon <= 11
&& tm.tm_mday >= 1 && tm.tm_mday <= 31
&& tm.tm_hour >= 0 && tm.tm_hour <= 23
&& tm.tm_min >= 0 && tm.tm_min <= 59
&& tm.tm_sec >= 0 && tm.tm_sec <= 60
if tm.tm_year >= 80
&& tm.tm_year <= 207
&& tm.tm_mon >= 0
&& tm.tm_mon <= 11
&& tm.tm_mday >= 1
&& tm.tm_mday <= 31
&& tm.tm_hour >= 0
&& tm.tm_hour <= 23
&& tm.tm_min >= 0
&& tm.tm_min <= 59
&& tm.tm_sec >= 0
&& tm.tm_sec <= 60
{
Ok(DateTime {
year: (tm.tm_year + 1900) as u16,
@ -125,8 +137,7 @@ impl DateTime {
minute: tm.tm_min as u8,
second: tm.tm_sec as u8,
})
}
else {
} else {
Err(())
}
}
@ -193,8 +204,7 @@ pub const DEFAULT_VERSION: u8 = 46;
/// Structure representing a ZIP file.
#[derive(Debug, Clone)]
pub struct ZipFileData
{
pub struct ZipFileData {
/// Compatibility of the file attribute information
pub system: System,
/// Specification version
@ -230,7 +240,8 @@ impl ZipFileData {
let no_null_filename = match self.file_name.find('\0') {
Some(index) => &self.file_name[0..index],
None => &self.file_name,
}.to_string();
}
.to_string();
// zip files can contain both / and \ as separators regardless of the OS
// and as we want to return a sanitized PathBuf that only supports the
@ -238,7 +249,7 @@ impl ZipFileData {
let separator = ::std::path::MAIN_SEPARATOR;
let opposite_separator = match separator {
'/' => '\\',
'\\' | _ => '/',
_ => '/',
};
let filename =
no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
@ -295,7 +306,10 @@ mod test {
data_start: 0,
external_attributes: 0,
};
assert_eq!(data.file_name_sanitized(), ::std::path::PathBuf::from("path/etc/passwd"));
assert_eq!(
data.file_name_sanitized(),
::std::path::PathBuf::from("path/etc/passwd")
);
}
#[test]
@ -399,7 +413,10 @@ mod test {
assert_eq!(dt.second(), 30);
#[cfg(feature = "time")]
assert_eq!(format!("{}", dt.to_time().rfc3339()), "2018-11-17T10:38:30Z");
assert_eq!(
format!("{}", dt.to_time().rfc3339()),
"2018-11-17T10:38:30Z"
);
}
#[test]
@ -414,7 +431,10 @@ mod test {
assert_eq!(dt.second(), 62);
#[cfg(feature = "time")]
assert_eq!(format!("{}", dt.to_time().rfc3339()), "2107-15-31T31:63:62Z");
assert_eq!(
format!("{}", dt.to_time().rfc3339()),
"2107-15-31T31:63:62Z"
);
let dt = DateTime::from_msdos(0x0000, 0x0000);
assert_eq!(dt.year(), 1980);
@ -425,7 +445,10 @@ mod test {
assert_eq!(dt.second(), 0);
#[cfg(feature = "time")]
assert_eq!(format!("{}", dt.to_time().rfc3339()), "1980-00-00T00:00:00Z");
assert_eq!(
format!("{}", dt.to_time().rfc3339()),
"1980-00-00T00:00:00Z"
);
}
#[cfg(feature = "time")]

View file

@ -1,15 +1,15 @@
//! Structs for creating a new zip archive
use crate::compression::CompressionMethod;
use crate::types::{ZipFileData, System, DEFAULT_VERSION, DateTime};
use crate::result::{ZipError, ZipResult};
use crate::spec;
use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION};
use byteorder::{LittleEndian, WriteBytesExt};
use crc32fast::Hasher;
use crate::result::{ZipResult, ZipError};
use std::default::Default;
use std::io;
use std::io::prelude::*;
use std::mem;
use podio::{WritePodExt, LittleEndian};
#[cfg(feature = "deflate")]
use flate2::write::DeflateEncoder;
@ -17,8 +17,7 @@ use flate2::write::DeflateEncoder;
#[cfg(feature = "bzip2")]
use bzip2::write::BzEncoder;
enum GenericZipWriter<W: Write + io::Seek>
{
enum GenericZipWriter<W: Write + io::Seek> {
Closed,
Storer(W),
#[cfg(feature = "deflate")]
@ -51,8 +50,7 @@ enum GenericZipWriter<W: Write + io::Seek>
///
/// println!("Result: {:?}", doit().unwrap());
/// ```
pub struct ZipWriter<W: Write + io::Seek>
{
pub struct ZipWriter<W: Write + io::Seek> {
inner: GenericZipWriter<W>,
files: Vec<ZipFileData>,
stats: ZipWriterStats,
@ -61,8 +59,7 @@ pub struct ZipWriter<W: Write + io::Seek>
}
#[derive(Default)]
struct ZipWriterStats
{
struct ZipWriterStats {
hasher: Hasher,
start: u64,
bytes_written: u64,
@ -80,10 +77,14 @@ impl FileOptions {
/// Construct a new FileOptions object
pub fn default() -> FileOptions {
FileOptions {
#[cfg(feature = "deflate")] compression_method: CompressionMethod::Deflated,
#[cfg(not(feature = "deflate"))] compression_method: CompressionMethod::Stored,
#[cfg(feature = "time")] last_modified_time: DateTime::from_time(time::now()).unwrap_or(DateTime::default()),
#[cfg(not(feature = "time"))] last_modified_time: DateTime::default(),
#[cfg(feature = "deflate")]
compression_method: CompressionMethod::Deflated,
#[cfg(not(feature = "deflate"))]
compression_method: CompressionMethod::Stored,
#[cfg(feature = "time")]
last_modified_time: DateTime::from_time(time::now()).unwrap_or_default(),
#[cfg(not(feature = "time"))]
last_modified_time: DateTime::default(),
permissions: None,
}
}
@ -124,53 +125,53 @@ impl Default for FileOptions {
}
}
impl<W: Write+io::Seek> Write for ZipWriter<W>
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize>
{
if !self.writing_to_file { return Err(io::Error::new(io::ErrorKind::Other, "No file has been started")) }
match self.inner.ref_mut()
{
impl<W: Write + io::Seek> Write for ZipWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if !self.writing_to_file {
return Err(io::Error::new(
io::ErrorKind::Other,
"No file has been started",
));
}
match self.inner.ref_mut() {
Some(ref mut w) => {
let write_result = w.write(buf);
if let Ok(count) = write_result {
self.stats.update(&buf[0..count]);
}
write_result
}
None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed")),
None => Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"ZipWriter was already closed",
)),
}
}
fn flush(&mut self) -> io::Result<()>
{
match self.inner.ref_mut()
{
fn flush(&mut self) -> io::Result<()> {
match self.inner.ref_mut() {
Some(ref mut w) => w.flush(),
None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed")),
None => Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"ZipWriter was already closed",
)),
}
}
}
impl ZipWriterStats
{
fn update(&mut self, buf: &[u8])
{
impl ZipWriterStats {
fn update(&mut self, buf: &[u8]) {
self.hasher.update(buf);
self.bytes_written += buf.len() as u64;
}
}
impl<W: Write+io::Seek> ZipWriter<W>
{
impl<W: Write + io::Seek> ZipWriter<W> {
/// Initializes the ZipWriter.
///
/// Before writing to this object, the start_file command should be called.
pub fn new(inner: W) -> ZipWriter<W>
{
ZipWriter
{
pub fn new(inner: W) -> ZipWriter<W> {
ZipWriter {
inner: GenericZipWriter::Storer(inner),
files: Vec::new(),
stats: Default::default(),
@ -180,13 +181,17 @@ impl<W: Write+io::Seek> ZipWriter<W>
}
/// Set ZIP archive comment. Defaults to 'zip-rs' if not set.
pub fn set_comment<S>(&mut self, comment: S) where S: Into<String> {
pub fn set_comment<S>(&mut self, comment: S)
where
S: Into<String>,
{
self.comment = comment.into();
}
/// Start a new file for with the requested options.
fn start_entry<S>(&mut self, name: S, options: FileOptions) -> ZipResult<()>
where S: Into<String>
where
S: Into<String>,
{
self.finish_file()?;
@ -197,8 +202,7 @@ impl<W: Write+io::Seek> ZipWriter<W>
let permissions = options.permissions.unwrap_or(0o100644);
let file_name = name.into();
let file_name_raw = file_name.clone().into_bytes();
let mut file = ZipFileData
{
let mut file = ZipFileData {
system: System::Unix,
version_made_by: DEFAULT_VERSION,
encrypted: false,
@ -207,10 +211,10 @@ impl<W: Write+io::Seek> ZipWriter<W>
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
file_name: file_name,
file_name_raw: file_name_raw,
file_name,
file_name_raw,
file_comment: String::new(),
header_start: header_start,
header_start,
data_start: 0,
external_attributes: permissions << 16,
};
@ -231,13 +235,11 @@ impl<W: Write+io::Seek> ZipWriter<W>
Ok(())
}
fn finish_file(&mut self) -> ZipResult<()>
{
fn finish_file(&mut self) -> ZipResult<()> {
self.inner.switch_to(CompressionMethod::Stored)?;
let writer = self.inner.get_plain();
let file = match self.files.last_mut()
{
let file = match self.files.last_mut() {
None => return Ok(()),
Some(f) => f,
};
@ -256,7 +258,8 @@ impl<W: Write+io::Seek> ZipWriter<W>
/// Starts a file.
pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
where S: Into<String>
where
S: Into<String>,
{
if options.permissions.is_none() {
options.permissions = Some(0o644);
@ -271,7 +274,11 @@ impl<W: Write+io::Seek> ZipWriter<W>
///
/// This function ensures that the '/' path seperator is used. It also ignores all non 'Normal'
/// Components, such as a starting '/' or '..' and '.'.
pub fn start_file_from_path(&mut self, path: &std::path::Path, options: FileOptions) -> ZipResult<()> {
pub fn start_file_from_path(
&mut self,
path: &std::path::Path,
options: FileOptions,
) -> ZipResult<()> {
self.start_file(path_to_string(path), options)
}
@ -279,7 +286,8 @@ impl<W: Write+io::Seek> ZipWriter<W>
///
/// You can't write data to the file afterwards.
pub fn add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
where S: Into<String>
where
S: Into<String>,
{
if options.permissions.is_none() {
options.permissions = Some(0o755);
@ -303,37 +311,37 @@ impl<W: Write+io::Seek> ZipWriter<W>
///
/// This function ensures that the '/' path seperator is used. It also ignores all non 'Normal'
/// Components, such as a starting '/' or '..' and '.'.
pub fn add_directory_from_path(&mut self, path: &std::path::Path, options: FileOptions) -> ZipResult<()> {
self.add_directory(path_to_string(path.into()), options)
pub fn add_directory_from_path(
&mut self,
path: &std::path::Path,
options: FileOptions,
) -> ZipResult<()> {
self.add_directory(path_to_string(path), options)
}
/// Finish the last file and write all other zip-structures
///
/// This will return the writer, but one should normally not append any data to the end of the file.
/// Note that the zipfile will also be finished on drop.
pub fn finish(&mut self) -> ZipResult<W>
{
pub fn finish(&mut self) -> ZipResult<W> {
self.finalize()?;
let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
Ok(inner.unwrap())
}
fn finalize(&mut self) -> ZipResult<()>
{
fn finalize(&mut self) -> ZipResult<()> {
self.finish_file()?;
{
let writer = self.inner.get_plain();
let central_start = writer.seek(io::SeekFrom::Current(0))?;
for file in self.files.iter()
{
for file in self.files.iter() {
write_central_directory_header(writer, file)?;
}
let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start;
let footer = spec::CentralDirectoryEnd
{
let footer = spec::CentralDirectoryEnd {
disk_number: 0,
disk_with_central_directory: 0,
number_of_files_on_this_disk: self.files.len() as u16,
@ -350,12 +358,9 @@ impl<W: Write+io::Seek> ZipWriter<W>
}
}
impl<W: Write+io::Seek> Drop for ZipWriter<W>
{
fn drop(&mut self)
{
if !self.inner.is_closed()
{
impl<W: Write + io::Seek> Drop for ZipWriter<W> {
fn drop(&mut self) {
if !self.inner.is_closed() {
if let Err(e) = self.finalize() {
let _ = write!(&mut io::stderr(), "ZipWriter drop failed: {:?}", e);
}
@ -363,34 +368,50 @@ impl<W: Write+io::Seek> Drop for ZipWriter<W>
}
}
impl<W: Write+io::Seek> GenericZipWriter<W>
{
fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()>
{
impl<W: Write + io::Seek> GenericZipWriter<W> {
fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()> {
match self.current_compression() {
Some(method) if method == compression => return Ok(()),
None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed"))?,
_ => {},
None => {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"ZipWriter was already closed",
)
.into())
}
_ => {}
}
let bare = match mem::replace(self, GenericZipWriter::Closed)
{
let bare = match mem::replace(self, GenericZipWriter::Closed) {
GenericZipWriter::Storer(w) => w,
#[cfg(feature = "deflate")]
GenericZipWriter::Deflater(w) => w.finish()?,
#[cfg(feature = "bzip2")]
GenericZipWriter::Bzip2(w) => w.finish()?,
GenericZipWriter::Closed => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed"))?,
GenericZipWriter::Closed => {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"ZipWriter was already closed",
)
.into())
}
};
*self = match compression
{
*self = match compression {
CompressionMethod::Stored => GenericZipWriter::Storer(bare),
#[cfg(feature = "deflate")]
CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(bare, flate2::Compression::default())),
CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(
bare,
flate2::Compression::default(),
)),
#[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::Default)),
CompressionMethod::Unsupported(..) => return Err(ZipError::UnsupportedArchive("Unsupported compression")),
CompressionMethod::Bzip2 => {
GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::Default))
}
#[allow(deprecated)]
CompressionMethod::Unsupported(..) => {
return Err(ZipError::UnsupportedArchive("Unsupported compression"))
}
};
Ok(())
@ -407,19 +428,15 @@ impl<W: Write+io::Seek> GenericZipWriter<W>
}
}
fn is_closed(&self) -> bool
{
match *self
{
fn is_closed(&self) -> bool {
match *self {
GenericZipWriter::Closed => true,
_ => false,
}
}
fn get_plain(&mut self) -> &mut W
{
match *self
{
fn get_plain(&mut self) -> &mut W {
match *self {
GenericZipWriter::Storer(ref mut w) => w,
_ => panic!("Should have switched to stored beforehand"),
}
@ -436,26 +453,28 @@ impl<W: Write+io::Seek> GenericZipWriter<W>
}
}
fn unwrap(self) -> W
{
match self
{
fn unwrap(self) -> W {
match self {
GenericZipWriter::Storer(w) => w,
_ => panic!("Should have switched to stored beforehand"),
}
}
}
fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
{
fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
// local file header signature
writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)?;
// version needed to extract
writer.write_u16::<LittleEndian>(file.version_needed())?;
// general purpose bit flag
let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
let flag = if !file.file_name.is_ascii() {
1u16 << 11
} else {
0
};
writer.write_u16::<LittleEndian>(flag)?;
// Compression method
#[allow(deprecated)]
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
// last mod file time and last mod file date
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
@ -479,8 +498,10 @@ fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipR
Ok(())
}
fn update_local_file_header<T: Write+io::Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
{
fn update_local_file_header<T: Write + io::Seek>(
writer: &mut T,
file: &ZipFileData,
) -> ZipResult<()> {
const CRC32_OFFSET: u64 = 14;
writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
writer.write_u32::<LittleEndian>(file.crc32)?;
@ -489,8 +510,7 @@ fn update_local_file_header<T: Write+io::Seek>(writer: &mut T, file: &ZipFileDat
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<()> {
// central file header signature
writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
// version made by
@ -499,9 +519,14 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
// version needed to extract
writer.write_u16::<LittleEndian>(file.version_needed())?;
// general puprose bit flag
let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
let flag = if !file.file_name.is_ascii() {
1u16 << 11
} else {
0
};
writer.write_u16::<LittleEndian>(flag)?;
// compression method
#[allow(deprecated)]
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
// last mod file time + date
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
@ -537,8 +562,7 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
Ok(())
}
fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>>
{
fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>> {
let writer = Vec::new();
// Future work
Ok(writer)
@ -547,26 +571,23 @@ fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>>
fn path_to_string(path: &std::path::Path) -> String {
let mut path_str = String::new();
for component in path.components() {
match component {
std::path::Component::Normal(os_str) => {
if path_str.len() != 0 {
if let std::path::Component::Normal(os_str) = component {
if !path_str.is_empty() {
path_str.push('/');
}
path_str.push_str(&*os_str.to_string_lossy());
}
_ => (),
}
}
path_str
}
#[cfg(test)]
mod test {
use std::io;
use std::io::Write;
use crate::types::DateTime;
use super::{FileOptions, ZipWriter};
use crate::compression::CompressionMethod;
use crate::types::DateTime;
use std::io;
use std::io::Write;
#[test]
fn write_empty_zip() {
@ -574,24 +595,38 @@ mod test {
writer.set_comment("ZIP");
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 25);
assert_eq!(*result.get_ref(), [80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 90, 73, 80]);
assert_eq!(
*result.get_ref(),
[80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 90, 73, 80]
);
}
#[test]
fn write_zip_dir() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer.add_directory("test", FileOptions::default().last_modified_time(
DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap()
)).unwrap();
assert!(writer.write(b"writing to a directory is not allowed, and will not write any data").is_err());
writer
.add_directory(
"test",
FileOptions::default().last_modified_time(
DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
),
)
.unwrap();
assert!(writer
.write(b"writing to a directory is not allowed, and will not write any data")
.is_err());
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 114);
assert_eq!(*result.get_ref(), &[
80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 116,
101, 115, 116, 47, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6,
0, 0, 0, 0, 1, 0, 1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 6, 0, 122, 105, 112, 45, 114, 115
] as &[u8]);
assert_eq!(
*result.get_ref(),
&[
80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0,
163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0,
1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 6, 0, 122, 105, 112, 45, 114, 115
] as &[u8]
);
}
#[test]
@ -603,7 +638,9 @@ mod test {
permissions: Some(33188),
};
writer.start_file("mimetype", options).unwrap();
writer.write(b"application/vnd.oasis.opendocument.text").unwrap();
writer
.write(b"application/vnd.oasis.opendocument.text")
.unwrap();
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 159);
let mut v = Vec::new();
@ -614,8 +651,10 @@ mod test {
#[test]
fn path_to_string() {
let mut path = std::path::PathBuf::new();
#[cfg(windows)] path.push(r"C:\");
#[cfg(unix)] path.push("/");
#[cfg(windows)]
path.push(r"C:\");
#[cfg(unix)]
path.push("/");
path.push("windows");
path.push("..");
path.push(".");

View file

@ -1,8 +1,8 @@
use std::collections::HashSet;
use std::io::prelude::*;
use zip::write::FileOptions;
use std::io::Cursor;
use std::iter::FromIterator;
use std::collections::HashSet;
use zip::write::FileOptions;
// This test asserts that after creating a zip file, then reading its contents back out,
// the extracted data will *always* be exactly the same as the original data.

View file

@ -1,26 +1,21 @@
use zip::read::ZipArchive;
use std::io::Cursor;
use zip::read::ZipArchive;
const BUF: &[u8] = &[
0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x00, 0x1c, 0x00, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69,
0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f,
0x55, 0x54, 0x09, 0x00, 0x03, 0xf4, 0x5c, 0x88, 0x5a, 0xf4, 0x5c, 0x88,
0x5a, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04,
0x0a, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x0a, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, // time part: 0 seconds, 0 minutes, 0 hours
0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x1c, 0x00, 0x69, 0x6e,
0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f,
0x55, 0x54, 0x09, 0x00, 0x03, 0xf4, 0x5c, 0x88, 0x5a, 0xf4, 0x5c, 0x88, 0x5a, 0x75, 0x78, 0x0b,
0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02,
0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, // time part: 0 seconds, 0 minutes, 0 hours
0x00, 0x00, // date part: day 0 (invalid), month 0 (invalid), year 0 (1980)
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xed, 0x41, 0x00, 0x00,
0x00, 0x00, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x55, 0x54, 0x05, 0x00,
0x03, 0xf4, 0x5c, 0x88, 0x5a, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8,
0x03, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x58, 0x00, 0x00, 0x00,
0x4c, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xed, 0x41, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6e,
0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f,
0x55, 0x54, 0x05, 0x00, 0x03, 0xf4, 0x5c, 0x88, 0x5a, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8,
0x03, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x01, 0x00, 0x58, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00,
];
#[test]

View file

@ -53,7 +53,7 @@
// 22c400260 00 00 50 4b 05 06 00 00 00 00 03 00 03 00 27 01 |..PK..........'.|
// 22c400270 00 00 ff ff ff ff 00 00 |........|
// 22c400278
use std::io::{self, Seek, SeekFrom, Read};
use std::io::{self, Read, Seek, SeekFrom};
const BLOCK1_LENGTH: u64 = 0x60;
const BLOCK1: [u8; BLOCK1_LENGTH as usize] = [
@ -139,20 +139,22 @@ impl Zip64File {
impl Seek for Zip64File {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match pos {
SeekFrom::Start(offset) => { self.pointer = offset; },
SeekFrom::Start(offset) => {
self.pointer = offset;
}
SeekFrom::End(offset) => {
if offset > 0 || offset < -(TOTAL_LENGTH as i64) {
return Err(io::Error::new(io::ErrorKind::Other, "Invalid seek offset"));
}
self.pointer = (TOTAL_LENGTH as i64 + offset) as u64;
},
}
SeekFrom::Current(offset) => {
let seekpos = self.pointer as i64 + offset;
if seekpos < 0 || seekpos as u64 > TOTAL_LENGTH {
return Err(io::Error::new(io::ErrorKind::Other, "Invalid seek offset"));
}
self.pointer = seekpos as u64;
},
}
}
Ok(self.pointer)
}
@ -166,19 +168,19 @@ impl Read for Zip64File {
match self.pointer {
BLOCK1_START..=BLOCK1_END => {
buf[0] = BLOCK1[(self.pointer - BLOCK1_START) as usize];
},
}
BLOCK2_START..=BLOCK2_END => {
buf[0] = BLOCK2[(self.pointer - BLOCK2_START) as usize];
},
}
BLOCK3_START..=BLOCK3_END => {
buf[0] = BLOCK3[(self.pointer - BLOCK3_START) as usize];
},
}
BLOCK4_START..=BLOCK4_END => {
buf[0] = BLOCK4[(self.pointer - BLOCK4_START) as usize];
},
}
_ => {
buf[0] = 0;
},
}
}
self.pointer += 1;
Ok(1)
@ -194,7 +196,12 @@ fn zip64_large() {
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let outpath = file.sanitized_name();
println!("Entry {} has name \"{}\" ({} bytes)", i, outpath.as_path().display(), file.size());
println!(
"Entry {} has name \"{}\" ({} bytes)",
i,
outpath.as_path().display(),
file.size()
);
match file.read_exact(&mut buf) {
Ok(()) => println!("The first {} bytes are: {:?}", buf.len(), buf),