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

[インデックス 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に完全に準拠していなかった点にあります。特に、以下の問題が指摘されていました。

  1. RFC 3986のパス解決例の不適合: RFC 3986のセクション5.4.1(Normal Examples)と5.4.2(Abnormal Examples)には、URI参照の解決に関する多数の具体的な例が示されています。Goの net/url パッケージは、これらの例の一部で期待される結果を返していませんでした。これは、URIの相互運用性や正確な解釈において問題を引き起こす可能性がありました。
  2. ドットセグメントの不適切な処理: URIパス内の「.」(カレントディレクトリ)や「..」(親ディレクトリ)といったドットセグメントの処理が、RFCの規定通りに行われていませんでした。これにより、予期せぬパス解決結果が生じることがありました。
  3. スキーム相対参照の誤解釈: 「//foo」のようなスキーム相対参照(scheme-relative reference)が、誤って絶対パスとして解釈され、不正確なURIが生成される問題がありました。これは、特にWebアプリケーションやネットワークプロトコルにおいて、URIの正確な構築と解釈が求められる場面で深刻なバグにつながる可能性がありました。
  4. 既存のバグ修正: 具体的には、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の各コンポーネント(スキーム、ホスト、パスなど)を表現し、ParseParseRequestURIResolveReference などの関数が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/../da/b/d に、a/b/c/./da/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.Splitswitch を用いた複雑なロジックが、より簡潔なループと 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」アルゴリズムを実装しています。

  1. full パスの決定:
    • ref が空の場合、base パスがそのまま full となります。
    • ref/ で始まらない場合(相対パス)、base パスの最後のスラッシュまでの部分に ref を結合します。
    • ref/ で始まる場合(絶対パス)、ref がそのまま full となります。
  2. ドットセグメントの削除:
    • full パスをスラッシュで分割し、各セグメントを src スライスに入れます。
    • dst スライスは、正規化されたパスセグメントを格納します。
    • elem (セグメント)について:
      • elem. の場合、何もせずスキップします(ドロップ)。
      • elem.. の場合、dst スライスの最後の要素を削除します。これにより、親ディレクトリへの移動がシミュレートされます。dst が空の場合は何もしません(ルートより上には行かない)。
      • それ以外の場合(通常のセグメント)、elemdst に追加します。
  3. 末尾のスラッシュの処理:
    • 元の full パスの最後のセグメントが . または .. であった場合、結果のパスの末尾にスラッシュを追加します。これは、RFC 3986で定義されているパスの正規化規則の一部です(例: foo/bar/.foo/bar/ に解決される)。
  4. 最終的なパスの整形:
    • 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」アルゴリズムを実装しています。

  1. 参照URIのコピー: まず、ref のコピー url を作成します。これにより、元の ref オブジェクトが変更されるのを防ぎます。
  2. スキームの継承: ref にスキームがない場合、ベースURI u のスキームを url.Scheme に設定します。
  3. 絶対URIまたはネットワークパス:
    • ref がスキーム、ホスト、またはユーザー情報を持つ場合、それは絶対URIまたはネットワークパス参照と見なされます。
    • この場合、ref のパスを resolvePath で正規化し、そのまま url として返します。ベースURIの他のコンポーネントは無視されます。
  4. Opaque URI:
    • refOpaque コンポーネントを持つ場合(例: mailto:)、それはパスやホストを持たない特殊なURIです。
    • この場合、url のユーザー情報、ホスト、パスをクリアし、Opaque コンポーネントのみを持つURIとして返します。
  5. パス、クエリ、フラグメントの解決:
    • ref がパスを持たない場合、ベースURIのクエリとフラグメントを継承します。
    • 最終的に、ベースURIのホストとユーザー情報を url に設定し、resolvePath(u.Path, ref.Path) を呼び出して、ベースURIのパスと参照URIのパスを結合・正規化します。

これらの変更により、net/url パッケージはURIの解決において、より正確で標準に準拠した動作を実現しています。

関連リンク

参考にした情報源リンク