JSで適当に生成したZIPがLinuxで文字化けした話

参考: zipの仕様書

https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

zipはリトルエンディアンであるため先に出現するデータが下位バイトである。

https://qiita.com/McbeEringi/items/5acc1f940ff7504f7e16

# なんの話

うちのjs製zip生成が吐いたzipをunzipするとutf8のファイル名が文字化けした。
具体的には`General Purpose Bit Flag`のutf8のビットは既に立ててあるにもかかわらず、asciiとしてパースされてしまった。

# 比較

`a/あああ`を作成し、js製zip生成でzipを作成、比較対象として`zip -r0DX`(再帰、圧縮0、ディレクトリエントリ抜き、拡張フィールド抜き)でzipを作成した。

diffをとったら二箇所違いがあった

## `Version Made By`

作成した環境を記録する2Bのデータである。
下位バイトがPKZIPのバージョン、上位ビットが作成したOSを表す。
js製zip生成では`0x000a`、zip -r0DXは`0x031e`であった。

作成OSについては仕様書4.4.2.2に記載があり、`0x00`はMS-DOS、`0x03`はUNIXである

後方互換性について考えると、ここで下位バイトがあまり意味を持たないことは容易に想像できる。
試しに`0x03`を`0x00`に変えたところ、文字化けが再現した。
作成OSのデータが解凍時の挙動に影響を与えていたことがわかる。

解凍できたは良いものの、解凍したファイルから再び`zip -r0DX`しようとしたところPermission Deniedで止まった。
どうやら解凍時に正しい権限設定がなされていないようである。

## `External File Attributes`
作成OSによりプロトコルの異なる4Bのデータである。
js製zip生成では`0x00000000`、zip -r0DXは`0x81a40000`であった。

調べたところ以下が当たった。

https://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute

どうやら上位16bitは"`ls -la`で見る権限のやつ"、"chmodでよく入れるあの数字"らしい。
フォーマットはglibcの`bits/stat.h`に記載されているとのこと。

要点を抜粋する
```c
/* Encoding of the file mode.  */

#define	__S_IFMT	0170000	/* These bits determine file type.  */

/* File types.  */
#define	__S_IFDIR	0040000	/* Directory.  */
#define	__S_IFCHR	0020000	/* Character device.  */
#define	__S_IFBLK	0060000	/* Block device.  */
#define	__S_IFREG	0100000	/* Regular file.  */
#define	__S_IFIFO	0010000	/* FIFO.  */
#define	__S_IFLNK	0120000	/* Symbolic link.  */
#define	__S_IFSOCK	0140000	/* Socket.  */

/* POSIX.1b objects.  Note that these macros always evaluate to zero.  But
   they do it by enforcing the correct use of the macros.  */
#define __S_TYPEISMQ(buf)  ((buf)->st_mode - (buf)->st_mode)
#define __S_TYPEISSEM(buf) ((buf)->st_mode - (buf)->st_mode)
#define __S_TYPEISSHM(buf) ((buf)->st_mode - (buf)->st_mode)

/* Protection bits.  */

#define	__S_ISUID	04000	/* Set user ID on execution.  */
#define	__S_ISGID	02000	/* Set group ID on execution.  */
#define	__S_ISVTX	01000	/* Save swapped text after use (sticky).  */
#define	__S_IREAD	0400	/* Read by owner.  */
#define	__S_IWRITE	0200	/* Write by owner.  */
#define	__S_IEXEC	0100	/* Execute by owner.  */
```
頭の0は8進数を表す0なのでここで使われているビットはちょうど16bitである。


また、sys/stat.hには以下の記載もある
```c
#define	S_IRUSR	__S_IREAD	/* Read by owner.  */
#define	S_IWUSR	__S_IWRITE	/* Write by owner.  */
#define	S_IXUSR	__S_IEXEC	/* Execute by owner.  */
/* Read, write, and execute by owner.  */
#define	S_IRWXU	(__S_IREAD|__S_IWRITE|__S_IEXEC)

#ifdef __USE_MISC
# define S_IREAD	S_IRUSR
# define S_IWRITE	S_IWUSR
# define S_IEXEC	S_IXUSR
#endif

#define	S_IRGRP	(S_IRUSR >> 3)	/* Read by group.  */
#define	S_IWGRP	(S_IWUSR >> 3)	/* Write by group.  */
#define	S_IXGRP	(S_IXUSR >> 3)	/* Execute by group.  */
/* Read, write, and execute by group.  */
#define	S_IRWXG	(S_IRWXU >> 3)

#define	S_IROTH	(S_IRGRP >> 3)	/* Read by others.  */
#define	S_IWOTH	(S_IWGRP >> 3)	/* Write by others.  */
#define	S_IXOTH	(S_IXGRP >> 3)	/* Execute by others.  */
/* Read, write, and execute by others.  */
#define	S_IRWXO	(S_IRWXG >> 3)
```
下位6bitについても確認が取れた。

`0x81a40000`を読んでみる。
上位16bitを8進数に下すと`0o100644`である。
上記を参照するとRegular File、権限が644ということになる。

# 知見

General Purpose Flag Bitを立てて満足してはいけない