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

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

このコミットは、Go言語の標準ライブラリである net/url パッケージにおける ResolveReference 関数の相対パス処理、特に .. (親ディレクトリ) の扱いに関するバグを修正するものです。具体的には、src/pkg/net/url/url.go 内の resolvePath 関数が修正され、それに対応するテストケースが src/pkg/net/url/url_test.go に追加されています。

コミット

commit 2f45f2801da90350c2a2dbf7e36cd97f5fb7ce0f
Author: Rick Arnold <rickarnoldjr@gmail.com>
Date:   Tue Dec 11 11:06:07 2012 -0500

    net/url: fix handling of relative paths in ResolveReference.
    
    Fixes #3560.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6886047

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

https://github.com/golang/go/commit/2f45f2801da90350c2a2dbf7e36cd97f5fb7ce0f

元コミット内容

net/url: fix handling of relative paths in ResolveReference. Fixes #3560.

変更の背景

このコミットは、Go言語の net/url パッケージが提供する ResolveReference 関数における、相対パスの解決に関する既存のバグ(Issue #3560)を修正するために行われました。

具体的には、ResolveReference 関数が . (カレントディレクトリ) や .. (親ディレクトリ) を含む相対パスを解決する際に、特にパスの中間に .. が出現する場合に、RFC 3986で定義されているURI解決のルールに厳密に従わない挙動を示すことが問題でした。例えば、http://foo.com/bar/baz をベースURIとして、quux/dotdot/../tail のような相対パスを解決しようとすると、期待される結果 (http://foo.com/bar/quux/tail) とは異なるURIが生成される可能性がありました。

この問題は、URIの正規化と解決の正確性に影響を与え、Webアプリケーションやネットワーク通信において予期せぬURIへのアクセスやリソースの誤認識を引き起こす可能性がありました。そのため、URI解決の仕様に完全に準拠するよう、resolvePath 関数のロジックを修正する必要がありました。

前提知識の解説

URI (Uniform Resource Identifier) と URL (Uniform Resource Locator)

URIは、リソースを一意に識別するための文字列です。URLはURIの一種であり、リソースの場所(ロケーション)を示すものです。Web上では、通常URLが使われます。

相対パスと絶対パス

  • 絶対パス: リソースの完全な場所を最初から指定するパスです。例えば、http://example.com/path/to/resource.html
  • 相対パス: 現在のベースURIからの相対的な位置でリソースを指定するパスです。例えば、../images/logo.pngsubfolder/document.pdf

URI解決 (URI Resolution)

URI解決とは、ベースURIと相対URI参照(相対パス)から、完全な絶対URIを導出するプロセスです。これは、HTMLドキュメント内のリンクや、HTTPリダイレクトなどで広く利用されます。

RFC 3986: Uniform Resource Identifier (URI): Generic Syntax

このRFCは、URIの一般的な構文と、URI解決のアルゴリズムを定義しています。特に、セクション5.2「Reference Resolution Examples」では、相対URI参照の解決方法に関する詳細なルールと例が示されています。

URI解決の重要なルールの一つに、パスセグメントの処理があります。

  • . (ドット): カレントディレクトリを示します。解決時には通常、無視されます。
  • .. (ドットドット): 親ディレクトリを示します。解決時には、パスの最後のセグメントを削除し、その親ディレクトリに移動します。

これらのセグメントがパスの途中に複数回出現する場合や、連続して出現する場合の処理が複雑になることがあります。特に、a/b/../c のようなパスは a/c に解決されるべきですが、実装によっては a/b/c のように誤って解釈されたり、a/c になるべきところが a/b/c になったりするバグが発生することがあります。

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

net/url パッケージは、Go言語でURLを解析、構築、操作するための機能を提供します。

  • url.URL 構造体: URLの各コンポーネント(スキーム、ホスト、パスなど)を表現します。
  • Parse() 関数: 文字列から url.URL 構造体を解析します。
  • ResolveReference(ref *URL) *URL メソッド: ベースURLに対して相対URL参照を解決し、新しい絶対URLを返します。このメソッドの内部で、パスの解決のために resolvePath 関数が利用されます。

このコミットで修正されたのは、ResolveReference の内部で呼び出される resolvePath 関数のロジックであり、.. の処理がRFC 3986に準拠していなかった点が問題でした。

技術的詳細

net/url パッケージの resolvePath 関数は、ベースパスと参照パスを受け取り、それらを結合して正規化されたパスを生成します。この関数は、パスを / で区切られたセグメントのリストとして扱い、... といった特殊なセグメントを処理します。

修正前の resolvePath 関数には、.. セグメントがパスの中間に出現した場合の処理に問題がありました。具体的には、.. を処理する際に、常に base[len(base)-1] = "" を実行して、base スライスの最後の要素を空文字列に設定していました。これは、.. がパスの末尾にある場合には正しい動作ですが、パスの途中に .. がある場合(例: a/b/../c)には、b が削除された後に a/ となり、その後に c が追加されて a/c となるべきです。しかし、常に最後の要素を空にするロジックでは、意図しないパスの短縮や、セグメントの誤った結合が発生する可能性がありました。

このコミットでは、rm (remove) という新しいブール変数を導入することで、この問題を解決しています。rm 変数は、現在のパスセグメントの処理後に、base スライスの最後の要素を空にする必要があるかどうかを制御します。

  • ref == "." の場合:
    • もし idx == 0 (参照パスの最初のセグメントが . の場合) であれば、base の最後の要素を空にし、rm = true とします。これは、./foo のような場合に foo となるべき挙動を保証します。
    • そうでなければ、rm = false とし、最後の要素を空にしません。これは、a/./b のような場合に a/b となるべき挙動を保証します。
  • ref == ".." の場合:
    • base スライスから最後のセグメントを削除します。
    • rmtrue の場合にのみ、base の最後の要素を空にします。これにより、.. がパスの途中にあっても、不必要にパスが短縮されることを防ぎます。
  • ref が通常のセグメントの場合:
    • rm = false とします。これは、通常のセグメントが追加された後は、最後の要素を空にする必要がないためです。

この変更により、resolvePath 関数は、RFC 3986で定義されているURI解決のルールに厳密に従い、... を含む複雑な相対パスも正しく解決できるようになりました。

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

src/pkg/net/url/url.goresolvePath 関数に以下の変更が加えられました。

diff --git a/src/pkg/net/url/url.go b/src/pkg/net/url/url.go
index 692a7fdc04..82db0367bc 100644
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -572,23 +572,33 @@ func resolvePath(basepath string, refpath string) string {
 	if len(base) == 0 {
 		base = []string{""}
 	}
+
+	rm := true
 	for idx, ref := range refs {
 		switch {
 		case ref == ".":
-			base[len(base)-1] = ""
+			if idx == 0 {
+				base[len(base)-1] = ""
+				rm = true
+			} else {
+				rm = false
+			}
 		case ref == "..":
 			newLen := len(base) - 1
 			if newLen < 1 {
 				newLen = 1
 			}
 			base = base[0:newLen]
-			base[len(base)-1] = ""
+			if rm {
+				base[len(base)-1] = ""
+			}
 		default:
 			if idx == 0 || base[len(base)-1] == "" {
 				base[len(base)-1] = ref
 			} else {
 				base = append(base, ref)
 			}
+			rm = false
 		}
 	}
 	return strings.Join(base, "/")

また、src/pkg/net/url/url_test.go には、Issue #3560に関連する複数の新しいテストケースが追加されました。

diff --git a/src/pkg/net/url/url_test.go b/src/pkg/net/url/url_test.go
index 64f1170027..4a09189403 100644
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -536,6 +536,15 @@ var resolveReferenceTests = []struct {
 	{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
 	{"http://foo.com/bar", "..", "http://foo.com/"},
 	{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
+	// ".." in the middle (issue 3560)
+	{"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
+	{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot"},
 
 	// "." and ".." in the base aren't special
 	{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/./dotdot/../baz"},

コアとなるコードの解説

修正の核心は、resolvePath 関数内で導入された rm (remove) というブール変数と、それを用いた条件分岐です。

  1. rm 変数の導入と初期化: rm := true rm は、現在のパスセグメントの処理後に、base スライスの最後の要素を空文字列 ("") に設定する必要があるかどうかを示すフラグです。初期値は true です。

  2. . (カレントディレクトリ) の処理:

    case ref == ".":
        if idx == 0 { // 参照パスの最初のセグメントが "." の場合
            base[len(base)-1] = "" // 最後の要素を空にする
            rm = true              // rmをtrueに維持
        } else { // 参照パスの途中に "." がある場合
            rm = false             // rmをfalseにする
        }
    
    • 参照パスの最初のセグメントが . の場合(例: ./foo)、ベースパスの最後のセグメントを空にし、rmtrue に保ちます。これにより、http://example.com/dir/./filehttp://example.com/dir/file となるように、dir/ の部分が dir に正規化されます。
    • 参照パスの途中に . がある場合(例: foo/./bar)、rmfalse に設定します。これにより、base[len(base)-1] = "" が実行されなくなり、foo/bar のように正しく解決されます。
  3. .. (親ディレクトリ) の処理:

    case ref == "..":
        newLen := len(base) - 1
        if newLen < 1 {
            newLen = 1
        }
        base = base[0:newLen] // 最後のセグメントを削除
        if rm { // rmがtrueの場合のみ
            base[len(base)-1] = "" // 最後の要素を空にする
        }
    
    • .. が出現した場合、まず base スライスから最後のセグメントを削除します。これは、親ディレクトリに移動する基本的な動作です。
    • 重要なのは、if rm { base[len(base)-1] = "" } という条件が追加された点です。これにより、rmtrue の場合にのみ、削除後の base スライスの最後の要素が空文字列に設定されます。
      • 例えば、http://foo.com/bar/baz をベースに ../quux を解決する場合、base["foo.com", "bar", "baz"] から ["foo.com", "bar"] になり、rmtrue なので ["foo.com", "bar", ""] となり、最終的に http://foo.com/bar/quux となります。
      • しかし、http://foo.com/bar/baz をベースに quux/dotdot/../tail を解決する場合、quux が追加された時点で rmfalse になります。次に dotdot が追加され、その次に .. が処理されます。この時 rmfalse のままなので、base[len(base)-1] = "" は実行されず、http://foo.com/bar/quux/tail のように正しく解決されます。
  4. 通常のセグメントの処理:

    default:
        if idx == 0 || base[len(base)-1] == "" {
            base[len(base)-1] = ref
        } else {
            base = append(base, ref)
        }
        rm = false // 通常のセグメントが追加されたらrmをfalseにする
    
    • 通常のパスセグメントが追加された後、rmfalse に設定します。これは、次に ... が出現した場合に、不必要なパスの短縮を防ぐためです。

これらの変更により、resolvePath 関数は、RFC 3986のURI解決アルゴリズム、特にパスの正規化ルールに厳密に準拠するようになりました。これにより、net/url.ResolveReference がより堅牢で正確なURI解決を提供できるようになりました。

関連リンク

参考にした情報源リンク