[インデックス 10240] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおいて、HTTPリクエストのフォームデータを解析する ParseForm
メソッドの挙動を修正するものです。具体的には、ParseForm
が application/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-Type
が text/plain
、application/x-www-form-urlencoded
、または空文字列(Content-Type
ヘッダが指定されていない場合)のいずれかであれば、リクエストボディをフォームデータとして解析していました。
このコミットでは、この条件が以下のように変更されました。
switch {
case ct == "application/x-www-form-urlencoded":
// ... フォームデータの解析ロジック ...
}
これにより、ParseForm()
は厳密に Content-Type
が application/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/plain
やapplication/unknown
のContent-Type
でParseForm
を呼び出した際にエラーが返されることを期待していましたが、変更後はこれらのContent-Type
ではParseForm
がエラーを返さず、単にフォームデータが解析されない(つまり、req.Form
が空になる)ことを期待するようになりました。テストは、err == nil
の場合にエラーを報告するように変更されており、これはParseForm
がエラーを返さないことを確認しています。
コアとなるコードの変更箇所
src/pkg/net/http/request.go
の ParseForm
メソッド内の 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-Type
がtext/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処理のセキュリティに関する情報を参考にしました。)