Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18450] ファイルの概要

このコミットは、Go言語の標準ライブラリ archive/zip パッケージ内のテストコード reader_test.go におけるバグ修正です。具体的には、ZIPアーカイブから読み込んだファイルの非圧縮サイズが正しく検証されていることを確認するためのテストロジックを修正しています。以前のテストでは、UncompressedSize フィールドの値が読み取り前後で変化しないことのみを確認しており、実際に読み込まれたデータのサイズと UncompressedSize が一致するかどうかを検証していませんでした。このコミットにより、実際に読み込まれたバイト数と UncompressedSize (または UncompressedSize64) の値が一致するかを検証するようになり、テストの信頼性が向上しました。

コミット

commit 413e28da0d13a2b3387c4a5eca2c848fe3ba790c
Author: Andrew Gerrand <adg@golang.org>
Date:   Tue Feb 11 16:09:42 2014 +1100

    archive/zip: actually test uncompressed size
    
    Fixes #7292.
    
    LGTM=dsymonds
    R=dsymonds
    CC=golang-codereviews
    https://golang.org/cl/61650046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/413e28da0d13a2b3387c4a5eca2c848fe3ba790c

元コミット内容

archive/zip: actually test uncompressed size

Fixes #7292.

LGTM=dsymonds
R=dsymonds
CC=golang-codereviews
https://golang.org/cl/61650046

変更の背景

この変更の背景には、archive/zip パッケージのテストが、ZIPファイル内のエントリの非圧縮サイズを適切に検証できていなかったという問題があります。コミットメッセージにある Fixes #7292 は、この問題がGoのIssueトラッカーで報告されていたことを示唆しています。

具体的には、readTestFile 関数内のテストロジックが不十分でした。以前のコードでは、f.UncompressedSize の値をファイルを開く前と開いた後で比較し、その値が変化しないことを確認していました。しかし、これは UncompressedSize フィールド自体が誤って変更されていないことを確認するだけであり、実際にファイルから読み出されたデータのバイト数が、その UncompressedSize と一致するかどうかを検証していませんでした。

この不備により、もしZIPファイル内の非圧縮サイズ情報が誤っていた場合でも、テストがそれを検知できない可能性がありました。例えば、ZIPヘッダに記載された非圧縮サイズが実際のデータサイズと異なっていても、テストはパスしてしまうという状況です。このコミットは、このテストのギャップを埋め、より堅牢な検証を行うことを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念が役立ちます。

  • ZIPファイルフォーマット: ZIPファイルは、複数のファイルを圧縮して一つにまとめるためのアーカイブフォーマットです。各ファイルエントリには、圧縮されたデータだけでなく、元のファイル名、圧縮方法、圧縮後のサイズ、非圧縮サイズなどのメタデータが含まれています。
  • Go言語の archive/zip パッケージ: Go言語の標準ライブラリには、ZIPアーカイブの読み書きをサポートする archive/zip パッケージが含まれています。
    • zip.File: ZIPアーカイブ内の個々のファイルエントリを表す構造体です。この構造体には、ファイルのメタデータが含まれます。
    • File.UncompressedSize: zip.File 構造体のフィールドで、ファイルの非圧縮時のサイズ(バイト単位)を保持します。これは32ビット値であり、4GBを超えるファイルサイズには対応できません。
    • File.UncompressedSize64: zip.File 構造体のフィールドで、ファイルの非圧縮時のサイズ(バイト単位)を保持します。これは64ビット値であり、4GBを超えるファイルサイズにも対応できます。ZIP64拡張が使用されている場合に利用されます。
    • File.Open(): zip.File から io.ReadCloser を返します。これにより、ZIPエントリの非圧縮データを読み出すことができます。
  • io.Copy: Go言語の io パッケージにある関数で、io.Reader から io.Writer へデータをコピーします。コピーされたバイト数を返します。
  • bytes.Buffer: bytes パッケージにある可変長のバイトバッファです。io.Writer インターフェースを実装しており、データを書き込むことができます。Len() メソッドでバッファに書き込まれたバイト数を取得できます。
  • Goのテストフレームワーク: Go言語には組み込みのテストフレームワークがあり、testing パッケージを使用します。
    • *testing.T: テスト関数に渡される構造体で、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。
    • t.Errorf(...): テストが失敗したことを報告し、エラーメッセージを出力します。

技術的詳細

このコミットの技術的な詳細は、src/pkg/archive/zip/reader_test.go 内の readTestFile 関数におけるテストロジックの変更に集約されます。

変更前の問題点: 変更前のコードでは、f.UncompressedSize の値を size0 に保存し、f.Open() を呼び出した後に再度 f.UncompressedSizesize1 に保存して size0 != size1 をチェックしていました。このチェックは、UncompressedSize フィールドがファイル読み取り処理中に意図せず変更されないことを確認するものでした。しかし、これはZIPヘッダに記載された非圧縮サイズが、実際にファイルから読み出されるデータ量と一致するかどうかを検証するものではありませんでした。

変更後の改善点: 新しいコードでは、以下の手順で非圧縮サイズの検証を行います。

  1. io.Copy(&b, r) を使用して、ZIPエントリの非圧縮データを bytes.Buffer b に完全に読み込みます。b.Len() は、実際に読み込まれたバイト数を示します。
  2. f.UncompressedSize の値を取得します。この値は32ビットの非圧縮サイズです。
  3. もし f.UncompressedSize1<<32-1 (つまり 0xFFFFFFFF) であった場合、これはZIP64拡張が使用されており、実際の非圧縮サイズが32ビットで表現できないことを意味します。この場合、より大きなサイズを保持できる f.UncompressedSize64 の値を使用します。
  4. 実際に読み込まれたバイト数 (b.Len()) と、ZIPヘッダから取得した非圧縮サイズ (size) を比較します。
  5. もし両者が一致しない場合、t.Errorf を呼び出してテストを失敗させ、エラーメッセージを出力します。このエラーメッセージは、どのファイルで、実際に読み込まれたバイト数と期待される非圧縮サイズが異なっているかを明確に示します。

