[インデックス 16920] ファイルの概要
このコミットは、Go言語のencoding/xml
パッケージにおけるXMLテキストのエスケープ処理に関するバグ修正です。具体的には、無効なUTF-8バイトがXML出力にそのまま渡されてしまう問題を解決し、XMLの仕様に準拠した正しいエスケープが行われるように変更されています。
コミット
commit 789e1c351e6fca96fe3cec467029732c03d55d64
Author: Alex A Skinner <alex@lx.lc>
Date: Tue Jul 30 14:11:47 2013 +1000
encoding/xml: Do not pass through invalid utf8 bytes
EscapeText now escapes 0xFFFD returned from DecodeRune as 0xFFFD, rather than passing through the original byte.
Fixes #5880.
R=golang-dev, r, bradfitz, adg
CC=golang-dev
https://golang.org/cl/11975043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/789e1c351e6fca96fe3cec467029732c03d55d64
元コミット内容
encoding/xml
: 無効なUTF8バイトをそのまま渡さない
EscapeText
は、DecodeRune
から返される0xFFFD
を、元のバイトをそのまま渡すのではなく、0xFFFD
としてエスケープするようになりました。
Fixes #5880
.
変更の背景
このコミットは、Go言語のencoding/xml
パッケージが、入力されたバイト列が無効なUTF-8シーケンスを含んでいた場合に、その無効なバイトをXML出力にそのまま含めてしまうという問題(Issue 5880)を修正するために行われました。
XMLの仕様では、特定の文字範囲(Unicodeの「文字」として定義される範囲)のみが許可されており、無効なバイトシーケンスや制御文字の一部はXMLドキュメント内に直接含めることができません。Goのunicode/utf8
パッケージのDecodeRune
関数は、無効なUTF-8シーケンスを検出した場合に、Unicodeの「Replacement Character」(U+FFFD)と、その無効なシーケンスを構成するバイト数(通常は1)を返します。
しかし、このコミット以前のencoding/xml
パッケージのEscapeText
関数は、DecodeRune
が0xFFFD
を返した場合でも、それが元の無効なバイトから変換されたものであることを適切に認識せず、その0xFFFD
が単一のバイト(width == 1
)として扱われる場合に、元の無効なバイトをそのままXML出力に含めてしまう可能性がありました。これにより、生成されるXMLドキュメントがXML仕様に違反し、他のXMLパーサーで正しく処理できないという問題が発生していました。
この問題は、特にバイナリデータや、文字エンコーディングが不適切な外部ソースからのデータをXMLに埋め込む際に顕在化します。XMLパーサーは、XMLドキュメントが整形式(well-formed)であることを要求するため、無効な文字が含まれているとパースエラーを引き起こします。
前提知識の解説
- XML (Extensible Markup Language): 構造化されたデータを表現するためのマークアップ言語です。XMLドキュメントは、特定の文字セット(通常はUnicode)と文字範囲に準拠する必要があります。無効な文字やバイトシーケンスが含まれていると、XMLパーサーはエラーを報告します。
- UTF-8: Unicode文字をエンコードするための可変長文字エンコーディングです。ASCII互換性があり、世界中のほとんどの文字を表現できます。UTF-8では、特定のバイトシーケンスが無効と見なされます(例: 継続バイトが先行バイトなしで出現する場合など)。
- Unicode Replacement Character (U+FFFD): 無効な、または表現できない文字シーケンスを置き換えるために使用される特殊なUnicode文字です。UTF-8デコーダは、入力ストリームで無効なバイトシーケンスを検出すると、通常この文字を返します。
unicode/utf8.DecodeRune
: Go言語の標準ライブラリunicode/utf8
パッケージに含まれる関数で、バイトスライスから次のUTF-8エンコードされたルーン(Unicodeコードポイント)をデコードします。この関数は、デコードされたルーンと、そのルーンを構成するバイト数を返します。入力が無効なUTF-8シーケンスの場合、DecodeRune
はU+FFFD
と、その無効なシーケンスを構成するバイト数(通常は1)を返します。encoding/xml
パッケージ: Go言語の標準ライブラリで、XMLドキュメントのエンコード(書き込み)とデコード(読み込み)を提供します。このパッケージは、Goの構造体とXML要素間のマッピングを容易にします。EscapeText
関数:encoding/xml
パッケージ内の関数で、XMLドキュメントに書き込むテキストコンテンツをエスケープする役割を担います。XMLの予約文字(<
,>
,&
,'
,"
)や、XMLで許可されていない文字(制御文字など)を適切なエンティティ参照(例:<
を<
に)に変換します。
技術的詳細
このコミットの核心は、encoding/xml
パッケージのEscapeText
関数における文字範囲チェックのロジックの変更です。
変更前のコードでは、EscapeText
関数は各ルーン(Unicodeコードポイント)を処理し、isInCharacterRange(r)
というヘルパー関数を使って、そのルーンがXMLで許可される文字範囲内にあるかどうかをチェックしていました。もしルーンが許可範囲外であれば、esc_fffd
(�
)としてエスケープされることになっていました。
しかし、問題はunicode/utf8.DecodeRune
が無効なUTF-8バイトシーケンスを検出した際に0xFFFD
を返す点にありました。この0xFFFD
自体はisInCharacterRange
関数によって「有効な文字」と判断されてしまうため、EscapeText
は0xFFFD
をエスケープせずにそのまま出力しようとしました。さらに、この0xFFFD
が元の無効なバイトから生成されたもので、かつその無効なバイトが単一のバイトとして扱われた場合(width == 1
)、元の無効なバイトがXML出力にそのまま含まれてしまうという挙動になっていました。これは、XMLの仕様に違反するものでした。
このコミットでは、EscapeText
関数の条件式が以下のように変更されました。
変更前:
if !isInCharacterRange(r) {
esc = esc_fffd
break
}
変更後:
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
esc = esc_fffd
break
}
この変更により、以下の条件のいずれかが満たされた場合に、ルーンが�
としてエスケープされるようになりました。
!isInCharacterRange(r)
: ルーンがXMLで許可される文字範囲外である場合(これは以前から存在した条件)。(r == 0xFFFD && width == 1)
: ルーンが0xFFFD
(Replacement Character)であり、かつその0xFFFD
が単一のバイトからデコードされたものである場合。
後者の条件が追加されたことで、unicode/utf8.DecodeRune
が無効な単一バイトを処理して0xFFFD
を返した場合に、その0xFFFD
がXML出力にそのまま含まれるのではなく、明示的に�
というXMLエンティティとしてエスケープされるようになりました。これにより、生成されるXMLドキュメントは常に整形式となり、無効なバイトが直接XMLに混入することを防ぎます。
テストケースTestIssue5880
では、無効なUTF-8バイトシーケンス({192, 168, 0, 1}
は、UTF-8の仕様では無効なバイトシーケンスです。特に192
は2バイトシーケンスの開始バイトですが、それに続くバイトが不正です)を含むバイトスライスをxml.Marshal
に渡し、その結果が有効なUTF-8であるかどうかをutf8.Valid
で検証しています。このテストが追加されたことで、将来的に同様の問題が再発しないように保証されます。
コアとなるコードの変更箇所
src/pkg/encoding/xml/xml.go
の EscapeText
関数内の条件式が変更されました。
--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -1758,7 +1758,7 @@ func EscapeText(w io.Writer, s []byte) error {
case '\r':
esc = esc_cr
default:
- if !isInCharacterRange(r) {
+ if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
esc = esc_fffd
break
}
また、src/pkg/encoding/xml/xml_test.go
に新しいテストケース TestIssue5880
が追加されました。
--- a/src/pkg/encoding/xml/xml_test.go
+++ b/src/pkg/encoding/xml/xml_test.go
@@ -11,6 +11,7 @@ import (
"reflect"
"strings"
"testing"
+ "unicode/utf8"
)
const testInput = `
@@ -714,3 +715,14 @@ func TestEscapeTextInvalidChar(t *testing.T) {
t.Errorf("have %v, want %v", text, expected)
}
}
+
+func TestIssue5880(t *testing.T) {
+ type T []byte
+ data, err := Marshal(T{192, 168, 0, 1})
+ if err != nil {
+ t.Errorf("Marshal error: %v", err)
+ }
+ if !utf8.Valid(data) {
+ t.Errorf("Marshal generated invalid UTF-8: %x", data)
+ }
+}
コアとなるコードの解説
xml.go
の変更は、EscapeText
関数がXMLテキストをエスケープする際のロジックを強化しています。
EscapeText
関数は、入力バイトスライスs
をルーンごとに処理し、XMLの仕様に準拠するように特殊文字をエスケープします。
変更された行は、default
ケース内の条件分岐です。
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1)
この条件式は、以下のいずれかの状況でesc_fffd
(�
)をエスケープシーケンスとして設定します。
!isInCharacterRange(r)
: 現在処理しているルーンr
が、XMLの文字として許可されていない範囲にある場合。これは、XML 1.0の仕様で許可されている文字(#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
)以外の文字を指します。(r == 0xFFFD && width == 1)
: 現在処理しているルーンr
がUnicodeのReplacement Character(U+FFFD)であり、かつそのルーンが入力バイトスライスから1バイトとしてデコードされた場合。これは、unicode/utf8.DecodeRune
が無効な単一バイトを検出した際に0xFFFD
とwidth=1
を返す挙動に対応しています。この条件を追加することで、無効なバイトが0xFFFD
としてデコードされたとしても、それがXMLに直接含まれるのではなく、XMLエンティティとして適切にエスケープされるようになります。
この修正により、encoding/xml
パッケージは、無効なUTF-8バイトを含む入力に対しても、常に整形式のXMLを生成するようになります。
xml_test.go
に追加されたTestIssue5880
は、この修正が正しく機能することを検証するためのものです。
このテストでは、{192, 168, 0, 1}
という無効なUTF-8バイトシーケンスを含むバイトスライスを定義し、これをxml.Marshal
関数に渡してXMLにエンコードします。
その後、utf8.Valid(data)
を使って、Marshal
によって生成されたXML出力が有効なUTF-8であるかどうかをチェックします。
もし修正がなければ、Marshal
は無効なバイトをそのまま出力に含めてしまい、utf8.Valid
はfalse
を返してテストが失敗します。修正が適用されたことで、無効なバイトは�
としてエスケープされ、結果として生成されるXMLは有効なUTF-8となるため、テストは成功します。
関連リンク
- Go Issue 5880: https://github.com/golang/go/issues/5880
- Go CL 11975043: https://golang.org/cl/11975043
- Unicode Replacement Character (U+FFFD): https://en.wikipedia.org/wiki/Replacement_character
- XML 1.0 Specification (Character Range): https://www.w3.org/TR/REC-xml/#charsets
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのGoリポジトリのコミット履歴とIssueトラッカー
- Unicode標準
- XML仕様 (W3C勧告)
- UTF-8エンコーディングに関する一般的な情報源
unicode/utf8
パッケージのドキュメントencoding/xml
パッケージのドキュメント- Go言語のテストに関するドキュメント# [インデックス 16920] ファイルの概要
このコミットは、Go言語のencoding/xml
パッケージにおけるXMLテキストのエスケープ処理に関するバグ修正です。具体的には、無効なUTF-8バイトがXML出力にそのまま渡されてしまう問題を解決し、XMLの仕様に準拠した正しいエスケープが行われるように変更されています。
コミット
commit 789e1c351e6fca96fe3cec467029732c03d55d64
Author: Alex A Skinner <alex@lx.lc>
Date: Tue Jul 30 14:11:47 2013 +1000
encoding/xml: Do not pass through invalid utf8 bytes
EscapeText now escapes 0xFFFD returned from DecodeRune as 0xFFFD, rather than passing through the original byte.
Fixes #5880.
R=golang-dev, r, bradfitz, adg
CC=golang-dev
https://golang.org/cl/11975043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/789e1c351e6fca96fe3cec467029732c03d55d64
元コミット内容
encoding/xml
: 無効なUTF8バイトをそのまま渡さない
EscapeText
は、DecodeRune
から返される0xFFFD
を、元のバイトをそのまま渡すのではなく、0xFFFD
としてエスケープするようになりました。
Fixes #5880
.
変更の背景
このコミットは、Go言語のencoding/xml
パッケージが、入力されたバイト列が無効なUTF-8シーケンスを含んでいた場合に、その無効なバイトをXML出力にそのまま含めてしまうという問題(Issue 5880)を修正するために行われました。
XMLの仕様では、特定の文字範囲(Unicodeの「文字」として定義される範囲)のみが許可されており、無効なバイトシーケンスや制御文字の一部はXMLドキュメント内に直接含めることができません。Goのunicode/utf8
パッケージのDecodeRune
関数は、無効なUTF-8シーケンスを検出した場合に、Unicodeの「Replacement Character」(U+FFFD)と、その無効なシーケンスを構成するバイト数(通常は1)を返します。
しかし、このコミット以前のencoding/xml
パッケージのEscapeText
関数は、DecodeRune
が0xFFFD
を返した場合でも、それが元の無効なバイトから変換されたものであることを適切に認識せず、その0xFFFD
が単一のバイト(width == 1
)として扱われる場合に、元の無効なバイトをそのままXML出力に含めてしまう可能性がありました。これにより、生成されるXMLドキュメントがXML仕様に違反し、他のXMLパーサーで正しく処理できないという問題が発生していました。
この問題は、特にバイナリデータや、文字エンコーディングが不適切な外部ソースからのデータをXMLに埋め込む際に顕在化します。XMLパーサーは、XMLドキュメントが整形式(well-formed)であることを要求するため、無効な文字が含まれているとパースエラーを引き起こします。
前提知識の解説
- XML (Extensible Markup Language): 構造化されたデータを表現するためのマークアップ言語です。XMLドキュメントは、特定の文字セット(通常はUnicode)と文字範囲に準拠する必要があります。無効な文字やバイトシーケンスが含まれていると、XMLパーサーはエラーを報告します。
- UTF-8: Unicode文字をエンコードするための可変長文字エンコーディングです。ASCII互換性があり、世界中のほとんどの文字を表現できます。UTF-8では、特定のバイトシーケンスが無効と見なされます(例: 継続バイトが先行バイトなしで出現する場合など)。
- Unicode Replacement Character (U+FFFD): 無効な、または表現できない文字シーケンスを置き換えるために使用される特殊なUnicode文字です。UTF-8デコーダは、入力ストリームで無効なバイトシーケンスを検出すると、通常この文字を返します。
unicode/utf8.DecodeRune
: Go言語の標準ライブラリunicode/utf8
パッケージに含まれる関数で、バイトスライスから次のUTF-8エンコードされたルーン(Unicodeコードポイント)をデコードします。この関数は、デコードされたルーンと、そのルーンを構成するバイト数を返します。入力が無効なUTF-8シーケンスの場合、DecodeRune
はU+FFFD
と、その無効なシーケンスを構成するバイト数(通常は1)を返します。encoding/xml
パッケージ: Go言語の標準ライブラリで、XMLドキュメントのエンコード(書き込み)とデコード(読み込み)を提供します。このパッケージは、Goの構造体とXML要素間のマッピングを容易にします。EscapeText
関数:encoding/xml
パッケージ内の関数で、XMLドキュメントに書き込むテキストコンテンツをエスケープする役割を担います。XMLの予約文字(<
,>
,&
,'
,"
)や、XMLで許可されていない文字(制御文字など)を適切なエンティティ参照(例:<
を<
に)に変換します。
技術的詳細
このコミットの核心は、encoding/xml
パッケージのEscapeText
関数における文字範囲チェックのロジックの変更です。
変更前のコードでは、EscapeText
関数は各ルーン(Unicodeコードポイント)を処理し、isInCharacterRange(r)
というヘルパー関数を使って、そのルーンがXMLで許可される文字範囲内にあるかどうかをチェックしていました。もしルーンが許可範囲外であれば、esc_fffd
(�
)としてエスケープされることになっていました。
しかし、問題はunicode/utf8.DecodeRune
が無効なUTF-8バイトシーケンスを検出した際に0xFFFD
を返す点にありました。この0xFFFD
自体はisInCharacterRange
関数によって「有効な文字」と判断されてしまうため、EscapeText
は0xFFFD
をエスケープせずにそのまま出力しようとしました。さらに、この0xFFFD
が元の無効なバイトから生成されたもので、かつその無効なバイトが単一のバイトとして扱われた場合(width == 1
)、元の無効なバイトがXML出力にそのまま含まれてしまうという挙動になっていました。これは、XMLの仕様に違反するものでした。
このコミットでは、EscapeText
関数の条件式が以下のように変更されました。
変更前:
if !isInCharacterRange(r) {
esc = esc_fffd
break
}
変更後:
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
esc = esc_fffd
break
}
この変更により、以下の条件のいずれかが満たされた場合に、ルーンが�
としてエスケープされるようになりました。
!isInCharacterRange(r)
: ルーンがXMLで許可される文字範囲外である場合(これは以前から存在した条件)。(r == 0xFFFD && width == 1)
: ルーンが0xFFFD
(Replacement Character)であり、かつその0xFFFD
が単一のバイトからデコードされたものである場合。
後者の条件が追加されたことで、unicode/utf8.DecodeRune
が無効な単一バイトを処理して0xFFFD
を返した場合に、その0xFFFD
がXML出力にそのまま含まれるのではなく、明示的に�
というXMLエンティティとしてエスケープされるようになりました。これにより、生成されるXMLドキュメントは常に整形式となり、無効なバイトが直接XMLに混入することを防ぎます。
テストケースTestIssue5880
では、無効なUTF-8バイトシーケンス({192, 168, 0, 1}
は、UTF-8の仕様では無効なバイトシーケンスです。特に192
は2バイトシーケンスの開始バイトですが、それに続くバイトが不正です)を含むバイトスライスをxml.Marshal
に渡し、その結果が有効なUTF-8であるかどうかをutf8.Valid
で検証しています。このテストが追加されたことで、将来的に同様の問題が再発しないように保証されます。
コアとなるコードの変更箇所
src/pkg/encoding/xml/xml.go
の EscapeText
関数内の条件式が変更されました。
--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -1758,7 +1758,7 @@ func EscapeText(w io.Writer, s []byte) error {
case '\r':
esc = esc_cr
default:
- if !isInCharacterRange(r) {
+ if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
esc = esc_fffd
break
}
また、src/pkg/encoding/xml/xml_test.go
に新しいテストケース TestIssue5880
が追加されました。
--- a/src/pkg/encoding/xml/xml_test.go
+++ b/src/pkg/encoding/xml/xml_test.go
@@ -11,6 +11,7 @@ import (
"reflect"
"strings"
"testing"
+ "unicode/utf8"
)
const testInput = `
@@ -714,3 +715,14 @@ func TestEscapeTextInvalidChar(t *testing.T) {
t.Errorf("have %v, want %v", text, expected)
}
}
+
+func TestIssue5880(t *testing.T) {
+ type T []byte
+ data, err := Marshal(T{192, 168, 0, 1})
+ if err != nil {
+ t.Errorf("Marshal error: %v", err)
+ }
+ if !utf8.Valid(data) {
+ t.Errorf("Marshal generated invalid UTF-8: %x", data)
+ }
+}
コアとなるコードの解説
xml.go
の変更は、EscapeText
関数がXMLテキストをエスケープする際のロジックを強化しています。
EscapeText
関数は、入力バイトスライスs
をルーンごとに処理し、XMLの仕様に準拠するように特殊文字をエスケープします。
変更された行は、default
ケース内の条件分岐です。
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1)
この条件式は、以下のいずれかの状況でesc_fffd
(�
)をエスケープシーケンスとして設定します。
!isInCharacterRange(r)
: 現在処理しているルーンr
が、XMLの文字として許可されていない範囲にある場合。これは、XML 1.0の仕様で許可されている文字(#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
)以外の文字を指します。(r == 0xFFFD && width == 1)
: 現在処理しているルーンr
がUnicodeのReplacement Character(U+FFFD)であり、かつそのルーンが入力バイトスライスから1バイトとしてデコードされた場合。これは、unicode/utf8.DecodeRune
が無効な単一バイトを検出した際に0xFFFD
とwidth=1
を返す挙動に対応しています。この条件を追加することで、無効なバイトが0xFFFD
としてデコードされたとしても、それがXMLに直接含まれるのではなく、XMLエンティティとして適切にエスケープされるようになります。
この修正により、encoding/xml
パッケージは、無効なUTF-8バイトを含む入力に対しても、常に整形式のXMLを生成するようになります。
xml_test.go
に追加されたTestIssue5880
は、この修正が正しく機能することを検証するためのものです。
このテストでは、{192, 168, 0, 1}
という無効なUTF-8バイトシーケンスを含むバイトスライスを定義し、これをxml.Marshal
関数に渡してXMLにエンコードします。
その後、utf8.Valid(data)
を使って、Marshal
によって生成されたXML出力が有効なUTF-8であるかどうかをチェックします。
もし修正がなければ、Marshal
は無効なバイトをそのまま出力に含めてしまい、utf8.Valid
はfalse
を返してテストが失敗します。修正が適用されたことで、無効なバイトは�
としてエスケープされ、結果として生成されるXMLは有効なUTF-8となるため、テストは成功します。
関連リンク
- Go Issue 5880: https://github.com/golang/go/issues/5880
- Go CL 11975043: https://golang.org/cl/11975043
- Unicode Replacement Character (U+FFFD): https://en.wikipedia.org/wiki/Replacement_character
- XML 1.0 Specification (Character Range): https://www.w3.org/TR/REC-xml/#charsets
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのGoリポジトリのコミット履歴とIssueトラッカー
- Unicode標準
- XML仕様 (W3C勧告)
- UTF-8エンコーディングに関する一般的な情報源
unicode/utf8
パッケージのドキュメントencoding/xml
パッケージのドキュメント- Go言語のテストに関するドキュメント