#![allow(dead_code)]
#![allow(unused_variables)]
use std::cmp::min;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::mem::size_of;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct Error {
kind: ErrorKind,
}
#[derive(Debug, Clone)]
pub enum ErrorKind {
IO(std::io::ErrorKind),
FromUtf8Error,
Other,
}
#[repr(u16)]
#[derive(Debug, Copy, Clone)]
pub enum CompressionMethod {
Uncompressed = 0,
Deflate = 8,
Unsupported = u16::MAX,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct ZipArchive<R: Read + io::Seek> {
reader: R,
eocd: ZipEndOfCentralDirectory,
files: Vec<ZipCentralDirectoryFile>,
names: HashMap<String, usize>,
}
#[derive(Debug)]
pub struct ZipFile {
header: ZipLocalFileHeader,
}
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
struct ZipLocalFileHeader {
signature: u32,
min_extract_ver: u16,
general_purpose_flag: u16,
compression_method: CompressionMethod,
last_mod_time: u16,
last_mod_date: u16,
crc32: u32,
compressed_len: u32,
uncompressed_len: u32,
file_name_len: u16,
extra_field_len: u16,
}
#[derive(Debug)]
pub struct ZipCentralDirectoryFile {
header: ZipCentralDirectoryFileHeader,
filename: String,
extra: Vec<u8>,
comment: String,
}
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
struct ZipCentralDirectoryFileHeader {
signature: u32,
made_by_ver: u16,
min_extract_ver: u16,
general_purpose_flag: u16,
compression_method: CompressionMethod,
last_mod_time: u16,
last_mod_date: u16,
crc32: u32,
compressed_len: u32,
uncompressed_len: u32,
file_name_len: u16,
extra_field_len: u16,
comment_len: u16,
start_disk: u16,
internal_attrib: u16,
external_attrib: u32,
relative_offset_of_local_header: u32,
}
#[derive(Debug)]
struct ZipEndOfCentralDirectory {
header: ZipEndOfCentralDirectoryHeader,
comment: String,
}
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
struct ZipEndOfCentralDirectoryHeader {
signature: u32,
disk_number: u16,
start_disk: u16,
num_disk_entries: u16,
num_entries: u16,
central_dir_len: u32,
cendral_dir_offset: u32,
comment_len: u16,
}
impl Error {
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error {
kind: ErrorKind::IO(error.kind()),
}
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(error: std::string::FromUtf8Error) -> Self {
Error {
kind: ErrorKind::FromUtf8Error,
}
}
}
impl<R: Read + io::Seek> ZipArchive<R> {
pub fn new(mut reader: R) -> Result<ZipArchive<R>> {
let eocd = ZipEndOfCentralDirectory::find(&mut reader)?;
reader.seek(io::SeekFrom::Start(eocd.header.cendral_dir_offset as u64))?;
let mut files = Vec::with_capacity(eocd.header.num_entries as usize);
let mut names = HashMap::with_capacity(eocd.header.num_entries as usize);
for _ in 0..eocd.header.num_entries {
let cdf = ZipCentralDirectoryFile::find(&mut reader)?;
names.insert(cdf.filename.clone(), files.len());
files.push(cdf);
}
Ok(ZipArchive {
reader: reader,
eocd: eocd,
files: files,
names: names,
})
}
pub fn files(&self) -> &Vec<ZipCentralDirectoryFile> {
&self.files
}
}
impl ZipArchive<File> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<ZipArchive<File>> {
match File::open(path) {
Ok(file) => Self::new(file),
_ => Err(Error {
kind: ErrorKind::Other,
}),
}
}
}
const BUF_SIZE: u64 = 65536;
const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
const CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x02014b50;
const END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06054b50;
impl ZipEndOfCentralDirectory {
fn find<R: Read + io::Seek>(reader: &mut R) -> Result<ZipEndOfCentralDirectory> {
let fsize = reader.seek(io::SeekFrom::End(0))?;
if fsize < size_of::<ZipEndOfCentralDirectoryHeader>() as u64 {
return Err(Error {
kind: ErrorKind::Other,
});
}
let nbytes = min(fsize, BUF_SIZE);
reader.seek(io::SeekFrom::Start(fsize - nbytes))?;
let mut buf = vec![0u8; nbytes as usize];
reader.read_exact(&mut buf)?;
let eocd_pos = match buf.windows(4).rev().position(|window| {
let array = <[u8; 4]>::try_from(window).unwrap();
u32::from_le_bytes(array) == END_OF_CENTRAL_DIRECTORY_SIGNATURE
}) {
Some(i) => i + 4,
None => {
return Err(Error {
kind: ErrorKind::Other,
})
}
};
let start = nbytes as usize - eocd_pos;
let mut bytes = &buf[start..start + size_of::<ZipEndOfCentralDirectoryHeader>()];
let signature = read_le_u32(&mut bytes);
let disk_number = read_le_u16(&mut bytes);
let start_disk = read_le_u16(&mut bytes);
let num_disk_entries = read_le_u16(&mut bytes);
let num_entries = read_le_u16(&mut bytes);
let central_dir_len = read_le_u32(&mut bytes);
let cendral_dir_offset = read_le_u32(&mut bytes);
let comment_len = read_le_u16(&mut bytes);
let start = start + size_of::<ZipEndOfCentralDirectoryHeader>();
let comment = String::from_utf8(buf[start..start + comment_len as usize].to_vec())?;
let header = ZipEndOfCentralDirectoryHeader {
signature: signature,
disk_number: disk_number,
start_disk: start_disk,
num_disk_entries: num_disk_entries,
num_entries: num_entries,
central_dir_len: central_dir_len,
cendral_dir_offset: cendral_dir_offset,
comment_len: comment_len,
};
Ok(ZipEndOfCentralDirectory {
header: header,
comment: comment,
})
}
}
impl ZipCentralDirectoryFile {
fn find<R: Read + io::Seek>(reader: &mut R) -> Result<ZipCentralDirectoryFile> {
let mut buf = [0u8; size_of::<ZipCentralDirectoryFileHeader>()];
reader.read_exact(&mut buf)?;
let mut bytes = &buf[..];
let signature = read_le_u32(&mut bytes);
if signature != CENTRAL_DIRECTORY_SIGNATURE {
return Err(Error {
kind: ErrorKind::Other,
});
}
let made_by_ver = read_le_u16(&mut bytes);
let min_extract_ver = read_le_u16(&mut bytes);
let general_purpose_flag = read_le_u16(&mut bytes);
let compression_method = read_le_u16(&mut bytes);
let last_mod_time = read_le_u16(&mut bytes);
let last_mod_date = read_le_u16(&mut bytes);
let crc32 = read_le_u32(&mut bytes);
let compressed_len = read_le_u32(&mut bytes);
let uncompressed_len = read_le_u32(&mut bytes);
let file_name_len = read_le_u16(&mut bytes);
let extra_field_len = read_le_u16(&mut bytes);
let comment_len = read_le_u16(&mut bytes);
let start_disk = read_le_u16(&mut bytes);
let internal_attrib = read_le_u16(&mut bytes);
let external_attrib = read_le_u32(&mut bytes);
let relative_offset_of_local_header = read_le_u32(&mut bytes);
let header = ZipCentralDirectoryFileHeader {
signature: signature,
made_by_ver: made_by_ver,
min_extract_ver: min_extract_ver,
general_purpose_flag: general_purpose_flag,
compression_method: compression_method.into(),
last_mod_time: last_mod_time,
last_mod_date: last_mod_date,
crc32: crc32,
compressed_len: compressed_len,
uncompressed_len: uncompressed_len,
file_name_len: file_name_len,
extra_field_len: extra_field_len,
comment_len: comment_len,
start_disk: start_disk,
internal_attrib: internal_attrib,
external_attrib: external_attrib,
relative_offset_of_local_header: relative_offset_of_local_header,
};
let mut buf =
vec![0u8; file_name_len as usize + extra_field_len as usize + comment_len as usize];
reader.read_exact(&mut buf)?;
let mut start = 0;
let mut end = file_name_len as usize;
let filename = match file_name_len {
0 => String::new(),
_ => String::from_utf8(buf[start..end].to_vec())?,
};
start = end;
end = start + extra_field_len as usize;
let extra = match extra_field_len {
0 => Vec::new(),
_ => buf[start..end].to_vec(),
};
start = end;
end = start + comment_len as usize;
let comment = match comment_len {
0 => String::new(),
_ => String::from_utf8(buf[start..end].to_vec())?,
};
Ok(ZipCentralDirectoryFile {
header: header,
filename: filename,
extra: extra,
comment: comment,
})
}
pub fn filename(&self) -> &String {
&self.filename
}
}
impl From<u16> for CompressionMethod {
fn from(n: u16) -> CompressionMethod {
match n {
0 => CompressionMethod::Uncompressed,
8 => CompressionMethod::Deflate,
_ => CompressionMethod::Unsupported,
}
}
}
fn read_le_u16(input: &mut &[u8]) -> u16 {
let (int_bytes, rest) = input.split_at(size_of::<u16>());
*input = rest;
u16::from_le_bytes(int_bytes.try_into().unwrap())
}
fn read_le_u32(input: &mut &[u8]) -> u32 {
let (int_bytes, rest) = input.split_at(size_of::<u32>());
*input = rest;
u32::from_le_bytes(int_bytes.try_into().unwrap())
}
fn read_le_u64(input: &mut &[u8]) -> u64 {
let (int_bytes, rest) = input.split_at(size_of::<u64>());
*input = rest;
u64::from_le_bytes(int_bytes.try_into().unwrap())
}