commit
6fc6b9c284
11 changed files with 77 additions and 115 deletions
|
@ -18,6 +18,7 @@ fn real_main() -> i32 {
|
|||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i).unwrap();
|
||||
#[allow(deprecated)]
|
||||
let outpath = file.sanitized_name();
|
||||
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@ fn real_main() -> i32 {
|
|||
|
||||
for i in 0..archive.len() {
|
||||
let file = archive.by_index(i).unwrap();
|
||||
#[allow(deprecated)]
|
||||
let outpath = file.sanitized_name();
|
||||
|
||||
{
|
||||
|
|
|
@ -80,6 +80,7 @@ where
|
|||
// Some unzip tools unzip files with directory paths correctly, some do not!
|
||||
if path.is_file() {
|
||||
println!("adding file {:?} as {:?} ...", path, name);
|
||||
#[allow(deprecated)]
|
||||
zip.start_file_from_path(name, options)?;
|
||||
let mut f = File::open(path)?;
|
||||
|
||||
|
@ -90,6 +91,7 @@ where
|
|||
// Only if not root! Avoids path spec / warning
|
||||
// and mapname conversion failed error on unzip
|
||||
println!("adding dir {:?} as {:?} ...", path, name);
|
||||
#[allow(deprecated)]
|
||||
zip.add_directory_from_path(name, options)?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,19 +3,25 @@
|
|||
use std::fmt;
|
||||
|
||||
#[allow(deprecated)]
|
||||
/// Compression methods for the contents of a ZIP file.
|
||||
/// Identifies the storage format used to compress a file within a ZIP archive.
|
||||
///
|
||||
/// Each file's compression method is stored alongside it, allowing the
|
||||
/// contents to be read without context.
|
||||
///
|
||||
/// When creating ZIP files, you may choose the method to use with
|
||||
/// [`zip::write::FileOptions::compression_method`]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum CompressionMethod {
|
||||
/// The file is stored (no compression)
|
||||
/// Store the file as is
|
||||
Stored,
|
||||
/// Deflate using any flate2 backend
|
||||
/// Compress the file using Deflate
|
||||
#[cfg(any(
|
||||
feature = "deflate",
|
||||
feature = "deflate-miniz",
|
||||
feature = "deflate-zlib"
|
||||
))]
|
||||
Deflated,
|
||||
/// File is compressed using BZIP2 algorithm
|
||||
/// Compress the file using BZIP2
|
||||
#[cfg(feature = "bzip2")]
|
||||
Bzip2,
|
||||
/// Unsupported compression method
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
//! A basic ZipReader/Writer crate
|
||||
//! An ergonomic API for reading and writing ZIP files.
|
||||
//!
|
||||
//! The current implementation is based on [PKWARE's APPNOTE.TXT v6.3.9](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
|
||||
// TODO(#184): Decide on the crate's bias: Do we prioritise permissiveness/correctness/speed/ergonomics?
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
|
|
81
src/read.rs
81
src/read.rs
|
@ -1,4 +1,4 @@
|
|||
//! Structs for reading a ZIP archive
|
||||
//! Types for reading ZIP archives
|
||||
|
||||
use crate::compression::CompressionMethod;
|
||||
use crate::crc32::Crc32Reader;
|
||||
|
@ -8,9 +8,7 @@ use crate::zipcrypto::ZipCryptoReader;
|
|||
use crate::zipcrypto::ZipCryptoReaderValid;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::{self, prelude::*};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::cp437::FromCp437;
|
||||
use crate::types::{DateTime, System, ZipFileData};
|
||||
|
@ -31,25 +29,19 @@ mod ffi {
|
|||
pub const S_IFREG: u32 = 0o0100000;
|
||||
}
|
||||
|
||||
/// Wrapper for reading the contents of a ZIP file.
|
||||
/// ZIP archive reader
|
||||
///
|
||||
/// ```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.
|
||||
/// let buf: &[u8] = &[0u8; 128];
|
||||
/// let mut reader = std::io::Cursor::new(buf);
|
||||
///
|
||||
/// fn list_zip_contents(reader: impl Read + Seek) -> zip::result::ZipResult<()> {
|
||||
/// let mut zip = zip::ZipArchive::new(reader)?;
|
||||
///
|
||||
/// for i in 0..zip.len() {
|
||||
/// let mut file = zip.by_index(i).unwrap();
|
||||
/// let mut file = zip.by_index(i)?;
|
||||
/// println!("Filename: {}", file.name());
|
||||
/// let first_byte = file.bytes().next().unwrap()?;
|
||||
/// println!("{}", first_byte);
|
||||
/// std::io::copy(&mut file, &mut std::io::stdout());
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -279,7 +271,9 @@ impl<R: Read + io::Seek> ZipArchive<R> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Opens a Zip archive and parses the central directory
|
||||
/// Read a ZIP archive, collecting the files it contains
|
||||
///
|
||||
/// This uses the central directory record of the ZIP file, and ignores local file headers
|
||||
pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
|
||||
let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
|
||||
|
||||
|
@ -314,58 +308,7 @@ impl<R: Read + io::Seek> ZipArchive<R> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Extract a Zip archive into a directory.
|
||||
///
|
||||
/// Paths are sanitized so that they cannot escape the given directory.
|
||||
///
|
||||
/// This bails on the first error and does not attempt cleanup.
|
||||
///
|
||||
/// # Platform-specific behaviour
|
||||
///
|
||||
/// On unix systems permissions from the zip file are preserved, if they exist.
|
||||
pub fn extract<P: AsRef<Path>>(&mut self, directory: P) -> ZipResult<()> {
|
||||
for i in 0..self.len() {
|
||||
let mut file = self.by_index(i)?;
|
||||
let filepath = file.sanitized_name();
|
||||
|
||||
let outpath = directory.as_ref().join(filepath);
|
||||
|
||||
if (file.name()).ends_with('/') {
|
||||
fs::create_dir_all(&outpath)?;
|
||||
} else {
|
||||
if let Some(p) = outpath.parent() {
|
||||
if !p.exists() {
|
||||
fs::create_dir_all(&p)?;
|
||||
}
|
||||
}
|
||||
let mut outfile = fs::File::create(&outpath)?;
|
||||
io::copy(&mut file, &mut outfile)?;
|
||||
}
|
||||
|
||||
// Get and Set permissions
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
if let Some(mode) = file.unix_mode() {
|
||||
fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Number of files contained in this zip.
|
||||
///
|
||||
/// ```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 {
|
||||
self.files.len()
|
||||
}
|
||||
|
@ -617,6 +560,11 @@ impl<'a> ZipFile<'a> {
|
|||
|
||||
/// 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.
|
||||
#[deprecated(
|
||||
since = "0.5.7",
|
||||
note = "by stripping `..`s from the path, the meaning of paths can change.
|
||||
You must use a sanitization strategy that's appropriate for your input"
|
||||
)]
|
||||
pub fn sanitized_name(&self) -> ::std::path::PathBuf {
|
||||
self.data.file_name_sanitized()
|
||||
}
|
||||
|
@ -941,6 +889,7 @@ mod test {
|
|||
|
||||
for i in 0..zip.len() {
|
||||
let zip_file = zip.by_index(i).unwrap();
|
||||
#[allow(deprecated)]
|
||||
let full_name = zip_file.sanitized_name();
|
||||
let file_name = full_name.file_name().unwrap().to_str().unwrap();
|
||||
assert!(
|
||||
|
|
|
@ -26,6 +26,12 @@ impl System {
|
|||
/// When constructed manually from a date and time, it will also check if the input is sensible
|
||||
/// (e.g. months are from [1, 12]), but when read from a zip some parts may be out of their normal
|
||||
/// bounds (e.g. month 0, or hour 31).
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// Some utilities use alternative timestamps to improve the accuracy of their
|
||||
/// ZIPs, but we don't parse them yet. [We're working on this](https://github.com/mvdnes/zip-rs/issues/156#issuecomment-652981904),
|
||||
/// however this API shouldn't be considered complete.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DateTime {
|
||||
year: u16,
|
||||
|
|
58
src/write.rs
58
src/write.rs
|
@ -1,4 +1,4 @@
|
|||
//! Structs for creating a new zip archive
|
||||
//! Types for creating ZIP archives
|
||||
|
||||
use crate::compression::CompressionMethod;
|
||||
use crate::result::{ZipError, ZipResult};
|
||||
|
@ -34,29 +34,33 @@ enum GenericZipWriter<W: Write + io::Seek> {
|
|||
Bzip2(BzEncoder<W>),
|
||||
}
|
||||
|
||||
/// Generator for ZIP files.
|
||||
/// ZIP archive generator
|
||||
///
|
||||
/// Handles the bookkeeping involved in building an archive, and provides an
|
||||
/// API to edit its contents.
|
||||
///
|
||||
/// ```
|
||||
/// fn doit() -> zip::result::ZipResult<()>
|
||||
/// {
|
||||
/// use std::io::Write;
|
||||
/// # fn doit() -> zip::result::ZipResult<()>
|
||||
/// # {
|
||||
/// # use zip::ZipWriter;
|
||||
/// use std::io::Write;
|
||||
/// use zip::write::FileOptions;
|
||||
///
|
||||
/// // For this example we write to a buffer, but normally you should use a File
|
||||
/// let mut buf: &mut [u8] = &mut [0u8; 65536];
|
||||
/// let mut w = std::io::Cursor::new(buf);
|
||||
/// let mut zip = zip::ZipWriter::new(w);
|
||||
/// // We use a buffer here, though you'd normally use a `File`
|
||||
/// let mut buf = [0; 65536];
|
||||
/// let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf[..]));
|
||||
///
|
||||
/// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
/// zip.start_file("hello_world.txt", options)?;
|
||||
/// zip.write(b"Hello, World!")?;
|
||||
/// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
/// zip.start_file("hello_world.txt", options)?;
|
||||
/// zip.write(b"Hello, World!")?;
|
||||
///
|
||||
/// // Optionally finish the zip. (this is also done on drop)
|
||||
/// zip.finish()?;
|
||||
/// // Apply the changes you've made.
|
||||
/// // Dropping the `ZipWriter` will have the same effect, but may silently fail
|
||||
/// zip.finish()?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// println!("Result: {:?}", doit().unwrap());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # doit().unwrap();
|
||||
/// ```
|
||||
pub struct ZipWriter<W: Write + io::Seek> {
|
||||
inner: GenericZipWriter<W>,
|
||||
|
@ -183,9 +187,9 @@ impl ZipWriterStats {
|
|||
}
|
||||
|
||||
impl<W: Write + io::Seek> ZipWriter<W> {
|
||||
/// Initializes the ZipWriter.
|
||||
/// Initializes the archive.
|
||||
///
|
||||
/// Before writing to this object, the start_file command should be called.
|
||||
/// Before writing to this object, the [`ZipWriter::start_file`] function should be called.
|
||||
pub fn new(inner: W) -> ZipWriter<W> {
|
||||
ZipWriter {
|
||||
inner: GenericZipWriter::Storer(inner),
|
||||
|
@ -196,7 +200,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set ZIP archive comment. Defaults to 'zip-rs' if not set.
|
||||
/// Set ZIP archive comment.
|
||||
pub fn set_comment<S>(&mut self, comment: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
|
@ -272,7 +276,9 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Starts a file.
|
||||
/// Create a file in the archive and start writing its' contents.
|
||||
///
|
||||
/// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
|
||||
pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
|
||||
where
|
||||
S: Into<String>,
|
||||
|
@ -290,6 +296,10 @@ 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 '.'.
|
||||
#[deprecated(
|
||||
since = "0.5.7",
|
||||
note = "by stripping `..`s from the path, the meaning of paths can change. Use `start_file` instead."
|
||||
)]
|
||||
pub fn start_file_from_path(
|
||||
&mut self,
|
||||
path: &std::path::Path,
|
||||
|
@ -327,6 +337,10 @@ 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 '.'.
|
||||
#[deprecated(
|
||||
since = "0.5.7",
|
||||
note = "by stripping `..`s from the path, the meaning of paths can change. Use `add_directory` instead."
|
||||
)]
|
||||
pub fn add_directory_from_path(
|
||||
&mut self,
|
||||
path: &std::path::Path,
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
extern crate zip;
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use zip::ZipArchive;
|
||||
|
||||
// This tests extracting the contents of a zip file
|
||||
#[test]
|
||||
fn extract() {
|
||||
let mut v = Vec::new();
|
||||
v.extend_from_slice(include_bytes!("../tests/data/files_and_dirs.zip"));
|
||||
let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
|
||||
|
||||
archive
|
||||
.extract(&PathBuf::from("test_directory"))
|
||||
.expect("extract failed");
|
||||
|
||||
// Cleanup
|
||||
fs::remove_dir_all("test_directory").expect("failed to remove extracted files");
|
||||
}
|
|
@ -195,6 +195,7 @@ fn zip64_large() {
|
|||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i).unwrap();
|
||||
#[allow(deprecated)]
|
||||
let outpath = file.sanitized_name();
|
||||
println!(
|
||||
"Entry {} has name \"{}\" ({} bytes)",
|
||||
|
|
|
@ -70,6 +70,7 @@ fn encrypted_file() {
|
|||
{
|
||||
// Correct password, read contents
|
||||
let mut file = archive.by_index_decrypt(0, "test".as_bytes()).unwrap();
|
||||
#[allow(deprecated)]
|
||||
let file_name = file.sanitized_name();
|
||||
assert_eq!(file_name, std::path::PathBuf::from("test.txt"));
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue