1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
use chrono::DateTime;
use format::*;
use std::fs;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};

use util;

pub struct ZipperOptions {
    pub path: PathBuf,
}

pub struct Zipper {
    opts: ZipperOptions,
    headers: Vec<CDFileHeader>,
    outfile: fs::File,
    offset: usize,
}

impl Zipper {
    pub fn new(opts: ZipperOptions) -> Self {
        debug!("Creating new zipper at {:?} ...", opts.path);

        let file = fs::File::create(&opts.path).unwrap();

        Self {
            opts: opts,
            headers: vec![],
            outfile: file,
            offset: 0,
        }
    }

    pub fn add_file(self: &mut Self, real_path: &Path, zip_path: &Path) {
        debug!("Adding {:?} as {:?} to zipper...", real_path, zip_path);

        let metadata = fs::metadata(real_path).unwrap();
        let mut file = fs::File::open(real_path).unwrap();
        let mut buffer = Vec::new();
        let bytes_read = file.read_to_end(&mut buffer).unwrap();

        // Setup headers
        let mut local_header = LocalFileHeader::default();
        let mut cd_header = CDFileHeader::default();

        // Set signatures
        local_header.signature = LOCAL_FILE_HEADER_SIGNATURE;
        cd_header.signature = CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE;

        // Calculate CRC32
        file.seek(SeekFrom::Start(0)).unwrap();
        let crc = util::calculate_crc(&mut file);
        debug!("- CRC32: {:#X?}", crc);
        local_header.crc32 = crc;
        cd_header.crc32 = crc;

        // Compression info
        local_header.compression_method = CompressionMethod::Stored as u16;
        cd_header.compression_method = CompressionMethod::Stored as u16;

        // Data metadata
        let file_len = metadata.len() as u32;
        local_header.compressed_size = file_len;
        cd_header.compressed_size = file_len;
        local_header.uncompressed_size = file_len;
        cd_header.uncompressed_size = file_len;

        // Store file name
        let file_name_data = zip_path.to_str().unwrap().as_bytes().to_vec();
        debug!("- File Name: {:?}", zip_path);
        local_header.file_name = file_name_data.clone();
        local_header.file_name_len = file_name_data.len() as u16;
        cd_header.file_name = file_name_data.clone();
        cd_header.file_name_len = file_name_data.len() as u16;

        // Date/time
        let last_modified = DateTime::from(metadata.modified().unwrap());
        debug!("- Last Modified: {:?}", last_modified);
        local_header.modified_time = util::get_timepart(&last_modified);
        local_header.modified_date = util::get_datepart(&last_modified);
        cd_header.modified_time = util::get_timepart(&last_modified);
        cd_header.modified_date = util::get_datepart(&last_modified);


        self.offset += self.outfile.write(&local_header.to_le_bytes()).unwrap();
        self.offset += self.outfile.write(&buffer).unwrap();

        cd_header.rel_offset = self.offset as u32;

        self.headers.push(cd_header);
    }

    pub fn finish(mut self) {
        debug!("Finishing zip...");

        // Write headers
        let mut bytes_written = 0;
        for header in &self.headers {
            bytes_written += self.outfile.write(&header.to_le_bytes()).unwrap();
        }

        // Setup EOCD
        let mut eocd = EndOfCD::default();
        eocd.signature = END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE;
        eocd.total_records = self.headers.len() as u16;
        eocd.cd_size = bytes_written as u32;
        eocd.cd_offset = self.offset as u32;

        self.outfile.write(&eocd.to_le_bytes()).unwrap();
    }
}