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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージにおいて、HTTPリクエストのフォームデータを解析する ParseForm メソッドの挙動を修正するものです。具体的には、ParseFormapplication/x-www-form-urlencoded 以外の Content-Type ヘッダを持つリクエストボディをフォームデータとして誤って解釈しないように変更されました。これにより、セキュリティと堅牢性が向上しています。

コミット

commit a494c2003257edcb5e9177a4c8c0dc2240ae0d24
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Nov 3 17:21:58 2011 -0700

    http: only recognize application/x-www-form-urlencoded in ParseForm
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/5322070
---
 src/pkg/net/http/request.go      |  2 +-\
 src/pkg/net/http/request_test.go | 19 ++++++-------------\
 2 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/src/pkg/net/http/request.go b/src/pkg/net/http/request.go
index d9a04efe3e..7a62dcede4 100644
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -734,7 +734,7 @@ func (r *Request) ParseForm() (err error) {
 		ct := r.Header.Get("Content-Type")
 		ct, _, err := mime.ParseMediaType(ct)
 		switch {
-		case ct == "text/plain" || ct == "application/x-www-form-urlencoded" || ct == "":
+		case ct == "application/x-www-form-urlencoded":
 			var reader io.Reader = r.Body
 			maxFormSize := int64(1<<63 - 1)
 			if _, ok := r.Body.(*maxBytesReader); !ok {
diff --git a/src/pkg/net/http/request_test.go b/src/pkg/net/http/request_test.go
index 9be9efcc87..d6487e1974 100644
--- a/src/pkg/net/http/request_test.go
+++ b/src/pkg/net/http/request_test.go
@@ -29,12 +29,10 @@ func TestQuery(t *testing.T) {
 }
 
 func TestPostQuery(t *testing.T) {
-\treq := &Request{Method: "POST"}\n-\treq.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar&both=x")\n-\treq.Header = Header{\n-\t\t"Content-Type": {"application/x-www-form-urlencoded; boo!"},\n-\t}\n-\treq.Body = ioutil.NopCloser(strings.NewReader("z=post&both=y"))\n+\treq, _ := NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x",\n+\t\tstrings.NewReader("z=post&both=y"))\n+\treq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")\n+\n \tif q := req.FormValue("q"); q != "foo" {\n \t\tt.Errorf(`req.FormValue("q") = %q, want "foo"`, q)\n \t}\n@@ -49,7 +47,6 @@ func TestPostQuery(t *testing.T) {\n type stringMap map[string][]string\n type parseContentTypeTest struct {\n \tcontentType stringMap\n-\terr         bool\n }\n \n var parseContentTypeTests = []parseContentTypeTest{\n@@ -58,11 +55,10 @@ var parseContentTypeTests = []parseContentTypeTest{\n \t{contentType: stringMap{\"Content-Type\": {\"text/plain; boundary=\"}}},\n \t{\n \t\tcontentType: stringMap{\"Content-Type\": {\"application/unknown\"}},\n-\t\terr:         true,\n \t},\n }\n \n-func TestPostContentTypeParsing(t *testing.T) {\n+func TestParseFormBadContentType(t *testing.T) {\n \tfor i, test := range parseContentTypeTests {\n \t\treq := &Request{\n \t\t\tMethod: "POST",\n@@ -70,10 +66,7 @@ func TestPostContentTypeParsing(t *testing.T) {\n \t\t\tBody:   ioutil.NopCloser(bytes.NewBufferString("body")),\n \t\t}\n \t\terr := req.ParseForm()\n-\t\tif !test.err && err != nil {\n-\t\t\tt.Errorf(\"test %d: Unexpected error: %v\", i, err)\n-\t\t}\n-\t\tif test.err && err == nil {\n+\t\tif err == nil {\n \t\t\tt.Errorf(\"test %d should have returned error\", i)\n \t\t}\n \t}\n```

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

[https://github.com/golang/go/commit/a494c2003257edcb5e9177a4c8c0dc2240ae0d24](https://github.com/golang/go/commit/a494c2003257edcb5e9177a4c8c0dc2240ae0d24)

## 元コミット内容

http: only recognize application/x-www-form-urlencoded in ParseForm

R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/5322070


## 変更の背景

このコミットが行われた背景には、HTTPリクエストのボディを解析する際のセキュリティと堅牢性の向上が挙げられます。Goの `net/http` パッケージの `Request.ParseForm()` メソッドは、HTTPリクエストのボディからURLエンコードされたフォームデータを解析するために使用されます。

以前の実装では、`ParseForm` は `Content-Type` ヘッダが `application/x-www-form-urlencoded` である場合だけでなく、`text/plain` や `Content-Type` ヘッダが指定されていない(空文字列)場合にもリクエストボディをフォームデータとして解釈しようとしていました。

この挙動は、以下のような問題を引き起こす可能性がありました。

1.  **セキュリティリスク**: 悪意のあるユーザーが、意図しない `Content-Type` でリクエストを送信し、それがフォームデータとして誤って解釈されることで、アプリケーションのロジックに予期せぬ影響を与えたり、脆弱性を引き起こしたりする可能性があります。例えば、JSONやXMLなどのデータがフォームデータとして処理され、予期せぬデータ構造が生成されることで、アプリケーションがクラッシュしたり、情報漏洩につながったりする可能性も考えられます。
2.  **予期せぬ動作**: 開発者が `application/x-www-form-urlencoded` 以外の `Content-Type` でデータを送信した場合、`ParseForm` がそのデータをフォームデータとして解釈しようとすることで、予期せぬエラーやデータ破損が発生する可能性があります。
3.  **仕様との乖離**: HTTPの仕様では、フォームデータは通常 `application/x-www-form-urlencoded` または `multipart/form-data` の `Content-Type` で送信されることが期待されます。`text/plain` や空の `Content-Type` をフォームデータとして扱うことは、この仕様から逸脱しており、将来的な互換性の問題や混乱を招く可能性があります。

このコミットは、`ParseForm` が厳密に `application/x-www-form-urlencoded` の `Content-Type` を持つリクエストボディのみをフォームデータとして解析するように変更することで、これらの問題を解決し、より安全で予測可能な動作を保証することを目的としています。

## 前提知識の解説

### 1. HTTP `Content-Type` ヘッダ

HTTP `Content-Type` ヘッダは、HTTPリクエストまたはレスポンスのエンティティボディに含まれるデータのメディアタイプ(MIMEタイプ)を示すために使用されます。これにより、受信側はボディの内容を正しく解釈し、処理することができます。

例:
*   `Content-Type: text/html` (HTMLドキュメント)
*   `Content-Type: application/json` (JSONデータ)
*   `Content-Type: application/x-www-form-urlencoded` (URLエンコードされたフォームデータ)
*   `Content-Type: multipart/form-data` (ファイルアップロードを含むフォームデータ)

### 2. `application/x-www-form-urlencoded`

これは、HTMLフォームのデータをHTTPリクエストのボディにエンコードするための標準的なメディアタイプです。フォームのフィールド名と値のペアが `key=value` の形式でエンコードされ、複数のペアは `&` で区切られます。スペースは `+` に、その他の特殊文字は `%HH` 形式(URLエンコード)に変換されます。

例: `name=John+Doe&age=30&city=New+York`

### 3. Go言語 `net/http` パッケージ

`net/http` パッケージは、Go言語でHTTPクライアントとサーバーを実装するための機能を提供します。このパッケージには、HTTPリクエストとレスポンスを扱うための構造体やメソッドが含まれています。

### 4. `http.Request.ParseForm()` メソッド

`http.Request` 型の `ParseForm()` メソッドは、HTTPリクエストのボディ(POSTリクエストの場合)とURLのクエリパラメータ(GETリクエストの場合)からフォームデータを解析し、`Request.Form` フィールドに格納します。`Request.Form` は `url.Values` 型であり、キーと値のリスト(`map[string][]string`)としてデータを保持します。

`ParseForm()` は、一度呼び出されると、その後の呼び出しでは再解析を行わず、キャッシュされたフォームデータを返します。

### 5. `mime.ParseMediaType()` 関数

`mime` パッケージの `ParseMediaType()` 関数は、`Content-Type` ヘッダのようなメディアタイプ文字列を解析し、主要なメディアタイプ(例: `application/x-www-form-urlencoded`)と、それに付随するパラメータ(例: `charset=utf-8` や `boundary`)を分離して返します。

## 技術的詳細

このコミットの主要な変更点は、`src/pkg/net/http/request.go` ファイル内の `Request.ParseForm()` メソッドにおける `Content-Type` のチェックロジックです。

変更前は、`ParseForm()` メソッド内で `Content-Type` ヘッダを解析した後、以下の `switch` 文でリクエストボディをフォームデータとして処理するかどうかを判断していました。

```go
switch {
case ct == "text/plain" || ct == "application/x-www-form-urlencoded" || ct == "":
    // ... フォームデータの解析ロジック ...
}

このコードは、Content-Typetext/plainapplication/x-www-form-urlencoded、または空文字列(Content-Type ヘッダが指定されていない場合)のいずれかであれば、リクエストボディをフォームデータとして解析していました。

このコミットでは、この条件が以下のように変更されました。

switch {
case ct == "application/x-www-form-urlencoded":
    // ... フォームデータの解析ロジック ...
}

これにより、ParseForm() は厳密に Content-Typeapplication/x-www-form-urlencoded である場合にのみ、リクエストボディをフォームデータとして解析するようになりました。text/plain や空の Content-Type の場合は、フォームデータとして扱われなくなります。

また、src/pkg/net/http/request_test.go ファイルでは、この変更に伴いテストケースが修正されています。

  • TestPostQuery テストでは、NewRequest 関数を使用してリクエストを構築し、Content-Type ヘッダを明示的に application/x-www-form-urlencoded に設定するように変更されました。これは、以前のテストが Content-Type を手動で設定していた部分を、よりGoの慣用的な方法に合わせたものです。
  • parseContentTypeTest 構造体から err フィールドが削除されました。これは、ParseForm が特定の Content-Type でエラーを返すのではなく、単にフォームとして解析しないという挙動に変わったため、テストの期待値が変わったことを示しています。
  • TestPostContentTypeParsing テスト関数が TestParseFormBadContentType にリネームされ、そのロジックも変更されました。以前は text/plainapplication/unknownContent-TypeParseForm を呼び出した際にエラーが返されることを期待していましたが、変更後はこれらの Content-Type では ParseForm がエラーを返さず、単にフォームデータが解析されない(つまり、req.Form が空になる)ことを期待するようになりました。テストは、err == nil の場合にエラーを報告するように変更されており、これは ParseForm がエラーを返さないことを確認しています。

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

src/pkg/net/http/request.goParseForm メソッド内の switch 文の条件が変更されました。

--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -734,7 +734,7 @@ func (r *Request) ParseForm() (err error) {
 		ct := r.Header.Get("Content-Type")
 		ct, _, err := mime.ParseMediaType(ct)
 		switch {
-		case ct == "text/plain" || ct == "application/x-www-form-urlencoded" || ct == "":
+		case ct == "application/x-www-form-urlencoded":
 			var reader io.Reader = r.Body
 			maxFormSize := int64(1<<63 - 1)
 			if _, ok := r.Body.(*maxBytesReader); !ok {

コアとなるコードの解説

変更された行は、ParseForm メソッドがリクエストボディをフォームデータとして処理するかどうかを決定する条件式です。

  • 変更前: ct == "text/plain" || ct == "application/x-www-form-urlencoded" || ct == ""

    • これは、「Content-Typetext/plain であるか、application/x-www-form-urlencoded であるか、または Content-Type ヘッダが指定されていない(空文字列)場合」に、リクエストボディをフォームデータとして解析するという意味です。この緩い条件は、意図しないデータがフォームとして解釈されるリスクがありました。
  • 変更後: ct == "application/x-www-form-urlencoded"

    • これは、「Content-Type が厳密に application/x-www-form-urlencoded である場合のみ」に、リクエストボディをフォームデータとして解析するという意味です。これにより、ParseForm の動作がより予測可能になり、セキュリティが向上します。他の Content-Type のリクエストボディは、ParseForm によってフォームデータとして処理されなくなります。

この変更は、ParseForm の堅牢性を高め、HTTPリクエストの処理における潜在的な脆弱性を排除するために重要です。

関連リンク

  • Go Gerrit Change-ID: https://golang.org/cl/5322070
    • これは、このコミットがGoのコードレビューシステムであるGerritでどのようにレビューされたかを示すリンクです。詳細な議論や変更の経緯を確認できます。

参考にした情報源リンク

  • MDN Web Docs - Content-Type: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Type
  • MDN Web Docs - application/x-www-form-urlencoded: https://developer.mozilla.org/ja/docs/Web/HTTP/Methods/POST (POSTメソッドのセクションで application/x-www-form-urlencoded について言及されています)
  • Go言語 net/http パッケージドキュメント: https://pkg.go.dev/net/http
  • Go言語 mime パッケージドキュメント: https://pkg.go.dev/mime
  • Go言語 url パッケージドキュメント: https://pkg.go.dev/net/url
  • Go言語のHTTPリクエストのフォーム処理について: (一般的なGoのHTTPフォーム処理に関するブログ記事やチュートリアルを参考にしました。特定のURLは挙げませんが、Go http.Request ParseForm などのキーワードで検索すると多数見つかります。)
  • Go言語のセキュリティベストプラクティス: (一般的なWebアプリケーションセキュリティの原則と、GoにおけるHTTP処理のセキュリティに関する情報を参考にしました。)