[インデックス 17814] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/url
パッケージにおける、相対URLのシリアライズに関するリグレッション(退行バグ)を修正するものです。具体的には、URLを文字列に変換する際に、ホストが存在しない相対パスに対して誤ってスラッシュが追加されてしまう問題を解決します。
コミット
commit f41b43a02431baab166d06b8b4c41467d9cc88e4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Oct 17 16:06:40 2013 -0700
net/url: fix regression when serializing relative URLs
Only add a slash to path if it's a separator between
a host and path.
Fixes #6609
R=golang-dev, dsymonds, r
CC=golang-dev
https://golang.org/cl/14815043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f41b43a02431baab166d06b8b4c41467d9cc88e4
元コミット内容
net/url: fix regression when serializing relative URLs
(net/url: 相対URLのシリアライズ時のリグレッションを修正)
Only add a slash to path if it's a separator between a host and path.
(パスにスラッシュを追加するのは、それがホストとパスの間の区切り文字である場合のみとする。)
Fixes #6609
(Issue #6609 を修正)
変更の背景
このコミットは、net/url
パッケージにおけるURLの文字列化(シリアライズ)処理に存在したバグを修正するために導入されました。以前のバージョンでは、URL
構造体を文字列に変換する際に、相対パス(例: a/b/c
)であるにもかかわらず、パスの先頭に余分なスラッシュが追加されてしまうという問題が発生していました。これは、URLの構造とパスの解釈に関する誤解、または以前の変更による意図しない副作用(リグレッション)が原因と考えられます。
具体的には、URL.String()
メソッドが、パスが空でなく、かつパスの先頭がスラッシュでない場合に無条件にスラッシュを追加するロジックになっていました。しかし、これはホストが存在しない相対パスの場合には不適切であり、a/b/c
のようなパスが /a/b/c
のように変換されてしまうという問題を引き起こしていました。このような誤ったシリアライズは、URLの解釈に影響を与え、アプリケーションの動作に予期せぬ問題を引き起こす可能性がありました。
この問題は Fixes #6609
として報告されており、ユーザーからのフィードバックに基づいて修正が行われたことが示唆されます。
前提知識の解説
URL (Uniform Resource Locator)
URLは、インターネット上のリソースの位置を示すための標準的な方法です。一般的なURLの構造は以下のようになります。
scheme://[user:password@]host[:port][/path][?query][#fragment]
- scheme (スキーム): リソースにアクセスするためのプロトコル(例:
http
,https
,ftp
,mailto
)。 - host (ホスト): リソースが配置されているサーバーのドメイン名またはIPアドレス。
- port (ポート): サーバー上の特定のアプリケーションを識別するための番号(通常はスキームによってデフォルト値が定められているため省略可能)。
- path (パス): サーバー上のリソースの場所を示す階層的な情報。
- query (クエリ): リソースに渡される追加のパラメータ(例:
?key=value&another=param
)。 - fragment (フラグメント): リソース内の特定の部分を指す識別子(例:
#section1
)。
相対URLと絶対URL
- 絶対URL: スキーム、ホスト、パスなど、リソースの完全な位置情報を含むURLです。どこからでも一意にリソースを特定できます。例:
https://www.example.com/path/to/resource.html
- 相対URL: 現在のベースURLからの相対的な位置でリソースを示すURLです。スキームやホストを含まず、パスの一部のみで構成されることがあります。例:
path/to/resource.html
(現在のディレクトリからの相対パス)、/path/to/resource.html
(現在のホストのルートからの絶対パス)。
net/url
パッケージ
Go言語の net/url
パッケージは、URLの解析、構築、および操作のための機能を提供します。url.URL
構造体は、URLの各コンポーネント(スキーム、ホスト、パスなど)をフィールドとして持ち、これらのコンポーネントを操作するためのメソッドを提供します。
url.Parse()
: 文字列からurl.URL
構造体を解析します。url.URL.String()
:url.URL
構造体を文字列にシリアライズ(変換)します。このメソッドが今回の修正の対象です。
URLのシリアライズ
URLのシリアライズとは、url.URL
構造体のようなプログラム内のURL表現を、HTTPリクエストやHTMLリンクなどで使用できる標準的な文字列形式に変換するプロセスです。このプロセスは、URLの各コンポーネントを正しい順序と形式で結合し、必要に応じてエスケープ処理を行います。
技術的詳細
このコミットの技術的な核心は、net/url
パッケージの URL.String()
メソッドにおける条件分岐の変更です。
変更前のコードは以下のようになっていました。
// src/pkg/net/url/url.go (変更前)
if u.Path != "" && u.Path[0] != '/' {
buf.WriteByte('/')
}
このロジックは、「パスが空でなく、かつパスの先頭がスラッシュでない場合」に、パスの前にスラッシュを追加するというものでした。これは、ホストが存在するURL(例: http://example.com/path
)において、パスがスラッシュで始まっていない場合に自動的にスラッシュを追加して http://example.com//path
のような二重スラッシュを防ぐ、あるいは http://example.compath
のようなホストとパスが連結してしまうのを防ぐための意図があったと考えられます。
しかし、この条件は相対URL(例: a/b/c
)の場合にも適用されてしまい、a/b/c
が /a/b/c
に誤って変換される原因となっていました。相対URLの場合、パスの先頭にスラッシュがないのは自然なことであり、そこにスラッシュを追加することはURLのセマンティクスを破壊します。
この問題を解決するため、コミットでは条件に u.Host != ""
という新しい条件が追加されました。
// src/pkg/net/url/url.go (変更後)
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
この変更により、スラッシュが追加されるのは「パスが空でなく、かつパスの先頭がスラッシュでなく、かつホストが存在する場合」に限定されるようになりました。これにより、ホストが存在しない相対URLに対しては余分なスラッシュが追加されなくなり、正しいシリアライズが行われるようになります。
この修正は、URLの構造と、特にホストとパスの間の区切り文字としてのスラッシュの役割を正確に理解していることを示しています。相対URLの場合、パスはホストとは独立して解釈されるため、ホストが存在しない場合にはパスの前にスラッシュを追加する必要がない、という原則に基づいています。
コアとなるコードの変更箇所
変更は src/pkg/net/url/url.go
ファイルの URL.String()
メソッド内の一箇所と、それに対応するテストファイル src/pkg/net/url/url_test.go
に追加されたテストケースです。
src/pkg/net/url/url.go
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -459,7 +459,7 @@ func (u *URL) String() string {
buf.WriteString(h)
}
}
- if u.Path != "" && u.Path[0] != '/' {
+ if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
buf.WriteString(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
@@ -260,6 +260,14 @@ var urltests = []URLTest{
},
"mailto:webmaster@golang.org",
},
+ // Relative path
+ {
+ "a/b/c",
+ &URL{
+ Path: "a/b/c",
+ },
+ "a/b/c",
+ },
}
// more useful string for debugging than fmt's struct printer
コアとなるコードの解説
src/pkg/net/url/url.go
の変更
URL.String()
メソッドは、URL
構造体の内容を標準的なURL文字列形式に変換する役割を担っています。このメソッドは、スキーム、ユーザー情報、ホスト、ポート、パス、クエリ、フラグメントといったURLの各コンポーネントを順に結合して文字列を構築します。
変更された行は、ホストの処理が完了し、パスの書き込みを開始する直前の部分にあります。
-
変更前:
if u.Path != "" && u.Path[0] != '/'
- この条件は、「パスが空でなく、かつパスの最初の文字がスラッシュでない場合」に真となります。
- この場合、
buf.WriteByte('/')
によって、パスの前にスラッシュが追加されます。 - 問題は、
u.Host
が空(つまり相対URL)の場合でもこの条件が真になり、a/b/c
のようなパスが/a/b/c
に変換されてしまうことでした。
-
変更後:
if u.Path != "" && u.Path[0] != '/' && u.Host != ""
- 新しく
&& u.Host != ""
という条件が追加されました。 - これにより、スラッシュが追加されるのは、「パスが空でなく、かつパスの最初の文字がスラッシュでなく、かつホストが存在する場合」に限定されます。
- ホストが存在しない相対URLの場合、
u.Host != ""
が偽となるため、このif
ブロックは実行されず、余分なスラッシュが追加されることはありません。 - 結果として、
a/b/c
はa/b/c
のまま正しくシリアライズされるようになります。
- 新しく
この修正は、URLの構造におけるホストとパスの間の関係性を正確に反映したものであり、URLのセマンティクスを維持するために重要です。
src/pkg/net/url/url_test.go
の変更
テストファイル url_test.go
には、urltests
という URLTest
構造体のスライスが定義されており、様々なURLの解析とシリアライズのテストケースが含まれています。
このコミットでは、以下の新しいテストケースが追加されました。
// Relative path
{
"a/b/c",
&URL{
Path: "a/b/c",
},
"a/b/c",
},
このテストケースは、相対パス a/b/c
が正しくシリアライズされることを検証するために追加されました。
- 最初の
"a/b/c"
は、テスト対象の入力文字列です。 &URL{Path: "a/b/c"}
は、入力文字列を解析した結果として期待されるURL
構造体です。- 最後の
"a/b/c"
は、URL
構造体を文字列にシリアライズした結果として期待される文字列です。
このテストケースが追加されたことで、修正が正しく機能し、相対パスが誤ってスラッシュ付きでシリアライズされないことが保証されます。これは、リグレッションを防ぎ、将来の変更がこの動作を壊さないようにするための重要なステップです。
関連リンク
- Go言語の
net/url
パッケージのドキュメント: https://pkg.go.dev/net/url - Go言語のIssueトラッカー (GitHub): https://github.com/golang/go/issues
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/ (コミットメッセージに記載されている
https://golang.org/cl/14815043
は、このGerritのリンク形式です。) - RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986 (URLの標準的な定義と構文に関する詳細)