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

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

このコミットは、Go言語の標準ライブラリ net/url パッケージにおいて、URL 型の String() メソッドがURLパスを文字列化する際に、先頭にスラッシュがない場合に自動的にスラッシュを追加するように修正するものです。これにより、不正なURLが生成されるバグが修正されました。

コミット

commit 39679ca88f8f37f6797daa9e866f8ba65ee18c9e
Author: Scott Ferguson <scottwferg@gmail.com>
Date:   Thu Aug 1 15:52:56 2013 -0700

    net/url: prepend slash to path in String()
    
    Previously if a path was set manually without a leading /, String()
    would not insert the slash when writing its output. This would lead
    to situations where a URL that should be http://www.google.com/search
    is output as http://www.google.comsearch
    
    Fixes #5927.
    
    R=golang-dev, bradfitz, rsc, 0xjnml
    CC=golang-dev
    https://golang.org/cl/11698045

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

https://github.com/golang/go/commit/39679ca88f8f37f6797daa9e866f8ba65ee18c9e

元コミット内容

以前の net/url パッケージの URL 型の String() メソッドでは、パスが手動で設定され、そのパスの先頭にスラッシュ (/) がない場合、String() メソッドがURLを生成する際にスラッシュを挿入しませんでした。この問題により、例えば http://www.google.com/search となるべきURLが http://www.google.comsearch のように誤って出力される状況が発生していました。このコミットは、この問題を修正し、String() メソッドがパスの先頭にスラッシュを適切に追加するように変更します。

この修正は、Issue #5927 に対応するものです。

変更の背景

この変更の背景には、net/url パッケージの URL 型が持つ String() メソッドの挙動に関するバグがありました。RFC 3986 (Uniform Resource Identifier (URI): Generic Syntax) など、URLの仕様では、パスは通常、ホスト名の後にスラッシュで区切られて続くことが期待されます。例えば、http://example.com/path/to/resource のように、ホスト名 example.com の後にパス /path/to/resource が続きます。

しかし、Goの net/url パッケージにおいて、URL 構造体の Path フィールドに手動でパスを設定する際に、開発者が誤って先頭のスラッシュを省略した場合(例: u.Path = "search")、URL.String() メソッドがその URL を文字列に変換する際に、この欠落したスラッシュを補完しませんでした。結果として、http://www.google.comsearch が結合され、http://www.google.comsearch のような不正なURLが生成されていました。

このような不正なURLは、ウェブブラウザやHTTPクライアントが正しく解釈できないだけでなく、ウェブサーバーがリクエストを適切にルーティングできない原因となり、アプリケーションの誤動作やセキュリティ上の問題を引き起こす可能性がありました。このコミットは、この仕様と実装の乖離を修正し、URL.String() が常に正しい形式のURLを生成するようにすることで、堅牢性と信頼性を向上させることを目的としています。

前提知識の解説

URL (Uniform Resource Locator)

URLは、インターネット上のリソース(ウェブページ、画像、ファイルなど)の場所を示す統一リソース識別子 (URI) の一種です。一般的なURLの構造は以下のようになります。

scheme://[user:password@]host[:port][/path][?query][#fragment]

  • scheme (スキーム): リソースにアクセスするためのプロトコル(例: http, https, ftp, file)。
  • host (ホスト): リソースが存在するサーバーのドメイン名またはIPアドレス。
  • port (ポート): サーバーがリクエストをリッスンしているポート番号(通常、HTTPは80、HTTPSは443)。
  • path (パス): ホスト上のリソースの場所を示す階層的な情報。通常、スラッシュ (/) で区切られたディレクトリとファイル名で構成されます。
  • query (クエリ): リソースに渡される追加のパラメータ。通常、? の後に key=value 形式のペアが & で区切られて続きます。
  • fragment (フラグメント): リソース内の特定の部分を示す識別子。通常、# の後に続きます。

このコミットで問題となっているのは、特に path の部分です。ホストの後に続くパスは、通常、先頭にスラッシュを持つべきです。

Go言語の net/url パッケージ

Go言語の標準ライブラリ net/url パッケージは、URLの解析、生成、および操作のための機能を提供します。主要な型は URL 構造体です。

