Simple ZIPs can be extracted!
This commit is contained in:
parent
3668fa5e33
commit
b6a0de8b3c
6 changed files with 154 additions and 50 deletions
39
src/bin/extract.rs
Normal file
39
src/bin/extract.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
extern crate zip;
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
let args = std::os::args();
|
||||||
|
let fname = Path::new(args[1].as_slice());
|
||||||
|
let file = std::io::File::open(&fname);
|
||||||
|
|
||||||
|
let mut zipcontainer = zip::reader::ZipContainer::new(file).unwrap();
|
||||||
|
for mut i in zipcontainer.files()
|
||||||
|
{
|
||||||
|
println!("File: {}", i.name);
|
||||||
|
|
||||||
|
if i.size == 0 { continue }
|
||||||
|
|
||||||
|
let outpath = Path::new(i.name.as_slice());
|
||||||
|
let dirname = Path::new(outpath.dirname());
|
||||||
|
|
||||||
|
std::io::fs::mkdir_recursive(&dirname, std::io::UserDir).unwrap();
|
||||||
|
|
||||||
|
let mut outfile = std::io::File::create(&outpath);
|
||||||
|
copy(&mut i.reader, &mut outfile).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy<R: Reader, W: Writer>(reader: &mut R, writer: &mut W) -> std::io::IoResult<()>
|
||||||
|
{
|
||||||
|
let mut buffer = [0u8, ..4096];
|
||||||
|
loop
|
||||||
|
{
|
||||||
|
match reader.read(&mut buffer)
|
||||||
|
{
|
||||||
|
Err(ref e) if e.kind == std::io::EndOfFile => break,
|
||||||
|
Ok(n) => try!(writer.write(buffer.slice_to(n))),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
extern crate zip;
|
|
||||||
|
|
||||||
fn main()
|
|
||||||
{
|
|
||||||
let args = std::os::args();
|
|
||||||
let fname = Path::new(args[1].as_slice());
|
|
||||||
let file = std::io::File::open(&fname);
|
|
||||||
|
|
||||||
let mut zipcontainer = zip::reader::ZipContainer::new(file).unwrap();
|
|
||||||
for i in zipcontainer.files()
|
|
||||||
{
|
|
||||||
println!("{}", i)
|
|
||||||
}
|
|
||||||
}
|
|
33
src/crc32.rs
33
src/crc32.rs
|
@ -62,6 +62,7 @@ pub struct Crc32Reader<R>
|
||||||
{
|
{
|
||||||
inner: R,
|
inner: R,
|
||||||
crc: u32,
|
crc: u32,
|
||||||
|
check: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Reader> Crc32Reader<R>
|
impl<R: Reader> Crc32Reader<R>
|
||||||
|
@ -72,6 +73,17 @@ impl<R: Reader> Crc32Reader<R>
|
||||||
{
|
{
|
||||||
inner: inner,
|
inner: inner,
|
||||||
crc: 0,
|
crc: 0,
|
||||||
|
check: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_check(inner: R, checksum: u32) -> Crc32Reader<R>
|
||||||
|
{
|
||||||
|
Crc32Reader
|
||||||
|
{
|
||||||
|
inner: inner,
|
||||||
|
crc: 0,
|
||||||
|
check: Some(checksum),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,13 +91,32 @@ impl<R: Reader> Crc32Reader<R>
|
||||||
{
|
{
|
||||||
self.crc
|
self.crc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_matches(&self) -> bool
|
||||||
|
{
|
||||||
|
match self.check
|
||||||
|
{
|
||||||
|
None => true,
|
||||||
|
Some(check) => check == self.crc
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Reader> Reader for Crc32Reader<R>
|
impl<R: Reader> Reader for Crc32Reader<R>
|
||||||
{
|
{
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::IoResult<uint>
|
fn read(&mut self, buf: &mut [u8]) -> io::IoResult<uint>
|
||||||
{
|
{
|
||||||
let count = try!(self.inner.read(buf));
|
let count = match self.inner.read(buf)
|
||||||
|
{
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(ref e) if e.kind == io::EndOfFile =>
|
||||||
|
{
|
||||||
|
return
|
||||||
|
if self.check_matches() { Err(e.clone()) }
|
||||||
|
else { Err(io::IoError { kind: io::OtherIoError, desc: "Invalid checksum", detail: None, }) }
|
||||||
|
},
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
self.crc = crc32(self.crc, buf.slice_to(count));
|
self.crc = crc32(self.crc, buf.slice_to(count));
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use spec;
|
use spec;
|
||||||
|
use crc32::Crc32Reader;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{IoResult, IoError};
|
use std::io::{IoResult, IoError};
|
||||||
|
use flate2::FlateReader;
|
||||||
|
|
||||||
pub struct ZipContainer<T>
|
pub struct ZipContainer<T>
|
||||||
{
|
{
|
||||||
|
@ -12,21 +14,29 @@ struct ZipFile
|
||||||
{
|
{
|
||||||
central_header: spec::CentralDirectoryHeader,
|
central_header: spec::CentralDirectoryHeader,
|
||||||
local_header: spec::LocalFileHeader,
|
local_header: spec::LocalFileHeader,
|
||||||
|
_data_descriptor: Option<spec::DataDescriptor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ZipFileNames<'a, T:'a>
|
pub struct ZipFileItems<'a, T:'a>
|
||||||
{
|
{
|
||||||
container: &'a mut ZipContainer<T>,
|
container: &'a mut ZipContainer<T>,
|
||||||
pos: uint,
|
pos: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unsupported_zip_error<T>(detail: Option<String>) -> IoResult<T>
|
pub struct ZipFileItem<'a>
|
||||||
|
{
|
||||||
|
pub name: String,
|
||||||
|
pub size: uint,
|
||||||
|
pub reader: Box<Reader+'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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: detail,
|
detail: Some(detail),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +47,13 @@ impl<T: Reader+Seek> ZipContainer<T>
|
||||||
let mut result = ZipContainer { inner: inner, files: Vec::new() };
|
let mut result = ZipContainer { inner: inner, files: Vec::new() };
|
||||||
let footer = try!(spec::CentralDirectoryEnd::find_and_parse(&mut result.inner));
|
let footer = try!(spec::CentralDirectoryEnd::find_and_parse(&mut result.inner));
|
||||||
|
|
||||||
if footer.number_of_disks > 1 { return unsupported_zip_error(Some("Support for multi-disk files is not implemented".to_string())) }
|
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 directory_start = footer.central_directory_offset as i64;
|
||||||
let number_of_files = footer.number_of_files_on_this_disk as uint;
|
let number_of_files = footer.number_of_files_on_this_disk as uint;
|
||||||
|
|
||||||
let mut files = Vec::with_capacity(number_of_files);
|
let mut files = Vec::with_capacity(number_of_files);
|
||||||
|
|
||||||
try!(result.inner.seek(directory_start, io::SeekSet));
|
try!(result.inner.seek(directory_start, io::SeekSet));
|
||||||
for i in range(0, number_of_files)
|
for i in range(0, number_of_files)
|
||||||
{
|
{
|
||||||
|
@ -58,21 +68,60 @@ impl<T: Reader+Seek> ZipContainer<T>
|
||||||
{
|
{
|
||||||
let cdh = try!(spec::CentralDirectoryHeader::parse(reader));
|
let cdh = try!(spec::CentralDirectoryHeader::parse(reader));
|
||||||
let pos = try!(reader.tell()) as i64;
|
let pos = try!(reader.tell()) as i64;
|
||||||
try!(reader.seek(cdh.file_offset as i64, io::SeekSet));
|
let result = ZipFile::new(reader, cdh);
|
||||||
let lfh = try!(spec::LocalFileHeader::parse(reader));
|
|
||||||
try!(reader.seek(pos, io::SeekSet));
|
try!(reader.seek(pos, io::SeekSet));
|
||||||
Ok(ZipFile { central_header: cdh, local_header: lfh } )
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn files<'a>(&'a mut self) -> ZipFileNames<'a, T>
|
pub fn files<'a>(&'a mut self) -> ZipFileItems<'a, T>
|
||||||
{
|
{
|
||||||
ZipFileNames { container: self, pos: 0 }
|
ZipFileItems { container: self, pos: 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Iterator<String> for ZipFileNames<'a, T>
|
impl ZipFile
|
||||||
{
|
{
|
||||||
fn next(&mut self) -> Option<String>
|
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<'a> ZipFileItem<'a>
|
||||||
|
{
|
||||||
|
fn new<S: Reader+Seek>(reader: &mut S, file: &ZipFile) -> IoResult<ZipFileItem<'a>>
|
||||||
|
{
|
||||||
|
let fname = file.central_header.file_name.clone();
|
||||||
|
let name = String::from_utf8(fname).unwrap_or("???".to_string());
|
||||||
|
let pos = file.local_header.header_end as i64;
|
||||||
|
|
||||||
|
try!(reader.seek(pos, io::SeekSet));
|
||||||
|
let lreader = io::util::LimitReader::new(reader.by_ref(), file.central_header.compressed_size as uint);
|
||||||
|
|
||||||
|
let reader = match file.central_header.compression_method
|
||||||
|
{
|
||||||
|
spec::Stored => box Crc32Reader::new_with_check(lreader, file.central_header.crc32) as Box<Reader>,
|
||||||
|
spec::Deflated => box Crc32Reader::new_with_check(lreader.deflate_decode(), file.central_header.crc32) as Box<Reader>,
|
||||||
|
_ => return unsupported_zip_error("Compression method not supported".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ZipFileItem { name: name, reader: reader, size: file.central_header.uncompressed_size as uint })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Reader+Seek> Iterator<ZipFileItem<'a>> for ZipFileItems<'a, T>
|
||||||
|
{
|
||||||
|
fn next(&mut self) -> Option<ZipFileItem<'a>>
|
||||||
{
|
{
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.pos - 1 >= self.container.files.len()
|
if self.pos - 1 >= self.container.files.len()
|
||||||
|
@ -81,8 +130,8 @@ impl<'a, T> Iterator<String> for ZipFileNames<'a, T>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
let fname = self.container.files.get(self.pos - 1).central_header.file_name.clone();
|
let result = ZipFileItem::new(&mut self.container.inner, &self.container.files[self.pos - 1]);
|
||||||
String::from_utf8(fname).ok()
|
result.ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
src/spec.rs
36
src/spec.rs
|
@ -34,29 +34,29 @@ pub enum CompressionMethod
|
||||||
#[deriving(Show)]
|
#[deriving(Show)]
|
||||||
pub struct LocalFileHeader
|
pub struct LocalFileHeader
|
||||||
{
|
{
|
||||||
extract_version: u16,
|
pub extract_version: u16,
|
||||||
|
|
||||||
// general purpose flags
|
// general purpose flags
|
||||||
encrypted: bool, // bit 0
|
pub encrypted: bool, // bit 0
|
||||||
// bit 1 & 2 unused
|
// bit 1 & 2 unused
|
||||||
has_descriptor: bool, // bit 3
|
pub has_descriptor: bool, // bit 3
|
||||||
// bit 4 unused
|
// bit 4 unused
|
||||||
is_compressed_patch: bool, // bit 5
|
pub is_compressed_patch: bool, // bit 5
|
||||||
strong_encryption: bool, // bit 6
|
pub strong_encryption: bool, // bit 6
|
||||||
// bit 7 - 10 unused
|
// bit 7 - 10 unused
|
||||||
is_utf8: bool, // bit 11
|
pub is_utf8: bool, // bit 11
|
||||||
// bit 12 unused
|
// bit 12 unused
|
||||||
is_masked: bool, // bit 13
|
pub is_masked: bool, // bit 13
|
||||||
// bit 14 & 15 unused
|
// bit 14 & 15 unused
|
||||||
|
|
||||||
compression_method: CompressionMethod,
|
pub compression_method: CompressionMethod,
|
||||||
last_modified: Tm,
|
pub last_modified: Tm,
|
||||||
pub crc32: u32,
|
pub crc32: u32,
|
||||||
compressed_size: u32,
|
pub compressed_size: u32,
|
||||||
uncompressed_size: u32,
|
pub uncompressed_size: u32,
|
||||||
pub file_name: Vec<u8>,
|
pub file_name: Vec<u8>,
|
||||||
extra_field: Vec<u8>,
|
pub extra_field: Vec<u8>,
|
||||||
header_end: u64,
|
pub header_end: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,11 +107,11 @@ impl LocalFileHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DataDescriptor
|
pub struct DataDescriptor
|
||||||
{
|
{
|
||||||
compressed_size: u32,
|
pub compressed_size: u32,
|
||||||
uncompressed_size: u32,
|
pub uncompressed_size: u32,
|
||||||
crc32: u32,
|
pub crc32: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataDescriptor
|
impl DataDescriptor
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
|
||||||
let months = (date & 0b0000000111100000) >> 5;
|
let months = (date & 0b0000000111100000) >> 5;
|
||||||
let years = (date & 0b1111111000000000) >> 9;
|
let years = (date & 0b1111111000000000) >> 9;
|
||||||
|
|
||||||
let datetime = format!("{:04u}-{:02u}-{:02u} {:02u}:{:02u}:{:02u}",
|
let datetime = format!("{:04u}-{:02u}-{:02u} {:02u}:{:02u}:{:02u}",
|
||||||
years as uint + 1980,
|
years as uint + 1980,
|
||||||
months,
|
months,
|
||||||
days,
|
days,
|
||||||
|
@ -19,7 +19,6 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
|
||||||
seconds);
|
seconds);
|
||||||
let format = "%Y-%m-%d %H:%M:%S";
|
let format = "%Y-%m-%d %H:%M:%S";
|
||||||
|
|
||||||
debug!("Parsing MsDos date: {}", datetime);
|
|
||||||
match time::strptime(datetime.as_slice(), format)
|
match time::strptime(datetime.as_slice(), format)
|
||||||
{
|
{
|
||||||
Ok(tm) => tm,
|
Ok(tm) => tm,
|
||||||
|
|
Loading…
Add table
Reference in a new issue