2007年3月3日土曜日

日本語のファイル名を含んだzipアーカイブが、unzipで展開すると文字化けすることがある

以前から気になっていたのですが、



  1. Windows上で、ファイル名に日本語を使っているファイルをzip形式でアーカイブして、


  2. FreeBSDに持っていて、


  3. FreeBSD上でunzipコマンドを使って展開すると、


ファイル名が文字化けすることがあります。たとえば、こんな具合。



200703021




Fedora CoreなLinuxマシンでためしても同じ文字化けをしたので、FreeBSDだけの問題ではないようです。



また、日本語のファイル名を使っていると100%常に文字化けするわけでもなく、ちゃんと日本語のファイル名で展開できることがあります。



文字化けする場合、その原因にはいくつもありまして、たとえば、ファイル名はShift-JISエンコーディングになってしまって、画面上では文字化けして見えてしまうこともあります。そのときは、たとえばconvmvというツールを使って(FreeBSDだとports/converters/convmvからインストールできます)、ファイル名の文字コード変換をすれば、正しい文字に直すことができます。



今回話題にとりあげた文字化け現象は、convmvを使ったところで、どうやっても正常なファイル名に戻せないような文字化けです。hdコマンド(16進ダンプ)を使って、正常な場合と、unzipしたあとの場合のコード、両方を見比べて、「こりゃ~わけわかんねーな」と結論に至った、文字化け現象です。



ネット検索してみると、そういった話題がちらほら見つかったので、unzipでファイル名が文字化けするのは、すでによく知られた問題のようです。



たとえば、これが参考になりました。

http://mail.nl.linux.org/linux-utf8/2005-06/msg00010.html

SUMMARY: Zip/Unzip and encoding problem (Was: Re: How to detect the encoding of a string?)



どうすればいいのか、ズバリな解決方法はのっていませんでしたので、これを手がかりに、ちょこっとunzipのソースコードを探ってみまして、一応、自分なりの解決方法を見つけました。ちょこっと細工をしてunzipをコンパイルすることで、文字化けを防ぐことができました。



☆ ☆ ☆ 



上記のページからリンクされている、
http://mail.nl.linux.org/linux-utf8/2005-06/msg00006.html
によれば、unzipが、わがままにファイル名の文字コード変換をしているとのことで、しかもこのコード変換をやめさせるようなオプションは存在しないらしいです。



じゃあ、ソースコードをかきかえちゃえっ!・・・というわけで、さっそくunzipのソースを見てみました。FreeBSDでportsを使っていると、「make patch」のコマンド1発でソースが展開されるので、便利ですねぇ。



とりあえず、文字コード変換をどこでやっているのか、あたりをつけるために、まずは、convとかlatin(←上記のWebページにでていた単語)とか、ありがちな単語でgrepしまくってみます。



くさい場所が絞り込まれてきたら、伝家の宝刀「printfデバッグ」メソッドを適用し、場所を特定します(笑)。



・・・というような、洗練されていない方法ではありますが、今回は、わりとすぐに見つかりました。その場所とは・・・



unzpriv.hというファイルで定義されている、Ext_ASCII_TO_Nativeというマクロでした。



/* Convert filename (and file comment string) into "internal" charset.
* This macro assumes that Zip entry filenames are coded in OEM (IBM DOS)
* codepage when made on
*  -> DOS (this includes 16-bit Windows 3.1)  (FS_FAT_)
*  -> OS/2                                    (FS_HPFS_)
*  -> Win95/WinNT with Nico Mak's WinZip      (FS_NTFS_ && hostver == "5.0")
* EXCEPTIONS:
*  PKZIP for Windows 2.5, 2.6, and 4.0 flag their entries as "FS_FAT_", but
*  the filename stored in the local header is coded in Windows ANSI (CP 1252
*  resp. ISO 8859-1 on US and western Europe locale settings).
*  Likewise, PKZIP for UNIX 2.51 flags its entries as "FS_FAT_", but the
*  filenames stored in BOTH the local and the central header are coded
*  in the local system's codepage (usually ANSI codings like ISO 8859-1).
*
* All other ports are assumed to code zip entry filenames in ISO 8859-1.
*/
#ifndef Ext_ASCII_TO_Native
#  define Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr) \
    if (((hostnum) == FS_FAT_ && \
         !(((islochdr) || (isuxatt)) && \
           ((hostver) == 25 || (hostver) == 26 || (hostver) == 40))) || \
        (hostnum) == FS_HPFS_ || \
        ((hostnum) == FS_NTFS_ && (hostver) == 50)) { \
        _OEM_INTERN((string)); \
    } else { \
        _ISO_INTERN((string)); \
    }