type URL struct {
    Scheme     string
    Opaque     string    // encoded opaque data
    User       *Userinfo // username and password information
    Host       string    // host or host:port
    Path       string    // path (relative paths may omit leading slash)
    RawPath    string    // encoded path hint (Go 1.5 and later)
    ForceQuery bool      // append a query ('?') even if RawQuery is empty
    RawQuery   string    // encoded query values, without '?'
    Fragment   string    // fragment for references, without '#'
    RawFragment string   // encoded fragment hint (Go 1.5 and later)
}
  • Path: URLのパス部分を保持します。このフィールドは、相対パスの場合には先頭のスラッシュを省略できるとコメントに記載されていますが、String() メソッドが絶対URLを生成する際には、適切なスラッシュの挿入が必要となります。
  • String() メソッド: URL 構造体の内容を標準的なURL文字列形式に変換して返します。このメソッドは、URLの各コンポーネント(スキーム、ホスト、パスなど)を結合して最終的なURL文字列を構築する責任を負います。

URLエンコーディング

URLは、特定の文字(例: スペース、日本語文字、一部の記号)をそのまま含めることができません。これらの文字は、パーセントエンコーディング(例: スペースは %20)と呼ばれる形式でエンコードされる必要があります。net/url パッケージは、URLの各部分を適切にエンコード/デコードする機能も提供しています。このコミットでは、escape 関数がパスのエンコーディングに使用されています。

技術的詳細

このコミットの技術的な核心は、URL 構造体の String() メソッドに、パスの先頭スラッシュの有無をチェックし、必要に応じて追加するロジックが導入された点です。

変更前の String() メソッドは、u.Path の内容をそのまま buf.WriteString(escape(u.Path, encodePath)) で書き出していました。このため、u.Path"search" のようにスラッシュなしで設定されている場合、結果のURLも http://www.google.comsearch のように、ホスト名の直後にパスが連結されてしまっていました。

修正後のコードでは、buf.WriteString(escape(u.Path, encodePath)) の前に以下の3行が追加されています。

		if u.Path != "" && u.Path[0] != '/' {
			buf.WriteByte('/')
		}

このコードブロックは以下の条件をチェックします。

  1. u.Path != ""Path フィールドが空文字列ではないことを確認します。空のパス(例: http://example.com)の場合、スラッシュを追加する必要はありません。
  2. u.Path[0] != '/'Path フィールドの最初の文字がスラッシュ (/) ではないことを確認します。つまり、パスが既にスラッシュで始まっている場合は、何もする必要がありません。

これら二つの条件が両方とも真である場合(つまり、パスが空ではなく、かつスラッシュで始まっていない場合)、buf.WriteByte('/') が実行され、バッファにスラッシュが1バイト書き込まれます。これにより、その後に続く buf.WriteString(escape(u.Path, encodePath)) によって元のパスが書き込まれる際に、必ずパスの先頭にスラッシュが付与されるようになります。

この変更は、URL 型の Path フィールドが相対パスを保持する可能性を考慮しつつも、String() メソッドが常に絶対URLの形式として適切なパスを生成するようにするためのものです。これにより、net/url パッケージの利用者が Path フィールドをどのように設定したかにかかわらず、String() メソッドの出力がURLの仕様に準拠するようになります。

また、この修正を検証するために、src/pkg/net/url/url_test.go に新しいテストケース TestURLString が追加されました。このテストケースは、Path フィールドに先頭スラッシュがない文字列(例: "search")を設定した URL オブジェクトを作成し、その String() メソッドの出力が期待される正しいURL(例: "http://www.google.com/search")と一致するかどうかを確認します。これにより、修正が正しく機能していることが保証されます。

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

--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -459,6 +459,9 @@ func (u *URL) String() string {
 				buf.WriteString(h)
 			}
 		}
