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

[インデックス 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は最も一般的なエンコーディングですが、ASCIIISO-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を設定したことです。

  1. charsetReader関数の導入:

    • charsetReader(charset string, input io.Reader) (io.Reader, error)という新しい関数がsrc/cmd/go/discovery.goに追加されました。
    • この関数は、XMLドキュメントのエンコーディング宣言(charset引数)を受け取り、そのエンコーディングに対応するio.Readerを返します。
    • 現在の実装では、"ascii"という文字セットが指定された場合、入力io.Readerをそのまま返します。これは、ASCIIUTF-8のサブセットであり、UTF-8デコーダがASCII文字を問題なく処理できるためです。0x7fを超える文字は拒否されません。
    • "ascii"以外のサポートされていない文字セットが指定された場合、fmt.Errorf("can't decode XML document using charset %q", charset)というエラーを返します。これにより、ユーザーはどのエンコーディングが問題を引き起こしているのかを明確に知ることができます。
  2. parseMetaGoImports関数でのCharsetReaderの設定:

    • src/cmd/go/discovery.goparseMetaGoImports関数は、HTMLからgo-importメタタグを解析する役割を担っています。
    • この関数内でxml.NewDecoder(r)によってxml.Decoderが初期化された直後に、d.CharsetReader = charsetReaderという行が追加されました。
    • これにより、xml.DecoderはXMLドキュメントのエンコーディング宣言を読み取った際に、新しく定義されたcharsetReader関数を呼び出して、適切な文字セット処理を行うようになります。
  3. エラーハンドリングの改善:

    • parseMetaGoImports関数は、以前はエラーを返していませんでしたが、この変更によりerr errorが戻り値に追加されました。d.Token()がエラーを返した場合、そのエラーが呼び出し元に伝播されるようになりました。
    • src/cmd/go/vcs.gorepoRootForImportDynamic関数では、parseMetaGoImportsの呼び出し元でエラーがチェックされるようになりました。parseMetaGoImportsがエラーを返した場合、fmt.Errorf("parsing %s: %v", importPath, err)という形式で、より詳細なエラーメッセージが生成され、ユーザーに表示されます。これにより、XML解析中のエンコーディングエラーがより明確に報告されるようになります。

これらの変更により、go getASCIIエンコーディングの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の変更

  1. charsetReader関数の追加:

    • この関数は、XMLパーサーがエンコーディング宣言を検出したときに呼び出されます。
    • strings.ToLower(charset)でエンコーディング名を小文字に変換し、"ascii"と一致するかどうかをチェックします。
    • "ascii"の場合、inputリーダーをそのまま返します。これは、ASCIIUTF-8と互換性があるため、特別な変換が不要であることを意味します。
    • それ以外のエンコーディングの場合、fmt.Errorfを使って「指定された文字セット%qを使用してXMLドキュメントをデコードできません」というエラーメッセージを生成し、返します。これにより、サポートされていないエンコーディングが使用されている場合に、go getがより具体的なエラーを報告できるようになります。
  2. 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の変更

  1. 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パッケージのドキュメント

参考にした情報源リンク