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

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

このコミットは、Go言語の標準ライブラリ mime パッケージにおけるメディアタイプ(MIMEタイプ)のフォーマット処理に関するバグ修正と機能改善を含んでいます。具体的には、メディアタイプの属性値に含まれるバックスラッシュ (\) のエスケープ処理が正しく行われるように修正され、関連するテストケースが追加されています。

コミット

commit b990c40d5e9c0339ee33d315ad5da69740809eb8
Author: Pieter Droogendijk <pieter@binky.org.uk>
Date:   Fri Aug 9 13:10:53 2013 -0700

    mime: escape backslash in attribute values
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12689045

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

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

元コミット内容

mime: escape backslash in attribute values

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12689045

変更の背景

MIMEタイプ(Media Type)は、インターネット上で転送されるデータの種類を識別するために使用されます。例えば、text/plain はプレーンテキスト、image/jpeg はJPEG画像を表します。MIMEタイプは、type/subtype; parameter=value の形式で、追加の属性(パラメータ)を持つことができます。

このコミットが行われる前、Go言語の mime パッケージの FormatMediaType 関数は、メディアタイプの属性値をフォーマットする際に、引用符 (") はエスケープしていましたが、バックスラッシュ (\) のエスケープ処理が欠落していました。RFC 2045 (MIME Part One: Format of Internet Message Bodies) のセクション 5.1 では、quoted-string の構文が定義されており、その中でバックスラッシュと引用符は \ でエスケープされる必要があると規定されています。

エスケープされていないバックスラッシュが属性値に含まれると、MIMEタイプをパースする際に誤った解釈を引き起こす可能性がありました。例えば、filename="foo\bar.txt" のような属性値が正しくエスケープされずに生成されると、受信側で foo\bar.txtfooar.txt と解釈されたり、パースエラーになったりする可能性があります。

このコミットは、この仕様の不備を修正し、FormatMediaType 関数がRFCの規定に従ってバックスラッシュを適切にエスケープするようにすることで、MIMEタイプの相互運用性と堅牢性を向上させることを目的としています。

前提知識の解説

MIMEタイプ (Media Type)

MIMEタイプは、インターネットメディアタイプとも呼ばれ、インターネット上で交換されるファイルやデータの種類を識別するための標準的な方法です。HTTPヘッダーの Content-Type や電子メールの添付ファイルなどで広く利用されています。

MIMEタイプは通常、type/subtype の形式で表現されます(例: text/html, application/json)。さらに、追加の情報を伝えるためにパラメータを持つことができます。パラメータは key=value の形式で、セミコロン (;) で区切られて指定されます(例: text/plain; charset=utf-8)。

RFC 2045: MIME Part One: Format of Internet Message Bodies

RFC 2045は、MIME(Multipurpose Internet Mail Extensions)の基本的なフォーマットを定義する重要な仕様です。このRFCは、電子メールのメッセージボディの構造を拡張し、テキスト以外のデータ(画像、音声、動画など)や複数のパートからなるメッセージを扱うためのメカニズムを提供します。

特に、セクション 5.1 では、MIMEヘッダーフィールドのパラメータ値の構文について記述されています。パラメータ値は、token または quoted-string のいずれかの形式を取ることができます。

  • token: 特定の文字セット(非特殊文字)で構成されるシンプルな文字列。
  • quoted-string: 引用符 (") で囲まれた文字列。この形式では、引用符 (") とバックスラッシュ (\) は、その文字の前にバックスラッシュを置くことでエスケープする必要があります。例えば、"\" となり、\\\ となります。これは、文字列の区切り文字やエスケープ文字自体が値の一部として含まれる場合に、パースの曖昧さを避けるために必要です。

このコミットは、まさにこの quoted-string 内でのバックスラッシュのエスケープルールに準拠するための修正です。

Go言語の mime パッケージ

Go言語の標準ライブラリには、MIMEタイプを扱うための mime パッケージが含まれています。このパッケージは、MIMEタイプのパース(文字列から構造体への変換)やフォーマット(構造体から文字列への変換)などの機能を提供します。

  • ParseMediaType(v string) (mediatype string, params map[string]string, err error): MIMEタイプの文字列をパースし、メディアタイプとパラメータのマップを返します。
  • FormatMediaType(mediatype string, params map[string]string) string: メディアタイプとパラメータのマップからMIMEタイプの文字列を生成します。

このコミットで修正されたのは、FormatMediaType 関数です。

技術的詳細

このコミットの技術的な核心は、FormatMediaType 関数における属性値のエスケープロジックの修正です。

元のコードでは、属性値に含まれる引用符 (") とキャリッジリターン (\r) をエスケープ対象としていました。しかし、RFC 2045の規定に従うと、キャリッジリターンではなくバックスラッシュ (\) をエスケープする必要があります。キャリッジリターンは通常、属性値に直接含まれるべきではない文字であり、もし含まれる場合は別のエンコーディングメカニズム(例: encoded-word)を使用するか、あるいは不正なMIMEタイプとして扱われるべきです。

修正後のコードでは、エスケープ対象の文字が引用符 (") とバックスラッシュ (\) に変更されました。これにより、属性値が quoted-string としてフォーマットされる際に、これらの特殊文字が正しく \ プレフィックス付きで出力されるようになります。

例えば、属性値が With \backslash and "quote" の場合、修正前は With \backslash and \"quote のようにバックスラッシュがエスケープされずに出力される可能性がありましたが、修正後は With \\backslash and \"quote" のように、バックスラッシュも適切にエスケープされて出力されます。

この変更は、MIMEタイプの生成がRFCに厳密に準拠することを保証し、異なるシステム間でのMIMEタイプの解釈の一貫性を高めます。

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

変更は主に src/pkg/mime/mediatype.goFormatMediaType 関数と、それに対応するテストファイル src/pkg/mime/mediatype_test.go にあります。

src/pkg/mime/mediatype.go

--- a/src/pkg/mime/mediatype.go
+++ b/src/pkg/mime/mediatype.go
@@ -47,7 +47,7 @@ func FormatMediaType(t string, param map[string]string) string {
 		b.WriteByte('"')
 		offset := 0
 		for index, character := range value {
-			if character == '"' || character == '\r' {
+			if character == '"' || character == '\\' {
 				b.WriteString(value[offset:index])
 				offset = index
 				b.WriteByte('\\')

この差分が示すように、if 文の条件が character == '\r' から character == '\\' に変更されています。これにより、属性値内のバックスラッシュ文字が検出された場合に、その前にエスケープ用のバックスラッシュが追加されるようになります。

src/pkg/mime/mediatype_test.go

--- a/src/pkg/mime/mediatype_test.go
+++ b/src/pkg/mime/mediatype_test.go
@@ -282,8 +282,17 @@ type formatTest struct {
 
 var formatTests = []formatTest{
 	{"noslash", nil, ""},
+	{"foo bar/baz", nil, ""},
+	{"foo/bar baz", nil, ""},
 	{"foo/BAR", nil, "foo/bar"},
 	{"foo/BAR", map[string]string{"X": "Y"}, "foo/bar; x=Y"},
+	{"foo/BAR", map[string]string{"space": "With space"}, `foo/bar; space="With space"`},
+	{"foo/BAR", map[string]string{"quote": `With "quote`}, `foo/bar; quote="With \"quote"`},
+	{"foo/BAR", map[string]string{"bslash": `With \backslash`}, `foo/bar; bslash="With \\backslash"`},
+	{"foo/BAR", map[string]string{"both": `With \backslash and "quote`}, `foo/bar; both="With \\backslash and \"quote"`},
+	{"foo/BAR", map[string]string{"": "empty attribute"}, ""},
+	{"foo/BAR", map[string]string{"bad attribute": "baz"}, ""},
+	{"foo/BAR", map[string]string{"nonascii": "not an ascii character: ä"}, ""},
 }
 
 func TestFormatMediaType(t *testing.T) {

テストファイルには、新しいテストケースが多数追加されています。これらは、スペース、引用符、バックスラッシュ、およびそれらの組み合わせを含む属性値が正しくフォーマットされることを検証しています。

特に注目すべきは以下のテストケースです。

  • {"bslash": With \backslash}, foo/bar; bslash="With \backslash"}: バックスラッシュを含む属性値が \\ とエスケープされることを確認。
  • {"both": With \backslash and "quote}, foo/bar; both="With \backslash and "quote"}: バックスラッシュと引用符の両方を含む属性値が正しくエスケープされることを確認。

これらのテストケースは、修正されたロジックが期待通りに機能することを保証するためのものです。

コアとなるコードの解説

FormatMediaType 関数は、メディアタイプ文字列とパラメータのマップを受け取り、RFCに準拠したMIMEタイプ文字列を生成します。

変更された部分のコードスニペットは以下の通りです。

		b.WriteByte('"') // 属性値を引用符で囲む
		offset := 0
		for index, character := range value {
			// 引用符またはバックスラッシュが検出された場合
			if character == '"' || character == '\\' {
				// 現在のオフセットから特殊文字の手前までを書き込む
				b.WriteString(value[offset:index])
				offset = index // オフセットを特殊文字の位置に更新
				b.WriteByte('\\') // エスケープ用のバックスラッシュを書き込む
			}
			// その他の文字はそのまま書き込む(ループの最後にまとめて書き込まれるか、次の特殊文字まで)
		}
		// 最後の特殊文字以降、または特殊文字がない場合の残りの文字列を書き込む
		b.WriteString(value[offset:])
		b.WriteByte('"') // 閉じ引用符を書き込む

このコードは、属性値 value をイテレートし、引用符 (") またはバックスラッシュ (\) が見つかるたびに、その文字の前にエスケープ用のバックスラッシュ (\) を挿入します。

  1. b.WriteByte('"'): まず、属性値の開始を示す引用符を書き込みます。
  2. for index, character := range value: 属性値の各文字をループで処理します。
  3. if character == '"' || character == '\\': 現在の文字が引用符またはバックスラッシュであるかをチェックします。
  4. b.WriteString(value[offset:index]): 特殊文字が見つかった場合、前回の offset から現在の index (特殊文字の手前) までの部分文字列をバッファに書き込みます。これにより、特殊文字ではない部分がそのまま出力されます。
  5. offset = index: offset を現在の index に更新します。これは、次に書き込むべき文字列の開始位置を示します。
  6. b.WriteByte('\\'): 特殊文字の前にエスケープ用のバックスラッシュを書き込みます。
  7. ループの最後に b.WriteString(value[offset:]) が実行され、最後の特殊文字以降の残りの文字列(または特殊文字が全くなかった場合の全体の文字列)が書き込まれます。
  8. b.WriteByte('"'): 最後に、属性値の終了を示す引用符を書き込みます。

このロジックにより、RFC 2045で定義されている quoted-string のエスケープルールが正確に実装されます。

関連リンク

参考にした情報源リンク

  • RFC 2045 - MIME Part One: Format of Internet Message Bodies (特にセクション 5.1)
  • Go言語の mime パッケージのソースコード (コミット当時のものと現在のもの)
  • MIMEタイプに関する一般的な情報源 (例: Wikipedia, MDN Web Docs)
  • Go言語のコミット履歴とコードレビューシステム (Gerrit/Go CL)
  • Go言語のIssueトラッカー (関連するバグ報告や議論がある場合)
  • Stack Overflowや技術ブログなど、MIMEタイプのエスケープに関する議論や解説