[インデックス 15688] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/url
パッケージにおけるパス解決ロジックの改善と、それに伴うテストの拡充を目的としています。特に、RFC 3986で定義されているURI参照の解決規則に厳密に準拠するように変更が加えられています。
コミット
commit 82e3ca7b7a1f7401e02d227f06c8b208a90c775b
Author: Rodrigo Moraes de Oliveira <rodrigo.moraes@gmail.com>
Date: Mon Mar 11 15:03:07 2013 -0400
net/url: better path resolution
This includes a simplified resolvePath function and tests for all normal and abnormal path resolution examples described in RFC 3986, sections 5.4.1 and 5.4.2 [1]. Some of those examples failed before (see http://play.golang.org/p/F0ApSaXniv).
Also, parsing a reference "//foo" now works as expected. It was treated as an absolute path with very weird results (see http://play.golang.org/p/089b-_xoNe).
During path resolution, all dot segments are removed as described by the RFC.
A few existing tests had to be changed because they expected the wrong output.
Fixes #4700.
Fixes #4706.
[1] http://tools.ietf.org/html/rfc3986#section-5.4.1
R=rsc, adg, bradfitz
CC=golang-dev
https://golang.org/cl/7203059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/82e3ca7b7a1f7401e02d227f06c8b208a90c775b
元コミット内容
net/url: better path resolution
このコミットは、net/url
パッケージのパス解決を改善します。
具体的には、簡素化された resolvePath
関数と、RFC 3986のセクション5.4.1および5.4.2で記述されているすべての正常および異常なパス解決の例に対するテストが含まれています。これらの例の一部は以前は失敗していました(例: http://play.golang.org/p/F0ApSaXniv)。
また、参照「//foo」のパースが期待通りに動作するようになりました。以前は非常に奇妙な結果を伴う絶対パスとして扱われていました(例: http://play.golang.org/p/089b-_xoNe)。
パス解決中、すべてのドットセグメントはRFCの記述通りに削除されます。
いくつかの既存のテストは、誤った出力を期待していたため変更する必要がありました。
Fixes #4700. Fixes #4706.
[1] http://tools.ietf.org/html/rfc3986#section-5.4.1
R=rsc, adg, bradfitz CC=golang-dev https://golang.org/cl/7203059
変更の背景
このコミットの主な背景は、Go言語の net/url
パッケージがURI(Uniform Resource Identifier)のパス解決において、国際標準であるRFC 3986に完全に準拠していなかった点にあります。特に、以下の問題が指摘されていました。
- RFC 3986のパス解決例の不適合: RFC 3986のセクション5.4.1(Normal Examples)と5.4.2(Abnormal Examples)には、URI参照の解決に関する多数の具体的な例が示されています。Goの
net/url
パッケージは、これらの例の一部で期待される結果を返していませんでした。これは、URIの相互運用性や正確な解釈において問題を引き起こす可能性がありました。 - ドットセグメントの不適切な処理: URIパス内の「.」(カレントディレクトリ)や「..」(親ディレクトリ)といったドットセグメントの処理が、RFCの規定通りに行われていませんでした。これにより、予期せぬパス解決結果が生じることがありました。
- スキーム相対参照の誤解釈: 「//foo」のようなスキーム相対参照(scheme-relative reference)が、誤って絶対パスとして解釈され、不正確なURIが生成される問題がありました。これは、特にWebアプリケーションやネットワークプロトコルにおいて、URIの正確な構築と解釈が求められる場面で深刻なバグにつながる可能性がありました。
- 既存のバグ修正: 具体的には、Go issue #4700と#4706がこのコミットによって修正されています。これらのissueは、それぞれURIパス解決の不正確さに関連するものでした。
これらの問題を解決し、net/url
パッケージの堅牢性とRFC準拠性を向上させることが、このコミットの重要な目的でした。
前提知識の解説
URI (Uniform Resource Identifier)
URIは、Web上のリソースを一意に識別するための文字列です。URL (Uniform Resource Locator) や URN (Uniform Resource Name) の上位概念にあたります。URIは、スキーム、オーソリティ(ユーザー情報、ホスト、ポート)、パス、クエリ、フラグメントといったコンポーネントで構成されます。
RFC 3986: Uniform Resource Identifier (URI): Generic Syntax
RFC 3986は、URIの一般的な構文と、URI参照の解決メカニズムを定義する重要な標準ドキュメントです。このRFCは、URIの各コンポーネントの定義、予約文字と非予約文字、そしてURI参照がどのようにベースURIに対して解決されるかについて詳細に記述しています。
- URI参照 (URI Reference): 完全なURIだけでなく、相対パスやフラグメントのみを含む部分的なURIもURI参照と呼びます。
- ベースURI (Base URI): 相対URI参照を解決するための基準となるURIです。通常、ドキュメントが取得されたURIや、HTMLの
<base>
タグで指定されたURIなどがベースURIとなります。 - URI参照の解決 (URI Reference Resolution): 相対URI参照をベースURIと組み合わせて、完全な絶対URIを生成するプロセスです。RFC 3986のセクション5.2で詳細に定義されています。
RFC 3986 セクション 5.4.1: Normal Examples
このセクションでは、URI参照の解決における「正常な」ケースの例が多数示されています。例えば、相対パス、クエリ、フラグメントの追加、ドットセグメント(.
や ..
)の処理などが含まれます。これらの例は、URI解決の実装がRFCに準拠しているかを確認するための重要なテストケースとなります。
RFC 3986 セクション 5.4.2: Abnormal Examples
このセクションでは、URI参照の解決における「異常な」ケースの例が示されています。これらは、通常のエッジケースや、誤った実装が陥りやすい落とし穴を浮き彫りにするものです。例えば、過剰な「..」セグメントの処理、空のパスセグメント、予約文字の扱いなどが含まれます。これらの例もまた、実装の堅牢性を検証するために不可欠です。
ドットセグメント (Dot Segments)
URIパス内の「.」(カレントディレクトリ)と「..」(親ディレクトリ)は、特別な意味を持つドットセグメントと呼ばれます。RFC 3986のセクション5.2.4「Remove Dot Segments」では、これらのセグメントをパスから削除し、正規化するアルゴリズムが定義されています。このプロセスは、URIの比較や解決において一貫した結果を得るために重要です。
Go言語の net/url
パッケージ
net/url
パッケージは、Go言語でURLをパース、構築、操作するための機能を提供します。URL
構造体はURIの各コンポーネント(スキーム、ホスト、パスなど)を表現し、Parse
、ParseRequestURI
、ResolveReference
などの関数がURIの処理を行います。このパッケージは、HTTPクライアントやサーバー、Webフレームワークなど、Go言語でネットワークアプリケーションを開発する上で不可欠なものです。
技術的詳細
このコミットは、net/url
パッケージの resolvePath
関数と ResolveReference
メソッドのロジックを大幅に修正しています。
resolvePath
関数の変更
- RFC 3986準拠への変更: 以前の
resolvePath
関数はRFC 2396に基づいていましたが、このコミットでRFC 3986に準拠するように更新されました。RFC 2396はURIの古い仕様であり、RFC 3986がその改訂版です。 - 簡素化されたアルゴリズム: パス解決のロジックがより簡潔で効率的なものに書き直されました。特に、ドットセグメント(
.
と..
)の処理がRFC 3986の「Remove Dot Segments」アルゴリズムに厳密に従うようになりました。- 新しい実装では、パスをスラッシュで分割し、各セグメントを順に処理します。
.
セグメントは単に無視されます。..
セグメントは、結果パスの最後のセグメントを削除します。ただし、結果パスがルート(/
)である場合はそれ以上削除しません。- これにより、
a/b/c/../d
がa/b/d
に、a/b/c/./d
がa/b/c/d
に正しく解決されるようになります。
- 絶対パスの強制:
resolvePath
の結果が常に絶対パス(先頭に/
が付く)になるように変更されました。これは、URIのパスコンポーネントが常に絶対パスとして扱われるべきであるというRFCの原則に合致します。
ResolveReference
メソッドの変更
- RFC 3986準拠への変更:
ResolveReference
メソッドも、URI参照の解決においてRFC 3986のセクション5.2に厳密に準拠するように修正されました。 - スキーム相対参照の正しい処理: 以前は「//foo」のようなスキーム相対参照が誤って絶対パスとして扱われる問題がありましたが、このコミットにより、これらの参照が正しく解決され、ベースURIのスキームを引き継ぎつつ、新しいオーソリティ(ホスト)を持つURIとして扱われるようになりました。
- Opaque URIの処理:
Opaque
コンポーネントを持つURI(例:mailto:user@example.com
)の解決ロジックが改善されました。Opaque
URIはパスやクエリを持たないため、参照解決時にこれらのコンポーネントがクリアされるように変更されています。 - 新しいURLインスタンスの返却:
ResolveReference
は常に新しいURL
インスタンスを返すように保証されています。これにより、元のURL
オブジェクトが意図せず変更されることを防ぎ、不変性を保ちます。
テストの拡充
- RFC 3986のテストケースの追加:
url_test.go
に、RFC 3986のセクション5.4.1(Normal Examples)と5.4.2(Abnormal Examples)に記載されているすべてのパス解決例がテストケースとして追加されました。これにより、実装が標準に厳密に準拠していることが検証されます。 - 既存テストの修正: 以前の誤ったパス解決ロジックに基づいて期待値が設定されていた既存のテストケースが、新しいRFC準拠のロジックに合わせて修正されました。
これらの変更により、net/url
パッケージはURIのパス解決においてより正確で予測可能な動作を提供するようになり、URIの相互運用性と堅牢性が大幅に向上しました。
コアとなるコードの変更箇所
src/pkg/net/url/url.go
func parse(rawurl string, viaRequest bool) (url *URL, err error)
:if rawurl == "" && viaRequest { ... }
の条件が追加され、リクエスト経由で空のURLが渡された場合にのみエラーを返すようになりました。これにより、内部的な空文字列の処理がより柔軟になります。
func resolvePath(base, ref string) string
:- 関数シグネチャが
(basepath string, refpath string)
から(base, ref string)
に変更され、引数名が簡潔になりました。 - 内部のパス解決ロジックが完全に書き直されました。
- 以前の
strings.Split
とswitch
を用いた複雑なロジックが、より簡潔なループとswitch
ステートメントに置き換えられました。 - ドットセグメント(
.
と..
)の処理がRFC 3986のアルゴリズムに厳密に準拠するように変更されました。 - 結果のパスが常に
/
で始まるように"/"+strings.TrimLeft(strings.Join(dst, "/"), "/")
で整形されます。
- 以前の
- 関数シグネチャが
func (u *URL) ResolveReference(ref *URL) *URL
:- URI参照解決のロジックが大幅に修正されました。
ref.IsAbs()
のチェックが削除され、スキーム、ホスト、ユーザー情報に基づいて絶対URIまたはネットワークパスのケースを判断するようになりました。ref.Opaque != ""
のケースが最初に処理され、Opaque URIの特性が正しく反映されるようになりました。- パス解決に
resolvePath
関数が使用され、ベースURIのパスと参照URIのパスが正しく結合されるようになりました。 - クエリやフラグメントの処理順序がRFC 3986の仕様に合わせて調整されました。
src/pkg/net/url/url_test.go
var resolvePathTests = []struct { ... }
:- 既存のテストケースの期待値が、新しい
resolvePath
の動作に合わせて修正されました。特に、結果のパスが常に/
で始まるように変更されています。
- 既存のテストケースの期待値が、新しい
var resolveReferenceTests = []struct { ... }
:- 既存のテストケースの期待値が、新しい
ResolveReference
の動作に合わせて修正されました。 - RFC 3986のNormal Examples (セクション5.4.1) と Abnormal Examples (セクション5.4.2) のすべてのテストケースが追加されました。 これがこのコミットのテスト面での最も大きな変更点です。
- スキーム相対参照(
//g
など)のテストケースが追加されました。
- 既存のテストケースの期待値が、新しい
func TestResolveReference(t *testing.T)
:- テストロジックが簡素化され、
mustParse
ヘルパー関数が導入されました。 URL.Parse
メソッドのテストもこの関数内で統合され、ResolveReference
と同様に新しいインスタンスが返されること、およびOpaque URIの処理が正しく行われることが検証されています。- 以前の冗長な「新しいインスタンスが返されること」のテストコードが削除され、より簡潔な形で検証されるようになりました。
- テストロジックが簡素化され、
コアとなるコードの解説
このコミットの核心は、URIのパス解決と参照解決のロジックをRFC 3986に完全に準拠させることです。
resolvePath
の新しいロジック
func resolvePath(base, ref string) string {
var full string
if ref == "" {
full = base
} else if ref[0] != '/' {
i := strings.LastIndex(base, "/")
full = base[:i+1] + ref
} else {
full = ref
}
if full == "" {
return ""
}
var dst []string
src := strings.Split(full, "/")
for _, elem := range src {
switch elem {
case ".":
// drop
case "..":
if len(dst) > 0 {
dst = dst[:len(dst)-1]
}
default:
dst = append(dst, elem)
}
}
if last := src[len(src)-1]; last == "." || last == ".." {
// Add final slash to the joined path.
dst = append(dst, "")
}
return "/" + strings.TrimLeft(strings.Join(dst, "/"), "/")
}
この resolvePath
関数は、RFC 3986のセクション5.2.4「Remove Dot Segments」アルゴリズムを実装しています。
full
パスの決定:ref
が空の場合、base
パスがそのままfull
となります。ref
が/
で始まらない場合(相対パス)、base
パスの最後のスラッシュまでの部分にref
を結合します。ref
が/
で始まる場合(絶対パス)、ref
がそのままfull
となります。
- ドットセグメントの削除:
full
パスをスラッシュで分割し、各セグメントをsrc
スライスに入れます。dst
スライスは、正規化されたパスセグメントを格納します。- 各
elem
(セグメント)について:elem
が.
の場合、何もせずスキップします(ドロップ)。elem
が..
の場合、dst
スライスの最後の要素を削除します。これにより、親ディレクトリへの移動がシミュレートされます。dst
が空の場合は何もしません(ルートより上には行かない)。- それ以外の場合(通常のセグメント)、
elem
をdst
に追加します。
- 末尾のスラッシュの処理:
- 元の
full
パスの最後のセグメントが.
または..
であった場合、結果のパスの末尾にスラッシュを追加します。これは、RFC 3986で定義されているパスの正規化規則の一部です(例:foo/bar/.
はfoo/bar/
に解決される)。
- 元の
- 最終的なパスの整形:
dst
スライスをスラッシュで結合し、先頭の不要なスラッシュをstrings.TrimLeft
で削除します。- 最後に、結果のパスの先頭に
/
を追加して、常に絶対パスとして返します。
ResolveReference
の新しいロジック
func (u *URL) ResolveReference(ref *URL) *URL {
url := *ref // refのコピーを作成
if ref.Scheme == "" {
url.Scheme = u.Scheme // refにスキームがない場合、ベースURIのスキームを使用
}
if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
// 絶対URIまたはネットワークパスのケース
url.Path = resolvePath(ref.Path, "") // refのパスを正規化
return &url
}
if ref.Opaque != "" {
// Opaque URIのケース
url.User = nil
url.Host = ""
url.Path = ""
return &url
}
if ref.Path == "" {
if ref.RawQuery == "" {
url.RawQuery = u.RawQuery // refにクエリがない場合、ベースURIのクエリを使用
if ref.Fragment == "" {
url.Fragment = u.Fragment // refにフラグメントがない場合、ベースURIのフラグメントを使用
}
}
}
// 絶対パスまたは相対パスのケース
url.Host = u.Host
url.User = u.User
url.Path = resolvePath(u.Path, ref.Path) // ベースURIのパスとrefのパスを解決
return &url
}
この ResolveReference
メソッドは、RFC 3986のセクション5.2「Reference Resolution」アルゴリズムを実装しています。
- 参照URIのコピー: まず、
ref
のコピーurl
を作成します。これにより、元のref
オブジェクトが変更されるのを防ぎます。 - スキームの継承:
ref
にスキームがない場合、ベースURIu
のスキームをurl.Scheme
に設定します。 - 絶対URIまたはネットワークパス:
ref
がスキーム、ホスト、またはユーザー情報を持つ場合、それは絶対URIまたはネットワークパス参照と見なされます。- この場合、
ref
のパスをresolvePath
で正規化し、そのままurl
として返します。ベースURIの他のコンポーネントは無視されます。
- Opaque URI:
ref
がOpaque
コンポーネントを持つ場合(例:mailto:
)、それはパスやホストを持たない特殊なURIです。- この場合、
url
のユーザー情報、ホスト、パスをクリアし、Opaque
コンポーネントのみを持つURIとして返します。
- パス、クエリ、フラグメントの解決:
ref
がパスを持たない場合、ベースURIのクエリとフラグメントを継承します。- 最終的に、ベースURIのホストとユーザー情報を
url
に設定し、resolvePath(u.Path, ref.Path)
を呼び出して、ベースURIのパスと参照URIのパスを結合・正規化します。
これらの変更により、net/url
パッケージはURIの解決において、より正確で標準に準拠した動作を実現しています。
関連リンク
- Go issue #4700: https://code.google.com/p/go/issues/detail?id=4700 (現在はGitHubに移行済み)
- Go issue #4706: https://code.google.com/p/go/issues/detail?id=4706 (現在はGitHubに移行済み)
- Go CL 7203059: https://golang.org/cl/7203059 (Gerrit Code Review)
参考にした情報源リンク
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://tools.ietf.org/html/rfc3986
- Section 5.4.1 - Normal Examples: https://tools.ietf.org/html/rfc3986#section-5.4.1
- Section 5.4.2 - Abnormal Examples: https://tools.ietf.org/html/rfc3986#section-5.4.2
- Section 5.2.4 - Remove Dot Segments: https://tools.ietf.org/html/rfc3986#section-5.2.4
- GoDoc net/url: https://pkg.go.dev/net/url
- Go Playground (コミットメッセージに記載の例):
- http://play.golang.org/p/F0ApSaXniv
- http://play.golang.org/p/089b-_xoNe
- RFC 2396 - Uniform Resource Identifiers (URI): Generic Syntax (旧版): https://tools.ietf.org/html/rfc2396
- Go issue #4700 (GitHub): https://github.com/golang/go/issues/4700
- Go issue #4706 (GitHub): https://github.com/golang/go/issues/4706