C# Zipファイルの操作

Zipファイルの中身のデータを操作するアプリケーションを作ったときに、注意しないといけなかった点のまとめ。
ググると結構出てくるネタだけど、自分なり解釈してまとめたもの。

  • 日本語ファイル名の取り扱い
  • 中身のデータを別のZipファイルにコピーする方法

上の2点に関して。

日本語ファイル名の取り扱い

ZIPファイル内のファイル名は次のようになっている、らしい。

  • general purpose bit flagのビット11が1の場合、UTF-8。(以下「UTF-8ビット」と呼称)
    • Windows11のシステムで日本語ファイル名を持つものをZip圧縮するとこれが建つ。
  • 0の場合、IBM Code Page 437。
    • 昔かアプリ等を使いWindowsでzip圧縮したものはこちらになる。
      実際のエンコードはシフトJIS(Code Page 932)が使われる。
    • Mac OSはUTF-8。UTF-8ビットが建ってるかどうかは不明。

以下参照ページ。stackoverflowは2012年のQAなので古いかな。

ZIPの仕様を日本語でまとめる
ZIPの仕様を日本語でまとめる. GitHub Gist: instantly share code, notes, and snippets.
Correctly decoding zip entry file names -- CP437, UTF-8 or?
I recently wrote a zip file I/O library called zipzap, but I'm struggling with correctly decoding zip entry file names f...

Windowsでの実際の処理方法

ZIPファイルのヘッダー部分を調べ、UTF-8ビットが1の場合はUTF-8エンコードを返し、0の場合はCode Page 932を返すような関数を用意、それ経由でエンコーダーを取得してZipファイルをオープンする。

Encoding? checkZipEncoding(String fileName)
{
	Encoding? encoder = null;
	if (File.Exists(fileName))
	{
		using (var fs = File.OpenRead(fileName))
		{
			byte[] bytes = new byte[8];
			if (fs.Read(bytes, 0, bytes.Length) == bytes.Length)
			{
				try
				{
					if ((bytes.Last() & 0x08) != 0)
					{
						// UTF-8
						encoder = Encoding.UTF8;
					}
					else
					{
						// シフトJIS
						encoder = System.Text.CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
					}
				}
				catch (Exception)
				{

					encoder = null;
				}
			}
		}
	}
	return encoder;
}

foreach (var name in args)
{
	var encode = checkZipEncoding(name);
	if (encode != null)
	{
		using (ZipArchive archive = ZipFile.Open(name, ZipArchiveMode.Read, encode))
		{
			foreach (var e in archive.Entries)
			{
				Console.WriteLine(e.Name);
			}
		}
	}

}

補足:文字列指定でのエンコーダー指定

UTF-8のような固定なのはEncoding.UTF8を使う。
shift_jisなどのマルチバイトのものは2通り。

コードページ番号を指定して取得するか、文字列で検索する。

どちらもGetEncoding()メソッドを使う。

コードページ番号の場合

システムが用意されていればリターンが来るが、システムで用意されていなければ例外が発生する。
Windowsの場合シフトJISは932。Windowsであれば用意されている。

文字列の場合

システムで用意されているかどうかは、System.Text.CodePagesEncodingProvider.Instance側のGetEncodingを使う必要がある。
Encoding.GetEncodingの場合は.Net Coreで用意されているデフォルトのものが検索対象になり、そちらにシフトJISは含まれていないので例外が発生する。

ただシステムで用意されていない文字列を指定しても例外が発生する。

EncodingProvider クラス (System.Text)
特定のプラットフォームで利用できないエンコードを提供する、エンコーディング プロバイダーの基底クラスを提供します。
Encoding.GetEncodings Method (System.Text)
Returns an array that contains all encodings.

こちら側にもちょっと書かれている。

指定可能な文字列は、GetEncodingsの集合内に入っているEncoding.WebNameが検索対象ということらしい。
一応、エイリアスは用意されているようだが、許容される文字がどこまでかのドキュメントがないので上のものを使用するのが良いと思われる。

Encoding.GetEncoding(String)で指定可能なエンコーディング名のエイリアスを登録するには
エンコーディングオブジェクトを取得する際、GetEncodingメソッド対して、以下のように「cp932」を引数として指定すると例外が発生します。 Encoding.GetEncoding("cp932") ※これは、「cp932」がエンコ...

エイリアスについてのQA。

中身のデータを別のZipファイルにコピーする方法

こちらは難しくなく、元ファイルをオープンしZipArchiveEntryを取得し、それを出力側にコピーするといった感じ。

using (var output = ZipFile.Open(zipFileName, ZipArchiveMode.Create, Encoding.GetEncoding(defaultCP)))
{
	foreach (var data in archive.Entries)
	{
		// データをコピーする
		var entry = output.CreateEntry(data.Name);
		using (var writer = entry.Open())
		{
			using (var reader = data.Open())
			{
				reader.CopyTo(writer);
			}
		}
	}
}

コメント

タイトルとURLをコピーしました