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

[インデックス 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ファイルは、複数のファイルを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。

  1. ローカルファイルヘッダ (Local File Header): 各ファイルデータの直前に配置され、ファイル名、圧縮方式、圧縮・非圧縮サイズ、CRC32チェックサムなどの情報を含みます。
  2. ファイルデータ (File Data): 実際のファイルの内容(圧縮されている場合もある)です。
  3. データディスクリプタ (Data Descriptor): ローカルファイルヘッダで圧縮・非圧縮サイズやCRC32チェックサムが 0 に設定されている場合(つまり、これらの情報が事前に不明な場合)に、ファイルデータの直後にこれらの実際の値を記述するために使用されます。ストリーミング圧縮など、ファイルサイズが事前に分からない場合に特に重要です。
  4. セントラルディレクトリ (Central Directory): ZIPファイルの末尾に配置され、アーカイブ内の全ファイルのローカルファイルヘッダのコピーのような情報(ファイル名、圧縮方式、サイズ、CRC32、ローカルファイルヘッダへのオフセットなど)を一元的に管理します。これにより、ZIPファイル全体をスキャンせずに特定のファイルにアクセスできます。
  5. セントラルディレクトリエンドレコード (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.gowriter.gostruct.go の3つの主要なファイルに影響を与えています。

writer.go の変更点

以前のGoの archive/zip パッケージは、データディスクリプタを書き込む際に、ZIP仕様で「オプション」とされている 0x08074b50 の署名を書き込んでいませんでした。このコミットでは、writer.gofileWriter.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仕様の推奨に従い、署名がある場合とない場合の両方に対応できるように修正されました。

主な変更点は以下の通りです。

  1. readDataDescriptor 関数の改善:

    • データディスクリプタを読み込む際に、まず最初の4バイトを読み込み、それがデータディスクリプタ署名 0x08074b50 と一致するかどうかを確認します。
    • 一致しない場合、その4バイトは署名ではなく、データディスクリプタの最初のフィールド(通常はCRC32)の一部であると判断し、そのまま処理を続行します。
    • 一致する場合、その4バイトは署名としてスキップし、残りのデータディスクリプタのフィールドを読み込みます。
    • これにより、署名の有無にかかわらず、データディスクリプタを堅牢にパースできるようになりました。
  2. 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
)

dataDescriptorLen16 に変更されたのは、データディスクリプタ署名(4バイト)が常に書き込まれるようになったため、データディスクリプタ全体の長さが4バイト増加したことを反映しています。

テストファイルの追加と修正

reader_test.gowriter_test.go には、これらの変更を検証するための新しいテストケースが追加されました。

  • go-no-datadesc-sig.zipgo-with-datadesc-sig.zip という2つの新しいテスト用ZIPファイルが追加されました。これらは、それぞれデータディスクリプタ署名がない場合とある場合のZIPファイルを表し、reader.go の修正が正しく機能するかを検証します。
  • Bad-CRC32-in-data-descriptor というテストケースが追加され、データディスクリプタ内のCRC32が破損している場合の挙動をテストします。
  • テストヘルパー関数 readTestFiletestFileMode の引数に ZipTestzipName が追加され、テストのコンテキストがより明確になりました。

これらのテストの追加により、データディスクリプタの書き込みと読み込みの堅牢性が向上したことが確認できるようになりました。

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

  • 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.goreadDataDescriptor 関数

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
}

この関数は、データディスクリプタを読み込む際の中心的なロジックを含んでいます。

  1. まず、io.ReadFull(r, buf[:4]) で最初の4バイトを読み込みます。これは、データディスクリプタ署名が存在するかどうかを判断するためです。
  2. maybeSig.uint32() != dataDescriptorSignature で、読み込んだ4バイトが期待される署名と一致するかをチェックします。
  3. 一致しない場合 (off += 4)、それは署名ではなく、データディスクリプタの最初のフィールド(CRC32)の一部であると判断し、オフセットを4バイト進めます。これにより、次の io.ReadFull が正しい位置から読み込みを開始します。
  4. 一致する場合、署名はスキップされ、オフセットは0のままです。
  5. 次に io.ReadFull(r, buf[off:12]) で、残りの12バイト(CRC32、圧縮サイズ、非圧縮サイズ)を読み込みます。off の値によって、署名がスキップされたかどうかが考慮されます。
  6. 最後に、読み込んだバイト列からCRC32、圧縮サイズ、非圧縮サイズを抽出し、File オブジェクトに設定します。

このロジックにより、署名の有無にかかわらずデータディスクリプタを正しくパースできるようになり、ZIPファイルの互換性が大幅に向上しました。

src/pkg/archive/zip/writer.gofileWriter.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で必要とされる」ため、互換性確保のために導入されました。

関連リンク

参考にした情報源リンク

