[インデックス 17005] ファイルの概要
このコミットは、Go言語のgo get
コマンドが、フェッチしたXMLドキュメントがASCIIエンコーディングを使用している場合に正しく動作しない問題を修正します。また、解析できないエンコーディングが検出された場合に、より分かりやすいエラーメッセージを提供するように改善されています。
コミット
commit bbf143002188a7af7d60e28da472f06c6d99aa03
Author: Alberto García Hierro <alberto@garciahierro.com>
Date: Fri Aug 2 14:15:33 2013 -0700
cmd/go: Fix go get when the fetched XML uses ASCII encoding
Also, add a meaningful error message when an encoding which
can't be parsed is found.
Fixes #5801.
R=golang-dev, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/12343043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bbf143002188a7af7d60e28da472f06c6d99aa03
元コミット内容
cmd/go: Fix go get when the fetched XML uses ASCII encoding
Also, add a meaningful error message when an encoding which
can't be parsed is found.
Fixes #5801.
変更の背景
go get
コマンドは、リモートリポジトリからGoパッケージを取得するために使用されます。このプロセスでは、HTMLページ内に埋め込まれたgo-import
メタタグを解析して、リポジトリの情報を発見することがあります。これらのHTMLページはXMLパーサーによって処理されますが、XMLドキュメントがUTF-8
以外のエンコーディング、特にASCII
エンコーディングを使用している場合に問題が発生していました。
従来のxml.NewDecoder
は、デフォルトではUTF-8
を期待しており、他のエンコーディングを適切に処理できませんでした。そのため、ASCII
エンコーディングのXMLを含むHTMLページをgo get
が処理しようとすると、エンコーディングの問題により解析が失敗し、パッケージの取得ができないというバグがありました。
また、エンコーディングがサポートされていない場合に、ユーザーに分かりにくいエラーメッセージが表示されることも問題でした。このコミットは、これらの問題を解決し、go get
の堅牢性とユーザーエクスペリエンスを向上させることを目的としています。
Fixes #5801
という記述がありますが、これは公式のGoリポジトリのIssue番号ではなく、mailgun/godebug
プロジェクトのIssue #88で報告された、h2_bundle.go
ファイルの5801行目に関連するエラーメッセージを参照している可能性があります。このコミットは、go get
がXMLドキュメントのエンコーディングを正しく処理できないという具体的な問題を解決しています。
前提知識の解説
go get
コマンド: Go言語のパッケージ管理ツールの一部で、指定されたインポートパスに基づいてリモートリポジトリからGoパッケージのソースコードをダウンロードし、ビルドしてインストールします。この際、HTMLページ内のmeta
タグ(特にgo-import
タグ)を解析して、どのバージョン管理システム(VCS)を使用し、どこからコードを取得すべきかを判断します。- XMLエンコーディング: XMLドキュメントは、その内容がどの文字エンコーディングで記述されているかを示すことができます。例えば、
<?xml version="1.0" encoding="UTF-8"?>
のように指定されます。UTF-8
は最も一般的なエンコーディングですが、ASCII
やISO-8859-1
なども存在します。 encoding/xml
パッケージ: Go言語の標準ライブラリで、XMLドキュメントのエンコードとデコードを提供します。xml.NewDecoder
はXMLストリームを読み込み、トークンに分割して解析します。xml.Decoder.CharsetReader
:encoding/xml
パッケージのxml.Decoder
構造体にはCharsetReader
というフィールドがあります。これは、XMLドキュメントのエンコーディング宣言に基づいて、特定の文字セットを処理するためのカスタムリーダー関数を設定するために使用されます。このフィールドに適切な関数を設定することで、UTF-8
以外のエンコーディングを持つXMLドキュメントも正しく解析できるようになります。io.Reader
インターフェース: Go言語の基本的なインターフェースの一つで、データの読み込み操作を抽象化します。ファイル、ネットワーク接続、メモリ上のデータなど、様々なソースからの読み込みを統一的に扱えます。
技術的詳細
このコミットの主要な変更点は、xml.NewDecoder
がXMLドキュメントのエンコーディングを適切に処理できるように、カスタムのCharsetReader
を設定したことです。
-
charsetReader
関数の導入:charsetReader(charset string, input io.Reader) (io.Reader, error)
という新しい関数がsrc/cmd/go/discovery.go
に追加されました。- この関数は、XMLドキュメントのエンコーディング宣言(
charset
引数)を受け取り、そのエンコーディングに対応するio.Reader
を返します。 - 現在の実装では、
"ascii"
という文字セットが指定された場合、入力io.Reader
をそのまま返します。これは、ASCII
がUTF-8
のサブセットであり、UTF-8
デコーダがASCII
文字を問題なく処理できるためです。0x7f
を超える文字は拒否されません。 "ascii"
以外のサポートされていない文字セットが指定された場合、fmt.Errorf("can't decode XML document using charset %q", charset)
というエラーを返します。これにより、ユーザーはどのエンコーディングが問題を引き起こしているのかを明確に知ることができます。
-
parseMetaGoImports
関数でのCharsetReader
の設定:src/cmd/go/discovery.go
のparseMetaGoImports
関数は、HTMLからgo-import
メタタグを解析する役割を担っています。- この関数内で
xml.NewDecoder(r)
によってxml.Decoder
が初期化された直後に、d.CharsetReader = charsetReader
という行が追加されました。 - これにより、
xml.Decoder
はXMLドキュメントのエンコーディング宣言を読み取った際に、新しく定義されたcharsetReader
関数を呼び出して、適切な文字セット処理を行うようになります。
-
エラーハンドリングの改善:
parseMetaGoImports
関数は、以前はエラーを返していませんでしたが、この変更によりerr error
が戻り値に追加されました。d.Token()
がエラーを返した場合、そのエラーが呼び出し元に伝播されるようになりました。src/cmd/go/vcs.go
のrepoRootForImportDynamic
関数では、parseMetaGoImports
の呼び出し元でエラーがチェックされるようになりました。parseMetaGoImports
がエラーを返した場合、fmt.Errorf("parsing %s: %v", importPath, err)
という形式で、より詳細なエラーメッセージが生成され、ユーザーに表示されます。これにより、XML解析中のエンコーディングエラーがより明確に報告されるようになります。
これらの変更により、go get
はASCII
エンコーディングのXMLを含むHTMLページを正しく解析できるようになり、また、サポートされていないエンコーディングが検出された場合には、ユーザーに分かりやすいエラーメッセージを提供するようになりました。
コアとなるコードの変更箇所
src/cmd/go/discovery.go
--- a/src/cmd/go/discovery.go
+++ b/src/cmd/go/discovery.go
@@ -13,17 +13,35 @@ package main
import (
"encoding/xml"
+ "fmt"
"io"
"strings"
)
+// charsetReader returns a reader for the given charset. Currently
+// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
+// error which is printed by go get, so the user can find why the package
+// wasn't downloaded if the encoding is not supported. Note that, in
+// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
+// greater than 0x7f are not rejected).
+func charsetReader(charset string, input io.Reader) (io.Reader, error) {
+ switch strings.ToLower(charset) {
+ case "ascii":
+ return input, nil
+ default:
+ return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
+ }
+}
+
// parseMetaGoImports returns meta imports from the HTML in r.
// Parsing ends at the end of the <head> section or the beginning of the <body>.
-func parseMetaGoImports(r io.Reader) (imports []metaImport) {
+func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) {
d := xml.NewDecoder(r)
+ d.CharsetReader = charsetReader
d.Strict = false
+ var t xml.Token
for {
-\t\tt, err := d.Token()
+\t\tt, err = d.Token()
\tif err != nil {
\t\treturn
\t}
src/cmd/go/vcs.go
--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -442,7 +442,11 @@ func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
return nil, fmt.Errorf("http/https fetch: %v", err)
}
defer body.Close()
-\tmetaImport, err := matchGoImport(parseMetaGoImports(body), importPath)\n+\timports, err := parseMetaGoImports(body)\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf("parsing %s: %v", importPath, err)\n+\t}\n+\tmetaImport, err := matchGoImport(imports, importPath)
if err != nil {
if err != errNoMatch {
return nil, fmt.Errorf("parse %s: %v", urlStr, err)
@@ -467,7 +471,10 @@ func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
if err != nil {
return nil, fmt.Errorf("fetch %s: %v", urlStr, err)
}\n-\t\timports := parseMetaGoImports(body)\n+\t\timports, err := parseMetaGoImports(body)\n+\t\tif err != nil {\n+\t\t\treturn nil, fmt.Errorf("parsing %s: %v", importPath, err)\n+\t\t}\n if len(imports) == 0 {
return nil, fmt.Errorf("fetch %s: no go-import meta tag", urlStr)
}
コアとなるコードの解説
src/cmd/go/discovery.go
の変更
-
charsetReader
関数の追加:- この関数は、XMLパーサーがエンコーディング宣言を検出したときに呼び出されます。
strings.ToLower(charset)
でエンコーディング名を小文字に変換し、"ascii"
と一致するかどうかをチェックします。"ascii"
の場合、input
リーダーをそのまま返します。これは、ASCII
がUTF-8
と互換性があるため、特別な変換が不要であることを意味します。- それ以外のエンコーディングの場合、
fmt.Errorf
を使って「指定された文字セット%q
を使用してXMLドキュメントをデコードできません」というエラーメッセージを生成し、返します。これにより、サポートされていないエンコーディングが使用されている場合に、go get
がより具体的なエラーを報告できるようになります。
-
parseMetaGoImports
関数の変更:- 関数のシグネチャが
func parseMetaGoImports(r io.Reader) (imports []metaImport)
からfunc parseMetaGoImports(r io.Reader) (imports []metaImport, err error)
に変更され、エラーを返すようになりました。 d := xml.NewDecoder(r)
でxml.Decoder
が作成された後、d.CharsetReader = charsetReader
という行が追加されました。これにより、XMLデコーダはカスタムのcharsetReader
関数を使用して、XMLドキュメントのエンコーディングを処理するようになります。- ループ内の
t, err := d.Token()
のerr
が、関数の戻り値のerr
に直接代入されるようになりました。これにより、XML解析中に発生したエラーが呼び出し元に適切に伝播されます。
- 関数のシグネチャが
src/cmd/go/vcs.go
の変更
repoRootForImportDynamic
関数の変更:- この関数は、動的なインポートパスに基づいてリポジトリのルートを特定する役割を担っています。
parseMetaGoImports(body)
の呼び出しが、imports, err := parseMetaGoImports(body)
のように変更され、返されるエラーも受け取るようになりました。if err != nil
ブロックが追加され、parseMetaGoImports
がエラーを返した場合に、fmt.Errorf("parsing %s: %v", importPath, err)
という形式でエラーメッセージを生成し、それを呼び出し元に返します。これにより、XML解析エラーがgo get
のユーザーに明確に報告されるようになります。- 同様の変更が、HTTPリクエストが成功した後の
parseMetaGoImports
の別の呼び出し箇所にも適用されています。
これらの変更により、go get
はXMLドキュメントのエンコーディングをより柔軟に処理できるようになり、特にASCII
エンコーディングのXMLを含むHTMLページからのメタタグの発見が改善されました。また、エラーメッセージがより具体的になり、デバッグが容易になりました。
関連リンク
- Go言語の
go get
コマンドに関する公式ドキュメントやブログ記事 - Go言語の
encoding/xml
パッケージのドキュメント
参考にした情報源リンク
- https://github.com/golang/go/commit/bbf143002188a7af7d60e28da472f06c6d99aa03
mailgun/godebug
issue #88 (https://github.com/mailgun/godebug/issues/88) -Fixes #5801
の背景に関する情報源。- Go言語の
encoding/xml
パッケージのGoDoc: https://pkg.go.dev/encoding/xml