[インデックス 19112] ファイルの概要
このコミットは、Go言語のnet/http
パッケージにおいて、ParseMultipartForm
関数がContent-Type
がmultipart/form-data
ではないリクエストに対してErrNotMultipart
エラーを正しく返すように修正し、関連するテストを追加・修正するものです。
コミット
commit 8d1b63abff2c948741e94c92d2a9a60069a71ddb
Author: Matthew Cottingham <mattcottingham@gmail.com>
Date: Thu Apr 10 22:50:04 2014 -0700
net/http: Return ErrNotMultipart from ParseMultipartForm if content-type isn't multipart/form-data.
Add test for multipart form requests with an invalid content-type to ensure
ErrNotMultipart is returned.
Change ParseMultipartForm to return ErrNotMultipart when it is returned by multipartReader.
Modify test for empty multipart request handling to use POST so that the body is checked.
Fixes #6334.
This is the first changeset working on multipart request handling. Further changesets
could add more tests and clean up the TODO.
LGTM=bradfitz
R=golang-codereviews, gobot, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/44040043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8d1b63abff2c948741e94c92d2a9a60069a71ddb
元コミット内容
net/http
: Content-Type
がmultipart/form-data
ではない場合、ParseMultipartForm
からErrNotMultipart
を返すようにする。
不正なContent-Type
を持つマルチパートフォームリクエストに対してErrNotMultipart
が返されることを確認するためのテストを追加。
ParseMultipartForm
がmultipartReader
からErrNotMultipart
が返されたときに、それを返すように変更。
空のマルチパートリクエスト処理のテストをPOST
を使用するように修正し、ボディがチェックされるようにする。
Fixes #6334
.
これはマルチパートリクエスト処理に関する最初の変更セットである。さらなる変更セットで、より多くのテストを追加し、TODOをクリーンアップできる可能性がある。
変更の背景
このコミットは、Goのnet/http
パッケージにおけるマルチパートフォームデータの処理に関するバグ(Fixes #6334
)を修正するために行われました。具体的には、http.Request
のParseMultipartForm
メソッドが、リクエストのContent-Type
ヘッダがmultipart/form-data
ではない場合に、期待されるhttp.ErrNotMultipart
エラーを返さず、代わりにnil
(エラーなし)を返してしまうという問題がありました。
この挙動は、アプリケーションがリクエストのContent-Type
を適切に検証せずにParseMultipartForm
を呼び出した場合、誤った形式のリクエストをマルチパートフォームとして処理しようとする可能性があり、予期せぬ動作やセキュリティ上の問題を引き起こす可能性がありました。例えば、通常のapplication/x-www-form-urlencoded
形式のリクエストや、全く異なるContent-Type
を持つリクエストが、誤ってマルチパートとして解釈され、その後の処理でエラーが発生したり、データが正しくパースされなかったりする原因となります。
この修正により、ParseMultipartForm
は、基盤となるmultipartReader()
がErrNotMultipart
を返した場合に、そのエラーを呼び出し元に透過的に伝播するようになり、より堅牢で予測可能なエラーハンドリングが実現されます。また、この変更を検証するために、不正なContent-Type
を持つリクエストに対するテストケースが追加されました。
前提知識の解説
1. net/http
パッケージ
Go言語の標準ライブラリであるnet/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。Webアプリケーションの構築において中心的な役割を果たし、HTTPリクエストの処理、レスポンスの生成、ルーティングなどを担当します。
2. HTTP Content-Type
ヘッダ
HTTPリクエストやレスポンスのヘッダの一つで、メッセージボディのメディアタイプ(MIMEタイプ)を示します。サーバーはこれを見て、ボディの内容をどのように解釈すべきかを判断します。
application/x-www-form-urlencoded
: HTMLフォームのデフォルトのエンコーディングタイプで、キーと値のペアが&
で区切られ、=
で結合されます。特殊文字はURLエンコードされます。multipart/form-data
: ファイルアップロードを含むフォームデータを送信する際に使用されるContent-Type
です。各フォームフィールドやファイルは、boundary
と呼ばれる区切り文字列によって分離された「パート」として送信されます。これは、単一のリクエストで複数の種類のデータを送信できるため、特にファイルアップロードで不可欠です。
3. http.Request
構造体
net/http
パッケージにおけるHTTPリクエストを表す構造体です。リクエストメソッド(GET, POSTなど)、URL、ヘッダ、ボディなどの情報を含みます。
4. ParseMultipartForm
メソッド
http.Request
構造体のメソッドの一つで、リクエストボディがmultipart/form-data
形式である場合に、その内容をパースしてRequest.MultipartForm
フィールドに格納します。maxMemory
引数は、メモリに保持する最大バイト数を指定し、これを超える部分は一時ファイルに書き込まれます。
5. MultipartReader
メソッド
http.Request
構造体のメソッドで、リクエストのContent-Type
がmultipart/form-data
である場合に、そのボディを読み取るためのmultipart.Reader
を返します。Content-Type
がmultipart/form-data
ではない場合、http.ErrNotMultipart
エラーを返します。
6. http.ErrNotMultipart
エラー
net/http
パッケージで定義されているエラーで、リクエストがマルチパート形式ではないことを示します。MultipartReader
関数が、リクエストのContent-Type
ヘッダがmultipart/form-data
ではない場合にこのエラーを返します。
技術的詳細
このコミットの主要な変更点は、src/pkg/net/http/request.go
内の(*Request).ParseMultipartForm
メソッドの挙動修正と、src/pkg/net/http/request_test.go
におけるテストの追加・修正です。
ParseMultipartForm
の修正
以前のParseMultipartForm
の実装では、内部でr.multipartReader()
を呼び出した際にErrNotMultipart
が返された場合、そのエラーを無視してnil
(エラーなし)を返していました。
// 修正前
mr, err := r.multipartReader()
if err == ErrNotMultipart {
return nil // ここでErrNotMultipartをnilとして扱っていた
} else if err != nil {
return err
}
この挙動は、Content-Type
がmultipart/form-data
ではないリクエストに対してParseMultipartForm
が呼び出された場合でも、エラーが報告されないという問題を引き起こしていました。これにより、呼び出し元はリクエストがマルチパート形式ではないことを検知できず、誤った処理を続行する可能性がありました。
今回の修正では、このif err == ErrNotMultipart { return nil }
のブロックが削除されました。
// 修正後
mr, err := r.multipartReader()
if err != nil { // ErrNotMultipartを含む全てのエラーを返す
return err
}
これにより、multipartReader()
がErrNotMultipart
を返した場合、ParseMultipartForm
もそのErrNotMultipart
を呼び出し元にそのまま返すようになります。これは、APIのセマンティクスとしてより正確であり、呼び出し元がリクエストのContent-Type
に基づいて適切なエラーハンドリングを行えるようになります。
テストの追加と修正
変更されたParseMultipartForm
の挙動を検証するために、src/pkg/net/http/request_test.go
に新しいテストケースが追加され、既存のテストが修正されました。
-
TestParseMultipartForm
の追加: この新しいテスト関数は、ParseMultipartForm
が不正なContent-Type
を持つリクエストに対してErrNotMultipart
を正しく返すことを確認します。Content-Type
がtext/plain
のリクエストを作成し、ParseMultipartForm
を呼び出します。- 返されたエラーが
ErrNotMultipart
であることをアサートします。これにより、修正が意図通りに機能していることが保証されます。
-
TestMultipartReader
の修正: 既存のTestMultipartReader
関数も、Content-Type
がtext/plain
の場合にMultipartReader()
がErrNotMultipart
を返すことを確認する部分が修正されました。以前はt.Errorf
でエラーを報告していましたが、より明確なテストロジックになりました。 -
TestEmptyMultipartRequest
の修正: このテストは、空のマルチパートリクエストの処理を検証するためのものでした。以前はGET
メソッドを使用していましたが、GET
リクエストは通常ボディを持たないため、マルチパートフォームのテストとしては不適切でした。 このコミットでは、テストリクエストのメソッドがPOST
に変更されました。POST
リクエストはボディを持つことができ、マルチパートフォームデータがボディに含まれるため、より現実的なテストシナリオとなります。これにより、ParseMultipartForm
がボディの内容を適切にチェックするようになります。 -
TestFormValueCallsParseMultipartForm
とTestFormFileCallsParseMultipartForm
の追加: これらのテストは、Request.FormValue
やRequest.FormFile
といったメソッドが、内部的にParseMultipartForm
を呼び出すことを確認します。これは、これらの高レベルなメソッドを使用する際に、自動的にマルチパートフォームのパースが行われることを保証するためのものです。
これらのテストの追加と修正により、net/http
パッケージのマルチパートフォーム処理の堅牢性が向上し、開発者がより信頼性の高いコードを書けるようになります。
コアとなるコードの変更箇所
src/pkg/net/http/request.go
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -789,9 +789,7 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error {
}
mr, err := r.multipartReader()
- if err == ErrNotMultipart {
- return nil
- } else if err != nil {
+ if err != nil {
return err
}
src/pkg/net/http/request_test.go
--- a/src/pkg/net/http/request_test.go
+++ b/src/pkg/net/http/request_test.go
@@ -154,7 +154,25 @@ func TestMultipartReader(t *testing.T) {
req.Header = Header{"Content-Type": {"text/plain"}}
multipart, err = req.MultipartReader()
if multipart != nil {
- t.Errorf("unexpected multipart for text/plain")
+ t.Error("unexpected multipart for text/plain")
+ }
+}
+
+func TestParseMultipartForm(t *testing.T) {
+ req := &Request{
+ Method: "POST",
+ Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
+ Body: ioutil.NopCloser(new(bytes.Buffer)),
+ }
+ err := req.ParseMultipartForm(25)
+ if err == nil {
+ t.Error("expected multipart EOF, got nil")
+ }
+
+ req.Header = Header{"Content-Type": {"text/plain"}}
+ err = req.ParseMultipartForm(25)
+ if err != ErrNotMultipart {
+ t.Error("expected ErrNotMultipart for text/plain")
}
}
@@ -220,16 +238,38 @@ func TestMultipartRequestAuto(t *testing.T) {
validateTestMultipartContents(t, req, true)
}
-func TestEmptyMultipartRequest(t *testing.T) {
- // Test that FormValue and FormFile automatically invoke
- // ParseMultipartForm and return the right values.
- req, err := NewRequest("GET", "/", nil)
- if err != nil {
- t.Errorf("NewRequest err = %q", err)
- }
- testMissingFile(t, req)
-}
-
+// Test that FormValue invokes ParseMultipartForm.
+func TestFormValueCallsParseMultipartForm(t *testing.T) {
+ req, _ := NewRequest("POST", "http://www.google.com/", strings.NewReader("z=post"))
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
+ if req.Form != nil {
+ t.Fatal("Unexpected request Form, want nil")
+ }
+ req.FormValue("z")
+ if req.Form == nil {
+ t.Fatal("ParseMultipartForm not called by FormValue")
+ }
+}
+
+// Test that FormFile invokes ParseMultipartForm.
+func TestFormFileCallsParseMultipartForm(t *testing.T) {
+ req := newTestMultipartRequest(t)
+ if req.Form != nil {
+ t.Fatal("Unexpected request Form, want nil")
+ }
+ req.FormFile("")
+ if req.Form == nil {
+ t.Fatal("ParseMultipartForm not called by FormFile")
+ }
+}
+
+func TestMissingFileMultipartRequest(t *testing.T) {
+ // Test that FormFile returns an error if
+ // the named file is missing.
+ req := newTestMultipartRequest(t)
+ testMissingFile(t, req)
+}
+
// Test that ParseMultipartForm errors if called
// after MultipartReader on the same request.
func TestParseMultipartFormOrder(t *testing.T) {
コアとなるコードの解説
src/pkg/net/http/request.go
の変更
ParseMultipartForm
メソッド内の変更は非常にシンプルですが、その影響は大きいです。
mr, err := r.multipartReader()
- if err == ErrNotMultipart {
- return nil
- } else if err != nil {
+ if err != nil {
return err
}
この変更は、multipartReader()
が返すエラーの処理方法を変更しています。
- 修正前:
multipartReader()
がErrNotMultipart
を返した場合、ParseMultipartForm
はnil
(エラーなし)を返していました。これは、リクエストがマルチパート形式ではないにもかかわらず、エラーを報告しないという誤った挙動でした。 - 修正後:
if err != nil
というシンプルな条件になり、multipartReader()
が返す全てのエラー(ErrNotMultipart
を含む)が、そのままParseMultipartForm
の呼び出し元に伝播されるようになりました。これにより、ParseMultipartForm
は、リクエストがマルチパート形式ではない場合にErrNotMultipart
を正しく返すようになります。
src/pkg/net/http/request_test.go
の変更
テストファイルでは、主に以下の点が変更されました。
-
TestParseMultipartForm
の追加: この新しいテストは、ParseMultipartForm
がContent-Type: text/plain
のような非マルチパートのリクエストに対してErrNotMultipart
を返すことを明示的に検証します。これは、request.go
の変更が意図通りに機能していることを確認するための重要なテストです。 -
TestMultipartReader
の修正: 既存のテスト内のt.Errorf
がt.Error
に変更されています。これは機能的な変更ではなく、テストフレームワークの慣用的な使用法に合わせた微調整です。 -
TestEmptyMultipartRequest
の削除と関連テストの追加: 元のTestEmptyMultipartRequest
はGET
リクエストを使用していましたが、マルチパートフォームは通常POST
リクエストのボディで送信されるため、テストの関連性が低かったです。このテストは削除され、代わりにTestMissingFileMultipartRequest
が追加されました。 さらに、TestFormValueCallsParseMultipartForm
とTestFormFileCallsParseMultipartForm
が追加されました。これらのテストは、FormValue
やFormFile
といった高レベルなAPIが、内部でParseMultipartForm
を適切に呼び出していることを確認します。これにより、開発者がこれらのAPIを使用する際に、マルチパートフォームのパースが自動的に行われることが保証されます。
これらの変更により、net/http
パッケージのマルチパートフォーム処理の正確性と堅牢性が向上し、開発者がより信頼性の高いWebアプリケーションを構築できるようになります。
関連リンク
- Go CL 44040043: https://golang.org/cl/44040043
- Go Issue 6334: https://code.google.com/p/go/issues/detail?id=6334 (当時のGoプロジェクトのIssueトラッカー)
参考にした情報源リンク
特になし。提供されたコミット情報とGo言語のnet/http
パッケージに関する一般的な知識に基づいて解説を生成しました。