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

[インデックス 17642] ファイルの概要

このコミットは、Go言語の標準ライブラリ net/http/cgi パッケージにおける RequestFromMap 関数に修正を加えるものです。具体的には、CGI環境変数 HTTPS が設定されている場合に、生成されるHTTPリクエストのURLスキームを http:// から https:// に変更する機能を追加しています。

コミット

commit fefa4f2b8916f6ae371ba2c899f64fcc2c986000
Author: Thomas Habets <habets@google.com>
Date:   Wed Sep 18 10:48:28 2013 +1000

    net/http/cgi: use 'https://' for urls if HTTPS is set.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/13700044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fefa4f2b8916f6ae371ba2c899f64fcc2c986000

元コミット内容

net/http/cgi: use 'https://' for urls if HTTPS is set.

このコミットは、CGI環境変数 HTTPS が設定されている場合に、URLに https:// スキームを使用するように net/http/cgi パッケージを修正します。

変更の背景

CGI (Common Gateway Interface) は、Webサーバーが外部プログラム(CGIスクリプト)と通信するための標準的なプロトコルです。CGIスクリプトは、Webサーバーから環境変数を通じてリクエストに関する情報を受け取ります。その環境変数の一つに HTTPS があります。

従来の net/http/cgi パッケージでは、CGIリクエストから http.Request オブジェクトを構築する際に、ホスト名が提供されている場合、デフォルトでURLスキームを http:// と仮定していました。しかし、WebサーバーがHTTPS経由でリクエストを受け取った場合、CGIスクリプトには HTTPS 環境変数が "on" や "1" などに設定されて渡されることが一般的です。この HTTPS 環境変数は、リクエストがセキュアな接続(HTTPS)を介して行われたことを示すデファクトスタンダードな方法として広く認識されています。

このコミットの背景には、net/http/cgi がHTTPSリクエストを正しく処理し、生成される http.Request オブジェクトのURLが実際のプロトコル(HTTPS)を反映するようにする必要があったという点があります。これにより、CGIアプリケーションが自身のURLを生成したり、リダイレクトを行ったりする際に、誤ってHTTPスキームを使用してしまい、セキュリティ上の問題や不正確な動作を引き起こす可能性を排除できます。

前提知識の解説

CGI (Common Gateway Interface)

CGIは、Webサーバーが外部の実行可能ファイル(CGIスクリプト)と情報をやり取りするための標準的なインターフェースです。Webサーバーはクライアントからのリクエストを受け取ると、CGIスクリプトを起動し、リクエストに関する情報を環境変数や標準入力(POSTリクエストの場合)を通じてスクリプトに渡します。スクリプトは処理結果を標準出力に書き出し、Webサーバーがそれを受け取ってクライアントに返します。

CGI環境変数 HTTPS

CGIの仕様には、リクエストがHTTPS経由で行われたかどうかを明示的に示す標準的な環境変数は含まれていません。しかし、多くのWebサーバー(Apacheのmod_sslなど)やCGI実装では、リクエストがHTTPS接続を介して行われた場合に HTTPS という環境変数を設定するデファクトスタンダードが確立されています。この変数は通常、"on"、"ON"、"1" などの値に設定されます。CGIスクリプトはこの変数をチェックすることで、現在のリクエストがセキュアな接続で行われたかどうかを判断できます。

http.Request オブジェクトのURLとTLSフィールド

Go言語の net/http パッケージにおける http.Request 構造体は、HTTPリクエストのすべての側面をカプセル化します。

  • URL フィールド: リクエストされたURLを表す *url.URL 型のポインタです。このURLにはスキーム(http または https)、ホスト、パス、クエリなどが含まれます。
  • TLS フィールド: リクエストがTLS(Transport Layer Security、HTTPSの基盤技術)接続を介して行われた場合に、その接続の状態に関する情報を含む *tls.ConnectionState 型のポインタです。TLS接続でない場合は nil になります。このフィールドが存在するかどうかは、リクエストがHTTPSであったかどうかの重要な指標となります。

技術的詳細

このコミットの技術的な核心は、net/http/cgi/child.go 内の RequestFromMap 関数にあります。この関数は、CGI環境変数のマップから *http.Request オブジェクトを構築する役割を担っています。

変更前は、http.RequestURL フィールドを構築する際に、r.Host が設定されている場合、無条件に http:// スキームを仮定していました。

