[インデックス 18420] ファイルの概要
このコミットは、Go言語の net/mail パッケージにおいて、メールアドレスのフォーマット時に名前に含まれる空白文字(ホワイトスペース)が正しく処理されないバグを修正するものです。具体的には、RFC 5322で許可されている quoted-string 内の空白文字が、不適切にエンコードされたり、エラーとして扱われたりする問題を解決します。
コミット
- コミットハッシュ:
d3b9567a15cd0f20a927c87b8172902717020304 - Author: Jakub Ryszard Czarnowicz j.czarnowicz@gmail.com
- Date: Fri Feb 7 10:49:10 2014 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d3b9567a15cd0f20a927c87b8172902717020304
元コミット内容
net/mail: correctly handle whitespaces when formatting an email address
Whitespace characters are allowed in quoted-string according to RFC 5322 without
being "Q"-encoding. Address.String() already always formats the name portion in
quoted string, so whitespace characters should be allowed in there.
Fixes #6641.
LGTM=dave, dsymonds
R=golang-codereviews, gobot, dsymonds, dave
CC=golang-codereviews
https://golang.org/cl/55770043
変更の背景
この変更は、Go言語の net/mail パッケージがメールアドレスの表示名(Address.Name)をフォーマットする際に、RFC 5322で定義されている quoted-string の仕様を完全に遵守していなかったことに起因します。
RFC 5322(Internet Message Format)では、メールアドレスの表示名に特殊文字や空白文字が含まれる場合、その表示名を二重引用符で囲んだ quoted-string 形式で表現することが許可されています。この quoted-string の内部では、特定の文字(バックスラッシュ \ や二重引用符 ")を除いて、空白文字(スペースやタブ)はエスケープなしでそのまま使用できると規定されています。
しかし、net/mail パッケージの Address.String() メソッドの実装では、quoted-string 内の空白文字を Q-エンコーディング(MIME "Q" Quoted-Printable Encoding)の対象として誤って扱ったり、あるいは isVchar(可視文字)や isQtext(quoted-stringテキスト文字)のチェックで弾いてしまったりする問題がありました。これにより、例えば Name: "Bob Jane" のような表示名を持つメールアドレスが、"Bob=20Jane" のように不適切にエンコードされたり、あるいはフォーマット時に予期せぬ挙動を引き起こしたりしていました。
この問題は、GoのIssue #6641として報告されており、このコミットはその問題を解決するために導入されました。既存の Address.String() が常に名前部分を quoted-string としてフォーマットしているため、その内部での空白文字の扱いをRFC 5322に準拠させる必要がありました。
前提知識の解説
RFC 5322 (Internet Message Format)
RFC 5322は、インターネットメッセージ(主に電子メール)の標準フォーマットを定義する仕様です。このRFCは、メッセージヘッダーの構文、日付と時刻のフォーマット、アドレスの構文など、電子メールの構造に関する詳細を規定しています。
アドレスの構文と quoted-string
RFC 5322では、メールアドレスは通常 display-name <local-part@domain> の形式で表現されます。ここで display-name はオプションの表示名です。
display-name には、空白文字や一部の特殊文字が含まれる場合、二重引用符で囲まれた quoted-string 形式を使用することができます。
quoted-string の定義は以下のようになります(簡略化):
quoted-string = DQUOTE *([FWS] qcontent) [FWS] DQUOTE
qcontent = qtext / quoted-pair
qtext = %d33 / %d35-91 / %d93-126 (printable US-ASCII characters except DQUOTE and backslash)
quoted-pair = "\" VCHAR / WSP
重要な点は、qtext には二重引用符(")とバックスラッシュ(\)以外の可視ASCII文字が含まれ、これらの文字は quoted-string 内でそのまま使用できるということです。また、quoted-pair の定義により、バックスラッシュでエスケープされた可視文字(VCHAR)や空白文字(WSP)も quoted-string 内で許可されます。
特に、WSP (Whitespace) はスペース( )と水平タブ(\t)を指し、これらは quoted-string 内でエスケープなしで直接使用できる文字です(ただし、quoted-pair の文脈ではエスケープされることもあります)。このコミットの焦点は、この「エスケープなしで空白文字が許可される」という点にあります。
Go言語の net/mail パッケージ
net/mail パッケージは、Go言語の標準ライブラリの一部であり、RFC 5322に準拠した電子メールメッセージの解析とフォーマットを提供します。このパッケージは、メールヘッダーの解析、アドレスの抽出、メッセージのエンコード/デコードなどの機能を提供します。
mail.Address構造体: メールアドレスを表す構造体で、Name(表示名) とAddress(メールアドレス本体) のフィールドを持ちます。Address.String()メソッド:mail.Address構造体をRFC 5322に準拠した文字列形式にフォーマットするメソッドです。このメソッドが、表示名に空白文字が含まれる場合のquoted-stringの生成を担当します。
isVchar と isQtext
コミットの変更箇所に登場する isVchar と isQtext は、文字の種類を判定するためのヘルパー関数です。
isVchar(c byte) bool:VCHAR(Visible Character) を判定します。RFC 5234 (ABNF) で定義されており、印刷可能なUS-ASCII文字(!から~まで)を指します。isQtext(c byte) bool:qtext(quoted-string text) を判定します。RFC 5322で定義されており、quoted-string内でエスケープなしで直接使用できる文字のうち、二重引用符(")とバックスラッシュ(\)を除く可視文字を指します。
技術的詳細
このバグは、net/mail パッケージの Address.String() メソッドが、表示名(a.Name)を quoted-string としてフォーマットする際に、空白文字の扱いを誤っていたことにあります。
元のコードでは、a.Name の各文字が allPrintable かどうかをチェックするループ内で、isVchar(a.Name[i]) のみを確認していました。isVchar は可視文字のみを真とするため、空白文字(スペースやタブ)は allPrintable を false にしてしまい、結果として quoted-string の内部で空白文字が適切に扱われない可能性がありました。
また、quoted-string の内容を構築するループ内では、!isQtext(a.Name[i]) の条件でバックスラッシュ(\)を挿入していました。isQtext も空白文字を真としないため、空白文字が isQtext の条件に合致せず、不必要にバックスラッシュでエスケープされてしまう可能性がありました。RFC 5322では、quoted-string 内の空白文字は通常エスケープ不要です。
このコミットでは、以下の2つの主要な変更によってこの問題を解決しています。
-
allPrintableの判定ロジックの修正:allPrintableフラグを判定する際に、isVchar(a.Name[i])に加えてisWSP(a.Name[i])(空白文字であるか)もチェックするように変更されました。これにより、表示名に空白文字が含まれていてもallPrintableがtrueになる可能性が広がり、quoted-stringとしての適切な処理パスに進むようになります。 コメント// isWSP here should actually be isFWS, // but we don't support folding yet.は、RFC 5322のFWS(Folding White Space) の概念に触れていますが、現在の実装では単純なWSPのチェックで十分であり、将来的なFWSのサポートを示唆しています。 -
quoted-string内のエスケープロジックの修正:quoted-stringの内容を構築する際に、!isQtext(a.Name[i])の条件に加えて!isWSP(a.Name[i])もチェックするように変更されました。これにより、文字がqtextでない、かつWSPでもない場合にのみバックスラッシュでエスケープされるようになります。つまり、空白文字はisQtextには該当しないものの、isWSPに該当するため、エスケープされずにそのままquoted-stringに挿入されるようになります。これはRFC 5322のquoted-stringの仕様に完全に準拠した挙動です。 -
isWSPヘルパー関数の追加: スペース()と水平タブ(\t)が空白文字(WSP)であるかを判定する新しいヘルパー関数isWSPが追加されました。これにより、コードの可読性が向上し、空白文字の判定ロジックが一箇所に集約されます。
これらの変更により、"Bob Jane" のような表示名が net/mail パッケージによって正しく "Bob Jane" <bob@example.com> の形式でフォーマットされるようになり、不適切なエンコーディングやエスケープが回避されます。
コアとなるコードの変更箇所
src/pkg/net/mail/message.go
--- a/src/pkg/net/mail/message.go
+++ b/src/pkg/net/mail/message.go
@@ -159,7 +159,9 @@ func (a *Address) String() string {
// If every character is printable ASCII, quoting is simple.
allPrintable := true
for i := 0; i < len(a.Name); i++ {
- if !isVchar(a.Name[i]) {
+ // isWSP here should actually be isFWS,
+ // but we don't support folding yet.
+ if !isVchar(a.Name[i]) && !isWSP(a.Name[i]) {
allPrintable = false
break
}
@@ -167,7 +169,7 @@ func (a *Address) String() string {
if allPrintable {
b := bytes.NewBufferString(`"`)
for i := 0; i < len(a.Name); i++ {
- if !isQtext(a.Name[i]) {
+ if !isQtext(a.Name[i]) && !isWSP(a.Name[i]) {
b.WriteByte('\\')
}
b.WriteByte(a.Name[i])
@@ -535,3 +537,9 @@ func isVchar(c byte) bool {
// Visible (printing) characters.
return '!' <= c && c <= '~'
}
+
+// isWSP returns true if c is a WSP (white space).
+// WSP is a space or horizontal tab (RFC5234 Appendix B).
+func isWSP(c byte) bool {
+ return c == ' ' || c == '\t'
+}
src/pkg/net/mail/message_test.go
--- a/src/pkg/net/mail/message_test.go
+++ b/src/pkg/net/mail/message_test.go
@@ -277,6 +277,14 @@ func TestAddressFormatting(t *testing.T) {
&Address{Name: "Böb", Address: "bob@example.com"},
`=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
},
+ {
+ &Address{Name: "Bob Jane", Address: "bob@example.com"},
+ `"Bob Jane" <bob@example.com>`,
+ },
+ {
+ &Address{Name: "Böb Jacöb", Address: "bob@example.com"},
+ `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`,
+ },
}
for _, test := range tests {
s := test.addr.String()
コアとなるコードの解説
src/pkg/net/mail/message.go の変更
-
Address.String()メソッド内のallPrintable判定ロジックの修正: 変更前:if !isVchar(a.Name[i]) {変更後:if !isVchar(a.Name[i]) && !isWSP(a.Name[i]) {この変更により、表示名a.Nameの文字が可視文字(isVchar)でない場合でも、それが空白文字(isWSP)であればallPrintableフラグがfalseになるのを防ぎます。つまり、空白文字はquoted-string内で「印刷可能」な文字として扱われるべきであるというRFC 5322の意図を反映しています。これにより、空白文字を含む表示名が正しくquoted-stringとして処理されるパスに進むようになります。 -
Address.String()メソッド内のquoted-stringエスケープロジックの修正: 変更前:if !isQtext(a.Name[i]) {変更後:if !isQtext(a.Name[i]) && !isWSP(a.Name[i]) {quoted-stringの内容を構築する際、文字がqtextでない場合にバックスラッシュでエスケープするロジックがありました。しかし、空白文字はisQtextではfalseを返すため、このままでは空白文字が不必要にエスケープされてしまいます。この変更により、「文字がqtextでない、かつ 空白文字でもない」場合にのみエスケープが行われるようになります。これにより、空白文字はエスケープされずにそのままquoted-stringに挿入され、RFC 5322の仕様に準拠します。 -
isWSP関数の追加:// isWSP returns true if c is a WSP (white space). // WSP is a space or horizontal tab (RFC5234 Appendix B). func isWSP(c byte) bool { return c == ' ' || c == '\t' }この新しいヘルパー関数は、与えられたバイトがスペース(
)または水平タブ(\t)であるかを判定します。これはRFC 5234で定義されているWSP(Whitespace) に対応します。この関数を導入することで、空白文字の判定ロジックが明確になり、コードの可読性と保守性が向上しています。
src/pkg/net/mail/message_test.go の変更
新しいテストケースが TestAddressFormatting 関数に追加されました。
-
{"Bob Jane","Bob Jane" bob@example.com}: このテストケースは、表示名にスペースが含まれる場合に、それが正しく二重引用符で囲まれ、スペースがそのまま保持されることを確認します。これは、このコミットが修正しようとしている主要なシナリオです。 -
{"Böb Jacöb",=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= bob@example.com}: このテストケースは、表示名に非ASCII文字とスペースが混在する場合の挙動を確認します。この場合、RFC 2047の"Q"エンコーディングが適用され、スペースはアンダースコア(_)に変換されることが期待されます。これは、このコミットの変更が既存のエンコーディングロジックに悪影響を与えないことを保証するためのものです。
これらのテストケースの追加により、修正が正しく機能していること、および他の関連するフォーマットロジックに回帰がないことが検証されます。
関連リンク
- Go Issue #6641: https://code.google.com/p/go/issues/detail?id=6641 (現在はGitHubに移行済み)
- Go CL (Change List): https://golang.org/cl/55770043
参考にした情報源リンク
- RFC 5322 - Internet Message Format: https://tools.ietf.org/html/rfc5322
- RFC 5234 - Augmented BNF for Syntax Specifications: ABNF: https://tools.ietf.org/html/rfc5234 (特に
WSPの定義) - RFC 2047 - MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text: https://tools.ietf.org/html/rfc2047 (特に "Q" エンコーディングについて)
- Go言語の
net/mailパッケージドキュメント: https://pkg.go.dev/net/mail - Go言語のソースコード (net/mail): https://github.com/golang/go/tree/master/src/net/mail
- Go Issue 6641 (GitHub): https://github.com/golang/go/issues/6641I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. The output is provided directly to standard output as requested.