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

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

このコミットでは、src/pkg/encoding/base64/base64.gosrc/pkg/encoding/base64/base64_test.go の2つのファイルが変更されています。base64.go は Base64 エンコーディング/デコーディングの主要なロジックを含み、base64_test.go はそのテストコードです。

コミット

commit a6d3cc2904e42338d31cca00024a153e7c1b502c
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Apr 16 11:32:41 2014 -0700

    encoding/base64: don't lose a byte of output when encountering trailing garbage
    
    Fixes #7733
    
    LGTM=minux.ma
    R=golang-codereviews, minux.ma
    CC=golang-codereviews, nigeltao, r, rsc
    https://golang.org/cl/88330044

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

https://github.com/golang/go/commit/a6d3cc2904e42338d31cca00024a153e7c1b502c

元コミット内容

encoding/base64: don't lose a byte of output when encountering trailing garbage

Fixes #7733

LGTM=minux.ma
R=golang-codereviews, minux.ma
CC=golang-codereviews, nigeltao, r, rsc
https://golang.org/cl/88330044

変更の背景

このコミットは、Go言語の encoding/base64 パッケージにおけるデコード処理のバグを修正するものです。具体的には、Base64エンコードされた入力文字列の末尾に「トレーリングガーベージ」(不正な文字)が存在する場合に、デコードされた出力の一部が失われる("lose a byte of output")という問題がありました。

従来のdecode関数は、トレーリングガーベージを検出すると即座にCorruptInputErrorを返していましたが、この際、エラーが検出される直前まで正しくデコードされていた有効なバイト数nが正しく反映されない可能性がありました。つまり、エラーを返す時点で、既にデコードが完了していたはずの最後の有効なバイトが、出力としてカウントされないまま関数が終了してしまうという挙動です。

この問題は、Fixes #7733 として参照されているGoのIssueで報告されたものと考えられます。この修正により、不正な入力であっても、それまでに正しくデコードされた部分は確実に返されるようになり、デコーダの堅牢性と正確性が向上しました。

前提知識の解説

Base64エンコーディング

Base64は、バイナリデータをASCII文字列の形式に変換するエンコーディング方式です。主に、バイナリデータをテキストベースのプロトコル(例: 電子メール、HTTPなど)で安全に転送するために使用されます。

  • 変換の基本: Base64は、3バイトのバイナリデータを4つのBase64文字(6ビットずつ)に変換します。
  • 文字セット: 大文字A-Z、小文字a-z、数字0-9、+/ の64文字を使用します。
  • パディング (=): 入力データのバイト数が3の倍数でない場合、出力の最後にパディング文字 = が追加されます。例えば、1バイトのデータは==、2バイトのデータは=でパディングされます。

トレーリングガーベージ (Trailing Garbage)

Base64エンコードされた文字列の末尾に、有効なBase64文字(A-Z, a-z, 0-9, +, /)でもパディング文字(=)でもない余分な文字が存在する場合、これを「トレーリングガーベージ」と呼びます。Base64デコーダは、このような不正な文字を検出した場合、通常はエラーとして処理する必要があります。

CorruptInputError

encoding/base64 パッケージでは、入力されたBase64文字列が不正な形式である場合に CorruptInputError というエラー型を返します。このエラーは、デコード処理中に不正な文字や不正なパディングが検出された際に発生します。

Go言語のエラーハンドリング

Go言語では、エラーは関数の戻り値として明示的に返されるのが一般的な慣習です。関数が正常に処理を完了した場合はnilを返し、エラーが発生した場合はerrorインターフェースを満たす値を返します。呼び出し元は、戻り値のエラーをチェックすることで、処理の成功/失敗を判断します。

技術的詳細

このコミットの核心は、encoding/base64 パッケージの decode メソッドにおけるエラー処理の変更です。

decode メソッドは、Base64エンコードされたバイトスライス src をデコードし、結果を dst に書き込みます。この関数は、デコードされたバイト数 n、入力の終端に達したかを示す end、および発生したエラー err の3つの値を返します。

元の実装では、デコードループの途中でトレーリングガーベージ(不正な文字)を検出した場合、以下のようなコードで即座にエラーを返していました。

// trailing garbage
return n, false, CorruptInputError(olen - len(src))

このreturn文は、エラーが検出された時点でdecode関数を即座に終了させます。問題は、このnの値が、エラーが検出される直前まで正しくデコードされていたバイト数を正確に反映していない可能性があったことです。特に、有効なBase64シーケンスの直後に不正な文字が続く場合、不正な文字を検出した時点で即座にエラーを返していたため、その直前まで正しくデコードされていた最後のバイトがnに含まれないまま返されてしまうというバグがありました。これにより、デコードされたはずのデータの一部が「失われる」という現象が発生していました。

このコミットによる修正は、この即座のreturnを以下のように変更することで、この問題を解決しています。

err = CorruptInputError(olen - len(src))

これにより、トレーリングガーベージが検出されても、decode関数は即座に終了せず、エラー情報をerr変数に格納するだけになります。そして、デコードループが終了した後、最終的なreturn文でn, end, errの3つの値を返します。

// 修正前
return n, end, nil

// 修正後
return n, end, err

この変更により、エラーが検出された場合でも、それまでにデコードされた有効なバイト数nが正しく計算され、最終的に返されるようになりました。つまり、入力が不正であっても、デコーダは可能な限り多くの有効なデータを抽出し、その上でエラーを報告する、より堅牢な挙動を示すようになります。

