[インデックス 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.