ZIPの構造を確認する

# はじめに

サーバーサイドの開発をしており、クライアントからS3へアップロードされたZIPファイルを操作するタスクがありました。
普段ZIPファイルを利用することはあっても仕様については知らないので少しだけ調べてみることにしました。

# ZIPの構造

日本語で確認するにはウィキペディアがわかりやすかったです。
https://ja.wikipedia.org/wiki/ZIP_(%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88)

### 登場人物

- ファイルエントリ: ZIPの中に格納されているファイルたち（ディレクトリも含む）
    - ローカルファイルヘッダ: ファイルサイズやファイル名などの情報
    - 拡張データフィールド
    - ファイルデータ
- セントラルディレクトリ: 各エントリの情報
- セントラルディレクトリ終端レコード（EOCD）: ZIPファイルの終わりを表すレコード



### 構造
![ZIP図](https://upload.wikimedia.org/wikipedia/commons/5/5e/ZIPformat_ja.jpg)
出典: [ZIP_(ファイルフォーマット) - Wikipedia](https://upload.wikimedia.org/wikipedia/commons/5/5e/ZIPformat_ja.jpg)。著者: T2y-1979。ライセンス: [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)。

中の構造としては各ファイルエントリ、セントラルディレクトリにも各ファイルの情報、そしてセントラルディレクトリの終端レコード（EOCD）という形になっている。

各領域は特定の4バイトの「シグネチャ」によって表される。

各ファイルの情報がファイルエントリとセントラルディレクトリに保持されるが、冗長性のためで、セントラルディレクトリの方が正式な情報である。


# 実際にZIPファイルを作成する

Macで下記のようなZIPファイルを作成しました。

```bash
❯ cat greeting/bar.txt
goodbye
❯ cat greeting/foo.txt
hello
❯ tree
.
├── greeting
│   ├── bar.txt
│   └── foo.txt
└── greeting.zip

```

# ダンプを確認する

ファイルを１６進数表示しASCII文字列も出してみます。

```bash
❯ hexdump -C greeting.zip
00000000  50 4b 03 04 0a 00 00 00  00 00 74 b3 51 58 00 00  |PK........t.QX..|
00000010  00 00 00 00 00 00 00 00  00 00 09 00 1c 00 67 72  |..............gr|
00000020  65 65 74 69 6e 67 2f 55  54 09 00 03 4b b4 d0 65  |eeting/UT...K..e|
00000030  57 b4 d0 65 75 78 0b 00  01 04 f5 01 00 00 04 14  |W..eux..........|
00000040  00 00 00 50 4b 03 04 0a  00 00 00 00 00 61 b3 51  |...PK........a.Q|
00000050  58 86 a6 10 36 05 00 00  00 05 00 00 00 10 00 1c  |X...6...........|
00000060  00 67 72 65 65 74 69 6e  67 2f 66 6f 6f 2e 74 78  |.greeting/foo.tx|
00000070  74 55 54 09 00 03 26 b4  d0 65 48 b4 d0 65 75 78  |tUT...&..eH..eux|
00000080  0b 00 01 04 f5 01 00 00  04 14 00 00 00 68 65 6c  |.............hel|
00000090  6c 6f 50 4b 03 04 0a 00  00 00 00 00 7c b3 51 58  |loPK........|.QX|
000000a0  74 d8 8f 0d 07 00 00 00  07 00 00 00 10 00 1c 00  |t...............|
000000b0  67 72 65 65 74 69 6e 67  2f 62 61 72 2e 74 78 74  |greeting/bar.txt|
000000c0  55 54 09 00 03 5c b4 d0  65 83 b4 d0 65 75 78 0b  |UT...\..e...eux.|
000000d0  00 01 04 f5 01 00 00 04  14 00 00 00 67 6f 6f 64  |............good|
000000e0  62 79 65 50 4b 01 02 1e  03 0a 00 00 00 00 00 74  |byePK..........t|
000000f0  b3 51 58 00 00 00 00 00  00 00 00 00 00 00 00 09  |.QX.............|
00000100  00 18 00 00 00 00 00 00  00 10 00 ed 41 00 00 00  |............A...|
00000110  00 67 72 65 65 74 69 6e  67 2f 55 54 05 00 03 4b  |.greeting/UT...K|
00000120  b4 d0 65 75 78 0b 00 01  04 f5 01 00 00 04 14 00  |..eux...........|
00000130  00 00 50 4b 01 02 1e 03  0a 00 00 00 00 00 61 b3  |..PK..........a.|
00000140  51 58 86 a6 10 36 05 00  00 00 05 00 00 00 10 00  |QX...6..........|
00000150  18 00 00 00 00 00 01 00  00 00 a4 81 43 00 00 00  |............C...|
00000160  67 72 65 65 74 69 6e 67  2f 66 6f 6f 2e 74 78 74  |greeting/foo.txt|
00000170  55 54 05 00 03 26 b4 d0  65 75 78 0b 00 01 04 f5  |UT...&..eux.....|
00000180  01 00 00 04 14 00 00 00  50 4b 01 02 1e 03 0a 00  |........PK......|
00000190  00 00 00 00 7c b3 51 58  74 d8 8f 0d 07 00 00 00  |....|.QXt.......|
000001a0  07 00 00 00 10 00 18 00  00 00 00 00 01 00 00 00  |................|
000001b0  a4 81 92 00 00 00 67 72  65 65 74 69 6e 67 2f 62  |......greeting/b|
000001c0  61 72 2e 74 78 74 55 54  05 00 03 5c b4 d0 65 75  |ar.txtUT...\..eu|
000001d0  78 0b 00 01 04 f5 01 00  00 04 14 00 00 00 50 4b  |x.............PK|
000001e0  05 06 00 00 00 00 03 00  03 00 fb 00 00 00 e3 00  |................|
000001f0  00 00 00 00                                       |....|
000001f4
```

各領域のシグネチャは下記の通りです。
- ローカルファイルヘッダ: 504B0304
- セントラルディレクトリエントリ: 504B0102
- ZIPセントラルディレクトリの終端レコード: 504B0506

該当部分に色をつけます。

00000000  <font color="Salmon">50 4b 03 04</font> 0a 00 00 00  00 00 74 b3 51 58 00 00  |PK........t.QX..|
00000010  00 00 00 00 00 00 00 00  00 00 09 00 1c 00 67 72  |..............gr|
00000020  65 65 74 69 6e 67 2f 55  54 09 00 03 4b b4 d0 65  |eeting/UT...K..e|
00000030  57 b4 d0 65 75 78 0b 00  01 04 f5 01 00 00 04 14  |W..eux..........|
00000040  00 00 00 <font color="Salmon">50 4b 03 04</font> 0a  00 00 00 00 00 61 b3 51  |...PK........a.Q|
00000050  58 86 a6 10 36 05 00 00  00 05 00 00 00 10 00 1c  |X...6...........|
00000060  00 67 72 65 65 74 69 6e  67 2f 66 6f 6f 2e 74 78  |.greeting/foo.tx|
00000070  74 55 54 09 00 03 26 b4  d0 65 48 b4 d0 65 75 78  |tUT...&..eH..eux|
00000080  0b 00 01 04 f5 01 00 00  04 14 00 00 00 68 65 6c  |.............hel|
00000090  6c 6f <font color="Salmon">50 4b 03 04</font> 0a 00  00 00 00 00 7c b3 51 58  |loPK........|.QX|
000000a0  74 d8 8f 0d 07 00 00 00  07 00 00 00 10 00 1c 00  |t...............|
000000b0  67 72 65 65 74 69 6e 67  2f 62 61 72 2e 74 78 74  |greeting/bar.txt|
000000c0  55 54 09 00 03 5c b4 d0  65 83 b4 d0 65 75 78 0b  |UT...\..e...eux.|
000000d0  00 01 04 f5 01 00 00 04  14 00 00 00 67 6f 6f 64  |............good|
000000e0  62 79 65 <font color="LightGreen">50 4b 01 02</font> 1e  03 0a 00 00 00 00 00 74  |byePK..........t|
000000f0  b3 51 58 00 00 00 00 00  00 00 00 00 00 00 00 09  |.QX.............|
00000100  00 18 00 00 00 00 00 00  00 10 00 ed 41 00 00 00  |............A...|
00000110  00 67 72 65 65 74 69 6e  67 2f 55 54 05 00 03 4b  |.greeting/UT...K|
00000120  b4 d0 65 75 78 0b 00 01  04 f5 01 00 00 04 14 00  |..eux...........|
00000130  00 00 <font color="LightGreen">50 4b 01 02</font> 1e 03  0a 00 00 00 00 00 61 b3  |..PK..........a.|
00000140  51 58 86 a6 10 36 05 00  00 00 05 00 00 00 10 00  |QX...6..........|
00000150  18 00 00 00 00 00 01 00  00 00 a4 81 43 00 00 00  |............C...|
00000160  67 72 65 65 74 69 6e 67  2f 66 6f 6f 2e 74 78 74  |greeting/foo.txt|
00000170  55 54 05 00 03 26 b4 d0  65 75 78 0b 00 01 04 f5  |UT...&..eux.....|
00000180  01 00 00 04 14 00 00 00  <font color="LightGreen">50 4b 01 02</font> 1e 03 0a 00  |........PK......|
00000190  00 00 00 00 7c b3 51 58  74 d8 8f 0d 07 00 00 00  |....|.QXt.......|
000001a0  07 00 00 00 10 00 18 00  00 00 00 00 01 00 00 00  |................|
000001b0  a4 81 92 00 00 00 67 72  65 65 74 69 6e 67 2f 62  |......greeting/b|
000001c0  61 72 2e 74 78 74 55 54  05 00 03 5c b4 d0 65 75  |ar.txtUT...\..eu|
000001d0  78 0b 00 01 04 f5 01 00  00 04 14 00 00 00 <font color="Cyan">50 4b</font>  |x.............PK|
000001e0  <font color="Cyan">05 06</font> 00 00 00 00 03 00  03 00 fb 00 00 00 e3 00  |................|
000001f0  00 00 00 00                                       |....|
000001f4

エントリが3つあることを確認できます。

### EOCDの情報

EOCDの情報に出力した実際の値を載せると下記の通りです。
複数バイトの値はリトルエンディアンで格納されるので小さい桁が先に表示されています。

| オフセット | サイズ | 内容                                                          | 実際の値              |
|------------|--------|---------------------------------------------------------------|-----------------------|
| 0          | 4      | セントラルディレクトリの終端レコードのシグネチャ = 0x504B0506（PK\005\006） | 50 4b 05 06          |
| 4          | 2      | このディスクの数                                              | 00 00                 |
| 6          | 2      | セントラルディレクトリが開始するディスク                        | 00 00                 |
| 8          | 2      | このディスク上のセントラルディレクトリレコードの数              | 03 00                 |
| 10         | 2      | セントラルディレクトリレコードの合計数                          | 03 00                 |
| 12         | 4      | セントラルディレクトリのサイズ (バイト)                         | fb 00 00 00           |
| 16         | 4      | セントラルディレクトリの開始位置のオフセット                    | e3 00 00 00           |
| 20         | 2      | ZIPファイルのコメントの長さ (n)                                | 00 00                 |
| 22         | n      | ZIPファイルのコメント                                          | -                     |


セントラルディレクトリのサイズのfbは10進数で251
セントラルディレクトリのオフセットのe3は10進数で227
数えると合っていそう。

### セントラルディレクトリの情報


| オフセット | サイズ | 内容                                      | 実際の値1                                   | 実際の値2                                  | 実際の値3                                  |
|------------|--------|-------------------------------------------|--------------------------------------------|-------------------------------------------|-------------------------------------------|
| 0          | 4      | セントラルディレクトリエントリのシグネチャ | 50 4b 01 02                                | 50 4b 01 02                               | 50 4b 01 02                               |
| 4          | 2      | 作成されたバージョン                      | 1e 03                                      | 1e 03                                     | 1e 03                                     |
| 6          | 2      | 展開に必要なバージョン                    | 0a 00                                      | 0a 00                                     | 0a 00                                     |
| 8          | 2      | 汎用目的のビットフラグ                    | 00 00                                      | 00 00                                     | 00 00                                     |
| 10         | 2      | 圧縮メソッド                              | 00 00                                      | 00 00                                     | 00 00                                     |
| 12         | 2      | ファイルの最終変更時間                    | 74 b3                                      | 61 b3                                     | 7c b3                                     |
| 14         | 2      | ファイルの最終変更日付                    | 51 58                                      | 51 58                                     | 51 58                                     |
| 16         | 4      | CRC-32                                    | 00 00 00 00                                | 86 a6 10 36                               | 74 d8 8f 0d                               |
| 20         | 4      | 圧縮サイズ                                | 00 00 00 00                                | 05 00 00 00                               | 07 00 00 00                               |
| 24         | 4      | 非圧縮サイズ                              | 00 00 00 00                                | 05 00 00 00                               | 07 00 00 00                               |
| 28         | 2      | ファイル名の長さ                          | 09 00                                      | 10 00                                     | 10 00                                     |
| 30         | 2      | 拡張フィールドの長さ                      | 18 00                                      | 18 00                                     | 18 00                                     |
| 32         | 2      | ファイルコメントの長さ                    | 00 00                                      | 01 00                                     | 01 00                                     |
| 34         | 2      | ファイルが開始するディスク番号            | 00 00                                      | 00 00                                     | 00 00                                     |
| 36         | 2      | 内部ファイル属性                          | 00 00                                      | 00 00                                     | 00 00                                     |
| 38         | 4      | 外部ファイル属性                          | 00 00 00 00                                | 00 00 00 00                               | 00 00 00 00                               |
| 42         | 4      | ローカルファイルヘッダの相対オフセット    | ed 41 00 00                                | a4 81 43 00                               | a4 81 92 00                               |
| 46         | n      | ファイル名                                | 00 00 67 72 65 65 74 69 6e 67 2f                                  | 67 72 65 65 74 69 6e 67 2f 66 6f 6f 2e 74 78 74                         | 67 72 65 65 74 69 6e 67 2f 62 61 72 2e 74 78 74                         |
| 46+n       | m      | 拡張フィールド                            | 略                    | 略                   | 略                   |


ファイルの更新変更時間の例えば`74 b3`は、リトルエンディアンであることを考慮して２進数に戻すと`1011001101110100`。
これを分解すると:
時: 10110 (22時)
分: 110110 (54分)
秒: 10000 (16秒、2秒の精度で丸めているらしい）


# おわりに

普段よくつかっているZIPについて少し詳しくなりました。
コメントなど任意の長さの項目があるのでS3のZIPファイルにRangeで指定のbyteを取得してそこからデータ全体をDLせずに中身を判定するのはなかなか難しそうに見えました。
ZIPをあげるクライアントを信用してルールを取り決めをしておくとかであればできるかも？
また、まだまったく学んでない項目があり、特に圧縮メソッドなどは気になるところなので調べてみたいと思います。