この変更により、テストはZIPヘッダの非圧縮サイズ情報が、実際のデータと整合していることを保証できるようになりました。これは、ZIPアーカイブの破損や不正な生成を検知する上で非常に重要です。

コアとなるコードの変更箇所

--- a/src/pkg/archive/zip/reader_test.go
+++ b/src/pkg/archive/zip/reader_test.go
@@ -355,8 +355,6 @@ func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) {\
 
 	testFileMode(t, zt.Name, f, ft.Mode)
 
-	size0 := f.UncompressedSize
-
 	var b bytes.Buffer
 	r, err := f.Open()
 	if err != nil {\
@@ -364,10 +362,6 @@ func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) {\
 		return
 	}\
 
-	if size1 := f.UncompressedSize; size0 != size1 {\
-		t.Errorf("file %q changed f.UncompressedSize from %d to %d", f.Name, size0, size1)
-	}\
-
 	_, err = io.Copy(&b, r)
 	if err != ft.ContentErr {\
 		t.Errorf("%s: copying contents: %v (want %v)", zt.Name, err, ft.ContentErr)\
@@ -377,6 +371,14 @@ func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) {\
 	}\
 	r.Close()\
 
+	size := int(f.UncompressedSize)
+	if size == 1<<32-1 {\
+		size = int(f.UncompressedSize64)
+	}\
+	if g := b.Len(); g != size {\
+		t.Errorf("%v: read %v bytes but f.UncompressedSize == %v", f.Name, g, size)
+	}\
+
 	var c []byte
 	if ft.Content != nil {\
 		c = ft.Content

コアとなるコードの解説

変更は src/pkg/archive/zip/reader_test.go ファイルの readTestFile 関数内で行われています。

削除された行:

-	size0 := f.UncompressedSize
-
-	// ...
-
-	if size1 := f.UncompressedSize; size0 != size1 {
-		t.Errorf("file %q changed f.UncompressedSize from %d to %d", f.Name, size0, size1)
-	}

これらの行は、f.UncompressedSize の値がファイル読み取り処理の前後で変化しないことを確認するためのものでした。このチェック自体は無意味ではありませんが、実際に読み込まれたデータ量との整合性を検証するものではないため、より重要なチェックに置き換えられました。

追加された行:

 	var b bytes.Buffer
 	r, err := f.Open()
 	// ... (エラーハンドリング)
 	_, err = io.Copy(&b, r) // ZIPエントリのデータをバッファにコピー
 	// ... (エラーハンドリング)
 	r.Close()

+	size := int(f.UncompressedSize)
+	if size == 1<<32-1 {
+		size = int(f.UncompressedSize64)
+	}
+	if g := b.Len(); g != size {
+		t.Errorf("%v: read %v bytes but f.UncompressedSize == %v", f.Name, g, size)
+	}
  1. var b bytes.Buffer: bytes.Buffer 型の変数 b を宣言します。これは、ZIPエントリから読み出される非圧縮データを一時的に保持するためのバッファとして機能します。
  2. r, err := f.Open(): zip.File オブジェクト f から io.ReadCloser を取得します。この r を通じて、ZIPエントリの非圧縮データにアクセスできます。
  3. _, err = io.Copy(&b, r): io.Copy 関数を使って、r (ZIPエントリのリーダー) から b (バイトバッファ) へデータをコピーします。これにより、ZIPエントリの非圧縮データが全て b に読み込まれます。io.Copy はコピーされたバイト数を返しますが、ここではその値は直接使用せず、b.Len() で後から取得します。
  4. r.Close(): 読み取りが完了したら、リーダーを閉じます。
  5. size := int(f.UncompressedSize): まず、f.UncompressedSize の値を size 変数に代入します。これは32ビットの非圧縮サイズです。
  6. if size == 1<<32-1: ここが重要なロジックです。1<<32-10xFFFFFFFF を表します。ZIPファイルフォーマットでは、非圧縮サイズが32ビットで表現できない(つまり4GB以上)場合、UncompressedSize フィールドに 0xFFFFFFFF が設定され、実際のサイズは UncompressedSize64 フィールドに64ビット値として格納されます。この条件は、ZIP64拡張が使用されているかどうかをチェックしています。
  7. size = int(f.UncompressedSize64): もしZIP64拡張が使用されている場合、f.UncompressedSize64 の値を size に代入し直します。これにより、正しい非圧縮サイズ(64ビット値)が取得されます。
  8. if g := b.Len(); g != size: ここで、実際に bytes.Buffer に読み込まれたバイト数 g (b.Len() の結果) と、ZIPヘッダから取得した非圧縮サイズ size を比較します。
  9. t.Errorf(...): もし gsize が一致しない場合、テストは失敗し、詳細なエラーメッセージが出力されます。このメッセージは、どのファイルで、実際に読み込まれたバイト数と期待される非圧縮サイズが異なっているかを報告します。

この変更により、archive/zip パッケージのテストは、ZIPエントリの非圧縮サイズがZIPヘッダのメタデータと実際に読み出されるデータ量で整合していることを厳密に検証できるようになりました。

関連リンク

参考にした情報源リンク