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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use super::zip_error::ZipReadError;
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use std::io::prelude::*;
use std::io::SeekFrom;

/// magick number of EOCD
const EOCD_MAGIC: [u8; 4] = [0x50, 0x4b, 0x5, 0x6];

/// EOCD (End of Central Directory) 情報を保持する構造体 /
/// Structure that contains EOCD (End of Central Directory) information
pub struct ZipEOCD {
    /// EOCDが存在するディスク番号 (0起算) /
    /// number of the disk where EOCD exists (0-based)
    pub eocd_disk_index: u16,
    /// セントラルディレクトリが始まるディスク番号 (0起算) /
    /// number of the disk with the start of the central directory (0-based)
    pub cd_start_disk_index: u16,
    /// EOCDがあるディスク内のセントラルディレクトリ総数 /
    /// total number of entries in the central directory on the disk whare EOCD exists
    pub n_cd_entries_in_disk: u16,
    /// セントラルディレクトリ総数 /
    /// total number of entries in the central directory
    pub n_cd_entries: u16,
    /// セントラルディレクトリのサイズ /
    /// size of the central directory
    pub cd_size: u32,
    /// セントラルディレクトリ開始位置 (絶対・0起算)
    /// offset of start of central directory (with respect to the starting disk number) (absolute value; 0-based)
    pub cd_starting_position: u32,
    /// ZIPコメント長
    /// ZIP file comment length
    pub comment_length: u16,
    /// ZIPコメント
    /// ZIP file comment
    pub comment: Vec<u8>,

    // EOCDのエントリここまで / End of EOCD entries
    /// EOCDの開始位置 (マジックナンバー) /
    /// (magick number of) EOCD starting position
    pub starting_position_with_signature: u64,
    /// EOCDの開始位置 (マジックナンバーすぐ次) /
    /// EOCD starting position (next to magick number)
    pub starting_position_without_signature: u64,
}

impl ZipEOCD {
    /// EOCDのマジックナンバーの次の文字が読み取り位置である`Read`オブジェクトから、EOCD情報オブジェクトを生成 /
    /// Generates an EOCD information object from a `Read` object whose read position is the next character of the magic number of the EOCD.
    ///
    /// # Arguments
    ///
    /// * `read` - マジックナンバーの直後を指している`Read`オブジェクト
    /// * `pos` - マジックナンバーの直後のファイル位置 (デフォルト: 0)
    fn from_reader_next_to_signature<T: ReadBytesExt + std::io::Seek>(
        &mut self,
        read: &mut T,
    ) -> Result<bool, std::io::Error> {
        self.starting_position_without_signature = read.seek(SeekFrom::Current(0))?;
        self.starting_position_with_signature =
            self.starting_position_without_signature - EOCD_MAGIC.len() as u64;
        self.eocd_disk_index = read.read_u16::<LE>()?;
        self.cd_start_disk_index = read.read_u16::<LE>()?;
        self.n_cd_entries_in_disk = read.read_u16::<LE>()?;
        self.n_cd_entries = read.read_u16::<LE>()?;
        self.cd_size = read.read_u32::<LE>()?;
        self.cd_starting_position = read.read_u32::<LE>()?;
        self.comment_length = read.read_u16::<LE>()?;
        // + 1 for EOF detection
        let mut comment = read.take((self.comment_length as u64) + 1);
        self.comment.reserve(
            ((self.comment_length as usize) + 1)
                .checked_sub(self.comment.len())
                .unwrap_or(0),
        );
        let read_comment_length = comment.read_to_end(&mut self.comment)?;
        if read_comment_length != (self.comment_length as usize) {
            return Ok(false);
        }
        return Ok(true);
    }

    ///空のEOCDオブジェクトを生成 /
    /// Generates an empty EOCD object
    fn empty() -> ZipEOCD {
        return ZipEOCD {
            eocd_disk_index: 0,
            cd_start_disk_index: 0,
            n_cd_entries_in_disk: 0,
            n_cd_entries: 0,
            cd_size: 0,
            cd_starting_position: 0,
            comment_length: 0,
            comment: vec![],
            starting_position_with_signature: 0,
            starting_position_without_signature: 0,
        };
    }