// 変更前のコードの一部
if r.Host != "" {
    // Hostname is provided, so we can reasonably construct a URL,
    // even if we have to assume 'http' for the scheme.
    rawurl := "http://" + r.Host + uriStr
    // ...
}

このコミットでは、rawurl を構築する前に、CGI環境変数 HTTPS の値をチェックするロジックが追加されました。

  1. HTTPS 環境変数のチェック:

    if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
        r.TLS = &tls.ConnectionState{HandshakeComplete: true}
    }
    

    このコードは、params マップ(CGI環境変数を表す)から HTTPS の値を取得し、それが "on"、"ON"、または "1" のいずれかであるかをチェックします。これらの値は、リクエストがHTTPS経由で行われたことを示すデファクトスタンダードな値です。 もし HTTPS 環境変数がこれらの値のいずれかであれば、http.Request オブジェクトの TLS フィールドにダミーの *tls.ConnectionState オブジェクトを設定します。HandshakeComplete: true は、TLSハンドシェイクが完了したことを示唆します。これにより、http.Request オブジェクトがHTTPSリクエストとして認識されるようになります。

  2. URLスキームの動的な決定: rawurl を構築する部分が修正され、r.TLS フィールドが nil でない(つまり、HTTPSリクエストであると判断された)場合に https:// スキームを使用し、そうでない場合に http:// スキームを使用するように変更されました。

    // 変更後のコードの一部
    if r.Host != "" {
        // Hostname is provided, so we can reasonably construct a URL.
        rawurl := r.Host + uriStr
        if r.TLS == nil {
            rawurl = "http://" + rawurl
        } else {
            rawurl = "https://" + rawurl
        }
        url, err := url.Parse(rawurl)
        // ...
    }
    

    この変更により、http.Request オブジェクトの URL フィールドが、実際のプロトコル(HTTPまたはHTTPS)を正確に反映するようになります。

テストファイル src/pkg/net/http/cgi/child_test.go にも変更が加えられ、TestRequestWithTLS という新しいテストケースが追加されました。このテストは、HTTPS 環境変数が "1" に設定された場合に、生成されるリクエストのURLが https:// スキームを持ち、TLS フィールドが nil でないことを検証します。既存の TestRequest からは HTTPS: "1" の設定が削除され、TLSがnilであることを確認するようになりました。

コアとなるコードの変更箇所

src/pkg/net/http/cgi/child.go

--- a/src/pkg/net/http/cgi/child.go
+++ b/src/pkg/net/http/cgi/child.go
@@ -100,10 +100,21 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {
 			uriStr += "?" + s
 		}
 	}
