0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-11-16

参考: zipの仕様書

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

なんの話

うちの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である

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

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

External File Attributes

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

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

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

要点を抜粋する

/* 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には以下の記載もある

#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を立てて満足してはいけない

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?