+		if u.Path != "" && u.Path[0] != '/' {
+			buf.WriteByte('/')
+		}
 		buf.WriteString(escape(u.Path, encodePath))
 	}
 	if u.RawQuery != "" {
diff --git a/src/pkg/net/url/url_test.go b/src/pkg/net/url/url_test.go
index 9d81289ceb..24f84e58ff 100644
--- a/src/pkg/net/url/url_test.go
+++ b/src/pkg/net/url/url_test.go
@@ -372,6 +372,22 @@ func DoTestString(t *testing.T, parse func(string) (*URL, error), name string, t
 
 func TestURLString(t *testing.T) {
 	DoTestString(t, Parse, "Parse", urltests)
+
+	// no leading slash on path should prepend
+	// slash on String() call
+	noslash := URLTest{
+		"http://www.google.com/search",
+		&URL{
+			Scheme: "http",
+			Host:   "www.google.com",
+			Path:   "search",
+		},
+		"",
+	}
+	s := noslash.out.String()
+	if s != noslash.in {
+		t.Errorf("Expected %s; go %s", noslash.in, s)
+	}
 }
 
 type EscapeTest struct {

コアとなるコードの解説

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

		if u.Path != "" && u.Path[0] != '/' {
			buf.WriteByte('/')
		}
		buf.WriteString(escape(u.Path, encodePath))

この部分が URL.String() メソッドに追加された主要なロジックです。

  1. if u.Path != "" && u.Path[0] != '/': この条件文は、u.Path フィールドが空文字列ではないこと(u.Path != "")と、そのパスの最初の文字がスラッシュ (/) ではないこと(u.Path[0] != '/')を同時にチェックします。
    • u.Path != "": パスが存在しない場合(例: http://example.com のようにホスト名のみでパスがない場合)は、スラッシュを追加する必要がないため、この条件で除外されます。
    • u.Path[0] != '/': パスが既にスラッシュで始まっている場合(例: /search)は、スラッシュを追加する必要がないため、この条件で除外されます。
  2. buf.WriteByte('/'): 上記の条件が両方とも真である場合、つまりパスが空ではなく、かつ先頭にスラッシュがない場合にのみ、バイトバッファ buf にスラッシュ文字 (/) を書き込みます。これにより、パスの前にスラッシュが挿入されます。
  3. buf.WriteString(escape(u.Path, encodePath)): その後、元の u.Path の内容が適切にURLエンコードされた上で、バッファに書き込まれます。この際、既にスラッシュが追加されているため、結果として ホスト名/パス の形式が保証されます。

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

func TestURLString(t *testing.T) {
	DoTestString(t, Parse, "Parse", urltests)

	// no leading slash on path should prepend
	// slash on String() call
	noslash := URLTest{
		"http://www.google.com/search",
		&URL{
			Scheme: "http",
			Host:   "www.google.com",
			Path:   "search",
		},
		"",
	}
	s := noslash.out.String()
	if s != noslash.in {
		t.Errorf("Expected %s; go %s", noslash.in, s)
	}
}

このテストケースは、修正が正しく機能していることを検証するために追加されました。

  1. noslash := URLTest{...}: URLTest 構造体のインスタンス noslash を作成します。
    • "http://www.google.com/search": 期待される最終的なURL文字列です。
    • &URL{Scheme: "http", Host: "www.google.com", Path: "search"}: テスト対象となる URL オブジェクトです。ここで注目すべきは、Path フィールドが "search" と、先頭スラッシュなしで設定されている点です。
  2. s := noslash.out.String(): noslashURL オブジェクト (noslash.out) の String() メソッドを呼び出し、その結果を s に格納します。
  3. if s != noslash.in { t.Errorf(...) }: sString() メソッドの出力)が noslash.in(期待される正しいURL文字列)と一致しない場合、テストは失敗し、エラーメッセージが出力されます。

このテストにより、Path フィールドに先頭スラッシュがない場合でも、String() メソッドが自動的にスラッシュを補完し、正しいURLを生成することが保証されます。

関連リンク

参考にした情報源リンク

  • RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986
  • GoDoc - net/url package: https://pkg.go.dev/net/url
  • Go言語のURL解析と生成 (Qiita記事など、一般的なGoのnet/url解説記事)
    • (具体的な記事は検索結果によるが、Go net/url で検索すると多数見つかる)
  • Go言語のテスト (Goのtestingパッケージに関する公式ドキュメントや解説記事)
    • (具体的な記事は検索結果によるが、Go testing で検索すると多数見つかる)
  • Go言語の文字列操作とバイトバッファ (bytes.Bufferに関する解説記事)
    • (具体的な記事は検索結果によるが、Go bytes.Buffer で検索すると多数見つかる)