この修正を検証するために、base64_test.goTestDecoderIssue7733 という新しいテスト関数が追加されました。このテストは、"YWJjZA=====" という入力文字列を使用しています。この文字列は、"abcd" をBase64エンコードした YWJjZA== に、余分な = が複数付加されたものです。

テストでは、この文字列をデコードした際に、以下の2つの条件が満たされることを検証しています。

  1. デコードされた文字列が "abcd" であること。これは、トレーリングガーベージが存在しても、有効な部分が正しくデコードされることを保証します。
  2. 返されるエラーが CorruptInputError(8) であること。これは、8バイト目(YWJjZAの後の最初の=)で不正な入力が検出されたことを示し、エラーが正しく報告されることを確認します。

このテストの追加により、修正が意図した通りに機能し、トレーリングガーベージが存在する場合でも有効な出力が失われないことが保証されました。

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

src/pkg/encoding/base64/base64.go

--- a/src/pkg/encoding/base64/base64.go
+++ b/src/pkg/encoding/base64/base64.go
@@ -250,7 +250,7 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
 				}
 				if len(src) > 0 {
 					// trailing garbage
-					return n, false, CorruptInputError(olen - len(src))
+					err = CorruptInputError(olen - len(src))
 				}
 				dlen, end = j, true
 				break
@@ -277,7 +277,7 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
 		n += dlen - 1
 	}

-	return n, end, nil
+	return n, end, err
 }

 // Decode decodes src using the encoding enc.  It writes at most

src/pkg/encoding/base64/base64_test.go

--- a/src/pkg/encoding/base64/base64_test.go
+++ b/src/pkg/encoding/base64/base64_test.go
@@ -165,6 +166,7 @@ func TestDecodeCorrupt(t *testing.T) {
 		{"AAA=", -1},
 		{"AAAA", -1},
 		{"AAAAAA=", 7},
+		{"YWJjZA=====", 8},
 	}
 	for _, tc := range testCases {
 		dbuf := make([]byte, StdEncoding.DecodedLen(len(tc.input)))
@@ -329,3 +331,14 @@ bqbPb06551Y4
 		t.Error("Decoded results not equal")
 	}
 }
+
+func TestDecoderIssue7733(t *testing.T) {
+	s, err := StdEncoding.DecodeString("YWJjZA=====")
+	want := CorruptInputError(8)
+	if !reflect.DeepEqual(want, err) {
+		t.Errorf("Error = %v; want CorruptInputError(8)")
+	}
+	if string(s) != "abcd" {
+		t.Errorf("DecodeString = %q; want abcd", s)
+	}
+}

コアとなるコードの解説

src/pkg/encoding/base64/base64.go の変更点

  1. エラーハンドリングの変更:

    • 変更前: return n, false, CorruptInputError(olen - len(src))
    • 変更後: err = CorruptInputError(olen - len(src)) この変更により、トレーリングガーベージが検出された際に、関数が即座に終了するのではなく、エラー情報をerr変数に格納するようになりました。これにより、デコードループが最後まで実行され、n(デコードされたバイト数)が正確に計算される機会が与えられます。
  2. 最終的な戻り値の変更:

    • 変更前: return n, end, nil
    • 変更後: return n, end, err デコードループの最後に、nilではなくerr変数の内容を返すように変更されました。これにより、ループ内でCorruptInputErrorが設定されていた場合、そのエラーが呼び出し元に正しく伝播されるようになります。この2つの変更が組み合わさることで、トレーリングガーベージが存在する場合でも、それまでにデコードされた有効なバイト数nが正しく返され、かつエラーも適切に報告されるようになりました。

src/pkg/encoding/base64/base64_test.go の変更点

  1. TestDecodeCorrupt へのテストケース追加:

    • {"YWJjZA=====", 8} という新しいテストケースが testCases スライスに追加されました。
    • "YWJjZA=====""abcd" をBase64エンコードした文字列に余分なパディング文字が付加されたものです。このテストケースは、8バイト目(YWJjZAの後の最初の=)で不正な入力が検出されることを期待しています。これは、デコーダがトレーリングガーベージを正しく識別できることを確認するためのものです。
  2. TestDecoderIssue7733 関数の追加:

    • この新しいテスト関数は、このコミットが修正する特定のシナリオを検証するために追加されました。
    • StdEncoding.DecodeString("YWJjZA=====") を呼び出し、返される文字列 s とエラー err をチェックします。
    • if !reflect.DeepEqual(want, err): 返されたエラーが CorruptInputError(8) と等しいことを検証します。これは、エラーが正しく報告され、そのオフセットも正しいことを確認します。
    • if string(s) != "abcd": デコードされた文字列が "abcd" であることを検証します。これは、トレーリングガーベージが存在する場合でも、それまでにデコードされた有効な出力が失われないという、このコミットの主要な目的を直接的に確認するものです。

これらの変更により、encoding/base64 パッケージのデコーダは、不正な入力に対する挙動がより正確かつ堅牢になりました。

関連リンク

  • Go CL (Change List): https://golang.org/cl/88330044 (このCLは古いものであり、現在のGoのコードレビューシステムでは直接アクセスできない可能性があります。)
  • Go Issue #7733: このコミットが修正した問題のIssue番号です。コミットメッセージに記載されていますが、古いIssueのため、現在の公開Issueトラッカーで詳細を見つけるのは難しい場合があります。しかし、コミットメッセージ自体が問題の概要(トレーリングガーベージによる出力の損失)を明確に示しています。

参考にした情報源リンク

  • コミットメッセージと差分 (./commit_data/19175.txt)
  • Base64エンコーディングに関する一般的な知識
  • Go言語のエラーハンドリングに関する一般的な知識