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

[インデックス 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/ca/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 構造体を文字列にシリアライズした結果として期待される文字列です。

このテストケースが追加されたことで、修正が正しく機能し、相対パスが誤ってスラッシュ付きでシリアライズされないことが保証されます。これは、リグレッションを防ぎ、将来の変更がこの動作を壊さないようにするための重要なステップです。

関連リンク

参考にした情報源リンク