[インデックス 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ファイルは、複数のファイルを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。

  1. ローカルファイルヘッダ (Local File Header): 各ファイルデータの直前に配置され、ファイル名、圧縮方式、圧縮・非圧縮サイズ、CRC32チェックサムなどの情報を含みます。
  2. ファイルデータ (File Data): 実際のファイルの内容(圧縮されている場合もある)です。
  3. データディスクリプタ (Data Descriptor): ローカルファイルヘッダで圧縮・非圧縮サイズやCRC32チェックサムが 0 に設定されている場合(つまり、これらの情報が事前に不明な場合)に、ファイルデータの直後にこれらの実際の値を記述するために使用されます。ストリーミング圧縮など、ファイルサイズが事前に分からない場合に特に重要です。
  4. セントラルディレクトリ (Central Directory): ZIPファイルの末尾に配置され、アーカイブ内の全ファイルのローカルファイルヘッダのコピーのような情報(ファイル名、圧縮方式、サイズ、CRC32、ローカルファイルヘッダへのオフセットなど)を一元的に管理します。これにより、ZIPファイル全体をスキャンせずに特定のファイルにアクセスできます。
  5. セントラルディレクトリエンドレコード (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.gowriter.gostruct.go の3つの主要なファイルに影響を与えています。

writer.go の変更点

以前のGoの archive/zip パッケージは、データディスクリプタを書き込む際に、ZIP仕様で「オプション」とされている 0x08074b50 の署名を書き込んでいませんでした。このコミットでは、writer.gofileWriter.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仕様の推奨に従い、署名がある場合とない場合の両方に対応できるように修正されました。

主な変更点は以下の通りです。

  1. readDataDescriptor 関数の改善:

    • データディスクリプタを読み込む際に、まず最初の4バイトを読み込み、それがデータディスクリプタ署名 0x08074b50 と一致するかどうかを確認します。
    • 一致しない場合、その4バイトは署名ではなく、データディスクリプタの最初のフィールド(通常はCRC32)の一部であると判断し、そのまま処理を続行します。
    • 一致する場合、その4バイトは署名としてスキップし、残りのデータディスクリプタのフィールドを読み込みます。
    • これにより、署名の有無にかかわらず、データディスクリプタを堅牢にパースできるようになりました。
  2. 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
)

dataDescriptorLen16 に変更されたのは、データディスクリプタ署名(4バイト)が常に書き込まれるようになったため、データディスクリプタ全体の長さが4バイト増加したことを反映しています。

テストファイルの追加と修正

reader_test.gowriter_test.go には、これらの変更を検証するための新しいテストケースが追加されました。

  • go-no-datadesc-sig.zipgo-with-datadesc-sig.zip という2つの新しいテスト用ZIPファイルが追加されました。これらは、それぞれデータディスクリプタ署名がない場合とある場合のZIPファイルを表し、reader.go の修正が正しく機能するかを検証します。
  • Bad-CRC32-in-data-descriptor というテストケースが追加され、データディスクリプタ内のCRC32が破損している場合の挙動をテストします。
  • テストヘルパー関数 readTestFiletestFileMode の引数に ZipTestzipName が追加され、テストのコンテキストがより明確になりました。

これらのテストの追加により、データディスクリプタの書き込みと読み込みの堅牢性が向上したことが確認できるようになりました。

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

  • 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.goreadDataDescriptor 関数

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
}

この関数は、データディスクリプタを読み込む際の中心的なロジックを含んでいます。

  1. まず、io.ReadFull(r, buf[:4]) で最初の4バイトを読み込みます。これは、データディスクリプタ署名が存在するかどうかを判断するためです。
  2. maybeSig.uint32() != dataDescriptorSignature で、読み込んだ4バイトが期待される署名と一致するかをチェックします。
  3. 一致しない場合 (off += 4)、それは署名ではなく、データディスクリプタの最初のフィールド(CRC32)の一部であると判断し、オフセットを4バイト進めます。これにより、次の io.ReadFull が正しい位置から読み込みを開始します。
  4. 一致する場合、署名はスキップされ、オフセットは0のままです。
  5. 次に io.ReadFull(r, buf[off:12]) で、残りの12バイト(CRC32、圧縮サイズ、非圧縮サイズ)を読み込みます。off の値によって、署名がスキップされたかどうかが考慮されます。
  6. 最後に、読み込んだバイト列からCRC32、圧縮サイズ、非圧縮サイズを抽出し、File オブジェクトに設定します。

このロジックにより、署名の有無にかかわらずデータディスクリプタを正しくパースできるようになり、ZIPファイルの互換性が大幅に向上しました。

src/pkg/archive/zip/writer.gofileWriter.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で必要とされる」ため、互換性確保のために導入されました。

関連リンク

参考にした情報源リンク