[インデックス 13118] ファイルの概要
コミット
commit f7277dac57c77fd596ef077beb1ab92ae8b20dce
Author: Alexey Borzenkov <snaury@gmail.com>
Date: Tue May 22 12:44:24 2012 -0400
net/url: better parsing of urls with @ symbol in authority
Fixes #3439
R=r, rsc, dsymonds, n13m3y3r
CC=golang-dev
https://golang.org/cl/6206090
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f7277dac57c77fd596ef077beb1ab92ae8b20dce
元コミット内容
net/url: authority部分に@記号を含むURLのより良いパース処理
Issue #3439を修正。
変更の背景
このコミットは、Go言語の標準ライブラリnet/urlパッケージにおけるURLパースの不具合を修正するために行われました。具体的には、URLのauthority(認証情報とホスト名を含む部分)に@記号が含まれる場合に、パースが正しく行われない問題がありました。
元の実装では、authority文字列内の最初の@記号をユーザー情報とホスト名の区切りとして扱っていました。しかし、ユーザー名やパスワード自体にエスケープされていない@記号が含まれる場合、このロジックでは誤った区切り位置を特定し、結果としてホスト名がユーザー情報の一部として誤って解釈される可能性がありました。
例えば、http://user@gmail.com:password@hostname.com:80/pathのようなURLをパースする際に、user@gmail.comがユーザー名、passwordがパスワード、hostname.com:80がホスト名として正しく認識されるべきですが、元の実装では最初の@で分割されるため、userがユーザー名、gmail.com:password@hostname.com:80がホスト名として誤って解釈されていました。
この問題は、GitHubのGoリポジトリのIssue #3439「net/url: Parse does not parse username and password correctly」として報告されており、このコミットはその報告された問題を解決することを目的としています。
前提知識の解説
URLの構造とauthority部分
URL (Uniform Resource Locator) は、インターネット上のリソースの位置を示すための標準的な方法です。一般的なURLの構造は以下のようになります。
scheme://[userinfo@]host[:port][/path][?query][#fragment]
この中で、今回のコミットが関連するのはauthority部分、すなわち[userinfo@]host[:port]です。
scheme: プロトコル(例:http,https,ftp)。userinfo: ユーザー名とパスワードを含むオプションの部分。通常はusername:passwordの形式で、ホスト名の前に@記号で区切られて配置されます。host: サーバーのドメイン名またはIPアドレス。port: サーバーのポート番号(オプション)。path: サーバー上のリソースのパス。query: クエリパラメータ(オプション)。fragment: フラグメント識別子(オプション)。
@記号の役割とエスケープ
authority部分における@記号は、userinfoとhostを区切るための特別なデリミタです。RFC 3986 (Uniform Resource Identifier (URI): Generic Syntax) では、@は「サブデリミタ」として定義されており、URIの特定のコンポーネント内で特別な意味を持つ文字の一つです。
もしユーザー名やパスワード自体に@記号を含めたい場合は、その@記号はパーセントエンコーディング(URLエンコーディング)によって%40のようにエスケープされる必要があります。これにより、URLパーサーはエスケープされた@とデリミタとしての@を区別できます。
しかし、現実にはエスケープされていない@がユーザー名やパスワードに含まれるURLも存在し、堅牢なURLパーサーはこれらを適切に処理する必要があります。このコミットは、まさにその「エスケープされていない@がユーザー名やパスワードに含まれるケース」に対応するためのものです。
strings.Indexとstrings.LastIndex
Go言語のstringsパッケージには、文字列内で部分文字列のインデックスを検索するための関数がいくつかあります。
strings.Index(s, substr string) int: 文字列s内でsubstrが最初に現れるインデックスを返します。見つからない場合は-1を返します。strings.LastIndex(s, substr string) int: 文字列s内でsubstrが最後に現れるインデックスを返します。見つからない場合は-1を返します。
URLのauthority部分において、userinfoとhostを区切る@は、常にuserinfoの末尾に現れるため、複数の@が存在する場合(ユーザー名やパスワードに@が含まれる場合)、最後の@がデリミタであると判断するのが正しいパースロジックとなります。
技術的詳細
このコミットの核心は、net/urlパッケージ内のparseAuthority関数のロジック変更にあります。この関数は、URLのauthority文字列を受け取り、そこからユーザー情報(Userinfo)とホスト名(host)を抽出する役割を担っています。
変更前のparseAuthority関数は、strings.Index(authority, "@")を使用して、authority文字列内で@記号が最初に現れる位置を検索していました。この最初の@が見つかると、その位置で文字列を分割し、前半をユーザー情報、後半をホスト名として扱っていました。
しかし、このアプローチには問題がありました。例えば、http://j@ne:password@google.comのようなURLを考えてみましょう。
authority文字列はj@ne:password@google.comです。- 最初の
@はjの直後にあります。 - 元のロジックでは、
jをユーザー名、ne:password@google.comをホスト名として誤って解釈してしまいます。
正しい解釈は、j@neがユーザー名、passwordがパスワード、google.comがホスト名です。この場合、ユーザー名j@neとパスワードpasswordの後に続く@が、ユーザー情報とホスト名を区切るデリミタとなります。つまり、最後の@が真のデリミタなのです。
このコミットでは、この問題を解決するために、strings.Indexをstrings.LastIndexに置き換えることで、authority文字列内の最後の@記号をユーザー情報とホスト名の区切りとして使用するように変更しました。これにより、ユーザー名やパスワードにエスケープされていない@記号が含まれていても、URLのauthority部分が正しくパースされるようになります。
また、この変更の正当性を保証するために、src/pkg/net/url/url_test.goに新しいテストケースが追加されました。これらのテストケースは、ユーザー名やパスワードに@記号が含まれる様々なシナリオを網羅しており、修正されたパースロジックが期待通りに機能することを確認しています。
コアとなるコードの変更箇所
変更は主にsrc/pkg/net/url/url.goとsrc/pkg/net/url/url_test.goの2つのファイルで行われています。
src/pkg/net/url/url.go
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -401,11 +401,12 @@ Error:
}
func parseAuthority(authority string) (user *Userinfo, host string, err error) {
- if strings.Index(authority, "@") < 0 {
+ i := strings.LastIndex(authority, "@")
+ if i < 0 {
host = authority
return
}
- userinfo, host := split(authority, '@', true)
+ userinfo, host := authority[:i], authority[i+1:]
if strings.Index(userinfo, ":") < 0 {
if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil {
return
src/pkg/net/url/url_test.go
--- a/src/pkg/net/url/url_test.go
+++ b/src/pkg/net/url/url_test.go
@@ -188,6 +188,37 @@ var urltests = []URLTest{
},
"http://user:password@google.com",
},
+ // unescaped @ in username should not confuse host
+ {
+ "http://j@ne:password@google.com",
+ &URL{
+ Scheme: "http",
+ User: UserPassword("j@ne", "password"),
+ Host: "google.com",
+ },
+ "http://j%40ne:password@google.com",
+ },
+ // unescaped @ in password should not confuse host
+ {
+ "http://jane:p@ssword@google.com",
+ &URL{
+ Scheme: "http",
+ User: UserPassword("jane", "p@ssword"),
+ Host: "google.com",
+ },
+ "http://jane:p%40ssword@google.com",
+ },
+ {
+ "http://j@ne:password@google.com/p@th?q=@go",
+ &URL{
+ Scheme: "http",
+ User: UserPassword("j@ne", "password"),
+ Host: "google.com",
+ Path: "/p@th",
+ RawQuery: "q=@go",
+ },
+ "http://j%40ne:password@google.com/p@th?q=@go",
+ },
{
"http://www.google.com/?q=go+language#foo",
&URL{
コアとなるコードの解説
src/pkg/net/url/url.goの変更
parseAuthority関数内の変更は以下の通りです。
i := strings.LastIndex(authority, "@")- 以前は
strings.Indexが使われていましたが、これをstrings.LastIndexに変更しました。これにより、authority文字列内で@記号が最後に現れる位置がiに格納されます。この最後の@が、ユーザー情報とホスト名を区切る正しいデリミタであると見なされます。
- 以前は
if i < 0 { ... }@記号が見つからない場合(iが-1の場合)は、authority全体がホスト名であると判断し、そのままhost = authorityとして関数を終了します。これは変更前と同じロジックです。
userinfo, host := authority[:i], authority[i+1:]@記号が見つかった場合、authority文字列をiの位置で分割します。authority[:i]は、文字列の先頭からiの直前までの部分(つまり、最後の@より前の部分)をuserinfoとして抽出します。authority[i+1:]は、最後の@の直後から文字列の末尾までの部分をhostとして抽出します。- 以前は
split関数が使われていましたが、strings.LastIndexでインデックスを取得したため、スライス操作で直接分割できるようになりました。これにより、より明確で効率的な分割が可能になります。
この変更により、ユーザー名やパスワードに@記号が含まれていても、最後の@が正しくデリミタとして機能し、URLのauthority部分が意図通りにパースされるようになります。
src/pkg/net/url/url_test.goの変更
テストファイルには、新しい3つのテストケースが追加されました。これらは、@記号がユーザー名やパスワードに含まれる場合のパースの正確性を検証します。
"http://j@ne:password@google.com"- ユーザー名に
@が含まれるケース。期待される結果は、ユーザー名がj@ne、パスワードがpassword、ホストがgoogle.comです。
- ユーザー名に
"http://jane:p@ssword@google.com"- パスワードに
@が含まれるケース。期待される結果は、ユーザー名がjane、パスワードがp@ssword、ホストがgoogle.comです。
- パスワードに
"http://j@ne:password@google.com/p@th?q=@go"- ユーザー名に
@が含まれ、さらにパスやクエリパラメータにも@が含まれる複合的なケース。これにより、authority部分のパースが他のURLコンポーネントに影響を与えないことも確認されます。
- ユーザー名に
これらのテストケースは、修正されたparseAuthority関数が、@記号の扱いに起因する以前のバグを確実に修正し、RFCの仕様に沿った堅牢なURLパースを実現していることを保証します。また、UserPasswordヘルパー関数を使用して、ユーザー名とパスワードが正しく抽出されていることを検証しています。
関連リンク
- Go CL (Code Review): https://golang.org/cl/6206090
- GitHub Issue #3439: https://github.com/golang/go/issues/3439
参考にした情報源リンク
- Web search results for "Go issue 3439": https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF2P4DlK99uhPd5VeSGPpWoBm8vsGGcmvkabAYYSuJH0pbijBD6nBjwjuOF-UJOkrTIH1FFVapFDlnwQI8nVtoHJ3WsVYwZWn9nLEzBOHPgBoLsqDa92MyDM5_LOjD0ogvj6A==
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986 (一般的なURL構造と
@記号の役割について) - Go strings package documentation: https://pkg.go.dev/strings (
strings.Indexとstrings.LastIndexについて)