[インデックス 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.UncompressedSize
を size1
に保存して size0 != size1
をチェックしていました。このチェックは、UncompressedSize
フィールドがファイル読み取り処理中に意図せず変更されないことを確認するものでした。しかし、これはZIPヘッダに記載された非圧縮サイズが、実際にファイルから読み出されるデータ量と一致するかどうかを検証するものではありませんでした。
変更後の改善点: 新しいコードでは、以下の手順で非圧縮サイズの検証を行います。
io.Copy(&b, r)
を使用して、ZIPエントリの非圧縮データをbytes.Buffer
b
に完全に読み込みます。b.Len()
は、実際に読み込まれたバイト数を示します。f.UncompressedSize
の値を取得します。この値は32ビットの非圧縮サイズです。- もし
f.UncompressedSize
が1<<32-1
(つまり0xFFFFFFFF
) であった場合、これはZIP64拡張が使用されており、実際の非圧縮サイズが32ビットで表現できないことを意味します。この場合、より大きなサイズを保持できるf.UncompressedSize64
の値を使用します。 - 実際に読み込まれたバイト数 (
b.Len()
) と、ZIPヘッダから取得した非圧縮サイズ (size
) を比較します。 - もし両者が一致しない場合、
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)
+ }
var b bytes.Buffer
:bytes.Buffer
型の変数b
を宣言します。これは、ZIPエントリから読み出される非圧縮データを一時的に保持するためのバッファとして機能します。r, err := f.Open()
:zip.File
オブジェクトf
からio.ReadCloser
を取得します。このr
を通じて、ZIPエントリの非圧縮データにアクセスできます。_, err = io.Copy(&b, r)
:io.Copy
関数を使って、r
(ZIPエントリのリーダー) からb
(バイトバッファ) へデータをコピーします。これにより、ZIPエントリの非圧縮データが全てb
に読み込まれます。io.Copy
はコピーされたバイト数を返しますが、ここではその値は直接使用せず、b.Len()
で後から取得します。r.Close()
: 読み取りが完了したら、リーダーを閉じます。size := int(f.UncompressedSize)
: まず、f.UncompressedSize
の値をsize
変数に代入します。これは32ビットの非圧縮サイズです。if size == 1<<32-1
: ここが重要なロジックです。1<<32-1
は0xFFFFFFFF
を表します。ZIPファイルフォーマットでは、非圧縮サイズが32ビットで表現できない(つまり4GB以上)場合、UncompressedSize
フィールドに0xFFFFFFFF
が設定され、実際のサイズはUncompressedSize64
フィールドに64ビット値として格納されます。この条件は、ZIP64拡張が使用されているかどうかをチェックしています。size = int(f.UncompressedSize64)
: もしZIP64拡張が使用されている場合、f.UncompressedSize64
の値をsize
に代入し直します。これにより、正しい非圧縮サイズ(64ビット値)が取得されます。if g := b.Len(); g != size
: ここで、実際にbytes.Buffer
に読み込まれたバイト数g
(b.Len()
の結果) と、ZIPヘッダから取得した非圧縮サイズsize
を比較します。t.Errorf(...)
: もしg
とsize
が一致しない場合、テストは失敗し、詳細なエラーメッセージが出力されます。このメッセージは、どのファイルで、実際に読み込まれたバイト数と期待される非圧縮サイズが異なっているかを報告します。
この変更により、archive/zip
パッケージのテストは、ZIPエントリの非圧縮サイズがZIPヘッダのメタデータと実際に読み出されるデータ量で整合していることを厳密に検証できるようになりました。
関連リンク
- Go言語の
archive/zip
パッケージドキュメント: https://pkg.go.dev/archive/zip - Go言語の
io
パッケージドキュメント: https://pkg.go.dev/io - Go言語の
bytes
パッケージドキュメント: https://pkg.go.dev/bytes - Go言語の
testing
パッケージドキュメント: https://pkg.go.dev/testing
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/413e28da0d13a2b3387c4a5eca2c848fe3ba790c
- Go CL (Code Review) ページ: https://golang.org/cl/61650046 (これはコミットメッセージに記載されているリンクです)
- ZIPファイルフォーマットの仕様 (例: PKWARE AppNote.txt): https://pkware.com/documents/casestudies/APPNOTE.TXT (一般的なZIPフォーマットの理解のため)
- Go言語のIssueトラッカー (Issue #7292は直接見つかりませんでしたが、同様のバグ報告の文脈で参照される可能性があります): https://go.dev/issue