[インデックス 14965] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/url
パッケージにおけるURLのパース処理のバグ修正に関するものです。具体的には、file
スキームを持つURL(例: file:///foo
)が正しくパースされず、Path
フィールドに余分なスラッシュが含まれてしまう問題を解決します。これにより、URLのパースと文字列化の挙動がよりRFCに準拠したものになります。
コミット
- Author: Andrew Gerrand adg@golang.org
- Date: Wed Jan 23 11:37:06 2013 +1100
- Commit Message:
net/url: generate correct Path when hostname empty Parse("file:///foo") previously returned a URL with Scheme "file" and Path "///foo". Now it returns a URL with Path "/foo", such that &URL{Scheme: "file", Path: "/foo"}.String() == "file:///foo" This means that parsing and stringifying the URL "file:/foo" returns "file:///foo", technically a regression but one that only affects a corner case. Fixes #4189. R=bradfitz, rsc CC=golang-dev https://golang.org/cl/7135051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cdd6ae128894abbaf3fef0401cdef319f3ec1d3d
元コミット内容
net/url: generate correct Path when hostname empty
Parse("file:///foo") previously returned a URL with Scheme "file"
and Path "///foo". Now it returns a URL with Path "/foo",
such that
&URL{Scheme: "file", Path: "/foo"}.String() == "file:///foo"
This means that parsing and stringifying the URL "file:/foo"
returns "file:///foo", technically a regression but one that only
affects a corner case.
Fixes #4189.
R=bradfitz, rsc
CC=golang-dev
https://golang.org/cl/7135051
変更の背景
この変更は、Go言語の net/url
パッケージが file
スキームのURLを正しく処理できないというバグ(Issue #4189)を修正するために行われました。具体的には、file:///foo
のような形式のURLをパースした際に、URL
構造体の Path
フィールドが ///foo
となってしまい、期待される /foo
とは異なる値が設定される問題がありました。
RFC 3986(URI Generic Syntax)では、file
スキームのURIは、ホスト名が省略された場合(つまり、ローカルファイルを参照する場合)、パスの前に3つのスラッシュ (///
) が続く形式が一般的です。しかし、net/url
パッケージの既存の実装では、この3つのスラッシュをパスの一部として誤って解釈していました。
この誤った解釈は、URLのパースと文字列化の間の不整合を引き起こし、特に file
スキームを使用するアプリケーションで問題となる可能性がありました。このコミットは、この不整合を解消し、file
スキームのURLがRFCの仕様に沿って正しく扱われるようにすることを目的としています。
コミットメッセージには、「file:/foo
をパースして文字列化すると file:///foo
になる」という「技術的にはリグレッションだが、ごく稀なケースにしか影響しない」という記述があります。これは、file:
の後にスラッシュが1つしかない形式は、RFCでは非推奨または未定義の挙動とされており、通常は file:///
の形式が使われるため、実用上の影響は小さいと判断されたことを示唆しています。
前提知識の解説
URL (Uniform Resource Locator)
URLは、インターネット上のリソースの位置を示すための文字列です。一般的なURLの構造は以下のようになります。
scheme://[user:password@]host:port/path?query#fragment
- Scheme (スキーム): リソースにアクセスするためのプロトコル(例:
http
,https
,ftp
,file
)。 - Authority (オーソリティ): ホスト名、ポート番号、ユーザー情報などを含む部分。
- User Information (ユーザー情報): ユーザー名とパスワード(オプション)。
- Host (ホスト): リソースが配置されているサーバーのドメイン名またはIPアドレス。
- Port (ポート): サーバーのポート番号(オプション)。
- Path (パス): サーバー上のリソースの場所を示す階層的な情報。
- Query (クエリ): リソースに渡される追加のパラメータ(例:
?key=value
)。 - Fragment (フラグメント): リソース内の特定の部分を指す識別子(例:
#section1
)。
file
スキーム
file
スキームは、ローカルファイルシステム上のリソースを参照するために使用されます。その特徴は以下の通りです。
- ホスト名: ローカルファイルを参照する場合、ホスト名は省略されるか、
localhost
が使用されます。ホスト名が省略される場合、パスの前に3つのスラッシュ (///
) が続くのが一般的です。- 例:
file:///path/to/file.txt
(ホスト名なし) - 例:
file://localhost/path/to/file.txt
(ホスト名あり)
- 例:
- パス: ファイルシステム上の絶対パスが指定されます。
RFC 3986 (URI Generic Syntax)
RFC 3986は、URI(Uniform Resource Identifier)の一般的な構文を定義する標準です。URLはURIの一種です。このRFCは、URIの各コンポーネント(スキーム、オーソリティ、パスなど)の解釈方法や、それらの間の関係について詳細に記述しています。
特に、パスの解釈において、オーソリティ部分が存在しない場合のパスの開始方法について規定しています。file
スキームのようにオーソリティが省略される場合、パスはスキームの後に続くスラッシュの数によって解釈が変わることがあります。
Go言語の net/url
パッケージ
Go言語の net/url
パッケージは、URLのパース、構築、エンコード、デコードを行うための機能を提供します。主要な型として url.URL
構造体があり、URLの各コンポーネント(Scheme
, Opaque
, User
, Host
, Path
, RawPath
, ForceQuery
, RawQuery
, Fragment
, RawFragment
)をフィールドとして持ちます。
url.Parse(rawurl string)
: 文字列形式のURLをurl.URL
構造体にパースします。url.URL.String()
:url.URL
構造体を文字列形式のURLに変換します。
このパッケージは、Webアプリケーションやネットワークプログラミングにおいて、URLを安全かつ正確に扱うために不可欠です。
技術的詳細
このコミットの技術的詳細は、net/url
パッケージ内のURLパースロジックと文字列化ロジックの変更に集約されます。
URLパースロジックの変更 (parse
関数)
変更前の parse
関数では、スキームが存在するか、またはリクエスト経由でない場合に、残りの文字列が //
で始まり、かつ ///
で始まらない場合にオーソリティ部分をパースしていました。
// 変更前
if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
// ... オーソリティのパース ...
}
この条件 !strings.HasPrefix(rest, "///")
が問題でした。file:///foo
のようなURLでは、rest
が ///foo
となるため、この条件が false
となり、オーソリティ部分が正しくパースされず、結果として Path
に ///foo
が含まれてしまっていました。
変更後のコードでは、この !strings.HasPrefix(rest, "///")
の条件が削除されました。
// 変更後
if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
// ... オーソリティのパース ...
}
この変更により、file:///foo
のようなURLでも strings.HasPrefix(rest, "//")
が true
となり、オーソリティのパースロジックに進むようになります。file
スキームでホスト名が空の場合、parseAuthority
関数は空のホスト名を返し、残りの ///foo
のうち最初のスラッシュがオーソリティの区切りとして消費され、結果的に Path
に /foo
が設定されるようになります。
ただし、!viaRequest && !strings.HasPrefix(rest, "///")
という条件が追加されています。これは、リクエスト経由でないURL(例: Parse("///foo")
のような相対パス)で、かつ ///
で始まる場合に、オーソリティとして解釈しないためのものです。これにより、file:///foo
のような絶対パス形式のURLと、相対パス形式の ///foo
を区別できるようになります。
URL文字列化ロジックの変更 (String
メソッド)
変更前の String
メソッドでは、Host
または User
が存在する場合にのみ //
を追加していました。
// 変更前
if u.Host != "" || u.User != nil {
result += "//"
// ...
result += u.Host
}
file:///foo
のようなURLは、Host
が空であり、User
もないため、この条件が false
となり、//
が追加されませんでした。その結果、&URL{Scheme: "file", Path: "/foo"}.String()
が file:/foo
のように出力されてしまい、期待される file:///foo
とは異なっていました。
変更後のコードでは、Scheme
が存在する場合にも //
を追加する条件が追加されました。
// 変更後
if u.Scheme != "" || u.Host != "" || u.User != nil {
result += "//"
// ...
if h := u.Host; h != "" {
result += u.Host
}
}
この変更により、file
スキームを持つURLの場合、u.Scheme != ""
が true
となるため、//
が追加されるようになります。さらに、ホスト名が空の場合でも u.Host
を追加しないように if h := u.Host; h != ""
というチェックが追加されています。これにより、file:///foo
のような形式が正しく生成されるようになります。
テストケースの変更
url_test.go
には、mailto:/webmaster@golang.org
のような「非オーソリティ」URLのテストケースがありました。変更前は、このURLの文字列化結果が空文字列 (""
) となっていましたが、変更後は mailto:///webmaster@golang.org
となるように修正されています。これは、mailto
スキームも file
スキームと同様にオーソリティを持たないが、パスの前に ///
が付く形式がより適切であるという判断に基づいています。コミットメッセージにある「unfortunate compromise(残念な妥協)」とは、この mailto
のケースで ///
が付くようになることを指していると考えられます。これは、RFC 6068 (The 'mailto' URI Scheme) では mailto:user@example.com
のようにスラッシュが一つも付かない形式が一般的であるため、///
が付くのは直感的ではないというニュアンスが含まれている可能性があります。しかし、net/url
パッケージの内部的な整合性を保つためには、このような挙動が避けられないと判断されたのでしょう。
また、file:///home/adg/rabbits
という新しいテストケースが追加され、このURLが正しくパースされ、Path
が /home/adg/rabbits
となり、文字列化しても元の形式に戻ることを確認しています。
コアとなるコードの変更箇所
src/pkg/net/url/url.go
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -386,7 +386,7 @@ func parse(rawurl string, viaRequest bool) (url *URL, err error) {
}
}
- if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
+ if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
var authority string
authority, rest = split(rest[2:], '/', false)
url.User, url.Host, err = parseAuthority(authority)
@@ -442,12 +442,14 @@ func (u *URL) String() string {
if u.Opaque != "" {
result += u.Opaque
} else {
- if u.Host != "" || u.User != nil {
+ if u.Scheme != "" || u.Host != "" || u.User != nil {
result += "//"
if u := u.User; u != nil {
result += u.String() + "@"
}
- result += u.Host
+ if h := u.Host; h != "" {
+ result += u.Host
+ }
}
result += escape(u.Path, encodePath)
}
src/pkg/net/url/url_test.go
--- a/src/pkg/net/url/url_test.go
+++ b/src/pkg/net/url/url_test.go
@@ -122,14 +122,14 @@ var urltests = []URLTest{
},
"http:%2f%2fwww.google.com/?q=go+language",
},
- // non-authority
+ // non-authority with path
{
"mailto:/webmaster@golang.org",
&URL{
Scheme: "mailto",
Path: "/webmaster@golang.org",
},
- "",
+ "mailto:///webmaster@golang.org", // unfortunate compromise
},
// non-authority
{
@@ -242,6 +242,15 @@ var urltests = []URLTest{
},
"http://www.google.com/?q=go+language#foo&bar",
},
+ {
+ "file:///home/adg/rabbits",
+ &URL{
+ Scheme: "file",
+ Host: "",
+ Path: "/home/adg/rabbits",
+ },
+ "file:///home/adg/rabbits",
+ },
}
// more useful string for debugging than fmt's struct printer
コアとなるコードの解説
src/pkg/net/url/url.go
の変更点
-
parse
関数の変更:- 変更前:
if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
- この条件は、
rest
が///
で始まる場合にオーソリティのパースをスキップしていました。これがfile:///foo
のようなURLで問題を引き起こしていました。
- この条件は、
- 変更後:
if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
!strings.HasPrefix(rest, "///")
の位置が変更され、!viaRequest
との論理積になりました。これにより、file:///foo
のようなURL(viaRequest
がfalse
で、rest
が///
で始まる)でも、strings.HasPrefix(rest, "//")
がtrue
であればオーソリティのパースに進むようになります。- この変更により、
file:///foo
はScheme: "file"
,Host: ""
,Path: "/foo"
と正しくパースされるようになります。
- 変更前:
-
String
メソッドの変更:- 変更前:
if u.Host != "" || u.User != nil { result += "//" ... result += u.Host }
Host
またはUser
が存在しない場合(file
スキームでホスト名が空の場合など)は//
が追加されませんでした。
- 変更後:
if u.Scheme != "" || u.Host != "" || u.User != nil { result += "//" ... if h := u.Host; h != "" { result += u.Host } }
u.Scheme != ""
という条件が追加されました。これにより、file
スキームのようにホスト名が空でもスキームが存在すれば//
が追加されるようになります。result += u.Host
の部分がif h := u.Host; h != "" { result += u.Host }
に変更されました。これにより、ホスト名が空の場合は余分な空文字列が追加されるのを防ぎます。- これらの変更により、
&URL{Scheme: "file", Path: "/foo"}
のようなURL
構造体がfile:///foo
と正しく文字列化されるようになります。
- 変更前:
src/pkg/net/url/url_test.go
の変更点
-
mailto
スキームのテストケースの修正:mailto:/webmaster@golang.org
のテストケースにおいて、期待される文字列化結果が""
からmailto:///webmaster@golang.org
に変更されました。これは、file
スキームと同様に、オーソリティを持たないスキームでもパスの前に///
が付く形式が内部的な整合性のため採用されたことを示しています。
-
新しい
file
スキームのテストケースの追加:file:///home/adg/rabbits
という新しいテストケースが追加されました。- このテストケースは、
file
スキームのURLが正しくパースされ(Scheme: "file"
,Host: ""
,Path: "/home/adg/rabbits"
)、そして正しく文字列化される(file:///home/adg/rabbits
)ことを検証します。これは、このコミットの主要な修正内容を直接的にテストするものです。
これらの変更により、net/url
パッケージは file
スキームのURLをより正確に、RFCの意図に沿って処理できるようになりました。
関連リンク
- Go Issue #4189: net/url: Parse("file:///foo") returns Path "///foo" · Issue #4189 · golang/go
- Go CL 7135051: net/url: generate correct Path when hostname empty - go.dev/cl/7135051
参考にした情報源リンク
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986
- RFC 6068 - The 'mailto' URI Scheme: https://datatracker.ietf.org/doc/html/rfc6068
- GoDoc - net/url package: https://pkg.go.dev/net/url
[インデックス 14965] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/url
パッケージにおけるURLのパース処理のバグ修正に関するものです。具体的には、file
スキームを持つURL(例: file:///foo
)が正しくパースされず、Path
フィールドに余分なスラッシュが含まれてしまう問題を解決します。これにより、URLのパースと文字列化の挙動がよりRFCに準拠したものになります。
コミット
- Author: Andrew Gerrand adg@golang.org
- Date: Wed Jan 23 11:37:06 2013 +1100
- Commit Message:
net/url: generate correct Path when hostname empty Parse("file:///foo") previously returned a URL with Scheme "file" and Path "///foo". Now it returns a URL with Path "/foo", such that &URL{Scheme: "file", Path: "/foo"}.String() == "file:///foo" This means that parsing and stringifying the URL "file:/foo" returns "file:///foo", technically a regression but one that only affects a corner case. Fixes #4189. R=bradfitz, rsc CC=golang-dev https://golang.org/cl/7135051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cdd6ae128894abbaf3fef0401cdef319f3ec1d3d
元コミット内容
net/url: generate correct Path when hostname empty
Parse("file:///foo") previously returned a URL with Scheme "file"
and Path "///foo". Now it returns a URL with Path "/foo",
such that
&URL{Scheme: "file", Path: "/foo"}.String() == "file:///foo"
This means that parsing and stringifying the URL "file:/foo"
returns "file:///foo", technically a regression but one that only
affects a corner case.
Fixes #4189.
R=bradfitz, rsc
CC=golang-dev
https://golang.org/cl/7135051
変更の背景
この変更は、Go言語の net/url
パッケージが file
スキームのURLを正しく処理できないというバグ(Issue #4189)を修正するために行われました。具体的には、file:///foo
のような形式のURLをパースした際に、URL
構造体の Path
フィールドが ///foo
となってしまい、期待される /foo
とは異なる値が設定される問題がありました。
RFC 3986(URI Generic Syntax)では、file
スキームのURIは、ホスト名が省略された場合(つまり、ローカルファイルを参照する場合)、パスの前に3つのスラッシュ (///
) が続く形式が一般的です。しかし、net/url
パッケージの既存の実装では、この3つのスラッシュをパスの一部として誤って解釈していました。
この誤った解釈は、URLのパースと文字列化の間の不整合を引き起こし、特に file
スキームを使用するアプリケーションで問題となる可能性がありました。このコミットは、この不整合を解消し、file
スキームのURLがRFCの仕様に沿って正しく扱われるようにすることを目的としています。
コミットメッセージには、「file:/foo
をパースして文字列化すると file:///foo
になる」という「技術的にはリグレッションだが、ごく稀なケースにしか影響しない」という記述があります。これは、file:
の後にスラッシュが1つしかない形式は、RFCでは非推奨または未定義の挙動とされており、通常は file:///
の形式が使われるため、実用上の影響は小さいと判断されたことを示唆しています。
前提知識の解説
URL (Uniform Resource Locator)
URLは、インターネット上のリソースの位置を示すための文字列です。一般的なURLの構造は以下のようになります。
scheme://[user:password@]host:port/path?query#fragment
- Scheme (スキーム): リソースにアクセスするためのプロトコル(例:
http
,https
,ftp
,file
)。 - Authority (オーソリティ): ホスト名、ポート番号、ユーザー情報などを含む部分。
- User Information (ユーザー情報): ユーザー名とパスワード(オプション)。
- Host (ホスト): リソースが配置されているサーバーのドメイン名またはIPアドレス。
- Port (ポート): サーバーのポート番号(オプション)。
- Path (パス): サーバー上のリソースの場所を示す階層的な情報。
- Query (クエリ): リソースに渡される追加のパラメータ(例:
?key=value
)。 - Fragment (フラグメント): リソース内の特定の部分を指す識別子(例:
#section1
)。
file
スキーム
file
スキームは、ローカルファイルシステム上のリソースを参照するために使用されます。その特徴は以下の通りです。
- ホスト名: ローカルファイルを参照する場合、ホスト名は省略されるか、
localhost
が使用されます。ホスト名が省略される場合、パスの前に3つのスラッシュ (///
) が続くのが一般的です。- 例:
file:///path/to/file.txt
(ホスト名なし) - 例:
file://localhost/path/to/file.txt
(ホスト名あり)
- 例:
- パス: ファイルシステム上の絶対パスが指定されます。
RFC 3986のセクション 3.3 "Path" およびセクション 3.2 "Authority" によると、URIのオーソリティコンポーネントはスキームの後に //
が続くことで示されます。file
スキームの場合、ホスト名が省略される(つまり、ローカルファイルを参照する)と、//
の後にホスト名が続かず、すぐにパスが始まります。このとき、パスの最初のセグメントが空であることを示すために、さらにスラッシュが追加され、結果として ///
となります。つまり、file:///path/to/file.txt
は、file
スキーム、空のホスト、そして /path/to/file.txt
というパスを持つURIとして解釈されます。
RFC 3986 (URI Generic Syntax)
RFC 3986は、URI(Uniform Resource Identifier)の一般的な構文を定義する標準です。URLはURIの一種です。このRFCは、URIの各コンポーネント(スキーム、オーソリティ、パスなど)の解釈方法や、それらの間の関係について詳細に記述しています。
特に、パスの解釈において、オーソリティ部分が存在しない場合のパスの開始方法について規定しています。file
スキームのようにオーソリティが省略される場合、パスはスキームの後に続くスラッシュの数によって解釈が変わることがあります。
Go言語の net/url
パッケージ
Go言語の net/url
パッケージは、URLのパース、構築、エンコード、デコードを行うための機能を提供します。主要な型として url.URL
構造体があり、URLの各コンポーネント(Scheme
, Opaque
, User
, Host
, RawPath
, ForceQuery
, RawQuery
, Fragment
, RawFragment
)をフィールドとして持ちます。
url.Parse(rawurl string)
: 文字列形式のURLをurl.URL
構造体にパースします。url.URL.String()
:url.URL
構造体を文字列形式のURLに変換します。
このパッケージは、Webアプリケーションやネットワークプログラミングにおいて、URLを安全かつ正確に扱うために不可欠です。
技術的詳細
このコミットの技術的詳細は、net/url
パッケージ内のURLパースロジックと文字列化ロジックの変更に集約されます。
URLパースロジックの変更 (parse
関数)
変更前の parse
関数では、スキームが存在するか、またはリクエスト経由でない場合に、残りの文字列が //
で始まり、かつ ///
で始まらない場合にオーソリティ部分をパースしていました。
// 変更前
if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
// ... オーソリティのパース ...
}
この条件 !strings.HasPrefix(rest, "///")
が問題でした。file:///foo
のようなURLでは、rest
が ///foo
となるため、この条件が false
となり、オーソリティ部分が正しくパースされず、結果として Path
に ///foo
が含まれてしまっていました。
変更後のコードでは、この !strings.HasPrefix(rest, "///")
の条件が削除され、!viaRequest
との論理積の形で strings.HasPrefix(rest, "///")
が評価されるようになりました。
// 変更後
if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
// ... オーソリティのパース ...
}
この変更により、file:///foo
のようなURLでも strings.HasPrefix(rest, "//")
が true
となるため、オーソリティのパースロジックに進むようになります。file
スキームでホスト名が空の場合、parseAuthority
関数は空のホスト名を返し、残りの ///foo
のうち最初のスラッシュがオーソリティの区切りとして消費され、結果的に Path
に /foo
が設定されるようになります。
ただし、!viaRequest && !strings.HasPrefix(rest, "///")
という条件が追加されています。これは、リクエスト経由でないURL(例: Parse("///foo")
のような相対パス)で、かつ ///
で始まる場合に、オーソリティとして解釈しないためのものです。これにより、file:///foo
のような絶対パス形式のURLと、相対パス形式の ///foo
を区別できるようになります。
URL文字列化ロジックの変更 (String
メソッド)
変更前の String
メソッドでは、Host
または User
が存在する場合にのみ //
を追加していました。
// 変更前
if u.Host != "" || u.User != nil {
result += "//"
// ...
result += u.Host
}
file:///foo
のようなURLは、Host
が空であり、User
もないため、この条件が false
となり、//
が追加されませんでした。その結果、&URL{Scheme: "file", Path: "/foo"}.String()
が file:/foo
のように出力されてしまい、期待される file:///foo
とは異なっていました。
変更後のコードでは、Scheme
が存在する場合にも //
を追加する条件が追加されました。
// 変更後
if u.Scheme != "" || u.Host != "" || u.User != nil {
result += "//"
// ...
if h := u.Host; h != "" {
result += u.Host
}
}
この変更により、file
スキームを持つURLの場合、u.Scheme != ""
が true
となるため、//
が追加されるようになります。さらに、ホスト名が空の場合でも u.Host
を追加しないように if h := u.Host; h != ""
というチェックが追加されています。これにより、file:///foo
のような形式が正しく生成されるようになります。
テストケースの変更
url_test.go
には、mailto:/webmaster@golang.org
のような「非オーソリティ」URLのテストケースがありました。変更前は、このURLの文字列化結果が空文字列 (""
) となっていましたが、変更後は mailto:///webmaster@golang.org
となるように修正されています。これは、mailto
スキームも file
スキームと同様にオーソリティを持たないが、パスの前に ///
が付く形式がより適切であるという判断に基づいています。コミットメッセージにある「unfortunate compromise(残念な妥協)」とは、この mailto
のケースで ///
が付くようになることを指していると考えられます。これは、RFC 6068 (The 'mailto' URI Scheme) では mailto:user@example.com
のようにスラッシュが一つも付かない形式が一般的であるため、///
が付くのは直感的ではないというニュアンスが含まれている可能性があります。しかし、net/url
パッケージの内部的な整合性を保つためには、このような挙動が避けられないと判断されたのでしょう。
また、file:///home/adg/rabbits
という新しいテストケースが追加され、このURLが正しくパースされ、Path
が /home/adg/rabbits
となり、文字列化しても元の形式に戻ることを確認しています。
コアとなるコードの変更箇所
src/pkg/net/url/url.go
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -386,7 +386,7 @@ func parse(rawurl string, viaRequest bool) (url *URL, err error) {
}
}
- if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
+ if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
var authority string
authority, rest = split(rest[2:], '/', false)
url.User, url.Host, err = parseAuthority(authority)
@@ -442,12 +442,14 @@ func (u *URL) String() string {
if u.Opaque != "" {
result += u.Opaque
} else {
- if u.Host != "" || u.User != nil {
+ if u.Scheme != "" || u.Host != "" || u.User != nil {
result += "//"
if u := u.User; u != nil {
result += u.String() + "@"
}
- result += u.Host
+ if h := u.Host; h != "" {
+ result += u.Host
+ }
}
result += escape(u.Path, encodePath)
}
src/pkg/net/url/url_test.go
--- a/src/pkg/net/url/url_test.go
+++ b/src/pkg/net/url/url_test.go
@@ -122,14 +122,14 @@ var urltests = []URLTest{
},
"http:%2f%2fwww.google.com/?q=go+language",
},
- // non-authority
+ // non-authority with path
{
"mailto:/webmaster@golang.org",
&URL{
Scheme: "mailto",
Path: "/webmaster@golang.org",
},
- "",
+ "mailto:///webmaster@golang.org", // unfortunate compromise
},
// non-authority
{
@@ -242,6 +242,15 @@ var urltests = []URLTest{
},
"http://www.google.com/?q=go+language#foo&bar",
},
+ {
+ "file:///home/adg/rabbits",
+ &URL{
+ Scheme: "file",
+ Host: "",
+ Path: "/home/adg/rabbits",
+ },
+ "file:///home/adg/rabbits",
+ },
}
// more useful string for debugging than fmt's struct printer
コアとなるコードの解説
src/pkg/net/url/url.go
の変更点
-
parse
関数の変更:- 変更前:
if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
- この条件は、
rest
が///
で始まる場合にオーソリティのパースをスキップしていました。これがfile:///foo
のようなURLで問題を引き起こしていました。
- この条件は、
- 変更後:
if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
!strings.HasPrefix(rest, "///")
の位置が変更され、!viaRequest
との論理積になりました。これにより、file:///foo
のようなURL(viaRequest
がfalse
で、rest
が///
で始まる)でも、strings.HasPrefix(rest, "//")
がtrue
であればオーソリティのパースに進むようになります。- この変更により、
file:///foo
はScheme: "file"
,Host: ""
,Path: "/foo"
と正しくパースされるようになります。
- 変更前:
-
String
メソッドの変更:- 変更前:
if u.Host != "" || u.User != nil { result += "//" ... result += u.Host }
Host
またはUser
が存在しない場合(file
スキームでホスト名が空の場合など)は//
が追加されませんでした。
- 変更後:
if u.Scheme != "" || u.Host != "" || u.User != nil { result += "//" ... if h := u.Host; h != "" { result += u.Host } }
u.Scheme != ""
という条件が追加されました。これにより、file
スキームのようにホスト名が空でもスキームが存在すれば//
が追加されるようになります。result += u.Host
の部分がif h := u.Host; h != "" { result += u.Host }
に変更されました。これにより、ホスト名が空の場合は余分な空文字列が追加されるのを防ぎます。- これらの変更により、
&URL{Scheme: "file", Path: "/foo"}
のようなURL
構造体がfile:///foo
と正しく文字列化されるようになります。
- 変更前:
src/pkg/net/url/url_test.go
の変更点
-
mailto
スキームのテストケースの修正:mailto:/webmaster@golang.org
のテストケースにおいて、期待される文字列化結果が""
からmailto:///webmaster@golang.org
に変更されました。これは、file
スキームと同様に、オーソリティを持たないスキームでもパスの前に///
が付く形式が内部的な整合性のため採用されたことを示しています。
-
新しい
file
スキームのテストケースの追加:file:///home/adg/rabbits
という新しいテストケースが追加されました。- このテストケースは、
file
スキームのURLが正しくパースされ(Scheme: "file"
,Host: ""
,Path: "/home/adg/rabbits"
)、そして正しく文字列化される(file:///home/adg/rabbits
)ことを検証します。これは、このコミットの主要な修正内容を直接的にテストするものです。
これらの変更により、net/url
パッケージは file
スキームのURLをより正確に、RFCの意図に沿って処理できるようになりました。
関連リンク
- Go Issue #4189: net/url: Parse("file:///foo") returns Path "///foo" · Issue #4189 · golang/go
- Go CL 7135051: net/url: generate correct Path when hostname empty - go.dev/cl/7135051
参考にした情報源リンク
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986
- RFC 6068 - The 'mailto' URI Scheme: https://datatracker.ietf.org/doc/html/rfc6068
- GoDoc - net/url package: https://pkg.go.dev/net/url