+
+	// There's apparently a de-facto standard for this.
+	// http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
+	if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
+		r.TLS = &tls.ConnectionState{HandshakeComplete: true}
+	}
+
 	if r.Host != "" {
-		// Hostname is provided, so we can reasonably construct a URL,
-		// even if we have to assume 'http' for the scheme.
-		rawurl := "http://" + r.Host + uriStr
+		// Hostname is provided, so we can reasonably construct a URL.
+		rawurl := r.Host + uriStr
+		if r.TLS == nil {
+			rawurl = "http://" + rawurl
+		} else {
+			rawurl = "https://" + rawurl
+		}
 		url, err := url.Parse(rawurl)
 		if err != nil {
 			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
@@ -120,12 +131,6 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {
 		r.URL = url
 	}
 
-	// There's apparently a de-facto standard for this.
-	// http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
-	if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
-		r.TLS = &tls.ConnectionState{HandshakeComplete: true}
-	}
-
 	// Request.RemoteAddr has its port set by Go's standard http
 	// server, so we do here too. We don't have one, though, so we
 	// use a dummy one.

src/pkg/net/http/cgi/child_test.go

--- a/src/pkg/net/http/cgi/child_test.go
+++ b/src/pkg/net/http/cgi/child_test.go
@@ -21,7 +21,6 @@ func TestRequest(t *testing.T) {
 		"REQUEST_URI":     "/path?a=b",
 		"CONTENT_LENGTH":  "123",
 		"CONTENT_TYPE":    "text/xml",
-		"HTTPS":           "1",
 		"REMOTE_ADDR":     "5.6.7.8",
 	}
 	req, err := RequestFromMap(env)
@@ -58,14 +57,37 @@ func TestRequest(t *testing.T) {
 	if req.Trailer == nil {
 		t.Errorf("unexpected nil Trailer")
 	}
-	if req.TLS == nil {
-		t.Errorf("expected non-nil TLS")
+	if req.TLS != nil {
+		t.Errorf("expected nil TLS")
 	}
 	if e, g := "5.6.7.8:0", req.RemoteAddr; e != g {
 		t.Errorf("RemoteAddr: got %q; want %q", g, e)
 	}
 }
 
+func TestRequestWithTLS(t *testing.T) {
+	env := map[string]string{
+		"SERVER_PROTOCOL": "HTTP/1.1",
+		"REQUEST_METHOD":  "GET",
+		"HTTP_HOST":       "example.com",
+		"HTTP_REFERER":    "elsewhere",
+		"REQUEST_URI":     "/path?a=b",
+		"CONTENT_TYPE":    "text/xml",
+		"HTTPS":           "1",
+		"REMOTE_ADDR":     "5.6.7.8",
+	}
+	req, err := RequestFromMap(env)
+	if err != nil {
+		t.Fatalf("RequestFromMap: %v", err)
+	}
+	if g, e := req.URL.String(), "https://example.com/path?a=b"; e != g {
+		t.Errorf("expected URL %q; got %q", e, g)
+	}
+	if req.TLS == nil {
+		t.Errorf("expected non-nil TLS")
+	}
+}
+
 func TestRequestWithoutHost(t *testing.T) {
 	env := map[string]string{
 		"SERVER_PROTOCOL": "HTTP/1.1",

コアとなるコードの解説

src/pkg/net/http/cgi/child.go の変更点

  1. HTTPS 環境変数による r.TLS の設定: RequestFromMap 関数の冒頭近くに、HTTPS 環境変数をチェックし、その値が "on", "ON", "1" のいずれかであれば、http.Request オブジェクトの TLS フィールドに &tls.ConnectionState{HandshakeComplete: true} を設定するロジックが追加されました。 これは、CGIリクエストがHTTPS経由で行われたことを http.Request オブジェクトに正しく反映させるための重要なステップです。TLS フィールドが nil でないことは、GoのHTTPサーバーがHTTPSリクエストを処理する際に内部的に設定する状態と一致させ、後続の処理でHTTPSであることを認識できるようにします。

  2. URLスキームの動的な決定: rawurl を構築する部分が変更されました。以前は無条件に http:// を使用していましたが、変更後は r.TLSnil でない(つまりHTTPSリクエストである)場合に https:// を使用し、そうでない場合に http:// を使用するように条件分岐が追加されました。 これにより、CGIアプリケーションが自身のURLを構築する際に、実際のプロトコル(HTTPまたはHTTPS)に基づいた正しいスキームが使用されるようになります。これは、リダイレクトや絶対URLの生成において特に重要です。

  3. r.TLS 設定ロジックの移動: 以前は r.URL のパース後に r.TLS を設定するロジックがありましたが、これが rawurl の構築前に移動されました。この順序の変更は理にかなっています。なぜなら、r.TLS の状態が rawurl のスキーム決定に影響を与えるため、r.TLSrawurl が構築される前に設定されている必要があるからです。

src/pkg/net/http/cgi/child_test.go の変更点

  1. TestRequestWithTLS の追加: この新しいテスト関数は、HTTPS 環境変数を "1" に設定したCGIリクエストをシミュレートします。そして、RequestFromMap が返す http.Request オブジェクトの URLhttps:// スキームを持ち、TLS フィールドが nil でないことを検証します。これにより、HTTPS環境変数の処理が正しく行われていることが保証されます。

  2. TestRequest から HTTPS 環境変数の削除: 既存の TestRequest から HTTPS: "1" の設定が削除されました。これは、TestRequest が通常のHTTPリクエストのケースをテストし、TLS フィールドが nil であることを期待するようになったためです。これにより、テストの意図が明確になり、各テストが特定のシナリオを独立して検証するようになります。

これらの変更により、net/http/cgi パッケージは、CGI環境変数 HTTPS を適切に解釈し、それに基づいて http.Request オブジェクトのURLスキームとTLS状態を正確に設定できるようになりました。

関連リンク

参考にした情報源リンク