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

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

このコミットは、Go言語の標準ライブラリ net/url パッケージにおける ParseQuery 関数が、クエリ文字列のパース中に発生した最初のエラーを正しく報告するように修正するものです。これにより、複数の不正なエスケープシーケンスが存在する場合でも、最初に検出されたエラーが返されるようになり、デバッグやエラーハンドリングが容易になります。

コミット

commit c7cc894ef5978746dce145227808287fee627dc0
Author: David Symonds <dsymonds@golang.org>
Date:   Tue Oct 9 08:10:32 2012 +1100

    net/url: report first error from ParseQuery.
    
    Fixes #4175.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6610068

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

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

元コミット内容

net/url: report first error from ParseQuery.

このコミットは、ParseQuery 関数がクエリ文字列のパース中に発生した最初のエラーを報告するように修正します。

Fixes #4175.

これはIssue #4175を修正します。

変更の背景

Go言語の net/url パッケージは、URLのパースやクエリパラメータの処理を行うための機能を提供します。ParseQuery 関数は、URLのクエリ文字列をキーと値のペアに分解し、url.Values 型のマップとして返します。

このコミットが導入される前は、ParseQuery 関数内部で QueryUnescape を呼び出す際に複数のエスケープエラーが発生した場合、最後に発生したエラーが上書きされてしまい、最初のエラーが失われる可能性がありました。これは、クエリ文字列に複数の不正なエスケープシーケンスが含まれている場合に問題となります。例えば、%gh&%ij のようなクエリ文字列では、%gh%ij の両方が不正なエスケープシーケンスですが、以前の実装では %ij のエラーのみが報告され、%gh のエラーは無視されていました。

開発者やユーザーがクエリ文字列のパースエラーをデバッグする際、最初に発生したエラーを知ることは問題の根本原因を特定するために非常に重要です。そのため、ParseQuery が最初のエラーを正確に報告するように修正する必要がありました。この修正は、GoのIssue #4175として報告され、その解決策としてこのコミットが作成されました。

前提知識の解説

  • URL (Uniform Resource Locator): インターネット上のリソースを一意に識別するための文字列。スキーム、ホスト、パス、クエリ、フラグメントなどのコンポーネントから構成されます。
  • クエリ文字列 (Query String): URLの一部で、リソースに渡される追加のパラメータを含む部分。通常、? の後に キー=値 の形式で記述され、複数のパラメータは & で区切られます。例: ?name=Alice&age=30
  • URLエンコーディング (URL Encoding): URLに使用できない文字(スペース、日本語など)を、% の後に16進数で表現する形式に変換すること。例えば、スペースは %20 に、日本語の「あ」は %E3%81%82 のようにエンコードされます。
  • URLデコーディング (URL Decoding): URLエンコードされた文字列を元の文字に戻すこと。
  • net/url パッケージ (Go言語): Go言語の標準ライブラリの一部で、URLのパース、構築、クエリパラメータの操作など、URLに関連する機能を提供します。
  • url.Values 型 (Go言語): map[string][]string のエイリアスで、URLのクエリパラメータやフォームデータを表現するために使用されます。同じキーに対して複数の値を持つことができるため、[]string のスライスとして値を保持します。
  • QueryUnescape 関数 (Go言語): net/url パッケージ内の関数で、URLエンコードされた文字列をデコードします。不正なエスケープシーケンス(例: %gh)が含まれている場合はエラーを返します。
  • エラーハンドリング (Error Handling): プログラム実行中に発生する可能性のあるエラーを検出し、適切に処理するメカニズム。Go言語では、関数がエラーを返す場合、通常は戻り値の最後の要素として error 型の値を返します。

技術的詳細

このコミットの核心は、src/pkg/net/url/url.go ファイル内の parseQuery 関数におけるエラーハンドリングロジックの変更です。

parseQuery 関数は、与えられたクエリ文字列を & で分割し、それぞれの キー=値 のペアを処理します。各キーと値は QueryUnescape 関数によってデコードされます。

変更前のコードでは、QueryUnescape がエラーを返した場合、そのエラーが err 変数に直接代入されていました。

// 変更前
key, err1 := QueryUnescape(key)
if err1 != nil {
    err = err1 // ここでエラーが上書きされる可能性があった
    continue
}
value, err1 = QueryUnescape(value)
if err1 != nil {
    err = err1 // ここでエラーが上書きされる可能性があった
    continue
}

