[インデックス 11538] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/base64
パッケージにおけるBase64デコード処理の改善に関するものです。具体的には、デコード時に改行文字(\r
および \n
)を無視するように変更が加えられました。これにより、改行文字を含むBase64エンコードされたデータストリームをより柔軟に処理できるようになります。
変更されたファイルは以下の通りです。
src/pkg/encoding/base64/base64.go
: Base64エンコード/デコードの主要ロジックが実装されているファイルです。デコード処理が改行文字を無視するように修正されました。src/pkg/encoding/base64/base64_test.go
:encoding/base64
パッケージのテストファイルです。改行文字を含むBase64文字列のデコードが正しく行われることを検証するための新しいテストケースが追加されました。
コミット
commit 2f2b6e55ef69126bef77396a3834915b2121fa80
Author: David Symonds <dsymonds@golang.org>
Date: Wed Feb 1 19:13:38 2012 +1100
encoding/base64: ignore new line characters during decode.
Fixes #2541.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5610045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2f2b6e55ef69126bef77396a3834915b2121fa80
元コミット内容
encoding/base64: ignore new line characters during decode.
Fixes #2541.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5610045
変更の背景
この変更は、Go言語のIssue #2541「encoding/base64: ignore \r \n during decode」に対応するものです。
Base64エンコードされたデータは、通常、76文字ごとに改行を挿入することがRFC 2045で推奨されています。しかし、多くのシステムやプロトコルでは、この改行の有無や位置が厳密に守られない場合があります。特に、他のプログラミング言語(例: Python)のBase64デコーダは、デコード時に改行文字を自動的に無視する実装が一般的です。
Go言語の encoding/base64
パッケージの以前の実装では、デコード対象の文字列に改行文字が含まれていると、CorruptInputError
を発生させていました。これは、異なるシステム間でBase64エンコードされたデータをやり取りする際に互換性の問題を引き起こす可能性がありました。例えば、改行を含むBase64文字列をGoでデコードしようとするとエラーになるが、Pythonでは問題なくデコードできる、といった状況が発生していました。
このコミットは、このような互換性の問題を解消し、より堅牢で実用的なBase64デコーダを提供することを目的としています。改行文字を無視することで、より多様な形式のBase64データに対応できるようになります。
前提知識の解説
Base64エンコーディング
Base64は、バイナリデータをASCII文字列の形式に変換するエンコーディング方式です。主に、テキストベースのプロトコル(例: 電子メールのMIME、HTTP)でバイナリデータを安全に転送するために使用されます。
- 原理: 3バイト(24ビット)のバイナリデータを、4つの6ビットのグループに分割し、それぞれをBase64アルファベット(A-Z, a-z, 0-9, +, /)のいずれかの文字にマッピングします。
- パディング: 入力データが3バイトの倍数でない場合、出力の最後に
=
文字が追加され、パディングが行われます。例えば、1バイトのデータは==
で、2バイトのデータは=
でパディングされます。 - 改行: RFC 2045では、Base64エンコードされた行の長さを76文字に制限し、それ以降に改行(CRLF:
\r\n
)を挿入することを推奨しています。これは、古いメールシステムなど、行の長さに制限がある環境での互換性を保つためです。しかし、これは必須の要件ではなく、多くの実装では無視されるか、異なる行長が使用されます。
改行文字
- CR (Carriage Return):
\r
(ASCIIコード 13)。カーソルを行の先頭に戻す制御文字です。 - LF (Line Feed):
\n
(ASCIIコード 10)。カーソルを次の行に移動する制御文字です。 - CRLF:
\r\n
。Windowsやインターネットプロトコル(HTTP、SMTPなど)で一般的に使用される改行コードです。 - LF:
\n
。Unix/Linux系システムで一般的に使用される改行コードです。
Go言語の encoding/base64
パッケージ
Go言語の encoding/base64
パッケージは、Base64エンコーディングとデコーディングの機能を提供します。
StdEncoding
: 標準のBase64エンコーディング(RFC 4648)。URLEncoding
: URLおよびファイル名セーフなBase64エンコーディング(RFC 4648)。DecodeString(s string) ([]byte, error)
: Base64文字列をデコードしてバイトスライスを返します。EncodeToString(src []byte) string
: バイトスライスをBase64文字列にエンコードして返します。
以前のバージョンでは、Decode
や DecodeString
メソッドは、入力文字列にBase64アルファベット、パディング文字 (=
) 以外の文字(特に改行文字)が含まれていると、不正な入力として CorruptInputError
を返していました。
技術的詳細
このコミットの主要な変更点は、encoding/base64
パッケージの内部デコード関数 (*Encoding).decode
が、入力ストリームから改行文字 (\r
および \n
) を読み飛ばすように修正されたことです。
以前の decode
関数は、入力 src
を4バイトの「量子(quantum)」単位で処理し、len(src)%4==0
であることを前提としていました。これは、Base64が4文字で3バイトのデータを表現するという性質に基づいています。しかし、改行文字が挿入されると、この4バイトの倍数という前提が崩れ、デコードエラーが発生していました。
新しい実装では、この前提が取り払われ、入力 src
を1バイトずつ読み進めるループ構造に変更されました。
- 入力ソースの変更:
src
スライスを直接操作し、読み込んだバイトをsrc
から削除していくことで、入力ストリームの現在位置を管理します。 - 改行文字の無視:
for j := 0; j < 4; { ... }
ループ内で、in := src[0]
で現在のバイトを読み込み、src = src[1:]
でsrc
スライスを更新します。この際、読み込んだバイトin
が\r
または\n
であった場合、continue
を使用してその文字をスキップし、j
(現在の量子内のバイト数)をインクリメントせずに次の文字を読み込みます。これにより、改行文字がBase64の量子の一部として扱われることなく、透過的に無視されます。 - パディングの処理の調整: パディング文字
=
の検出ロジックも、len(src)
を使用して残りの入力の長さを確認するように調整されました。これにより、改行文字がスキップされた後でも、正しいパディングの検出と処理が行われます。 - エラーオフセットの修正:
CorruptInputError
が発生した場合のオフセット計算も、元の入力osrc
と現在のsrc
の長さの差に基づいて行われるように修正され、改行文字がスキップされた場合でも正確なエラー位置を報告できるようになりました。 Decode
関数の変更:Decode
関数からif len(src)%4 != 0 { ... }
という入力長のチェックが削除されました。これは、decode
関数が改行文字を無視するようになったため、入力長が4の倍数である必要がなくなったためです。
これらの変更により、encoding/base64
パッケージは、改行文字が混在するBase64エンコードされたデータストリームを、他の一般的なBase64デコーダと同様に、エラーなくデコードできるようになりました。
コアとなるコードの変更箇所
src/pkg/encoding/base64/base64.go
--- a/src/pkg/encoding/base64/base64.go
+++ b/src/pkg/encoding/base64/base64.go
@@ -208,22 +208,30 @@ func (e CorruptInputError) Error() string {
// decode is like Decode but returns an additional 'end' value, which
// indicates if end-of-message padding was encountered and thus any
-// additional data is an error. decode also assumes len(src)%4==0,
-// since it is meant for internal use.
+// additional data is an error.
func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
-\tfor i := 0; i < len(src)/4 && !end; i++ {\n+\tosrc := src
+\tfor len(src) > 0 && !end {
\t\t// Decode quantum using the base64 alphabet
\t\tvar dbuf [4]byte
\t\tdlen := 4
\tdbufloop:
-\t\tfor j := 0; j < 4; j++ {\n-\t\t\tin := src[i*4+j]\n-\t\t\tif in == '=' && j >= 2 && i == len(src)/4-1 {\n+\t\tfor j := 0; j < 4; {
+\t\t\tif len(src) == 0 {
+\t\t\t\treturn n, false, CorruptInputError(len(osrc) - len(src) - j)
+\t\t\t}
+\t\t\tin := src[0]
+\t\t\tsrc = src[1:]
+\t\t\tif in == '\r' || in == '\n' {
+\t\t\t\t// Ignore this character.
+\t\t\t\tcontinue
+\t\t\t}
+\t\t\tif in == '=' && j >= 2 && len(src) < 4 {
\t\t\t\t// We've reached the end and there's
\t\t\t\t// padding
-\t\t\t\tif src[i*4+3] != '=' {\n-\t\t\t\t\treturn n, false, CorruptInputError(i*4 + 2)\n+\t\t\t\tif len(src) > 0 && src[0] != '=' {
+\t\t\t\t\treturn n, false, CorruptInputError(len(osrc) - len(src) - 1)
\t\t\t\t}
\t\t\t\tdlen = j
\t\t\t\tend = true
@@ -231,22 +249,24 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
\t\t\t}\n \t\t\tdbuf[j] = enc.decodeMap[in]\n \t\t\tif dbuf[j] == 0xFF {\n-\t\t\t\treturn n, false, CorruptInputError(i*4 + j)\n+\t\t\t\treturn n, false, CorruptInputError(len(osrc) - len(src) - 1)
\t\t\t}\n+\t\t\tj++
\t\t}\n \n \t\t// Pack 4x 6-bit source blocks into 3 byte destination
\t\t// quantum
\t\tswitch dlen {\n \t\tcase 4:\n-\t\t\tdst[i*3+2] = dbuf[2]<<6 | dbuf[3]\n+\t\t\tdst[2] = dbuf[2]<<6 | dbuf[3]
\t\t\tfallthrough\n \t\tcase 3:\n-\t\t\tdst[i*3+1] = dbuf[1]<<4 | dbuf[2]>>2\n+\t\t\tdst[1] = dbuf[1]<<4 | dbuf[2]>>2
\t\t\tfallthrough\n \t\tcase 2:\n-\t\t\tdst[i*3+0] = dbuf[0]<<2 | dbuf[1]>>4\n+\t\t\tdst[0] = dbuf[0]<<2 | dbuf[1]>>4
\t\t}\n+\t\tdst = dst[3:]
\t\tn += dlen - 1
\t}\n \n@@ -257,11 +267,8 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
// DecodedLen(len(src)) bytes to dst and returns the number of bytes
// written. If src contains invalid base64 data, it will return the
// number of bytes successfully written and CorruptInputError.
+// New line characters (\r and \n) are ignored.
func (enc *Encoding) Decode(dst, src []byte) (n int, err error) {
-\tif len(src)%4 != 0 {\n-\t\treturn 0, CorruptInputError(len(src) / 4 * 4)\n-\t}\n-\n \tn, _, err = enc.decode(dst, src)\n \treturn
}\n```
### `src/pkg/encoding/base64/base64_test.go`
```diff
--- a/src/pkg/encoding/base64/base64_test.go
+++ b/src/pkg/encoding/base64/base64_test.go
@@ -197,3 +197,29 @@ func TestBig(t *testing.T) {
\t\tt.Errorf("Decode(Encode(%d-byte string)) failed at offset %d", n, i)
\t}\n }\n+\n+func TestNewLineCharacters(t *testing.T) {
+\t// Each of these should decode to the string "sure", without errors.
+\tconst expected = "sure"
+\texamples := []string{
+\t\t"c3VyZQ==",
+\t\t"c3VyZQ==\r",
+\t\t"c3VyZQ==\n",
+\t\t"c3VyZQ==\r\n",
+\t\t"c3VyZ\r\nQ==",
+\t\t"c3V\ryZ\nQ==",
+\t\t"c3V\nyZ\rQ==",
+\t\t"c3VyZ\nQ==",
+\t\t"c3VyZQ\n==",
+\t}
+\tfor _, e := range examples {
+\t\tbuf, err := StdEncoding.DecodeString(e)
+\t\tif err != nil {
+\t\t\tt.Errorf("Decode(%q) failed: %v", e, err)
+\t\t\tcontinue
+\t\t}
+\t\tif s := string(buf); s != expected {
+\t\t\tt.Errorf("Decode(%q) = %q, want %q", e, s, expected)
+\t\t}\n+\t}
+}\n```
## コアとなるコードの解説
### `src/pkg/encoding/base64/base64.go` の変更点
* **`decode` 関数のシグネチャ変更とコメント更新**:
* 以前は `decode` 関数が `len(src)%4==0` を前提としていましたが、この前提が削除されました。
* コメントも「`decode also assumes len(src)%4==0, since it is meant for internal use.`」から、この前提がなくなったことを反映するように変更されました。
* **入力ソースのイテレーション方法の変更**:
* 以前は `for i := 0; i < len(src)/4 && !end; i++` のように、4バイト単位でインデックス `i` を進めていました。
* 新しいコードでは `tosrc := src` で元のソースを保存し、`for len(src) > 0 && !end` ループと `src = src[1:]` を使用して、入力 `src` を1バイトずつ消費するように変更されました。これにより、入力ストリームから任意の文字を読み飛ばすことが可能になります。
* **改行文字の無視ロジックの追加**:
* 内部の `for j := 0; j < 4; { ... }` ループ内で、`in := src[0]` で現在のバイトを読み込み、`src = src[1:]` で `src` スライスを更新します。
* `if in == '\r' || in == '\n' { continue }` という条件が追加されました。これにより、読み込んだ文字が改行文字であった場合、その文字を無視して次の文字の処理に進みます。`j` はインクリメントされないため、Base64の4文字の量子を構成する有効な文字が揃うまでループが続行されます。
* **パディング処理の調整**:
* パディング文字 `=` の検出条件 `if in == '=' && j >= 2 && i == len(src)/4-1` が `if in == '=' && j >= 2 && len(src) < 4` に変更されました。これは、`src` の残りの長さに基づいてパディングを検出するように調整されたものです。
* パディング文字が不正な場合の `CorruptInputError` のオフセット計算も `len(osrc) - len(src) - 1` のように、元のソース `osrc` と現在の `src` の長さの差に基づいて行われるように修正されました。
* **`CorruptInputError` のオフセット計算の修正**:
* 不正なBase64文字が検出された場合のエラーオフセット計算 `return n, false, CorruptInputError(i*4 + j)` が `return n, false, CorruptInputError(len(osrc) - len(src) - 1)` に変更されました。これにより、改行文字がスキップされた場合でも、エラーが発生した元の入力ストリーム内の正確な位置を報告できるようになります。
* **`dst` スライスへの書き込みインデックスの変更**:
* 以前は `dst[i*3+2]`, `dst[i*3+1]`, `dst[i*3+0]` のように `i` を使って書き込み位置を計算していましたが、`dst[2]`, `dst[1]`, `dst[0]` に変更され、`dst = dst[3:]` で `dst` スライス自体を3バイトずつ進めるようになりました。これは、入力のイテレーション方法の変更に合わせて、出力の書き込みも相対的に行うようにしたものです。
* **`Decode` 関数からの入力長チェックの削除**:
* `Decode` 関数から `if len(src)%4 != 0 { return 0, CorruptInputError(len(src) / 4 * 4) }` という行が削除されました。これは、`decode` 関数が改行文字を無視するようになったため、入力文字列の長さが4の倍数であるという厳密な要件がなくなったためです。
* **`Decode` 関数のコメント更新**:
* `Decode` 関数のコメントに「`New line characters (\r and \n) are ignored.`」という記述が追加され、この変更の意図が明確に示されました。
### `src/pkg/encoding/base64/base64_test.go` の変更点
* **`TestNewLineCharacters` 関数の追加**:
* この新しいテスト関数は、様々な位置に改行文字 (`\r`, `\n`, `\r\n`) が挿入されたBase64文字列が、すべて正しく "sure" という文字列にデコードされることを検証します。
* `examples` スライスには、改行文字を含まない標準的なBase64文字列から、文字列の末尾、先頭、中間、パディング文字の直前など、様々なパターンで改行文字が挿入されたBase64文字列が定義されています。
* 各例に対して `StdEncoding.DecodeString` を呼び出し、エラーが発生しないこと、およびデコード結果が期待される文字列 "sure" と一致することを確認しています。これにより、改行文字を無視する新しいデコードロジックが正しく機能していることが保証されます。
これらの変更により、Go言語の `encoding/base64` パッケージは、より堅牢で、他のシステムとの互換性が高いBase64デコーダとして機能するようになりました。
## 関連リンク
* Go Issue #2541: [https://github.com/golang/go/issues/2541](https://github.com/golang/go/issues/2541)
* Go CL 5610045: [https://golang.org/cl/5610045](https://golang.org/cl/5610045)
## 参考にした情報源リンク
* Go issue 2541 refers to an issue in the official Go programming language repository on GitHub, titled "encoding/base64: ignore \r \n during decode". (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF_21oef6sZ7SoHxdFrhL50_DzW6RfAiB-Wg10REnlbpUHF26CeNr_QVZC5SLuJj-LqWKwHq9klOpYFs6iT1ciehfTTZtoLMPTg89dpt-O7gQWwvcN-5fxhpL-f_sgD7Y785WE=)