[インデックス 11209] ファイルの概要
このコミットは、Go言語の標準ライブラリ mime
パッケージにおける FormatMediaType
関数のシグネチャ変更と、それに伴う内部ロジックの修正を扱っています。具体的には、FormatMediaType
関数がメディアタイプ(例: text/plain
)を構成する際に、これまで type
と subtype
を別々の引数として受け取っていたものを、full type
(例: text/plain
のような完全な文字列) を単一の引数として受け取るように変更し、一貫性を向上させています。これにより、RFC 2045およびRFC 2616に準拠したメディアタイプのフォーマットがより直感的かつ堅牢になります。
コミット
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Tue Jan 17 11:57:42 2012 -0800
- Commit Message:
mime: make FormatMediaType take full type for consistency Fixes #2405 R=rsc CC=golang-dev https://golang.org/cl/5539048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a00de45bbbd44bf592654dd0811fa4bcb883b1ae
元コミット内容
commit a00de45bbbd44bf592654dd0811fa4bcb883b1ae
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Jan 17 11:57:42 2012 -0800
mime: make FormatMediaType take full type for consistency
Fixes #2405
R=rsc
CC=golang-dev
https://golang.org/cl/5539048
---
src/pkg/mime/mediatype.go | 17 +++++++++++------
src/pkg/mime/mediatype_test.go | 21 +++++++++++++++++++++
src/pkg/mime/type.go | 15 ++++-----------
3 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/src/pkg/mime/mediatype.go b/src/pkg/mime/mediatype.go
index 2bf79788c7..41844c25f2 100644
--- a/src/pkg/mime/mediatype.go
+++ b/src/pkg/mime/mediatype.go
@@ -12,17 +12,22 @@ import (
"unicode"
)
-// FormatMediaType serializes type t, subtype sub and the paramaters
-// param as a media type conform RFC 2045 and RFC 2616.
-// The type, subtype, and parameter names are written in lower-case.
+// FormatMediaType serializes mediatype t and the parameters
+// param as a media type conforming to RFC 2045 and RFC 2616.
+// The type and parameter names are written in lower-case.
// When any of the arguments result in a standard violation then
// FormatMediaType returns the empty string.
-func FormatMediaType(t, sub string, param map[string]string) string {
- if !(IsToken(t) && IsToken(sub)) {
+func FormatMediaType(t string, param map[string]string) string {
+ slash := strings.Index(t, "/")
+ if slash == -1 {
+ return ""
+ }
+ major, sub := t[:slash], t[slash+1:]
+ if !IsToken(major) || !IsToken(sub) {
return ""
}
var b bytes.Buffer
- b.WriteString(strings.ToLower(t))
+ b.WriteString(strings.ToLower(major))
b.WriteByte('/')
b.WriteString(strings.ToLower(sub))
diff --git a/src/pkg/mime/mediatype_test.go b/src/pkg/mime/mediatype_test.go
index c06f167ddc..64ab291341 100644
--- a/src/pkg/mime/mediatype_test.go
+++ b/src/pkg/mime/mediatype_test.go
@@ -253,3 +253,24 @@ func TestParseMediaTypeBogus(t *testing.T) {
t.Errorf("expected invalid media parameter; got error %q", err)
}
}
+
+type formatTest struct {
+ typ string
+ params map[string]string
+ want string
+}
+
+var formatTests = []formatTest{
+ {"noslash", nil, ""},
+ {"foo/BAR", nil, "foo/bar"},
+ {"foo/BAR", map[string]string{"X": "Y"}, "foo/bar; x=Y"},
+}
+
+func TestFormatMediaType(t *testing.T) {
+ for i, tt := range formatTests {
+ got := FormatMediaType(tt.typ, tt.params)
+ if got != tt.want {
+ t.Errorf("%d. FormatMediaType(%q, %v) = %q; want %q", i, tt.typ, tt.params, got, tt.want)
+ }
+ }
+}
diff --git a/src/pkg/mime/type.go b/src/pkg/mime/type.go
index e3d968fb81..00cff263ba 100644
--- a/src/pkg/mime/type.go
+++ b/src/pkg/mime/type.go
@@ -62,21 +62,14 @@ func AddExtensionType(ext, typ string) error {
}
func setExtensionType(extension, mimeType string) error {
- full, param, err := ParseMediaType(mimeType)
+ _, param, err := ParseMediaType(mimeType)
if err != nil {
return err
}
- if split := strings.Index(full, "/"); split < 0 {
- return fmt.Errorf(`mime: malformed MIME type "%s"`, mimeType)
- } else {
- main := full[:split]
- sub := full[split+1:]
- if main == "text" && param["charset"] == "" {
- param["charset"] = "utf-8"
- }
- mimeType = FormatMediaType(main, sub, param)
+ if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" {
+ param["charset"] = "utf-8"
+ mimeType = FormatMediaType(mimeType, param)
}
-
mimeLock.Lock()
mimeTypes[extension] = mimeType
mimeLock.Unlock()
変更の背景
このコミットの背景には、Go言語の mime
パッケージにおける FormatMediaType
関数のAPI設計の一貫性の問題がありました。以前の FormatMediaType
関数は、メディアタイプを構成する際に、type
(例: text
) と subtype
(例: plain
) を別々の文字列引数として受け取っていました。しかし、メディアタイプは通常 type/subtype
の形式で一つの文字列として扱われることが多く、ParseMediaType
のような他の関連関数は完全なメディアタイプ文字列を引数として受け取っていました。
この不一致は、開発者が mime
パッケージを使用する際に混乱を招く可能性があり、特に ParseMediaType
で解析した結果を FormatMediaType
に渡すようなシナリオで、type
と subtype
を手動で分割する必要があるなど、不必要な手間が発生していました。
コミットメッセージにある "Fixes #2405" は、この変更がIssue 2405を解決することを示しています。Issue 2405の内容は直接確認できませんが、おそらく FormatMediaType
のAPIの一貫性に関する問題や使いにくさが報告されていたと推測されます。この変更は、APIの統一性を図り、開発者体験を向上させることを目的としています。
前提知識の解説
MIMEタイプ (Media Type)
MIMEタイプ(Multipurpose Internet Mail Extensions Type)は、インターネット上でやり取りされるデータの種類を識別するための標準的な方法です。HTTP通信、電子メール、ファイルシステムなどで広く利用されています。MIMEタイプは通常、type/subtype
の形式で表現されます。
- Type (Major Type): データの一般的なカテゴリを示します。例:
text
,image
,audio
,video
,application
。 - Subtype (Minor Type): 特定のデータ形式を示します。例:
plain
(text/plain),jpeg
(image/jpeg),json
(application/json)。 - Parameters: オプションで、メディアタイプに関する追加情報を提供します。例:
charset=utf-8
(text/plain; charset=utf-8)。
RFC 2045 (MIME Part One: Format of Internet Message Bodies)
RFC 2045は、MIMEの基本的な構造とヘッダーフィールドの定義を定めています。特に、Content-Type
ヘッダーフィールドの構文と意味について詳細に記述されており、MIMEタイプの type/subtype
構造とパラメータの定義が含まれています。このRFCは、インターネットメッセージのボディがどのように構造化され、異なる種類のデータがどのように表現されるかを規定しています。
RFC 2616 (Hypertext Transfer Protocol -- HTTP/1.1)
RFC 2616は、HTTP/1.1プロトコルの仕様を定義しています。このRFCでは、HTTPヘッダーフィールドの一つである Content-Type
の使用方法についても言及しており、MIMEタイプがHTTPメッセージのエンティティボディのメディアタイプを示すためにどのように使われるかを規定しています。FormatMediaType
関数が「RFC 2045およびRFC 2616に準拠」とあるのは、生成されるメディアタイプ文字列がこれらの標準で定義された構文と意味論に従うことを意味します。
Go言語の mime
パッケージ
Go言語の標準ライブラリ mime
パッケージは、MIMEメディアタイプを解析および生成するための機能を提供します。主な関数には以下のようなものがあります。
ParseMediaType(v string) (mediatype string, params map[string]string, err error)
: メディアタイプ文字列を解析し、主要なメディアタイプとパラメータのマップを返します。FormatMediaType(mediatype string, params map[string]string) string
: メディアタイプとパラメータのマップからメディアタイプ文字列を生成します。AddExtensionType(ext, typ string) error
: ファイル拡張子とMIMEタイプのマッピングを追加します。TypeByExtension(ext string) string
: ファイル拡張子に対応するMIMEタイプを返します。
このコミットは、特に FormatMediaType
のAPIと内部実装に焦点を当てています。
技術的詳細
このコミットの主要な技術的変更点は、mime.FormatMediaType
関数のシグネチャ変更です。
変更前:
func FormatMediaType(t, sub string, param map[string]string) string
t
: メディアの主要タイプ (例: "text")sub
: メディアのサブタイプ (例: "plain")param
: パラメータのマップ
変更後:
func FormatMediaType(t string, param map[string]string) string
t
: 完全なメディアタイプ文字列 (例: "text/plain")param
: パラメータのマップ
この変更により、FormatMediaType
は ParseMediaType
と同様に、type/subtype
形式の完全なメディアタイプ文字列を単一の引数として受け取るようになりました。これにより、APIの一貫性が向上し、開発者はメディアタイプを扱う際に type
と subtype
を個別に管理する手間が省けます。
内部的には、新しい FormatMediaType
関数は、受け取った完全なメディアタイプ文字列 t
を /
で分割して major
(主要タイプ) と sub
(サブタイプ) を抽出し、それぞれが有効なトークンであるか (IsToken
関数でチェック) を検証します。この検証に失敗した場合や /
が存在しない場合は、空文字列を返します。その後、major
と sub
を小文字に変換して /
で結合し、パラメータを追加して最終的なメディアタイプ文字列を構築します。
また、src/pkg/mime/type.go
内の setExtensionType
関数も、この FormatMediaType
のシグネチャ変更に合わせて修正されています。以前は ParseMediaType
の結果から full
(完全なメディアタイプ文字列) を取得し、それをさらに /
で分割して main
と sub
を抽出し、FormatMediaType(main, sub, param)
を呼び出していました。変更後は、ParseMediaType
の結果から full
を取得する必要がなくなり、直接 FormatMediaType(mimeType, param)
を呼び出すことで、より簡潔なコードになっています。特に mimeType
が text/
で始まる場合に charset=utf-8
を追加するロジックも、新しい FormatMediaType
の呼び出し方に合わせて調整されています。
この変更は、mime
パッケージの内部的な整合性を高め、外部APIの使いやすさを向上させるための重要なリファクタリングと言えます。
コアとなるコードの変更箇所
このコミットでは、以下の3つのファイルが変更されています。
-
src/pkg/mime/mediatype.go
:FormatMediaType
関数のシグネチャが変更されました。- 変更前:
func FormatMediaType(t, sub string, param map[string]string) string
- 変更後:
func FormatMediaType(t string, param map[string]string) string
- 変更前:
- 関数内部で、新しい
t
(完全なメディアタイプ文字列) からmajor
とsub
を抽出し、それらのトークン検証を行うロジックが追加されました。 - 文字列の結合部分で、
strings.ToLower(t)
がstrings.ToLower(major)
に変更されました。
-
src/pkg/mime/mediatype_test.go
:TestFormatMediaType
という新しいテスト関数が追加されました。formatTest
という新しい構造体が定義され、テストケースを構造化しています。formatTests
というテストケースのスライスが定義され、FormatMediaType
の新しいシグネチャに対応するテストデータが含まれています。これには、スラッシュがない場合の無効な入力、有効な入力、パラメータ付きの有効な入力などが含まれます。
-
src/pkg/mime/type.go
:setExtensionType
関数内のFormatMediaType
の呼び出し方が変更されました。- 変更前は
FormatMediaType(main, sub, param)
のようにmain
とsub
を個別に渡していましたが、変更後はFormatMediaType(mimeType, param)
のように完全なmimeType
文字列を渡すようになりました。 ParseMediaType
の戻り値でfull
変数を受け取る必要がなくなり、_
で無視されるようになりました。full
からmain
とsub
を抽出するロジックが削除されました。
コアとなるコードの解説
src/pkg/mime/mediatype.go
の変更
// FormatMediaType serializes mediatype t and the parameters
// param as a media type conforming to RFC 2045 and RFC 2616.
// The type and parameter names are written in lower-case.
// When any of the arguments result in a standard violation then
// FormatMediaType returns the empty string.
func FormatMediaType(t string, param map[string]string) string {
slash := strings.Index(t, "/") // (1) スラッシュの位置を検索
if slash == -1 { // (2) スラッシュがない場合は無効なメディアタイプとして空文字列を返す
return ""
}
major, sub := t[:slash], t[slash+1:] // (3) スラッシュで主要タイプとサブタイプを分割
if !IsToken(major) || !IsToken(sub) { // (4) 主要タイプまたはサブタイプが有効なトークンでない場合は空文字列を返す
return ""
}
var b bytes.Buffer
b.WriteString(strings.ToLower(major)) // (5) 主要タイプを小文字で書き込む
b.WriteByte('/') // (6) スラッシュを書き込む
b.WriteString(strings.ToLower(sub)) // (7) サブタイプを小文字で書き込む
// ... (パラメータの処理は省略)
}
strings.Index(t, "/")
: 入力された完全なメディアタイプ文字列t
の中で、スラッシュ (/
) の位置を探します。if slash == -1
: スラッシュが見つからない場合、それは有効なtype/subtype
形式ではないため、空文字列を返してエラーを示します。major, sub := t[:slash], t[slash+1:]
: スラッシュの位置を基準に、文字列t
をmajor
(主要タイプ) とsub
(サブタイプ) に分割します。if !IsToken(major) || !IsToken(sub)
:IsToken
関数は、RFCで定義された「トークン」の規則(特定の文字セットのみを含む)に文字列が準拠しているかを確認します。主要タイプまたはサブタイプが有効なトークンでない場合、これも無効なメディアタイプとして空文字列を返します。b.WriteString(strings.ToLower(major))
:bytes.Buffer
を使用して文字列を効率的に構築します。主要タイプを小文字に変換してバッファに書き込みます。RFCではメディアタイプの大文字・小文字は区別されないため、慣例的に小文字が使用されます。b.WriteByte('/')
: スラッシュを書き込みます。b.WriteString(strings.ToLower(sub))
: サブタイプを小文字に変換してバッファに書き込みます。
この変更により、FormatMediaType
は入力の検証を強化し、より堅牢なメディアタイプ文字列の生成を保証します。
src/pkg/mime/type.go
の変更
func setExtensionType(extension, mimeType string) error {
_, param, err := ParseMediaType(mimeType) // (1) ParseMediaTypeの戻り値からfullを無視
if err != nil {
return err
}
// 変更前:
// if split := strings.Index(full, "/"); split < 0 { ... } else {
// main := full[:split]
// sub := full[split+1:]
// if main == "text" && param["charset"] == "" { ... }
// mimeType = FormatMediaType(main, sub, param)
// }
// 変更後:
if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" { // (2) mimeTypeが"text/"で始まるかチェック
param["charset"] = "utf-8"
mimeType = FormatMediaType(mimeType, param) // (3) 新しいFormatMediaTypeのシグネチャで呼び出し
}
// ... (ロックとマップへの書き込みは省略)
}
_, param, err := ParseMediaType(mimeType)
:ParseMediaType
の最初の戻り値である完全なメディアタイプ文字列 (full
) は、もはや直接使用されないため、_
で無視されます。必要なのはパラメータparam
とエラーerr
のみです。if strings.HasPrefix(mimeType, "text/")
: 以前はmain == "text"
とチェックしていましたが、新しいFormatMediaType
のシグネチャに合わせて、入力されたmimeType
がtext/
で始まるかどうかを直接チェックするようになりました。これにより、text
タイプのメディアに対してcharset=utf-8
を自動的に追加するロジックが維持されます。mimeType = FormatMediaType(mimeType, param)
: ここが最も重要な変更点です。以前はParseMediaType
で分割したmain
とsub
を個別にFormatMediaType
に渡していましたが、新しいFormatMediaType
のシグネチャに合わせて、元の完全なmimeType
文字列とparam
を直接渡すようになりました。これにより、コードが簡潔になり、APIの一貫性が保たれます。
これらの変更は、mime
パッケージの内部的な整合性を高め、FormatMediaType
のAPIをより直感的で使いやすいものにすることを目的としています。
関連リンク
- Go CL (Code Review): https://golang.org/cl/5539048
- Go Issue: Fixes #2405 (このコミットが解決したIssueの番号)
参考にした情報源リンク
- RFC 2045 - Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies: https://datatracker.ietf.org/doc/html/rfc2045
- RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1: https://datatracker.ietf.org/doc/html/rfc2616
- Go言語
mime
パッケージのドキュメント (当時のバージョンに近いもの、または現在のもの):- Go 1.0
mime
package documentation (当時のバージョン): https://pkg.go.dev/mime@go1.0 (Go 1.0は2012年3月にリリースされたため、コミット時期と近い) - 現在の
mime
package documentation: https://pkg.go.dev/mime
- Go 1.0
- MIMEタイプに関する一般的な情報: https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types