[インデックス 14565] ファイルの概要
このコミットは、Go言語のnet/http
パッケージにおけるhttp_proxy
環境変数のパースに関するバグ修正です。具体的には、http_proxy
の値がプロトコル(http://
など)を含まない形式で指定された場合に、url.Parse
がホスト名をスキームとして誤認識し、プロキシ設定が正しく適用されない問題を解決します。
コミット
commit a034fc9855b307ab5e9e5da04602823d6414f512
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Dec 5 19:08:42 2012 -0800
net/http: fix bug parsing http_proxy lacking a protocol
Per the curl man page, the http_proxy configuration can be
of the form:
[protocol://]<host>[:port]
And we had a test that <ip>:<port> worked, but if
the host began with a letter, url.Parse parsed the hostname
as the scheme instead, confusing ProxyFromEnvironment.
R=golang-dev
CC=golang-dev
https://golang.org/cl/6875060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a034fc9855b307ab5e9e5da04602823d6414f512
元コミット内容
net/http
: プロトコルを持たないhttp_proxy
のパースバグを修正。
curl
のmanページによると、http_proxy
の設定は[protocol://]<host>[:port]
の形式を取り得ます。
既存のテストでは<ip>:<port>
形式が機能することを確認していましたが、ホスト名が文字で始まる場合、url.Parse
がホスト名をスキームとしてパースしてしまい、ProxyFromEnvironment
を混乱させていました。
変更の背景
この変更の背景には、Goのnet/http
パッケージが、http_proxy
環境変数の解釈において、curl
などの一般的なツールが許容する形式の一部を正しく扱えていなかったという問題があります。
http_proxy
環境変数は、HTTPリクエストをプロキシサーバー経由で送信するために広く利用されています。curl
のmanページに記載されているように、この変数の値は[protocol://]<host>[:port]
という形式を取ることができます。ここで重要なのは、protocol://
の部分がオプションであるという点です。例えば、cache.corp.example.com:1234
のように、プロトコルを明示せずにホスト名とポート番号のみを指定する形式も有効とされています。
しかし、Goのnet/http
パッケージ内のProxyFromEnvironment
関数が内部で利用しているurl.Parse
関数は、このプロトコルが省略された形式を正しく解釈できないケースがありました。具体的には、127.0.0.1:8080
のようなIPアドレスで始まるホスト名の場合は問題なくパースできていましたが、cache.corp.example.com:1234
のようにホスト名がアルファベットで始まる場合、url.Parse
はcache.corp.example.com
全体をURLのスキーム(プロトコル部分)として誤って認識してしまっていました。
これにより、ProxyFromEnvironment
関数は、プロキシURLのスキームが空であると判断し、プロキシ設定を適用できないというバグが発生していました。このバグは、ユーザーが環境変数でプロキシを設定しているにもかかわらず、Goアプリケーションがプロキシを使用しない、または誤ったプロキシ設定で動作するという問題を引き起こしていました。
このコミットは、このurl.Parse
の誤解釈を修正し、http_proxy
環境変数がプロトコルを省略した形式でも正しく機能するようにすることを目的としています。
前提知識の解説
http_proxy
環境変数
http_proxy
は、多くのUnix系システムやWindowsで利用される環境変数で、HTTPリクエストを送信する際に使用するプロキシサーバーのアドレスを指定するために使われます。通常、http_proxy=http://proxy.example.com:8080
のように設定されますが、本コミットの背景にあるように、proxy.example.com:8080
のようにプロトコルを省略した形式も一部のツール(curl
など)では許容されます。
Go言語のnet/url
パッケージとurl.Parse
関数
Go言語の標準ライブラリであるnet/url
パッケージは、URLのパース、生成、クエリパラメータの操作など、URLに関する機能を提供します。
url.Parse(rawurl string)
関数は、与えられた文字列をURLとしてパースし、*url.URL
構造体を返します。この構造体には、スキーム(http
, https
など)、ホスト、パス、クエリなどのURLの各要素が格納されます。
url.Parse
の挙動の特性として、入力文字列に://
が含まれない場合、最初の:
までの部分をスキームとして解釈しようとします。例えば、http://example.com
はhttp
をスキームとして認識します。しかし、example.com:8080
のような文字列の場合、example.com
をスキームとして誤って解釈してしまうことがあります。これは、URLのスキームがアルファベットで始まるという一般的なルールに合致するためです。
Go言語のnet/http
パッケージとProxyFromEnvironment
関数
Go言語のnet/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。
http.ProxyFromEnvironment(req *Request)
関数は、環境変数(HTTP_PROXY
, HTTPS_PROXY
, NO_PROXY
など)に基づいて、指定されたリクエストに対するプロキシURLを決定します。この関数は、内部で環境変数の値を読み取り、それをurl.Parse
でパースしてプロキシURLを構築します。
本コミットの修正前は、ProxyFromEnvironment
関数内でurl.Parse
の結果をチェックする際に、proxyURL.Scheme == ""
という条件を使用していました。これは、プロキシURLにスキームが全く含まれていない場合に、http://
を補完して再パースするというロジックでした。しかし、前述のurl.Parse
の誤解釈により、cache.corp.example.com:1234
のようなケースではcache.corp.example.com
がスキームとして認識されてしまい、proxyURL.Scheme
が空ではなくなるため、http://
の補完ロジックが発動せず、結果としてプロキシ設定が正しく適用されない問題が発生していました。
技術的詳細
このバグは、net/http/transport.go
内のProxyFromEnvironment
関数におけるurl.Parse
の戻り値の解釈方法に起因していました。
元のコードでは、http_proxy
環境変数の値(例: cache.corp.example.com:1234
)をurl.Parse
でパースした後、以下の条件でプロキシURLのスキームをチェックしていました。
if err != nil || proxyURL.Scheme == "" {
if u, err := url.Parse("http://" + proxy); err == nil {
proxyURL = u
err = nil
}
}
このロジックは、「パースエラーが発生した」または「パースされたURLのスキームが空である」場合に、http://
をプレフィックスとして追加して再パースするというものでした。
問題は、cache.corp.example.com:1234
のような文字列をurl.Parse
が処理する際に発生しました。url.Parse
は、://
がない場合、最初のコロンまでの部分をスキームとして解釈しようとします。この場合、cache.corp.example.com
がスキームとして認識されてしまいます。これは、cache.corp.example.com
が有効なスキーム名(アルファベットで始まる)の形式に合致するためです。
その結果、proxyURL.Scheme
はcache.corp.example.com
となり、空ではありません。したがって、上記のif
条件のproxyURL.Scheme == ""
の部分がfalse
となり、http://
を補完して再パースするロジックが実行されませんでした。これにより、proxyURL
はcache.corp.example.com:1234
という不正なスキームを持つURLとして扱われ、プロキシとして機能しなくなっていました。
修正は、このスキームのチェック方法を変更することによって行われました。新しい条件は以下の通りです。
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
// ... (http://を補完するロジック)
}
この変更により、url.Parse
が返したスキームがhttp
で始まらない場合(例えば、cache.corp.example.com
のようにホスト名がスキームとして誤認識された場合)、http://
を補完して再パースするロジックが実行されるようになります。これにより、cache.corp.example.com:1234
のような形式のhttp_proxy
も正しくhttp://cache.corp.example.com:1234
として解釈され、プロキシとして機能するようになります。
また、この修正を検証するために、transport_test.go
に新しいテストケースが追加されました。これにより、プロトコルが省略されたホスト名形式のhttp_proxy
が正しくパースされることが保証されます。
コアとなるコードの変更箇所
diff --git a/src/pkg/net/http/transport.go b/src/pkg/net/http/transport.go
index 48f7ac0e53..068c50ff0c 100644
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -90,7 +90,7 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
return nil, nil
}
proxyURL, err := url.Parse(proxy)
- if err != nil || proxyURL.Scheme == "" {
+ if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
if u, err := url.Parse("http://" + proxy); err == nil {
proxyURL = u
err = nil
diff --git a/src/pkg/net/http/transport_test.go b/src/pkg/net/http/transport_test.go
index e49f14fa58..0e6cf85281 100644
--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -1068,6 +1068,9 @@ var proxyFromEnvTests = []struct {
wanterr error
}{
{"127.0.0.1:8080", "http://127.0.0.1:8080", nil},
+ {"cache.corp.example.com:1234", "http://cache.corp.example.com:1234", nil},
+ {"cache.corp.example.com", "http://cache.corp.example.com", nil},
+ {"https://cache.corp.example.com", "https://cache.corp.example.com", nil},
{"http://127.0.0.1:8080", "http://127.0.0.1:8080", nil},
{"https://127.0.0.1:8080", "https://127.0.0.1:8080", nil},
{"", "<nil>", nil},
コアとなるコードの解説
src/pkg/net/http/transport.go
変更の中心は、ProxyFromEnvironment
関数内のプロキシURLのスキームチェックロジックです。
-
変更前:
if err != nil || proxyURL.Scheme == "" {
この条件は、「
url.Parse
でエラーが発生した」または「パースされたURLのスキームが空文字列である」場合に、http://
を補完して再パースする処理を実行していました。しかし、前述の通り、cache.corp.example.com:1234
のようなケースではproxyURL.Scheme
がcache.corp.example.com
となり、空ではないため、この条件がfalse
となり、補完処理がスキップされていました。 -
変更後:
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
この新しい条件は、「
url.Parse
でエラーが発生した」または「パースされたURLのスキームがhttp
で始まらない」場合に、http://
を補完して再パースする処理を実行します。strings.HasPrefix(proxyURL.Scheme, "http")
は、proxyURL.Scheme
がhttp
またはhttps
で始まるかどうかをチェックします。 もしproxyURL.Scheme
がcache.corp.example.com
のようにhttp
で始まらない場合、!strings.HasPrefix(...)
がtrue
となり、http://
を補完するロジックが実行されます。これにより、cache.corp.example.com:1234
は正しくhttp://cache.corp.example.com:1234
として解釈されるようになります。
src/pkg/net/http/transport_test.go
このファイルには、ProxyFromEnvironment
関数の動作を検証するためのテストケースが定義されています。今回の修正に合わせて、以下の3つの新しいテストケースがproxyFromEnvTests
スライスに追加されました。
{"cache.corp.example.com:1234", "http://cache.corp.example.com:1234", nil},
- これは、プロトコルが省略され、ホスト名がアルファベットで始まる形式のプロキシ設定が、正しく
http://
が補完されてパースされることをテストします。
- これは、プロトコルが省略され、ホスト名がアルファベットで始まる形式のプロキシ設定が、正しく
{"cache.corp.example.com", "http://cache.corp.example.com", nil},
- これは、プロトコルとポート番号が省略され、ホスト名のみが指定された形式のプロキシ設定が、正しく
http://
が補完されてパースされることをテストします。
- これは、プロトコルとポート番号が省略され、ホスト名のみが指定された形式のプロキシ設定が、正しく
{"https://cache.corp.example.com", "https://cache.corp.example.com", nil},
- これは、明示的に
https://
プロトコルが指定されたプロキシ設定が、正しくパースされることを確認するためのテストです。これは既存の動作の確認ですが、新しいロジックが既存の正しい動作を壊さないことを保証します。
- これは、明示的に
これらのテストケースの追加により、http_proxy
環境変数のパースロジックが、curl
のmanページで定義されているような多様な形式を正しく扱えるようになったことが検証されます。
関連リンク
- Go CL (Change List): https://golang.org/cl/6875060
参考にした情報源リンク
curl
man page (http_proxy formatに関する情報):