    /// Writes EOCD to stream.
    ///
    /// # Errors
    ///
    /// Returns an error if writing fails.
    pub fn write<T: WriteBytesExt>(&self, write: &mut T) -> std::io::Result<()> {
        write.write_all(&EOCD_MAGIC)?;
        write.write_u16::<LE>(self.eocd_disk_index)?;
        write.write_u16::<LE>(self.cd_start_disk_index)?;
        write.write_u16::<LE>(self.n_cd_entries_in_disk)?;
        write.write_u16::<LE>(self.n_cd_entries)?;
        write.write_u32::<LE>(self.cd_size)?;
        write.write_u32::<LE>(self.cd_starting_position)?;
        write.write_u16::<LE>(self.comment_length)?;
        write.write_all(self.comment.as_slice())?;
        return Ok(());
    }

    pub fn from_reader<T: ReadBytesExt + std::io::Seek>(
        read: &mut T,
    ) -> Result<ZipEOCD, ZipReadError> {
        let mut eocd = ZipEOCD::empty();
        let zip_size = read.seek(SeekFrom::End(0))?;
        // comment is 65535 bytes at most
        // from: https://github.com/mvdnes/zip-rs/blob/003440bfe3823a01f11047c42e441999f0554daf/src/spec.rs
        let zip_eocd_left_bound_pos = zip_size
            .checked_sub(
                (u16::MAX as u64)
                    + (std::mem::size_of::<ZipEOCD>() as u64)
                    + (EOCD_MAGIC.len() as u64),
            )
            .unwrap_or(0);
        let mut pos = read.seek(SeekFrom::Start(zip_eocd_left_bound_pos))?;

        // Start searching for candicdates of magick numbers
        let mut eocd_magic_point: usize = 0;
        let mut buf_u8: [u8; 1] = [0];
        while read.read_exact(&mut buf_u8).is_ok() {
            // not magick numbers
            if EOCD_MAGIC[eocd_magic_point] != buf_u8[0] {
                eocd_magic_point = if EOCD_MAGIC[0] == buf_u8[0] {
                    1 as usize
                } else {
                    0 as usize
                };

                pos += 1;
                continue;
            }
            eocd_magic_point += 1;
            // magick numbers found
            if eocd_magic_point >= EOCD_MAGIC.len() {
                if eocd.from_reader_next_to_signature(read)? {
                    return Ok(eocd);
                }
                // not magick numbers
                // Restore pre-check state
                read.seek(SeekFrom::Start(pos))?;
                eocd_magic_point = 0;
            }
            pos += 1;
        }
        return Err(ZipReadError::InvalidZipArchive {
            reason: format!(
                "valid end of central directory signature (PK\\x05\\x06) was not found"
            ),
        });
    }

    /// 分割されたZIPファイルでなければtrue /
    /// Returns `true` if ZIP archive is NOT splitted
    pub fn is_single_archive(&self) -> bool {
        return self.eocd_disk_index == 0 && self.n_cd_entries == self.n_cd_entries_in_disk;
    }

    /// ZIP64ならtrue /
    /// Returns `true` if ZIP archive is ZIP64
    pub fn is_zip64(&self) -> bool {
        // Prioritize the ones that are likely to overflow.
        return self.cd_starting_position == u32::MAX
            || self.cd_size == u32::MAX
            || self.n_cd_entries == u16::MAX
            || self.n_cd_entries_in_disk == u16::MAX
            || self.eocd_disk_index == u16::MAX
            || self.cd_start_disk_index == u16::MAX;
    }

    /// Checks if ZIP archive is not supported.
    ///
    /// # Errors
    ///
    /// If not supported, returns `ZipReadError`.
    pub fn check_unsupported_zip_type(&self) -> Result<(), ZipReadError> {
        if !self.is_single_archive() {
            return Err(ZipReadError::UnsupportedZipArchive {
                reason: "it is one of splitted arvhives".to_string(),
            });
        }
        if self.is_zip64() {
            return Err(ZipReadError::UnsupportedZipArchive {
                reason: "it is ZIP64 formatted".to_string(),
            });
        }
        return Ok(());
    }
}