[インデックス 12546] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/zip
パッケージにおけるZIPファイルのデータディスクリプタの書き込みと読み込みに関する重要な修正を含んでいます。特に、macOS (OS X) との互換性の問題を解決し、ZIPファイル仕様におけるデータディスクリプタの署名の扱いに関するバグを修正しています。
コミット
commit 3cea4131dfa3d07f74b53a4f26412d4a0470717e
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Mar 9 14:12:02 2012 -0800
archive/zip: write data descriptor signature for OS X; fix bugs reading it
We now always write the "optional" streaming data descriptor
signature, which turns out to be required for OS X.
Also, handle reading the data descriptor with or without the
signature, per the spec's recommendation. Fix data descriptor
reading bugs found in the process.
Fixes #3252
R=golang-dev, alex.brainman, nigeltao, rsc
CC=golang-dev
https://golang.org/cl/5787062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3cea4131dfa3d07f74b53a4f26412d4a0470717e
元コミット内容
archive/zip: write data descriptor signature for OS X; fix bugs reading it
このコミットの目的は、ZIPファイルのデータディスクリプタの書き込み時に、macOS (OS X) で必要とされる「オプション」のストリーミングデータディスクリプタ署名を常に書き込むようにすること、および、ZIPファイル仕様の推奨に従って、署名の有無にかかわらずデータディスクリプタを読み取れるようにするバグを修正することです。これにより、関連するデータディスクリプタの読み取りバグも修正されました。
変更の背景
この変更の主な背景は、Go言語の archive/zip
パッケージで作成されたZIPファイルがmacOS (OS X) で正しく扱われないという互換性の問題でした。具体的には、Goで生成されたZIPファイルがmacOSのFinderやアーカイブユーティリティで開けない、または内容が正しく表示されないという問題が発生していました。
ZIPファイルフォーマットには、ファイルデータが圧縮される前にそのサイズやCRC32チェックサムが不明な場合に利用される「データディスクリプタ (Data Descriptor)」という構造があります。これは、ストリーミング圧縮など、事前にファイルサイズが確定しない場合に特に有用です。ZIPの仕様では、このデータディスクリプタの先頭に 0x08074b50
という署名(シグネチャ)を付けることが「オプション」とされています。しかし、macOSのZIP実装は、この「オプション」の署名が存在することを期待しており、署名がない場合にZIPファイルを正しく解釈できないという問題がありました。
Goの archive/zip
パッケージは、この署名を書き込んでいなかったため、macOSとの互換性問題を引き起こしていました。このコミットは、この互換性問題を解決するために、データディスクリプタ署名を常に書き込むように writer.go
を変更し、同時に、署名の有無にかかわらずデータディスクリプタを堅牢に読み取れるように reader.go
のバグを修正しました。これにより、Goで作成されたZIPファイルがmacOSを含む様々な環境でより広く互換性を持つようになりました。
この修正は、Go issue #3252 で報告された問題に対応しています。
前提知識の解説
ZIPファイルフォーマットの基本
ZIPファイルは、複数のファイルを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。
- ローカルファイルヘッダ (Local File Header): 各ファイルデータの直前に配置され、ファイル名、圧縮方式、圧縮・非圧縮サイズ、CRC32チェックサムなどの情報を含みます。
- ファイルデータ (File Data): 実際のファイルの内容(圧縮されている場合もある)です。
- データディスクリプタ (Data Descriptor): ローカルファイルヘッダで圧縮・非圧縮サイズやCRC32チェックサムが
0
に設定されている場合(つまり、これらの情報が事前に不明な場合)に、ファイルデータの直後にこれらの実際の値を記述するために使用されます。ストリーミング圧縮など、ファイルサイズが事前に分からない場合に特に重要です。 - セントラルディレクトリ (Central Directory): ZIPファイルの末尾に配置され、アーカイブ内の全ファイルのローカルファイルヘッダのコピーのような情報(ファイル名、圧縮方式、サイズ、CRC32、ローカルファイルヘッダへのオフセットなど)を一元的に管理します。これにより、ZIPファイル全体をスキャンせずに特定のファイルにアクセスできます。
- セントラルディレクトリエンドレコード (End of Central Directory Record): セントラルディレクトリの終端を示し、セントラルディレクトリの開始位置やサイズなどの情報を含みます。
データディスクリプタと署名
データディスクリプタは、ローカルファイルヘッダの General Purpose Bit Flag
のビット3がセットされている場合に存在します。このフラグがセットされていると、ローカルファイルヘッダ内の圧縮・非圧縮サイズおよびCRC32フィールドは無効となり、これらの実際の値はファイルデータの後に続くデータディスクリプタに記述されます。
データディスクリプタの構造は通常、以下の3つの uint32
値で構成されます。
- CRC32チェックサム
- 圧縮サイズ
- 非圧縮サイズ
しかし、ZIPファイル仕様では、この3つの uint32
の前に 0x08074b50
という4バイトの署名(シグネチャ)を付けることが「オプション」とされています。この署名は、データディスクリプタの開始を明示的に示すためのもので、一部のZIPツール(特にmacOSのFinderなど)はこの署名が存在することを前提として動作します。仕様上はオプションであるにもかかわらず、実用上は特定の環境で必須となるケースがあるため、互換性の問題を引き起こすことがあります。
CRC32チェックサム
CRC32 (Cyclic Redundancy Check) は、データ転送や保存中に発生したエラーを検出するためのチェックサムアルゴリズムです。ZIPファイルでは、各ファイルのデータ整合性を検証するために使用されます。ファイルの内容から計算されたCRC32値が、ZIPヘッダまたはデータディスクリプタに格納されている値と一致しない場合、データが破損していると判断されます。
技術的詳細
このコミットは、Go言語の archive/zip
パッケージ内の reader.go
、writer.go
、struct.go
の3つの主要なファイルに影響を与えています。
writer.go
の変更点
以前のGoの archive/zip
パッケージは、データディスクリプタを書き込む際に、ZIP仕様で「オプション」とされている 0x08074b50
の署名を書き込んでいませんでした。このコミットでは、writer.go
の fileWriter.close()
メソッド内でデータディスクリプタを書き込む際に、この署名を常に書き込むように変更されました。
// src/pkg/archive/zip/writer.go
// ...
func (w *fileWriter) close() error {
// ...
// write data descriptor
var buf [dataDescriptorLen]byte
b := writeBuf(buf[:])
b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X
b.uint32(fh.CRC32)
b.uint32(fh.CompressedSize)
b.uint32(fh.UncompressedSize)
// ...
}
この変更により、Goで作成されたZIPファイルがmacOSのFinderなどのツールで正しく認識され、互換性が向上しました。
reader.go
の変更点
reader.go
では、データディスクリプタの読み込みロジックが大幅に改善されました。以前の実装では、データディスクリプタの署名の有無を適切に処理できていませんでした。このコミットでは、ZIP仕様の推奨に従い、署名がある場合とない場合の両方に対応できるように修正されました。
主な変更点は以下の通りです。
-
readDataDescriptor
関数の改善:- データディスクリプタを読み込む際に、まず最初の4バイトを読み込み、それがデータディスクリプタ署名
0x08074b50
と一致するかどうかを確認します。 - 一致しない場合、その4バイトは署名ではなく、データディスクリプタの最初のフィールド(通常はCRC32)の一部であると判断し、そのまま処理を続行します。
- 一致する場合、その4バイトは署名としてスキップし、残りのデータディスクリプタのフィールドを読み込みます。
- これにより、署名の有無にかかわらず、データディスクリプタを堅牢にパースできるようになりました。
- データディスクリプタを読み込む際に、まず最初の4バイトを読み込み、それがデータディスクリプタ署名
-
checksumReader
構造体の変更:checksumReader
は、ファイルの内容を読み込みながらCRC32チェックサムを計算し、ファイル終端でデータディスクリプタを読み込んで検証する役割を担います。- 以前は
zipr io.Reader
を持っていた部分がdesr io.Reader
に変更され、データディスクリプタを読み込むための専用のio.Reader
が渡されるようになりました。これにより、データディスクリプタの読み込みがより明確に分離されました。 Read
メソッド内で、io.EOF
に達した際にr.desr
が非nilであれば、readDataDescriptor
を呼び出してデータディスクリプタを読み込み、CRC32チェックサムを検証するロジックが追加されました。
struct.go
の変更点
struct.go
では、ZIPファイルフォーマットに関連する定数が定義されています。このコミットでは、データディスクリプタ署名の定数 dataDescriptorSignature
が追加され、dataDescriptorLen
の値が 12
から 16
に変更されました。
// src/pkg/archive/zip/struct.go
// ...
const (
// ...
dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder
// ...
dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size
)
dataDescriptorLen
が 16
に変更されたのは、データディスクリプタ署名(4バイト)が常に書き込まれるようになったため、データディスクリプタ全体の長さが4バイト増加したことを反映しています。
テストファイルの追加と修正
reader_test.go
と writer_test.go
には、これらの変更を検証するための新しいテストケースが追加されました。
go-no-datadesc-sig.zip
とgo-with-datadesc-sig.zip
という2つの新しいテスト用ZIPファイルが追加されました。これらは、それぞれデータディスクリプタ署名がない場合とある場合のZIPファイルを表し、reader.go
の修正が正しく機能するかを検証します。Bad-CRC32-in-data-descriptor
というテストケースが追加され、データディスクリプタ内のCRC32が破損している場合の挙動をテストします。- テストヘルパー関数
readTestFile
やtestFileMode
の引数にZipTest
やzipName
が追加され、テストのコンテキストがより明確になりました。
これらのテストの追加により、データディスクリプタの書き込みと読み込みの堅牢性が向上したことが確認できるようになりました。
コアとなるコードの変更箇所
src/pkg/archive/zip/reader.go
: データディスクリプタの読み込みロジック、特にreadDataDescriptor
関数とchecksumReader
構造体の修正。src/pkg/archive/zip/reader_test.go
: データディスクリプタの読み込みに関する新しいテストケースの追加と既存テストの修正。src/pkg/archive/zip/struct.go
:dataDescriptorSignature
定数の追加とdataDescriptorLen
の更新。src/pkg/archive/zip/testdata/go-no-datadesc-sig.zip
: データディスクリプタ署名なしのテスト用ZIPファイル(新規追加)。src/pkg/archive/zip/testdata/go-with-datadesc-sig.zip
: データディスクリプタ署名ありのテスト用ZIPファイル(新規追加)。src/pkg/archive/zip/writer.go
: データディスクリプタ書き込み時に署名を常に含めるように修正。src/pkg/archive/zip/writer_test.go
:testFileMode
関数の呼び出し修正。
コアとなるコードの解説
src/pkg/archive/zip/reader.go
の readDataDescriptor
関数
func readDataDescriptor(r io.Reader, f *File) error {
var buf [dataDescriptorLen]byte
// The spec says: "Although not originally assigned a
// signature, the value 0x08074b50 has commonly been adopted
// as a signature value for the data descriptor record.
// Implementers should be aware that ZIP files may be
// encountered with or without this signature marking data
// descriptors and should account for either case when reading
// ZIP files to ensure compatibility."
//
// dataDescriptorLen includes the size of the signature but
// first read just those 4 bytes to see if it exists.
if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
off := 0
maybeSig := readBuf(buf[:4])
if maybeSig.uint32() != dataDescriptorSignature {
// No data descriptor signature. Keep these four
// bytes.
off += 4
}
if _, err := io.ReadFull(r, buf[off:12]); err != nil {
return err
}
b := readBuf(buf[:12])
f.CRC32 = b.uint32()
f.CompressedSize = b.uint32()
f.UncompressedSize = b.uint32()
return nil
}
この関数は、データディスクリプタを読み込む際の中心的なロジックを含んでいます。
- まず、
io.ReadFull(r, buf[:4])
で最初の4バイトを読み込みます。これは、データディスクリプタ署名が存在するかどうかを判断するためです。 maybeSig.uint32() != dataDescriptorSignature
で、読み込んだ4バイトが期待される署名と一致するかをチェックします。- 一致しない場合 (
off += 4
)、それは署名ではなく、データディスクリプタの最初のフィールド(CRC32)の一部であると判断し、オフセットを4バイト進めます。これにより、次のio.ReadFull
が正しい位置から読み込みを開始します。 - 一致する場合、署名はスキップされ、オフセットは0のままです。
- 次に
io.ReadFull(r, buf[off:12])
で、残りの12バイト(CRC32、圧縮サイズ、非圧縮サイズ)を読み込みます。off
の値によって、署名がスキップされたかどうかが考慮されます。 - 最後に、読み込んだバイト列からCRC32、圧縮サイズ、非圧縮サイズを抽出し、
File
オブジェクトに設定します。
このロジックにより、署名の有無にかかわらずデータディスクリプタを正しくパースできるようになり、ZIPファイルの互換性が大幅に向上しました。
src/pkg/archive/zip/writer.go
の fileWriter.close()
メソッド
// ...
func (w *fileWriter) close() error {
// ...
// write data descriptor
var buf [dataDescriptorLen]byte
b := writeBuf(buf[:])
b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X
b.uint32(fh.CRC32)
b.uint32(fh.CompressedSize)
b.uint32(fh.UncompressedSize)
if _, err := w.w.Write(buf[:]); err != nil {
return err
}
// ...
}
この部分では、データディスクリプタをZIPファイルに書き込む処理が行われます。
重要な変更点は、b.uint32(dataDescriptorSignature)
の行が追加されたことです。これにより、データディスクリプタの先頭に 0x08074b50
という署名が常に書き込まれるようになりました。コメントにもあるように、これは「デファクトスタンダードであり、OS X Finderで必要とされる」ため、互換性確保のために導入されました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/3cea4131dfa3d07f74b53a4f26412d4a0470717e
- Go Issue #3252: https://golang.org/issue/3252
- Go CL 5787062: https://golang.org/cl/5787062
参考にした情報源リンク
- ZIP File Format Specification (特に "4.3.9 Data descriptor" セクション)
- Go issue #3252: archive/zip: zip files created by go are not readable by OS X
- Stack Overflow: Why is the ZIP data descriptor signature 0x08074b50?
- Wikipedia: ZIP (file format)
- GoDoc: archive/zip package
[インデックス 12546] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/zip
パッケージにおけるZIPファイルのデータディスクリプタの書き込みと読み込みに関する重要な修正を含んでいます。特に、macOS (OS X) との互換性の問題を解決し、ZIPファイル仕様におけるデータディスクリプタの署名の扱いに関するバグを修正しています。
コミット
commit 3cea4131dfa3d07f74b53a4f26412d4a0470717e
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Mar 9 14:12:02 2012 -0800
archive/zip: write data descriptor signature for OS X; fix bugs reading it
We now always write the "optional" streaming data descriptor
signature, which turns out to be required for OS X.
Also, handle reading the data descriptor with or without the
signature, per the spec's recommendation. Fix data descriptor
reading bugs found in the process.
Fixes #3252
R=golang-dev, alex.brainman, nigeltao, rsc
CC=golang-dev
https://golang.org/cl/5787062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3cea4131dfa3d07f74b53a4f26412d4a0470717e
元コミット内容
archive/zip: write data descriptor signature for OS X; fix bugs reading it
このコミットの目的は、ZIPファイルのデータディスクリプタの書き込み時に、macOS (OS X) で必要とされる「オプション」のストリーミングデータディスクリプタ署名を常に書き込むようにすること、および、ZIPファイル仕様の推奨に従って、署名の有無にかかわらずデータディスクリプタを読み取れるようにするバグを修正することです。これにより、関連するデータディスクリプタの読み取りバグも修正されました。
変更の背景
この変更の主な背景は、Go言語の archive/zip
パッケージで作成されたZIPファイルがmacOS (OS X) で正しく扱われないという互換性の問題でした。具体的には、Goで生成されたZIPファイルがmacOSのFinderやアーカイブユーティリティで開けない、または内容が正しく表示されないという問題が発生していました。
ZIPファイルフォーマットには、ファイルデータが圧縮される前にそのサイズやCRC32チェックサムが不明な場合に利用される「データディスクリプタ (Data Descriptor)」という構造があります。これは、ストリーミング圧縮など、事前にファイルサイズが確定しない場合に特に有用です。ZIPの仕様では、このデータディスクリプタの先頭に 0x08074b50
という署名(シグネチャ)を付けることが「オプション」とされています。しかし、macOSのZIP実装は、この「オプション」の署名が存在することを期待しており、署名がない場合にZIPファイルを正しく解釈できないという問題がありました。
Goの archive/zip
パッケージは、この署名を書き込んでいなかったため、macOSとの互換性問題を引き起こしていました。このコミットは、この互換性問題を解決するために、データディスクリプタ署名を常に書き込むように writer.go
を変更し、同時に、署名の有無にかかわらずデータディスクリプタを堅牢に読み取れるように reader.go
のバグを修正しました。これにより、Goで作成されたZIPファイルがmacOSを含む様々な環境でより広く互換性を持つようになりました。
この修正は、Go issue #3252 で報告された問題に対応しています。
前提知識の解説
ZIPファイルフォーマットの基本
ZIPファイルは、複数のファイルを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。
- ローカルファイルヘッダ (Local File Header): 各ファイルデータの直前に配置され、ファイル名、圧縮方式、圧縮・非圧縮サイズ、CRC32チェックサムなどの情報を含みます。
- ファイルデータ (File Data): 実際のファイルの内容(圧縮されている場合もある)です。
- データディスクリプタ (Data Descriptor): ローカルファイルヘッダで圧縮・非圧縮サイズやCRC32チェックサムが
0
に設定されている場合(つまり、これらの情報が事前に不明な場合)に、ファイルデータの直後にこれらの実際の値を記述するために使用されます。ストリーミング圧縮など、ファイルサイズが事前に分からない場合に特に重要です。 - セントラルディレクトリ (Central Directory): ZIPファイルの末尾に配置され、アーカイブ内の全ファイルのローカルファイルヘッダのコピーのような情報(ファイル名、圧縮方式、サイズ、CRC32、ローカルファイルヘッダへのオフセットなど)を一元的に管理します。これにより、ZIPファイル全体をスキャンせずに特定のファイルにアクセスできます。
- セントラルディレクトリエンドレコード (End of Central Directory Record): セントラルディレクトリの終端を示し、セントラルディレクトリの開始位置やサイズなどの情報を含みます。
データディスクリプタと署名
データディスクリプタは、ローカルファイルヘッダの General Purpose Bit Flag
のビット3がセットされている場合に存在します。このフラグがセットされていると、ローカルファイルヘッダ内の圧縮・非圧縮サイズおよびCRC32フィールドは無効となり、これらの実際の値はファイルデータの後に続くデータディスクリプタに記述されます。
データディスクリプタの構造は通常、以下の3つの uint32
値で構成されます。
- CRC32チェックサム
- 圧縮サイズ
- 非圧縮サイズ
しかし、ZIPファイル仕様では、この3つの uint32
の前に 0x08074b50
という4バイトの署名(シグネチャ)を付けることが「オプション」とされています。この署名は、データディスクリプタの開始を明示的に示すためのもので、一部のZIPツール(特にmacOSのFinderなど)はこの署名が存在することを前提として動作します。仕様上はオプションであるにもかかわらず、実用上は特定の環境で必須となるケースがあるため、互換性の問題を引き起こすことがあります。
CRC32チェックサム
CRC32 (Cyclic Redundancy Check) は、データ転送や保存中に発生したエラーを検出するためのチェックサムアルゴリズムです。ZIPファイルでは、各ファイルのデータ整合性を検証するために使用されます。ファイルの内容から計算されたCRC32値が、ZIPヘッダまたはデータディスクリプタに格納されている値と一致しない場合、データが破損していると判断されます。
技術的詳細
このコミットは、Go言語の archive/zip
パッケージ内の reader.go
、writer.go
、struct.go
の3つの主要なファイルに影響を与えています。
writer.go
の変更点
以前のGoの archive/zip
パッケージは、データディスクリプタを書き込む際に、ZIP仕様で「オプション」とされている 0x08074b50
の署名を書き込んでいませんでした。このコミットでは、writer.go
の fileWriter.close()
メソッド内でデータディスクリプタを書き込む際に、この署名を常に書き込むように変更されました。
// src/pkg/archive/zip/writer.go
// ...
func (w *fileWriter) close() error {
// ...
// write data descriptor
var buf [dataDescriptorLen]byte
b := writeBuf(buf[:])
b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X
b.uint32(fh.CRC32)
b.uint32(fh.CompressedSize)
b.uint32(fh.UncompressedSize)
// ...
}
この変更により、Goで作成されたZIPファイルがmacOSのFinderなどのツールで正しく認識され、互換性が向上しました。
reader.go
の変更点
reader.go
では、データディスクリプタの読み込みロジックが大幅に改善されました。以前の実装では、データディスクリプタの署名の有無を適切に処理できていませんでした。このコミットでは、ZIP仕様の推奨に従い、署名がある場合とない場合の両方に対応できるように修正されました。
主な変更点は以下の通りです。
-
readDataDescriptor
関数の改善:- データディスクリプタを読み込む際に、まず最初の4バイトを読み込み、それがデータディスクリプタ署名
0x08074b50
と一致するかどうかを確認します。 - 一致しない場合、その4バイトは署名ではなく、データディスクリプタの最初のフィールド(通常はCRC32)の一部であると判断し、そのまま処理を続行します。
- 一致する場合、その4バイトは署名としてスキップし、残りのデータディスクリプタのフィールドを読み込みます。
- これにより、署名の有無にかかわらず、データディスクリプタを堅牢にパースできるようになりました。
- データディスクリプタを読み込む際に、まず最初の4バイトを読み込み、それがデータディスクリプタ署名
-
checksumReader
構造体の変更:checksumReader
は、ファイルの内容を読み込みながらCRC32チェックサムを計算し、ファイル終端でデータディスクリプタを読み込んで検証する役割を担います。- 以前は
zipr io.Reader
を持っていた部分がdesr io.Reader
に変更され、データディスクリプタを読み込むための専用のio.Reader
が渡されるようになりました。これにより、データディスクリプタの読み込みがより明確に分離されました。 Read
メソッド内で、io.EOF
に達した際にr.desr
が非nilであれば、readDataDescriptor
を呼び出してデータディスクリプタを読み込み、CRC32チェックサムを検証するロジックが追加されました。
struct.go
の変更点
struct.go
では、ZIPファイルフォーマットに関連する定数が定義されています。このコミットでは、データディスクリプタ署名の定数 dataDescriptorSignature
が追加され、dataDescriptorLen
の値が 12
から 16
に変更されました。
// src/pkg/archive/zip/struct.go
// ...
const (
// ...
dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder
// ...
dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size
)
dataDescriptorLen
が 16
に変更されたのは、データディスクリプタ署名(4バイト)が常に書き込まれるようになったため、データディスクリプタ全体の長さが4バイト増加したことを反映しています。
テストファイルの追加と修正
reader_test.go
と writer_test.go
には、これらの変更を検証するための新しいテストケースが追加されました。
go-no-datadesc-sig.zip
とgo-with-datadesc-sig.zip
という2つの新しいテスト用ZIPファイルが追加されました。これらは、それぞれデータディスクリプタ署名がない場合とある場合のZIPファイルを表し、reader.go
の修正が正しく機能するかを検証します。Bad-CRC32-in-data-descriptor
というテストケースが追加され、データディスクリプタ内のCRC32が破損している場合の挙動をテストします。- テストヘルパー関数
readTestFile
やtestFileMode
の引数にZipTest
やzipName
が追加され、テストのコンテキストがより明確になりました。
これらのテストの追加により、データディスクリプタの書き込みと読み込みの堅牢性が向上したことが確認できるようになりました。
コアとなるコードの変更箇所
src/pkg/archive/zip/reader.go
: データディスクリプタの読み込みロジック、特にreadDataDescriptor
関数とchecksumReader
構造体の修正。src/pkg/archive/zip/reader_test.go
: データディスクリプタの読み込みに関する新しいテストケースの追加と既存テストの修正。src/pkg/archive/zip/struct.go
:dataDescriptorSignature
定数の追加とdataDescriptorLen
の更新。src/pkg/archive/zip/testdata/go-no-datadesc-sig.zip
: データディスクリプタ署名なしのテスト用ZIPファイル(新規追加)。src/pkg/archive/zip/testdata/go-with-datadesc-sig.zip
: データディスクリプタ署名ありのテスト用ZIPファイル(新規追加)。src/pkg/archive/zip/writer.go
: データディスクリプタ書き込み時に署名を常に含めるように修正。src/pkg/archive/zip/writer_test.go
:testFileMode
関数の呼び出し修正。
コアとなるコードの解説
src/pkg/archive/zip/reader.go
の readDataDescriptor
関数
func readDataDescriptor(r io.Reader, f *File) error {
var buf [dataDescriptorLen]byte
// The spec says: "Although not originally assigned a
// signature, the value 0x08074b50 has commonly been adopted
// as a signature value for the data descriptor record.
// Implementers should be aware that ZIP files may be
// encountered with or without this signature marking data
// descriptors and should account for either case when reading
// ZIP files to ensure compatibility."
//
// dataDescriptorLen includes the size of the signature but
// first read just those 4 bytes to see if it exists.
if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
off := 0
maybeSig := readBuf(buf[:4])
if maybeSig.uint32() != dataDescriptorSignature {
// No data descriptor signature. Keep these four
// bytes.
off += 4
}
if _, err := io.ReadFull(r, buf[off:12]); err != nil {
return err
}
b := readBuf(buf[:12])
f.CRC32 = b.uint32()
f.CompressedSize = b.uint32()
f.UncompressedSize = b.uint32()
return nil
}
この関数は、データディスクリプタを読み込む際の中心的なロジックを含んでいます。
- まず、
io.ReadFull(r, buf[:4])
で最初の4バイトを読み込みます。これは、データディスクリプタ署名が存在するかどうかを判断するためです。 maybeSig.uint32() != dataDescriptorSignature
で、読み込んだ4バイトが期待される署名と一致するかをチェックします。- 一致しない場合 (
off += 4
)、それは署名ではなく、データディスクリプタの最初のフィールド(CRC32)の一部であると判断し、オフセットを4バイト進めます。これにより、次のio.ReadFull
が正しい位置から読み込みを開始します。 - 一致する場合、署名はスキップされ、オフセットは0のままです。
- 次に
io.ReadFull(r, buf[off:12])
で、残りの12バイト(CRC32、圧縮サイズ、非圧縮サイズ)を読み込みます。off
の値によって、署名がスキップされたかどうかが考慮されます。 - 最後に、読み込んだバイト列からCRC32、圧縮サイズ、非圧縮サイズを抽出し、
File
オブジェクトに設定します。
このロジックにより、署名の有無にかかわらずデータディスクリプタを正しくパースできるようになり、ZIPファイルの互換性が大幅に向上しました。
src/pkg/archive/zip/writer.go
の fileWriter.close()
メソッド
// ...
func (w *fileWriter) close() error {
// ...
// write data descriptor
var buf [dataDescriptorLen]byte
b := writeBuf(buf[:])
b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X
b.uint32(fh.CRC32)
b.uint32(fh.CompressedSize)
b.uint32(fh.UncompressedSize)
if _, err := w.w.Write(buf[:]); err != nil {
return err
}
// ...
}
この部分では、データディスクリプタをZIPファイルに書き込む処理が行われます。
重要な変更点は、b.uint32(dataDescriptorSignature)
の行が追加されたことです。これにより、データディスクリプタの先頭に 0x08074b50
という署名が常に書き込まれるようになりました。コメントにもあるように、これは「デファクトスタンダードであり、OS X Finderで必要とされる」ため、互換性確保のために導入されました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/3cea4131dfa3d07f74b53a4f26412d4a0470717e
- Go Issue #3252: https://golang.org/issue/3252
- Go CL 5787062: https://golang.org/cl/5787062