このロジックの問題点は、ループ内で複数の QueryUnescape 呼び出しがあり、それぞれがエラーを返す可能性があることです。もし最初の QueryUnescape がエラーを返し、その後に続く QueryUnescape もエラーを返した場合、err 変数は後者のエラーで上書きされてしまい、最初の重要なエラー情報が失われていました。

このコミットでは、この問題を解決するために、err 変数がまだ nil である場合にのみ、QueryUnescape から返されたエラーを代入するように変更されました。

// 変更後
key, err1 := QueryUnescape(key)
if err1 != nil {
    if err == nil { // errがまだnilの場合のみ代入
        err = err1
    }
    continue
}
value, err1 = QueryUnescape(value)
if err1 != nil {
    if err == nil { // errがまだnilの場合のみ代入
        err = err1
    }
    continue
}

この変更により、parseQuery 関数は、クエリ文字列全体をパースする過程で最初に遭遇したエラーを保持し、最終的にそのエラーを呼び出し元に返すことが保証されます。これにより、エラーの根本原因を特定しやすくなり、デバッグの効率が向上します。

テストケース TestParseFailure が追加され、この新しい動作が検証されています。具体的には、%gh&%ij という不正なクエリ文字列を ParseQuery に渡し、返されたエラー文字列が最初の不正なシーケンスである %gh を含んでいることを確認しています。

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

src/pkg/net/url/url.go ファイルの parseQuery 関数内の変更です。

--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -521,12 +521,16 @@ func parseQuery(m Values, query string) (err error) {
 		}
 		key, err1 := QueryUnescape(key)
 		if err1 != nil {
-			err = err1
+			if err == nil {
+				err = err1
+			}
 			continue
 		}
 		value, err1 = QueryUnescape(value)
 		if err1 != nil {
-			err = err1
+			if err == nil {
+				err = err1
+			}
 			continue
 		}
 		m[key] = append(m[key], value)

src/pkg/net/url/url_test.go ファイルに新しいテストケース TestParseFailure が追加されました。

--- a/src/pkg/net/url/url_test.go
+++ b/src/pkg/net/url/url_test.go
@@ -7,6 +7,7 @@ package url
 import (
 	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -779,3 +780,13 @@ func TestRequestURI(t *testing.T) {
 		}
 	}
 }
+
+func TestParseFailure(t *testing.T) {
+	// Test that the first parse error is returned.
+	const url = "%gh&%ij"
+	_, err := ParseQuery(url)
+	errStr := fmt.Sprint(err)
+	if !strings.Contains(errStr, "%gh") {
+		t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh")
+	}
+}

コアとなるコードの解説

src/pkg/net/url/url.go の変更点:

parseQuery 関数内で、QueryUnescape からエラー err1 が返された際に、既存のエラー変数 errnil である場合にのみ、err1err に代入するように条件が追加されました。

if err1 != nil {
    if err == nil { // ここが追加された条件
        err = err1
    }
    continue
}

この if err == nil という条件が、複数のエラーが発生した場合に最初のエラーを保持し、それ以降のエラーで上書きされないようにする役割を果たします。

src/pkg/net/url/url_test.go の変更点:

TestParseFailure という新しいテスト関数が追加されました。 このテストでは、%gh&%ij という、複数の不正なエスケープシーケンスを含むURLクエリ文字列を定義しています。 ParseQuery 関数を呼び出し、返されたエラーを err 変数に格納します。 fmt.Sprint(err) を使用してエラーを文字列に変換し、errStr に格納します。 strings.Contains(errStr, "%gh") を使って、エラー文字列が最初の不正なシーケンスである %gh を含んでいるかどうかを検証します。 もし %gh が含まれていなければ、テストは失敗し、期待されるエラーメッセージが出力されます。これにより、ParseQuery が最初のエラーを正しく報告していることが保証されます。

関連リンク

参考にした情報源リンク

  • Go言語のIssue #4175 (FrozenDueToAge): https://goissues.org/issue/4175
  • Go言語のコードレビューツール (Gerrit) の変更セット: https://golang.org/cl/6610068 (これはコミットメッセージに記載されているリンクですが、現在はGitHubに移行しているため、直接アクセスしてもリダイレクトされるか、古い情報が表示される可能性があります。)