More elegant way of enforcing single reads
This commit is contained in:
parent
bb4f56346a
commit
33efdcc539
6 changed files with 167 additions and 129 deletions
|
@ -6,21 +6,21 @@ fn main()
|
||||||
let fname = Path::new(args[1].as_slice());
|
let fname = Path::new(args[1].as_slice());
|
||||||
let file = std::io::File::open(&fname);
|
let file = std::io::File::open(&fname);
|
||||||
|
|
||||||
let mut zipcontainer = zip::reader::ZipContainer::new(file).unwrap();
|
let zipcontainer = zip::reader::ZipContainer::new(file).unwrap();
|
||||||
|
|
||||||
for i in zipcontainer.files()
|
for file in zipcontainer.files()
|
||||||
{
|
{
|
||||||
println!("{}", String::from_utf8_lossy(i.name.as_slice()));
|
println!("{}", file.file_name_string());
|
||||||
|
|
||||||
if i.size == 0 { continue }
|
if file.uncompressed_size == 0 { continue }
|
||||||
|
|
||||||
let outpath = Path::new(i.name.as_slice());
|
let outpath = Path::new(file.file_name.as_slice());
|
||||||
let dirname = Path::new(outpath.dirname());
|
let dirname = Path::new(outpath.dirname());
|
||||||
|
|
||||||
std::io::fs::mkdir_recursive(&dirname, std::io::UserDir).unwrap();
|
std::io::fs::mkdir_recursive(&dirname, std::io::UserDir).unwrap();
|
||||||
|
|
||||||
let mut outfile = std::io::File::create(&outpath);
|
let mut outfile = std::io::File::create(&outpath);
|
||||||
let mut reader = zipcontainer.read_file(&i);
|
let mut reader = zipcontainer.read_file(file);
|
||||||
copy(&mut reader, &mut outfile).unwrap();
|
copy(&mut reader, &mut outfile).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,3 +8,4 @@ mod util;
|
||||||
mod spec;
|
mod spec;
|
||||||
pub mod crc32;
|
pub mod crc32;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
|
pub mod types;
|
||||||
|
|
185
src/reader.rs
185
src/reader.rs
|
@ -1,43 +1,25 @@
|
||||||
use spec;
|
use spec;
|
||||||
use crc32::Crc32Reader;
|
use crc32::Crc32Reader;
|
||||||
|
use types::ZipFile;
|
||||||
|
use types;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{IoResult, IoError};
|
use std::io::{IoResult, IoError};
|
||||||
|
use std::cell::RefCell;
|
||||||
use flate2::FlateReader;
|
use flate2::FlateReader;
|
||||||
|
|
||||||
pub struct ZipContainer<T>
|
pub struct ZipContainer<T>
|
||||||
{
|
{
|
||||||
inner: T,
|
inner: RefCell<T>,
|
||||||
files: Vec<ZipFile>,
|
files: Vec<ZipFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(Clone)]
|
fn unsupported_zip_error<T>(detail: &str) -> IoResult<T>
|
||||||
struct ZipFile
|
|
||||||
{
|
|
||||||
central_header: spec::CentralDirectoryHeader,
|
|
||||||
local_header: spec::LocalFileHeader,
|
|
||||||
_data_descriptor: Option<spec::DataDescriptor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ZipFileItems
|
|
||||||
{
|
|
||||||
list: Vec<ZipFile>,
|
|
||||||
pos: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ZipFileItem
|
|
||||||
{
|
|
||||||
pub name: Vec<u8>,
|
|
||||||
pub size: uint,
|
|
||||||
index: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsupported_zip_error<T>(detail: String) -> IoResult<T>
|
|
||||||
{
|
{
|
||||||
Err(IoError
|
Err(IoError
|
||||||
{
|
{
|
||||||
kind: io::OtherIoError,
|
kind: io::OtherIoError,
|
||||||
desc: "This ZIP file is not supported",
|
desc: "This ZIP file is not supported",
|
||||||
detail: Some(detail),
|
detail: Some(detail.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,101 +27,110 @@ impl<T: Reader+Seek> ZipContainer<T>
|
||||||
{
|
{
|
||||||
pub fn new(inner: T) -> IoResult<ZipContainer<T>>
|
pub fn new(inner: T) -> IoResult<ZipContainer<T>>
|
||||||
{
|
{
|
||||||
let mut result = ZipContainer { inner: inner, files: Vec::new() };
|
let mut result = ZipContainer { inner: RefCell::new(inner), files: Vec::new() };
|
||||||
let footer = try!(spec::CentralDirectoryEnd::find_and_parse(&mut result.inner));
|
|
||||||
|
|
||||||
if footer.number_of_disks > 1 { return unsupported_zip_error("Support for multi-disk files is not implemented".to_string()) }
|
|
||||||
|
|
||||||
let directory_start = footer.central_directory_offset as i64;
|
|
||||||
let number_of_files = footer.number_of_files_on_this_disk as uint;
|
|
||||||
|
|
||||||
let mut files = Vec::with_capacity(number_of_files);
|
|
||||||
|
|
||||||
try!(result.inner.seek(directory_start, io::SeekSet));
|
|
||||||
for i in range(0, number_of_files)
|
|
||||||
{
|
{
|
||||||
files.push(try!(ZipContainer::parse_directory(&mut result.inner)));
|
let reader = &mut *result.inner.borrow_mut();
|
||||||
|
let footer = try!(spec::CentralDirectoryEnd::find_and_parse(reader));
|
||||||
|
|
||||||
|
if footer.number_of_disks > 1 { 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 uint;
|
||||||
|
|
||||||
|
let mut files = Vec::with_capacity(number_of_files);
|
||||||
|
|
||||||
|
try!(reader.seek(directory_start, io::SeekSet));
|
||||||
|
for i in range(0, number_of_files)
|
||||||
|
{
|
||||||
|
files.push(try!(ZipContainer::parse_directory(reader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.files = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.files = files;
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_directory(reader: &mut T) -> IoResult<ZipFile>
|
fn parse_directory(reader: &mut T) -> IoResult<ZipFile>
|
||||||
{
|
{
|
||||||
let cdh = try!(spec::CentralDirectoryHeader::parse(reader));
|
let cdh = try!(spec::CentralDirectoryHeader::parse(reader));
|
||||||
|
// Remember position
|
||||||
let pos = try!(reader.tell()) as i64;
|
let pos = try!(reader.tell()) as i64;
|
||||||
let result = ZipFile::new(reader, cdh);
|
let result = ZipContainer::parse_file(reader, cdh);
|
||||||
|
// Jump back for next directory
|
||||||
try!(reader.seek(pos, io::SeekSet));
|
try!(reader.seek(pos, io::SeekSet));
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn files(&self) -> ZipFileItems
|
fn parse_file(reader: &mut T, central: spec::CentralDirectoryHeader) -> IoResult<ZipFile>
|
||||||
{
|
{
|
||||||
ZipFileItems { list: self.files.clone(), pos: 0 }
|
try!(reader.seek(central.file_offset as i64, io::SeekSet));
|
||||||
|
let local = try!(spec::LocalFileHeader::parse(reader));
|
||||||
|
|
||||||
|
Ok(ZipFile
|
||||||
|
{
|
||||||
|
encrypted: central.encrypted,
|
||||||
|
compression_method: central.compression_method,
|
||||||
|
last_modified_time: central.last_modified_time,
|
||||||
|
crc32: central.crc32,
|
||||||
|
compressed_size: central.compressed_size as u64,
|
||||||
|
uncompressed_size: central.uncompressed_size as u64,
|
||||||
|
file_name: central.file_name.clone(),
|
||||||
|
file_comment: central.file_comment.clone(),
|
||||||
|
data_start: local.header_end,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file(&mut self, item: &ZipFileItem) -> IoResult<Box<Reader>>
|
pub fn files(&self) -> ::std::slice::Items<ZipFile>
|
||||||
{
|
{
|
||||||
let file = self.files.get_mut(item.index);
|
self.files.as_slice().iter()
|
||||||
let reader = &mut self.inner;
|
}
|
||||||
let pos = file.local_header.header_end as i64;
|
|
||||||
|
|
||||||
try!(reader.seek(pos, io::SeekSet));
|
pub fn read_file(&self, file: &ZipFile) -> IoResult<Box<Reader>>
|
||||||
let lreader = io::util::LimitReader::new(reader.by_ref(), file.central_header.compressed_size as uint);
|
{
|
||||||
|
let mut inner_reader = match self.inner.try_borrow_mut()
|
||||||
let reader = match file.central_header.compression_method
|
|
||||||
{
|
{
|
||||||
spec::Stored => box Crc32Reader::new_with_check(lreader, file.central_header.crc32) as Box<Reader>,
|
Some(reader) => reader,
|
||||||
spec::Deflated => box Crc32Reader::new_with_check(lreader.deflate_decode(), file.central_header.crc32) as Box<Reader>,
|
None => return Err(IoError
|
||||||
_ => return unsupported_zip_error("Compression method not supported".to_string()),
|
{
|
||||||
|
kind: io::ResourceUnavailable,
|
||||||
|
desc: "There is already a ZIP reader active",
|
||||||
|
detail: None
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let pos = file.data_start as i64;
|
||||||
|
|
||||||
|
if file.encrypted
|
||||||
|
{
|
||||||
|
return unsupported_zip_error("Encrypted files are not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(inner_reader.seek(pos, io::SeekSet));
|
||||||
|
let refmut_reader = ::util::RefMutReader::new(inner_reader);
|
||||||
|
let limit_reader = io::util::LimitReader::new(refmut_reader, file.compressed_size as uint);
|
||||||
|
|
||||||
|
let reader = match file.compression_method
|
||||||
|
{
|
||||||
|
types::Stored =>
|
||||||
|
{
|
||||||
|
box
|
||||||
|
Crc32Reader::new_with_check(
|
||||||
|
limit_reader,
|
||||||
|
file.crc32)
|
||||||
|
as Box<Reader>
|
||||||
|
},
|
||||||
|
types::Deflated =>
|
||||||
|
{
|
||||||
|
let deflate_reader = limit_reader.deflate_decode();
|
||||||
|
box
|
||||||
|
Crc32Reader::new_with_check(
|
||||||
|
deflate_reader,
|
||||||
|
file.crc32)
|
||||||
|
as Box<Reader>
|
||||||
|
},
|
||||||
|
_ => return unsupported_zip_error("Compression method not supported"),
|
||||||
};
|
};
|
||||||
Ok(reader)
|
Ok(reader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZipFile
|
|
||||||
{
|
|
||||||
pub fn new<T: Reader+Seek>(reader: &mut T, central_directory_header: spec::CentralDirectoryHeader) -> IoResult<ZipFile>
|
|
||||||
{
|
|
||||||
try!(reader.seek(central_directory_header.file_offset as i64, io::SeekSet));
|
|
||||||
let lfh = try!(spec::LocalFileHeader::parse(reader));
|
|
||||||
let desc = if lfh.has_descriptor
|
|
||||||
{
|
|
||||||
try!(reader.seek(lfh.compressed_size as i64, io::SeekCur));
|
|
||||||
Some(try!(spec::DataDescriptor::parse(reader)))
|
|
||||||
}
|
|
||||||
else { None };
|
|
||||||
|
|
||||||
|
|
||||||
Ok(ZipFile { central_header: central_directory_header, local_header: lfh, _data_descriptor: desc })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ZipFileItem
|
|
||||||
{
|
|
||||||
fn new(list: &Vec<ZipFile>, index: uint) -> IoResult<ZipFileItem>
|
|
||||||
{
|
|
||||||
let file = &list[index];
|
|
||||||
|
|
||||||
let name = file.central_header.file_name.clone();
|
|
||||||
|
|
||||||
Ok(ZipFileItem { name: name, size: file.central_header.uncompressed_size as uint, index: index })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator<ZipFileItem> for ZipFileItems
|
|
||||||
{
|
|
||||||
fn next(&mut self) -> Option<ZipFileItem>
|
|
||||||
{
|
|
||||||
self.pos += 1;
|
|
||||||
if self.pos - 1 >= self.list.len()
|
|
||||||
{
|
|
||||||
None
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ZipFileItem::new(&self.list, self.pos - 1).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
31
src/spec.rs
31
src/spec.rs
|
@ -2,6 +2,7 @@ use std::io;
|
||||||
use std::io::{IoResult, IoError};
|
use std::io::{IoResult, IoError};
|
||||||
use std::iter::range_step_inclusive;
|
use std::iter::range_step_inclusive;
|
||||||
use time::Tm;
|
use time::Tm;
|
||||||
|
use types;
|
||||||
use util;
|
use util;
|
||||||
|
|
||||||
static LOCAL_FILE_HEADER_SIGNATURE : u32 = 0x04034b50;
|
static LOCAL_FILE_HEADER_SIGNATURE : u32 = 0x04034b50;
|
||||||
|
@ -9,28 +10,6 @@ static DATA_DESCRIPTOR_SIGNATURE : u32 = 0x08074b50;
|
||||||
static CENTRAL_DIRECTORY_HEADER_SIGNATURE : u32 = 0x02014b50;
|
static CENTRAL_DIRECTORY_HEADER_SIGNATURE : u32 = 0x02014b50;
|
||||||
static CENTRAL_DIRECTORY_END_SIGNATURE : u32 = 0x06054b50;
|
static CENTRAL_DIRECTORY_END_SIGNATURE : u32 = 0x06054b50;
|
||||||
|
|
||||||
#[deriving(FromPrimitive, Clone)]
|
|
||||||
pub enum CompressionMethod
|
|
||||||
{
|
|
||||||
Stored = 0,
|
|
||||||
Shrunk = 1,
|
|
||||||
Reduced1 = 2,
|
|
||||||
Reduced2 = 3,
|
|
||||||
Reduced3 = 4,
|
|
||||||
Reduced4 = 5,
|
|
||||||
Imploded = 6,
|
|
||||||
Deflated = 8,
|
|
||||||
Deflate64 = 9,
|
|
||||||
PkwareImploding = 10,
|
|
||||||
Bzip2 = 12,
|
|
||||||
LZMA = 14,
|
|
||||||
IBMTerse = 18,
|
|
||||||
LZ77 = 19,
|
|
||||||
WavPack = 97,
|
|
||||||
PPMdI1 = 98,
|
|
||||||
Unknown = 100000,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deriving(Clone)]
|
#[deriving(Clone)]
|
||||||
pub struct LocalFileHeader
|
pub struct LocalFileHeader
|
||||||
{
|
{
|
||||||
|
@ -49,7 +28,7 @@ pub struct LocalFileHeader
|
||||||
pub is_masked: bool, // bit 13
|
pub is_masked: bool, // bit 13
|
||||||
// bit 14 & 15 unused
|
// bit 14 & 15 unused
|
||||||
|
|
||||||
pub compression_method: CompressionMethod,
|
pub compression_method: types::CompressionMethod,
|
||||||
pub last_modified: Tm,
|
pub last_modified: Tm,
|
||||||
pub crc32: u32,
|
pub crc32: u32,
|
||||||
pub compressed_size: u32,
|
pub compressed_size: u32,
|
||||||
|
@ -95,7 +74,7 @@ impl LocalFileHeader
|
||||||
strong_encryption: (flags & (1 << 6)) != 0,
|
strong_encryption: (flags & (1 << 6)) != 0,
|
||||||
is_utf8: (flags & (1 << 11)) != 0,
|
is_utf8: (flags & (1 << 11)) != 0,
|
||||||
is_masked: (flags & (1 << 13)) != 0,
|
is_masked: (flags & (1 << 13)) != 0,
|
||||||
compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(Unknown),
|
compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(types::Unknown),
|
||||||
last_modified: util::msdos_datetime_to_tm(last_mod_time, last_mod_date),
|
last_modified: util::msdos_datetime_to_tm(last_mod_time, last_mod_date),
|
||||||
crc32: crc,
|
crc32: crc,
|
||||||
compressed_size: compressed_size,
|
compressed_size: compressed_size,
|
||||||
|
@ -160,7 +139,7 @@ pub struct CentralDirectoryHeader
|
||||||
pub is_masked: bool, // bit 13
|
pub is_masked: bool, // bit 13
|
||||||
// bit 14 & 15 unused
|
// bit 14 & 15 unused
|
||||||
|
|
||||||
pub compression_method: CompressionMethod,
|
pub compression_method: types::CompressionMethod,
|
||||||
pub last_modified_time: Tm,
|
pub last_modified_time: Tm,
|
||||||
pub crc32: u32,
|
pub crc32: u32,
|
||||||
pub compressed_size: u32,
|
pub compressed_size: u32,
|
||||||
|
@ -215,7 +194,7 @@ impl CentralDirectoryHeader
|
||||||
strong_encryption: flags & (1 << 6) != 0,
|
strong_encryption: flags & (1 << 6) != 0,
|
||||||
is_utf8: flags & (1 << 11) != 0,
|
is_utf8: flags & (1 << 11) != 0,
|
||||||
is_masked: flags & (1 << 13) != 0,
|
is_masked: flags & (1 << 13) != 0,
|
||||||
compression_method: FromPrimitive::from_u16(compression).unwrap_or(Unknown),
|
compression_method: FromPrimitive::from_u16(compression).unwrap_or(types::Unknown),
|
||||||
last_modified_time: util::msdos_datetime_to_tm(last_mod_time, last_mod_date),
|
last_modified_time: util::msdos_datetime_to_tm(last_mod_time, last_mod_date),
|
||||||
crc32: crc,
|
crc32: crc,
|
||||||
compressed_size: compressed_size,
|
compressed_size: compressed_size,
|
||||||
|
|
45
src/types.rs
Normal file
45
src/types.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use time;
|
||||||
|
|
||||||
|
#[deriving(FromPrimitive, Clone)]
|
||||||
|
pub enum CompressionMethod
|
||||||
|
{
|
||||||
|
Stored = 0,
|
||||||
|
Shrunk = 1,
|
||||||
|
Reduced1 = 2,
|
||||||
|
Reduced2 = 3,
|
||||||
|
Reduced3 = 4,
|
||||||
|
Reduced4 = 5,
|
||||||
|
Imploded = 6,
|
||||||
|
Deflated = 8,
|
||||||
|
Deflate64 = 9,
|
||||||
|
PkwareImploding = 10,
|
||||||
|
Bzip2 = 12,
|
||||||
|
LZMA = 14,
|
||||||
|
IBMTerse = 18,
|
||||||
|
LZ77 = 19,
|
||||||
|
WavPack = 97,
|
||||||
|
PPMdI1 = 98,
|
||||||
|
Unknown = 100000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct ZipFile
|
||||||
|
{
|
||||||
|
pub encrypted: bool,
|
||||||
|
pub compression_method: CompressionMethod,
|
||||||
|
pub last_modified_time: time::Tm,
|
||||||
|
pub crc32: u32,
|
||||||
|
pub compressed_size: u64,
|
||||||
|
pub uncompressed_size: u64,
|
||||||
|
pub file_name: Vec<u8>,
|
||||||
|
pub file_comment: Vec<u8>,
|
||||||
|
pub data_start: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZipFile
|
||||||
|
{
|
||||||
|
pub fn file_name_string(&self) -> String
|
||||||
|
{
|
||||||
|
String::from_utf8_lossy(self.file_name.as_slice()).into_string()
|
||||||
|
}
|
||||||
|
}
|
22
src/util.rs
22
src/util.rs
|
@ -1,5 +1,6 @@
|
||||||
use time;
|
use time;
|
||||||
use time::Tm;
|
use time::Tm;
|
||||||
|
use std::cell::RefMut;
|
||||||
|
|
||||||
pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
|
pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
|
||||||
{
|
{
|
||||||
|
@ -25,3 +26,24 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
|
||||||
Err(m) => { debug!("Failed parsing date: {}", m); time::empty_tm() },
|
Err(m) => { debug!("Failed parsing date: {}", m); time::empty_tm() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RefMutReader<'a, R:'a>
|
||||||
|
{
|
||||||
|
inner: RefMut<'a, R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: Reader> RefMutReader<'a, R>
|
||||||
|
{
|
||||||
|
pub fn new(inner: RefMut<'a, R>) -> RefMutReader<'a, R>
|
||||||
|
{
|
||||||
|
RefMutReader { inner: inner, }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: Reader> Reader for RefMutReader<'a, R>
|
||||||
|
{
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> ::std::io::IoResult<uint>
|
||||||
|
{
|
||||||
|
self.inner.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue