はじめに
サーバーサイドの開発をしており、クライアントから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_(ファイルフォーマット) - Wikipedia。著者: T2y-1979。ライセンス: CC BY-SA 3.0。
中の構造としては各ファイルエントリ、セントラルディレクトリにも各ファイルの情報、そしてセントラルディレクトリの終端レコード(EOCD)という形になっている。
各領域は特定の4バイトの「シグネチャ」によって表される。
各ファイルの情報がファイルエントリとセントラルディレクトリに保持されるが、冗長性のためで、セントラルディレクトリの方が正式な情報である。
実際にZIPファイルを作成する
Macで下記のようなZIPファイルを作成しました。
❯ cat greeting/bar.txt
goodbye
❯ cat greeting/foo.txt
hello
❯ tree
.
├── greeting
│ ├── bar.txt
│ └── foo.txt
└── greeting.zip
ダンプを確認する
ファイルを16進数表示しASCII文字列も出してみます。
❯ 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 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
エントリが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
は、リトルエンディアンであることを考慮して2進数に戻すと1011001101110100
。
これを分解すると:
時: 10110 (22時)
分: 110110 (54分)
秒: 10000 (16秒、2秒の精度で丸めているらしい)
おわりに
普段よくつかっているZIPについて少し詳しくなりました。
コメントなど任意の長さの項目があるのでS3のZIPファイルにRangeで指定のbyteを取得してそこからデータ全体をDLせずに中身を判定するのはなかなか難しそうに見えました。
ZIPをあげるクライアントを信用してルールを取り決めをしておくとかであればできるかも?
また、まだまったく学んでない項目があり、特に圧縮メソッドなどは気になるところなので調べてみたいと思います。