[インデックス 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
について)