#endif


これを見ると、どのソフトでZIPアーカイブを作成したか、など、ある種の条件によって、コード変換をするかしないかを切り替えているので、冒頭で述べたように、文字化けしたりしなかったり、というのも納得です。ちなみに、文字化けしたZIPアーカイブは、Windows XPで、NTFS上のファイルを、Lhaplusでアーカイブしたものでした。


なお、このマクロが使われているのは、fileio.cの中の、このあたりです。

            if (option == DISPL_8) {
                /* translate the text coded in the entry's host-dependent
                   "extended ASCII" charset into the compiler's (system's)
                   internal text code page */
                Ext_ASCII_TO_Native((char *)G.outbuf, G.pInfo->hostnum,
                                    G.pInfo->hostver, G.pInfo->HasUxAtt,
                                    FALSE);


        /* translate the Zip entry filename coded in host-dependent "extended
           ASCII" into the compiler's (system's) internal text code page */
        Ext_ASCII_TO_Native(G.filename, G.pInfo->hostnum, G.pInfo->hostver,
                            G.pInfo->HasUxAtt, (option == DS_FN_L));

        if (G.pInfo->lcflag)      /* replace with lowercase filename */
            STRLOWER(G.filename, G.filename);



さてさて、というわけで、このマクロを無効にしてしまえば、ファイル名の文字コード変換が行われなくなります。マクロ定義の内容部分をコメントアウトしてもいいですし、#ifndefでガードが書かれていますから、



#ifndef Ext_ASCII_TO_Native



の行の手前に、

#define Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)



を入れちゃうってのもいいかもしれません。



ソースコードを変更しないおしゃれな方法としては、コンパイラの-Dオプションを使って、-D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)'というように、マクロ定義をしてしまう方法もあります・・・というか、今回GCCのinfo(正確にはCPPのinfo)を調べてみてはじめて知ったんですが、-Dオプションで、引数つきのマクロ定義もできたんですね。shellにカッコとかの文字が食べられちゃうことがあるので、そのへんをうまくエスケープさせるなど配慮が必要なようです。



FreeBSDでportsを使ってコンパイルする場合(/usr/ports/archivers/unzip/)、こんなコマンド1発で、文字コード変換を無効にしたunzipをビルドできます。



% make "CFLAGS+=-D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)'"



参考までにコンパイル中のログ。



===>  Extracting for unzip-5.52_3
=> MD5 Checksum OK for unzip552.tar.gz.
=> SHA256 Checksum OK for unzip552.tar.gz.
===>  Patching for unzip-5.52_3
===>  Applying FreeBSD patches for unzip-5.52_3
===>  Configuring for unzip-5.52_3
===>  Building for unzip-5.52_3
NOTE:  use bsd target for non-Intel FreeBSD compiles (if any).
make unzips CC="cc" LD="cc" AS="cc" CF="-Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK" AF="-Di386 -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK" CRC32=crc_gcc
cc -c -Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK unzip.c
cc -Di386 -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK -x assembler-with-cpp -c -o crc_gcc.o crc_i386.S
cc -c -Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK crctab.c



マクロを無力化させたため、fileio.cをコンパイルするときに、warningがでてますね。



cc -c -Wall -I. -DASM_CRC -DUNIX -DBSD -D'Ext_ASCII_TO_Native(string, hostnum, hostver, isuxatt, islochdr)' -DUSE_UNSHRINK fileio.c
fileio.c: In function `do_string':
fileio.c:2062: warning: statement with no effect
fileio.c:2144: warning: statement with no effect



こうやってビルドしたunzipを試してみると・・・文字化けしなくなりました。



200703022




ここでは一応、nkfで文字コード変換して、見た目で文字化けしないようにしています(unzipして展開されたファイルのファイル名はShift-JISになっていました)。



あと、細かいことは気にしないように。





これで副作用が発生しないか自信がないので、unzip-noconvという別の名前で、インストールすることにしました。



2 件のコメント:

  1. この問題ってSolarisについてるunzipでも同じことが起きるんですよねぇ。
    私が試した限りでは、XP標準機能でzip圧縮したものは問題なかったのですが、lhasaで圧縮したものはNGでした。
    まぁzipにするならascii文字以外使うなよぉってことなのかもしらんですが(^^;)

    返信削除
  2. 同じ問題で困っていました。
    環境はlinuxですが同様に解決できました。ありがとうございます。
    distribution: debian-etch-i386
    unzip_version: unzip-5.52

    返信削除