Merge branch 'io_reform'

This commit is contained in:
Mathijs van de Nes 2015-03-01 11:57:36 +01:00
commit cd52df6a18
16 changed files with 579 additions and 459 deletions

View file

@ -1,7 +1,7 @@
[package]
name = "zip"
version = "0.0.12"
version = "0.1.0"
authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>"]
license = "MIT"
repository = "https://github.com/mvdnes/zip-rs.git"
@ -10,6 +10,6 @@ Library to support the reading and writing of zip files.
"""
[dependencies]
flate2 = "^0.1"
bzip2 = "^0.1"
flate2 = "^0.2"
bzip2 = "^0.2"
time = "*"

View file

@ -1,8 +1,9 @@
#![feature(old_path, old_io, env)]
#![feature(old_path, io, fs, env)]
extern crate zip;
use std::old_io;
use std::io;
use std::fs;
fn main()
{
@ -13,40 +14,41 @@ fn main()
return;
}
let fname = Path::new(&*args[1]);
let file = old_io::File::open(&fname).unwrap();
let file = fs::File::open(&fname).unwrap();
let zipcontainer = zip::ZipReader::new(file).unwrap();
let mut archive = zip::ZipArchive::new(file).unwrap();
for file in zipcontainer.files()
for i in 1..archive.len()
{
let outpath = sanitize_filename(&*file.file_name);
let mut file = archive.by_index(i).unwrap();
let outpath = sanitize_filename(file.name());
println!("{}", outpath.display());
let comment = &file.file_comment;
if comment.len() > 0 { println!(" File comment: {}", comment); }
{
let comment = file.comment();
if comment.len() > 0 { println!(" File comment: {}", comment); }
}
old_io::fs::mkdir_recursive(&outpath.dir_path(), old_io::USER_DIR).unwrap();
fs::create_dir_all(&outpath.dir_path()).unwrap();
if (&*file.file_name).ends_with("/") {
if (&*file.name()).ends_with("/") {
create_directory(outpath);
}
else {
write_file(&zipcontainer, file, outpath);
write_file(&mut file, outpath);
}
}
}
fn write_file(zipcontainer: &zip::ZipReader<old_io::File>, file: &zip::ZipFile, outpath: Path)
fn write_file(reader: &mut zip::read::ZipFileReader, outpath: Path)
{
let mut outfile = old_io::File::create(&outpath);
let mut reader = zipcontainer.read_file(file).unwrap();
old_io::util::copy(&mut reader, &mut outfile).unwrap();
old_io::fs::chmod(&outpath, old_io::USER_FILE).unwrap();
let mut outfile = fs::File::create(&outpath).unwrap();
io::copy(reader, &mut outfile).unwrap();
}
fn create_directory(outpath: Path)
{
old_io::fs::mkdir_recursive(&outpath, old_io::USER_DIR).unwrap();
fs::create_dir_all(&outpath).unwrap();
}
fn sanitize_filename(filename: &str) -> Path

View file

@ -1,4 +1,6 @@
#![feature(old_path, old_io, env)]
#![feature(old_path, io, fs, env)]
use std::io::prelude::*;
extern crate zip;
@ -11,17 +13,17 @@ fn main()
return;
}
let fname = Path::new(&*args[1]);
let file = std::old_io::File::open(&fname).unwrap();
let zipfile = std::fs::File::open(&fname).unwrap();
let zipcontainer = zip::ZipReader::new(file).unwrap();
let mut archive = zip::ZipArchive::new(zipfile).unwrap();
let file = match zipcontainer.get("test/lorem_ipsum.txt")
let mut file = match archive.by_name("test/lorem_ipsum.txt")
{
Some(file) => file,
None => { println!("File test/lorem_ipsum.txt not found"); return }
Ok(file) => file,
Err(..) => { println!("File test/lorem_ipsum.txt not found"); return }
};
let data = zipcontainer.read_file(file).unwrap().read_to_end().unwrap();
let contents = String::from_utf8(data).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
println!("{}", contents);
}

View file

@ -1,4 +1,6 @@
#![feature(old_io, old_path, env)]
#![feature(io, fs, old_path, env)]
use std::io::prelude::*;
extern crate zip;
@ -22,7 +24,7 @@ fn main()
fn doit(filename: &str) -> zip::result::ZipResult<()>
{
let path = Path::new(filename);
let file = std::old_io::File::create(&path).unwrap();
let file = std::fs::File::create(&path).unwrap();
let mut zip = zip::ZipWriter::new(file);

View file

@ -37,5 +37,5 @@ pub enum CompressionMethod
/// PPMd version I, Rev 1
PPMdI1 = 98,
/// Unknown (invalid) compression
Unknown = 100000,
Unknown = 10000,
}

View file

@ -1,6 +1,7 @@
//! Helper module to compute a CRC32 checksum
use std::old_io;
use std::io;
use std::io::prelude::*;
static CRC32_TABLE : [u32; 256] = [
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
@ -69,7 +70,7 @@ pub struct Crc32Reader<R>
check: u32,
}
impl<R: Reader> 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>
@ -88,19 +89,14 @@ impl<R: Reader> Crc32Reader<R>
}
}
impl<R: Reader> Reader for Crc32Reader<R>
impl<R: Read> Read for Crc32Reader<R>
{
fn read(&mut self, buf: &mut [u8]) -> old_io::IoResult<usize>
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", None)) },
Ok(n) => n,
Err(ref e) if e.kind == old_io::EndOfFile =>
{
return
if self.check_matches() { Err(e.clone()) }
else { Err(old_io::IoError { kind: old_io::OtherIoError, desc: "Invalid checksum", detail: None, }) }
},
Err(e) => return Err(e),
};
self.crc = update(self.crc, &buf[0..count]);

View file

@ -3,25 +3,22 @@
#![feature(unsafe_destructor)]
#![warn(missing_docs)]
#![feature(core, collections, old_io, std_misc)]
#![feature(core, old_io, io)]
extern crate time;
extern crate flate2;
extern crate bzip2;
pub use reader::ZipReader;
pub use writer::ZipWriter;
pub use read::ZipArchive;
pub use write::ZipWriter;
pub use compression::CompressionMethod;
pub use types::ZipFile;
mod util;
mod spec;
mod reader_spec;
mod writer_spec;
mod crc32;
mod reader;
mod types;
pub mod compression;
mod writer;
pub mod read;
mod compression;
pub mod write;
mod cp437;
pub mod result;

308
src/read.rs Normal file
View file

@ -0,0 +1,308 @@
//! Structs for reading a ZIP archive
use crc32::Crc32Reader;
use compression::CompressionMethod;
use spec;
use result::{ZipResult, ZipError};
use std::io;
use std::io::prelude::*;
use std::collections::HashMap;
use std::num::FromPrimitive;
use flate2;
use flate2::FlateReadExt;
use bzip2::reader::BzDecompressor;
use util;
use util::ReadIntExt;
use types::ZipFileData;
/// Wrapper for reading the contents of a ZIP file.
///
/// ```
/// fn doit() -> zip::result::ZipResult<()>
/// {
/// use std::io::prelude::*;
///
/// // 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);
///
/// let mut zip = try!(zip::ZipArchive::new(reader));
///
/// for i in 1..zip.len()
/// {
/// let mut file = zip.by_index(i).unwrap();
/// println!("Filename: {}", file.name());
/// let first_byte = try!(file.bytes().next().unwrap());
/// println!("{}", first_byte);
/// }
/// Ok(())
/// }
///
/// println!("Result: {:?}", doit());
/// ```
pub struct ZipArchive<R: Read + io::Seek>
{
reader: R,
files: Vec<ZipFileData>,
names_map: HashMap<String, usize>,
}
enum ZipFileReader<'a> {
Stored(Crc32Reader<io::Take<&'a mut Read>>),
Deflated(Crc32Reader<flate2::read::DeflateDecoder<io::Take<&'a mut Read>>>),
Bzip2(Crc32Reader<BzDecompressor<io::Take<&'a mut Read>>>),
}
/// A struct for reading a zip file
pub struct ZipFile<'a> {
data: &'a ZipFileData,
reader: ZipFileReader<'a>,
}
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>
{
Err(ZipError::UnsupportedArchive(detail))
}
impl<R: Read+io::Seek> ZipArchive<R>
{
/// Opens a Zip archive and parses the central directory
pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
let footer = try!(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") }
let directory_start = footer.central_directory_offset as u64;
let number_of_files = footer.number_of_files_on_this_disk as usize;
let mut files = Vec::with_capacity(number_of_files);
let mut names_map = HashMap::new();
try!(reader.seek(io::SeekFrom::Start(directory_start)));
for _ in (0 .. number_of_files)
{
let file = try!(central_header_to_zip_file(&mut reader));
names_map.insert(file.file_name.clone(), files.len());
files.push(file);
}
Ok(ZipArchive { reader: reader, files: files, names_map: names_map })
}
/// Number of files contained in this zip.
///
/// ```
/// fn iter() {
/// let mut zip = zip::ZipArchive::new(std::io::Cursor::new(vec![])).unwrap();
///
/// for i in 1..zip.len() {
/// let mut file = zip.by_index(i).unwrap();
/// // Do something with file i
/// }
/// }
/// ```
pub fn len(&self) -> usize
{
self.files.len()
}
/// Search for a file entry by name
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>>
{
let index = match self.names_map.get(name) {
Some(index) => *index,
None => { return Err(ZipError::FileNotFound); },
};
self.by_index(index)
}
/// Get a contained file by index
pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>>
{
if file_number >= self.files.len() { return Err(ZipError::FileNotFound); }
let ref data = self.files[file_number];
let pos = data.data_start as u64;
if data.encrypted
{
return unsupported_zip_error("Encrypted files are not supported")
}
try!(self.reader.seek(io::SeekFrom::Start(pos)));
let limit_reader = (self.reader.by_ref() as &mut Read).take(data.compressed_size as u64);
let reader = match data.compression_method
{
CompressionMethod::Stored =>
{
ZipFileReader::Stored(Crc32Reader::new(
limit_reader,
data.crc32))
},
CompressionMethod::Deflated =>
{
let deflate_reader = limit_reader.deflate_decode();
ZipFileReader::Deflated(Crc32Reader::new(
deflate_reader,
data.crc32))
},
CompressionMethod::Bzip2 =>
{
let bzip2_reader = BzDecompressor::new(limit_reader);
ZipFileReader::Bzip2(Crc32Reader::new(
bzip2_reader,
data.crc32))
},
_ => return unsupported_zip_error("Compression method not supported"),
};
Ok(ZipFile { reader: reader, data: data })
}
/// Unwrap and return the inner reader object
///
/// The position of the reader is undefined.
pub fn into_inner(self) -> R
{
self.reader
}
}
fn central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R) -> ZipResult<ZipFileData>
{
// Parse central header
let signature = try!(reader.read_le_u32());
if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE
{
return Err(ZipError::InvalidArchive("Invalid Central Directory header"))
}
try!(reader.read_le_u16());
try!(reader.read_le_u16());
let flags = try!(reader.read_le_u16());
let encrypted = flags & 1 == 1;
let is_utf8 = flags & (1 << 11) != 0;
let compression_method = try!(reader.read_le_u16());
let last_mod_time = try!(reader.read_le_u16());
let last_mod_date = try!(reader.read_le_u16());
let crc32 = try!(reader.read_le_u32());
let compressed_size = try!(reader.read_le_u32());
let uncompressed_size = try!(reader.read_le_u32());
let file_name_length = try!(reader.read_le_u16()) as usize;
let extra_field_length = try!(reader.read_le_u16()) as usize;
let file_comment_length = try!(reader.read_le_u16()) as usize;
try!(reader.read_le_u16());
try!(reader.read_le_u16());
try!(reader.read_le_u32());
let offset = try!(reader.read_le_u32()) as u64;
let file_name_raw = try!(reader.read_exact(file_name_length));
let extra_field = try!(reader.read_exact(extra_field_length));
let file_comment_raw = try!(reader.read_exact(file_comment_length));
let file_name = match is_utf8
{
true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
false => ::cp437::to_string(&*file_name_raw),
};
let file_comment = match is_utf8
{
true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
false => ::cp437::to_string(&*file_comment_raw),
};
// Remember end of central header
let return_position = try!(reader.seek(io::SeekFrom::Current(0)));
// Parse local header
try!(reader.seek(io::SeekFrom::Start(offset)));
let signature = try!(reader.read_le_u32());
if signature != spec::LOCAL_FILE_HEADER_SIGNATURE
{
return Err(ZipError::InvalidArchive("Invalid local file header"))
}
try!(reader.seek(io::SeekFrom::Current(22)));
let file_name_length = try!(reader.read_le_u16()) as u64;
let extra_field_length = try!(reader.read_le_u16()) as u64;
let magic_and_header = 4 + 22 + 2 + 2;
let data_start = offset as u64 + magic_and_header + file_name_length + extra_field_length;
// Construct the result
let mut result = ZipFileData
{
encrypted: encrypted,
compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(CompressionMethod::Unknown),
last_modified_time: util::msdos_datetime_to_tm(last_mod_time, last_mod_date),
crc32: crc32,
compressed_size: compressed_size as u64,
uncompressed_size: uncompressed_size as u64,
file_name: file_name,
file_comment: file_comment,
header_start: offset as u64,
data_start: data_start,
};
try!(parse_extra_field(&mut result, &*extra_field));
// Go back after the central header
try!(reader.seek(io::SeekFrom::Start(return_position)));
Ok(result)
}
fn parse_extra_field(_file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>
{
let mut reader = io::Cursor::new(data);
while (reader.position() as usize) < data.len()
{
let kind = try!(reader.read_le_u16());
let len = try!(reader.read_le_u16());
match kind
{
_ => try!(reader.seek(io::SeekFrom::Current(len as i64))),
};
}
Ok(())
}
/// Methods for retreiving information on zip files
impl<'a> ZipFile<'a> {
fn get_reader(&mut self) -> &mut Read {
match self.reader {
ZipFileReader::Stored(ref mut r) => r as &mut Read,
ZipFileReader::Deflated(ref mut r) => r as &mut Read,
ZipFileReader::Bzip2(ref mut r) => r as &mut Read,
}
}
/// Get the name of the file
pub fn name(&self) -> &str {
&*self.data.file_name
}
/// Get the comment of the file
pub fn comment(&self) -> &str {
&*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) -> ::time::Tm {
self.data.last_modified_time
}
}
impl<'a> Read for ZipFile<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.get_reader().read(buf)
}
}

View file

@ -1,155 +0,0 @@
use crc32::Crc32Reader;
use types::ZipFile;
use compression::CompressionMethod;
use spec;
use reader_spec;
use result::{ZipResult, ZipError};
use std::old_io;
use std::cell::{RefCell, BorrowState};
use std::collections::HashMap;
use flate2::FlateReader;
use bzip2::reader::BzDecompressor;
/// Wrapper for reading the contents of a ZIP file.
///
/// ```
/// fn doit() -> zip::result::ZipResult<()>
/// {
/// // For demonstration purposes we read from an empty buffer.
/// // Normally a File object would be used.
/// let buf = [0u8; 128];
/// let mut reader = std::old_io::BufReader::new(&buf);
///
/// let zip = try!(zip::ZipReader::new(reader));
///
/// for file in zip.files()
/// {
/// println!("Filename: {}", file.file_name);
/// let mut file_reader = try!(zip.read_file(file));
/// let first_byte = try!(file_reader.read_byte());
/// println!("{}", first_byte);
/// }
/// Ok(())
/// }
///
/// println!("Result: {:?}", doit());
/// ```
pub struct ZipReader<T>
{
inner: RefCell<T>,
files: Vec<ZipFile>,
names_map: HashMap<String, usize>,
}
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>
{
Err(ZipError::UnsupportedZipFile(detail))
}
impl<T: Reader+Seek> ZipReader<T>
{
/// Opens a ZIP file and parses the content headers.
pub fn new(mut reader: T) -> ZipResult<ZipReader<T>>
{
let footer = try!(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") }
let directory_start = footer.central_directory_offset as i64;
let number_of_files = footer.number_of_files_on_this_disk as usize;
let mut files = Vec::with_capacity(number_of_files);
let mut names_map = HashMap::new();
try!(reader.seek(directory_start, old_io::SeekSet));
for _ in (0 .. number_of_files)
{
let file = try!(reader_spec::central_header_to_zip_file(&mut reader));
names_map.insert(file.file_name.clone(), files.len());
files.push(file);
}
Ok(ZipReader { inner: RefCell::new(reader), files: files, names_map: names_map })
}
/// An iterator over the information of all contained files.
pub fn files(&self) -> ::std::slice::Iter<ZipFile>
{
(&*self.files).iter()
}
/// Search for a file entry by name
pub fn get(&self, name: &str) -> Option<&ZipFile>
{
self.names_map.get(name).map(|index| &self.files[*index])
}
/// Gets a reader for a contained zipfile.
///
/// May return `ReaderUnavailable` if there is another reader borrowed.
pub fn read_file<'a>(&'a self, file: &ZipFile) -> ZipResult<Box<Reader+'a>>
{
let mut inner_reader = match self.inner.borrow_state()
{
BorrowState::Unused => self.inner.borrow_mut(),
_ => return Err(ZipError::ReaderUnavailable),
};
let pos = file.data_start as i64;
if file.encrypted
{
return unsupported_zip_error("Encrypted files are not supported")
}
try!(inner_reader.seek(pos, old_io::SeekSet));
let refmut_reader = ::util::RefMutReader::new(inner_reader);
let limit_reader = old_io::util::LimitReader::new(refmut_reader, file.compressed_size as usize);
let reader = match file.compression_method
{
CompressionMethod::Stored =>
{
Box::new(
Crc32Reader::new(
limit_reader,
file.crc32))
as Box<Reader>
},
CompressionMethod::Deflated =>
{
let deflate_reader = limit_reader.deflate_decode();
Box::new(
Crc32Reader::new(
deflate_reader,
file.crc32))
as Box<Reader>
},
CompressionMethod::Bzip2 =>
{
let bzip2_reader = BzDecompressor::new(limit_reader);
Box::new(
Crc32Reader::new(
bzip2_reader,
file.crc32))
as Box<Reader>
},
_ => return unsupported_zip_error("Compression method not supported"),
};
Ok(reader)
}
/// Unwrap and return the inner reader object
///
/// The position of the reader is undefined.
pub fn into_inner(self) -> T
{
self.inner.into_inner()
}
/// Deprecated method equal to `into_inner()`
#[deprecated="renamed to into_inner()"]
pub fn unwrap(self) -> T
{
self.into_inner()
}
}

View file

@ -1,104 +0,0 @@
use std::old_io;
use std::num::FromPrimitive;
use result::{ZipResult, ZipError};
use types::ZipFile;
use compression::CompressionMethod;
use spec;
use util;
pub fn central_header_to_zip_file<R: Reader+Seek>(reader: &mut R) -> ZipResult<ZipFile>
{
// Parse central header
let signature = try!(reader.read_le_u32());
if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE
{
return Err(ZipError::InvalidZipFile("Invalid Central Directory header"))
}
try!(reader.read_le_u16());
try!(reader.read_le_u16());
let flags = try!(reader.read_le_u16());
let encrypted = flags & 1 == 1;
let is_utf8 = flags & (1 << 11) != 0;
let compression_method = try!(reader.read_le_u16());
let last_mod_time = try!(reader.read_le_u16());
let last_mod_date = try!(reader.read_le_u16());
let crc32 = try!(reader.read_le_u32());
let compressed_size = try!(reader.read_le_u32());
let uncompressed_size = try!(reader.read_le_u32());
let file_name_length = try!(reader.read_le_u16()) as usize;
let extra_field_length = try!(reader.read_le_u16()) as usize;
let file_comment_length = try!(reader.read_le_u16()) as usize;
try!(reader.read_le_u16());
try!(reader.read_le_u16());
try!(reader.read_le_u32());
let offset = try!(reader.read_le_u32()) as i64;
let file_name_raw = try!(reader.read_exact(file_name_length));
let extra_field = try!(reader.read_exact(extra_field_length));
let file_comment_raw = try!(reader.read_exact(file_comment_length));
let file_name = match is_utf8
{
true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
false => ::cp437::to_string(&*file_name_raw),
};
let file_comment = match is_utf8
{
true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
false => ::cp437::to_string(&*file_comment_raw),
};
// Remember end of central header
let return_position = try!(reader.tell()) as i64;
// Parse local header
try!(reader.seek(offset, old_io::SeekSet));
let signature = try!(reader.read_le_u32());
if signature != spec::LOCAL_FILE_HEADER_SIGNATURE
{
return Err(ZipError::InvalidZipFile("Invalid local file header"))
}
try!(reader.seek(22, old_io::SeekCur));
let file_name_length = try!(reader.read_le_u16()) as u64;
let extra_field_length = try!(reader.read_le_u16()) as u64;
let magic_and_header = 4 + 22 + 2 + 2;
let data_start = offset as u64 + magic_and_header + file_name_length + extra_field_length;
// Construct the result
let mut result = ZipFile
{
encrypted: encrypted,
compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(CompressionMethod::Unknown),
last_modified_time: util::msdos_datetime_to_tm(last_mod_time, last_mod_date),
crc32: crc32,
compressed_size: compressed_size as u64,
uncompressed_size: uncompressed_size as u64,
file_name: file_name,
file_comment: file_comment,
header_start: offset as u64,
data_start: data_start,
};
try!(parse_extra_field(&mut result, &*extra_field));
// Go back after the central header
try!(reader.seek(return_position, old_io::SeekSet));
Ok(result)
}
fn parse_extra_field(_file: &mut ZipFile, data: &[u8]) -> ZipResult<()>
{
let mut reader = old_io::BufReader::new(data);
while !reader.eof()
{
let kind = try!(reader.read_le_u16());
let len = try!(reader.read_le_u16());
match kind
{
_ => try!(reader.seek(len as i64, old_io::SeekCur)),
}
}
Ok(())
}

View file

@ -1,6 +1,6 @@
//! Error types that can be emitted from this library
use std::old_io::IoError;
use std::io;
use std::error;
use std::fmt;
@ -12,16 +12,16 @@ pub type ZipResult<T> = Result<T, ZipError>;
pub enum ZipError
{
/// An Error caused by I/O
Io(IoError),
Io(io::Error),
/// This file is probably not a zipfile. The argument is enclosed.
InvalidZipFile(&'static str),
/// This file is probably not a zip archive
InvalidArchive(&'static str),
/// This file is unsupported. The reason is enclosed.
UnsupportedZipFile(&'static str),
/// This archive is not supported
UnsupportedArchive(&'static str),
/// The ZipReader is not available.
ReaderUnavailable,
/// The requested file could not be found in the archive
FileNotFound,
}
impl ZipError
@ -36,19 +36,19 @@ impl ZipError
ZipError::Io(ref io_err) => {
("Io Error: ".to_string() + io_err.description()).into_cow()
},
ZipError::InvalidZipFile(msg) | ZipError::UnsupportedZipFile(msg) => {
ZipError::InvalidArchive(msg) | ZipError::UnsupportedArchive(msg) => {
(self.description().to_string() + ": " + msg).into_cow()
},
ZipError::ReaderUnavailable => {
ZipError::FileNotFound => {
self.description().into_cow()
},
}
}
}
impl error::FromError<IoError> for ZipError
impl error::FromError<io::Error> for ZipError
{
fn from_error(err: IoError) -> ZipError
fn from_error(err: io::Error) -> ZipError
{
ZipError::Io(err)
}
@ -69,9 +69,9 @@ impl error::Error for ZipError
match *self
{
ZipError::Io(ref io_err) => io_err.description(),
ZipError::InvalidZipFile(..) => "Invalid Zip File",
ZipError::UnsupportedZipFile(..) => "Unsupported Zip File",
ZipError::ReaderUnavailable => "No reader available",
ZipError::InvalidArchive(..) => "Invalid Zip archive",
ZipError::UnsupportedArchive(..) => "Unsupported Zip archive",
ZipError::FileNotFound => "Specified file not found in archive",
}
}

View file

@ -1,6 +1,8 @@
use std::old_io;
use std::io;
use std::io::prelude::*;
use result::{ZipResult, ZipError};
use std::iter::range_step_inclusive;
use util::{ReadIntExt, WriteIntExt};
pub static LOCAL_FILE_HEADER_SIGNATURE : u32 = 0x04034b50;
pub static CENTRAL_DIRECTORY_HEADER_SIGNATURE : u32 = 0x02014b50;
@ -19,12 +21,12 @@ pub struct CentralDirectoryEnd
impl CentralDirectoryEnd
{
pub fn parse<T: Reader>(reader: &mut T) -> ZipResult<CentralDirectoryEnd>
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd>
{
let magic = try!(reader.read_le_u32());
if magic != CENTRAL_DIRECTORY_END_SIGNATURE
{
return Err(ZipError::UnsupportedZipFile("Invalid digital signature header"))
return Err(ZipError::InvalidArchive("Invalid digital signature header"))
}
let disk_number = try!(reader.read_le_u16());
let disk_with_central_directory = try!(reader.read_le_u16());
@ -47,32 +49,31 @@ impl CentralDirectoryEnd
})
}
pub fn find_and_parse<T: Reader+Seek>(reader: &mut T) -> ZipResult<CentralDirectoryEnd>
pub fn find_and_parse<T: Read+io::Seek>(reader: &mut T) -> ZipResult<CentralDirectoryEnd>
{
let header_size = 22;
let bytes_between_magic_and_comment_size = header_size - 6;
try!(reader.seek(0, old_io::SeekEnd));
let file_length = try!(reader.tell()) as i64;
let file_length = try!(reader.seek(io::SeekFrom::End(0))) as i64;
let search_upper_bound = ::std::cmp::max(0, file_length - header_size - ::std::u16::MAX as i64);
for pos in range_step_inclusive(file_length - header_size, search_upper_bound, -1)
{
try!(reader.seek(pos, old_io::SeekSet));
try!(reader.seek(io::SeekFrom::Start(pos as u64)));
if try!(reader.read_le_u32()) == CENTRAL_DIRECTORY_END_SIGNATURE
{
try!(reader.seek(bytes_between_magic_and_comment_size, old_io::SeekCur));
try!(reader.seek(io::SeekFrom::Current(bytes_between_magic_and_comment_size)));
let comment_length = try!(reader.read_le_u16()) as i64;
if file_length - pos - header_size == comment_length
{
try!(reader.seek(pos, old_io::SeekSet));
try!(reader.seek(io::SeekFrom::Start(pos as u64)));
return CentralDirectoryEnd::parse(reader);
}
}
}
Err(ZipError::UnsupportedZipFile("Could not find central directory end"))
Err(ZipError::InvalidArchive("Could not find central directory end"))
}
pub fn write<T: Writer>(&self, writer: &mut T) -> ZipResult<()>
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()>
{
try!(writer.write_le_u32(CENTRAL_DIRECTORY_END_SIGNATURE));
try!(writer.write_le_u16(self.disk_number));

View file

@ -2,9 +2,8 @@
use time;
#[derive(Clone)]
/// Structure representing a ZIP file.
pub struct ZipFile
pub struct ZipFileData
{
/// True if the file is encrypted.
pub encrypted: bool,

View file

@ -1,6 +1,7 @@
use time;
use time::Tm;
use std::cell::RefMut;
use std::io;
use std::io::prelude::*;
pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
{
@ -40,23 +41,81 @@ pub fn tm_to_msdos_date(time: Tm) -> u16
(time.tm_mday | ((time.tm_mon + 1) << 5) | ((time.tm_year - 80) << 9)) as u16
}
pub struct RefMutReader<'a, R:'a>
{
inner: RefMut<'a, R>,
/// Additional integer write methods for a io::Read
pub trait WriteIntExt {
/// Write a u32 in little-endian mode
fn write_le_u32(&mut self, u32) -> io::Result<()>;
/// Write a u16 in little-endian mode
fn write_le_u16(&mut self, u16) -> io::Result<()>;
}
impl<'a, R: Reader> RefMutReader<'a, R>
{
pub fn new(inner: RefMut<'a, R>) -> RefMutReader<'a, R>
{
RefMutReader { inner: inner, }
impl<W: Write> WriteIntExt for W {
fn write_le_u32(&mut self, val: u32) -> io::Result<()> {
let mut buf = [0u8; 4];
let v = val;
buf[0] = ((v >> 0) & 0xFF) as u8;
buf[1] = ((v >> 8) & 0xFF) as u8;
buf[2] = ((v >> 16) & 0xFF) as u8;
buf[3] = ((v >> 24) & 0xFF) as u8;
self.write_all(&buf)
}
fn write_le_u16(&mut self, val: u16) -> io::Result<()> {
let mut buf = [0u8; 2];
let v = val;
buf[0] = ((v >> 0) & 0xFF) as u8;
buf[1] = ((v >> 8) & 0xFF) as u8;
self.write_all(&buf)
}
}
/// Additional integer write methods for a io::Read
pub trait ReadIntExt {
/// Read a u32 in little-endian mode
fn read_le_u32(&mut self) -> io::Result<u32>;
/// Read a u16 in little-endian mode
fn read_le_u16(&mut self) -> io::Result<u16>;
/// Read exactly n bytes
fn read_exact(&mut self, usize) -> io::Result<Vec<u8>>;
}
impl<'a, R: Reader> Reader for RefMutReader<'a, R>
{
fn read(&mut self, buf: &mut [u8]) -> ::std::old_io::IoResult<usize>
{
self.inner.read(buf)
fn fill_exact<R: Read>(reader: &mut R, buf: &mut [u8]) -> io::Result<()> {
let mut idx = 0;
while idx < buf.len() {
match reader.read(&mut buf[idx..]) {
Err(v) => return Err(v),
Ok(0) => return Err(io::Error::new(io::ErrorKind::ResourceUnavailable, "Could not fill the buffer", None)),
Ok(i) => idx += i,
}
}
Ok(())
}
impl<R: Read> ReadIntExt for R {
fn read_le_u32(&mut self) -> io::Result<u32> {
let mut buf = [0u8; 4];
try!(fill_exact(self, &mut buf));
Ok(
buf[0] as u32
| ((buf[1] as u32) << 8)
| ((buf[2] as u32) << 16)
| ((buf[3] as u32) << 24)
)
}
fn read_le_u16(&mut self) -> io::Result<u16> {
let mut buf = [0u8; 2];
try!(fill_exact(self, &mut buf));
Ok(
buf[0] as u16
| ((buf[1] as u16) << 8)
)
}
fn read_exact(&mut self, n: usize) -> io::Result<Vec<u8>> {
let mut res = vec![0u8; n];
try!(fill_exact(self, &mut res));
Ok(res)
}
}

View file

@ -1,20 +1,26 @@
//! Structs for creating a new zip archive
use compression::CompressionMethod;
use types::ZipFile;
use types::ZipFileData;
use spec;
use writer_spec;
use crc32;
use result::{ZipResult, ZipError};
use std::default::Default;
use std::old_io;
use std::io;
use std::io::prelude::*;
use std::mem;
use std::error::Error;
use std::ascii::AsciiExt;
use time;
use flate2;
use flate2::FlateWriter;
use flate2::writer::DeflateEncoder;
use flate2::FlateWriteExt;
use flate2::write::DeflateEncoder;
use bzip2;
use bzip2::writer::BzCompressor;
use util;
use util::WriteIntExt;
enum GenericZipWriter<W>
enum GenericZipWriter<W: Write + io::Seek>
{
Closed,
Storer(W),
@ -27,13 +33,15 @@ enum GenericZipWriter<W>
/// ```
/// fn doit() -> zip::result::ZipResult<()>
/// {
/// use std::io::Write;
///
/// // For this example we write to a buffer, but normally you should use a File
/// let mut buf = [0u8; 65536];
/// let w = std::old_io::BufWriter::new(&mut buf);
/// let mut buf: &mut [u8] = &mut [0u8; 65536];
/// let mut w = std::io::Cursor::new(buf);
/// let mut zip = zip::ZipWriter::new(w);
///
/// try!(zip.start_file("hello_world.txt", zip::CompressionMethod::Stored));
/// try!(zip.write_all(b"Hello, World!"));
/// try!(zip.write(b"Hello, World!"));
///
/// // Optionally finish the zip. (this is also done on drop)
/// try!(zip.finish());
@ -43,10 +51,10 @@ enum GenericZipWriter<W>
///
/// println!("Result: {:?}", doit());
/// ```
pub struct ZipWriter<W>
pub struct ZipWriter<W: Write + io::Seek>
{
inner: GenericZipWriter<W>,
files: Vec<ZipFile>,
files: Vec<ZipFileData>,
stats: ZipWriterStats,
}
@ -58,18 +66,29 @@ struct ZipWriterStats
bytes_written: u64,
}
impl<W: Writer+Seek> Writer for ZipWriter<W>
impl<W: Write+io::Seek> Write for ZipWriter<W>
{
fn write_all(&mut self, buf: &[u8]) -> old_io::IoResult<()>
fn write(&mut self, buf: &[u8]) -> io::Result<usize>
{
if self.files.len() == 0 { return Err(old_io::IoError { kind: old_io::OtherIoError, desc: "No file has been started", detail: None, }) }
if self.files.len() == 0 { return Err(io::Error::new(io::ErrorKind::Other, "No file has been started", None)) }
self.stats.update(buf);
match self.inner
{
GenericZipWriter::Storer(ref mut w) => w.write_all(buf),
GenericZipWriter::Deflater(ref mut w) => w.write_all(buf),
GenericZipWriter::Bzip2(ref mut w) => w.write_all(buf),
GenericZipWriter::Closed => Err(old_io::standard_error(old_io::Closed)),
GenericZipWriter::Storer(ref mut w) => w.write(buf),
GenericZipWriter::Deflater(ref mut w) => w.write(buf),
GenericZipWriter::Bzip2(ref mut w) => w.write(buf),
GenericZipWriter::Closed => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed", None)),
}
}
fn flush(&mut self) -> io::Result<()>
{
let result = self.finalize();
self.inner = GenericZipWriter::Closed;
match result {
Ok(..) => Ok(()),
Err(ZipError::Io(io_err)) => Err(io_err),
Err(zip_err) => Err(io::Error::new(io::ErrorKind::Other, "A zip error occured", Some(zip_err.description().to_string()))),
}
}
}
@ -83,7 +102,7 @@ impl ZipWriterStats
}
}
impl<W: Writer+Seek> ZipWriter<W>
impl<W: Write+io::Seek> ZipWriter<W>
{
/// Initializes the ZipWriter.
///
@ -105,9 +124,9 @@ impl<W: Writer+Seek> ZipWriter<W>
{
let writer = self.inner.get_plain();
let header_start = try!(writer.tell());
let header_start = try!(writer.seek(io::SeekFrom::Current(0)));
let mut file = ZipFile
let mut file = ZipFileData
{
encrypted: false,
compression_method: compression,
@ -115,14 +134,14 @@ impl<W: Writer+Seek> ZipWriter<W>
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
file_name: String::from_str(name),
file_name: name.to_string(),
file_comment: String::new(),
header_start: header_start,
data_start: 0,
};
try!(writer_spec::write_local_file_header(writer, &file));
try!(write_local_file_header(writer, &file));
let header_end = try!(writer.tell());
let header_end = try!(writer.seek(io::SeekFrom::Current(0)));
self.stats.start = header_end;
file.data_start = header_end;
@ -149,10 +168,10 @@ impl<W: Writer+Seek> ZipWriter<W>
};
file.crc32 = self.stats.crc32;
file.uncompressed_size = self.stats.bytes_written;
file.compressed_size = try!(writer.tell()) - self.stats.start;
file.compressed_size = try!(writer.seek(io::SeekFrom::Current(0))) - self.stats.start;
try!(writer_spec::update_local_file_header(writer, file));
try!(writer.seek(0, old_io::SeekEnd));
try!(update_local_file_header(writer, file));
try!(writer.seek(io::SeekFrom::End(0)));
Ok(())
}
@ -174,12 +193,12 @@ impl<W: Writer+Seek> ZipWriter<W>
{
let writer = self.inner.get_plain();
let central_start = try!(writer.tell());
let central_start = try!(writer.seek(io::SeekFrom::Current(0)));
for file in self.files.iter()
{
try!(writer_spec::write_central_directory_header(writer, file));
try!(write_central_directory_header(writer, file));
}
let central_size = try!(writer.tell()) - central_start;
let central_size = try!(writer.seek(io::SeekFrom::Current(0))) - central_start;
let footer = spec::CentralDirectoryEnd
{
@ -200,20 +219,20 @@ impl<W: Writer+Seek> ZipWriter<W>
}
#[unsafe_destructor]
impl<W: Writer+Seek> Drop for ZipWriter<W>
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 old_io::stdio::stderr(), "ZipWriter drop failed: {:?}", e);
let _ = write!(&mut ::std::old_io::stdio::stderr(), "ZipWriter drop failed: {:?}", e);
}
}
}
}
impl<W: Writer+Seek> GenericZipWriter<W>
impl<W: Write+io::Seek> GenericZipWriter<W>
{
fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()>
{
@ -222,15 +241,15 @@ impl<W: Writer+Seek> GenericZipWriter<W>
GenericZipWriter::Storer(w) => w,
GenericZipWriter::Deflater(w) => try!(w.finish()),
GenericZipWriter::Bzip2(w) => match w.into_inner() { Ok(r) => r, Err((_, err)) => try!(Err(err)) },
GenericZipWriter::Closed => try!(Err(old_io::standard_error(old_io::Closed))),
GenericZipWriter::Closed => try!(Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed", None))),
};
*self = match compression
{
CompressionMethod::Stored => GenericZipWriter::Storer(bare),
CompressionMethod::Deflated => GenericZipWriter::Deflater(bare.deflate_encode(flate2::CompressionLevel::Default)),
CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzCompressor::new(bare, bzip2::CompressionLevel::Default)),
_ => return Err(ZipError::UnsupportedZipFile("Unsupported compression")),
CompressionMethod::Deflated => GenericZipWriter::Deflater(bare.deflate_encode(flate2::Compression::Default)),
CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzCompressor::new(bare, bzip2::Compress::Default)),
_ => return Err(ZipError::UnsupportedArchive("Unsupported compression")),
};
Ok(())
@ -263,3 +282,68 @@ impl<W: Writer+Seek> GenericZipWriter<W>
}
}
}
fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
{
try!(writer.write_le_u32(spec::LOCAL_FILE_HEADER_SIGNATURE));
try!(writer.write_le_u16(20));
let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
try!(writer.write_le_u16(flag));
try!(writer.write_le_u16(file.compression_method as u16));
try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time)));
try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16));
let extra_field = try!(build_extra_field(file));
try!(writer.write_le_u16(extra_field.len() as u16));
try!(writer.write_all(file.file_name.as_bytes()));
try!(writer.write_all(extra_field.as_slice()));
Ok(())
}
fn update_local_file_header<T: Write+io::Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
{
static CRC32_OFFSET : u64 = 14;
try!(writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
Ok(())
}
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
{
try!(writer.write_le_u32(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE));
try!(writer.write_le_u16(0x14FF));
try!(writer.write_le_u16(20));
let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
try!(writer.write_le_u16(flag));
try!(writer.write_le_u16(file.compression_method as u16));
try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time)));
try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16));
let extra_field = try!(build_extra_field(file));
try!(writer.write_le_u16(extra_field.len() as u16));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u32(0));
try!(writer.write_le_u32(file.header_start as u32));
try!(writer.write_all(file.file_name.as_bytes()));
try!(writer.write_all(extra_field.as_slice()));
Ok(())
}
fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>>
{
let writer = Vec::new();
// Future work
Ok(writer)
}

View file

@ -1,71 +0,0 @@
use std::old_io;
use std::ascii::AsciiExt;
use types::ZipFile;
use result::ZipResult;
use spec;
use util;
pub fn write_local_file_header<T: Writer>(writer: &mut T, file: &ZipFile) -> ZipResult<()>
{
try!(writer.write_le_u32(spec::LOCAL_FILE_HEADER_SIGNATURE));
try!(writer.write_le_u16(20));
let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
try!(writer.write_le_u16(flag));
try!(writer.write_le_u16(file.compression_method as u16));
try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time)));
try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16));
let extra_field = try!(build_extra_field(file));
try!(writer.write_le_u16(extra_field.len() as u16));
try!(writer.write_all(file.file_name.as_bytes()));
try!(writer.write_all(extra_field.as_slice()));
Ok(())
}
pub fn update_local_file_header<T: Writer+Seek>(writer: &mut T, file: &ZipFile) -> ZipResult<()>
{
static CRC32_OFFSET : i64 = 14;
try!(writer.seek(file.header_start as i64 + CRC32_OFFSET, old_io::SeekSet));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
Ok(())
}
pub fn write_central_directory_header<T: Writer>(writer: &mut T, file: &ZipFile) -> ZipResult<()>
{
try!(writer.write_le_u32(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE));
try!(writer.write_le_u16(0x14FF));
try!(writer.write_le_u16(20));
let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
try!(writer.write_le_u16(flag));
try!(writer.write_le_u16(file.compression_method as u16));
try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time)));
try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16));
let extra_field = try!(build_extra_field(file));
try!(writer.write_le_u16(extra_field.len() as u16));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u32(0));
try!(writer.write_le_u32(file.header_start as u32));
try!(writer.write_all(file.file_name.as_bytes()));
try!(writer.write_all(extra_field.as_slice()));
Ok(())
}
fn build_extra_field(_file: &ZipFile) -> ZipResult<Vec<u8>>
{
let writer = old_io::MemWriter::new();
// Future work
Ok(writer.into_inner())
}