[インデックス 14023] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/mail
パッケージにおいて、メールアドレスのパース機能の一部をより公開されたAPIとして提供するように変更するものです。具体的には、既存のメールヘッダーからアドレスリストをパースする機能が Header.AddressList
メソッドとして提供されていましたが、このコミットにより、任意の文字列から単一のメールアドレスまたはメールアドレスのリストをパースするための新しい公開関数 ParseAddress
と ParseAddressList
が追加されます。これにより、net/mail
パッケージの利用者は、メールメッセージ全体をパースすることなく、独立してメールアドレスのパース機能を利用できるようになります。
コミット
commit 9f807fcc4ae1bdd38ee637a88128a1c72d984a30
Author: Graham Miller <graham.miller@gmail.com>
Date: Fri Oct 5 10:08:54 2012 +1000
net/mail: make address parsing (more) public
Code for parsing email addresses was already partially part of the public API with "func (Header) AddressList". This CL adds a trivial implementation for two public methods to parse address and lists from a string. With tests.
R=dsymonds
CC=golang-dev
https://golang.org/cl/5676067
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9f807fcc4ae1bdd38ee637a88128a1c72d984a30
元コミット内容
net/mail: make address parsing (more) public
メールアドレスのパースコードは、すでに func (Header) AddressList
として公開APIの一部となっていました。この変更リスト(CL)は、文字列からアドレスとリストをパースするための2つの公開メソッドの簡単な実装を追加します。テストも含まれています。
変更の背景
Go言語の net/mail
パッケージは、電子メールメッセージの解析を目的としています。これまで、メールヘッダー内のアドレスリストを解析する機能は Header
型のメソッド AddressList
として提供されていました。これは、特定のメールヘッダー(例: From
, To
, Cc
など)からアドレスを抽出する際に便利でしたが、任意の文字列(例えば、ユーザー入力やデータベースから取得した単一のメールアドレス文字列)を直接パースする公開された汎用的な関数は存在しませんでした。
このコミットの背景には、メールメッセージ全体を扱うことなく、RFC 5322に準拠したメールアドレスの構文解析機能だけを独立して利用したいというニーズがあったと考えられます。例えば、アプリケーションがユーザーからメールアドレスを入力として受け取り、その形式が正しいか検証したい場合や、アドレス帳機能でメールアドレスの文字列を Address
構造体に変換したい場合などです。既存の Header.AddressList
は Header
型に依存しており、汎用的な文字列パースには不向きでした。
この変更により、net/mail
パッケージの機能がよりモジュール化され、再利用性が向上します。開発者は、メールメッセージのヘッダーを介さずに、直接メールアドレスの文字列をパースできるようになり、より柔軟なアプリケーション開発が可能になります。
前提知識の解説
1. RFC 5322 (Internet Message Format)
RFC 5322は、インターネット電子メールメッセージの標準フォーマットを定義するIETF(Internet Engineering Task Force)の文書です。これは、電子メールメッセージのヘッダーフィールド(例: From, To, Subject, Dateなど)とメッセージ本文の構文規則を詳細に規定しています。
特にメールアドレスに関しては、RFC 5322は以下のような形式を定義しています。
- "表示名" user@domain.com : 例:
"John Doe" <john.doe@example.com>
- user@domain.com : 例:
jane.smith@example.org
net/mail
パッケージは、これらのRFC 5322で定義された構文規則に基づいてメールアドレスを解析します。ただし、Goの net/mail
パッケージはRFC 5322の全ての仕様を完全に実装しているわけではなく、一部の古い形式や複雑な空白(CFWS: Folding White Space)の扱いに制限がある点に注意が必要です。例えば、RFC 5322で定義されているグループアドレス(例: Group: John Doe <john@example.com>, Jane Smith <jane@example.com>;
)や、古いルーティング情報を含むアドレス形式はサポートしていません。
2. Go言語の net/mail
パッケージ
Go言語の net/mail
パッケージは、電子メールメッセージの解析を目的とした標準ライブラリです。主な機能は以下の通りです。
mail.ReadMessage
:io.Reader
からメールメッセージを読み込み、ヘッダーとボディを解析します。mail.Header
: メールヘッダーを表す型で、Get
メソッドなどで特定のヘッダーフィールドの値を取得できます。mail.Address
: 単一のメールアドレスを表す構造体で、Name
(表示名) とAddress
(メールアドレス本体、例:user@domain.com
) のフィールドを持ちます。Header.AddressList
: 特定のヘッダーフィールド(例: "From")から複数のメールアドレスを解析し、[]*mail.Address
のスライスとして返します。
このコミット以前は、Header.AddressList
がメールアドレス解析の主要な公開インターフェースでした。このメソッドは内部的に newAddrParser
という非公開のパーサーを使用していました。
3. 構造体とメソッド、関数の違い
- 構造体 (Struct): 関連するデータをまとめた複合型です。
mail.Address
のように、Name
とAddress
という複数のフィールドを持つことができます。 - メソッド (Method): 特定の型(構造体など)に関連付けられた関数です。レシーバ引数(例:
(h Header)
)を持ち、その型のインスタンスのデータにアクセスしたり操作したりできます。Header.AddressList
はHeader
型のメソッドです。 - 関数 (Function): どの型にも関連付けられていない独立したコードブロックです。引数を取り、値を返すことができます。このコミットで追加される
ParseAddress
やParseAddressList
は関数です。
このコミットは、既存のメソッドが提供していた機能を、より汎用的な関数として公開することで、利用者がより柔軟にメールアドレスのパース機能を利用できるようにすることを目的としています。
技術的詳細
このコミットの技術的な核心は、net/mail
パッケージ内部でメールアドレスのパースに使用されていた非公開のパーサーロジックを、新しい公開関数 ParseAddress
と ParseAddressList
を介して外部に公開することにあります。
1. newAddrParser
の再利用
コミット前の Header.AddressList
メソッドは、内部で newAddrParser(hdr).parseAddressList()
を呼び出してアドレスリストを解析していました。newAddrParser
は、解析対象の文字列を受け取り、アドレス解析を行うための内部的なパーサーオブジェクトを生成する役割を担っていました。
このコミットでは、この既存の newAddrParser
とそれに続く parseAddress()
および parseAddressList()
という内部的なパースロジックをそのまま利用しています。新しい公開関数 ParseAddress
と ParseAddressList
は、単にこれらの内部関数をラップし、外部から呼び出せるようにする「薄いラッパー」として機能します。
2. ParseAddress
関数の追加
ParseAddress(address string) (*Address, error)
は、単一のRFC 5322形式のメールアドレス文字列(例: "Barry Gibbs <bg@example.com>"
)を受け取り、それを *mail.Address
型のポインタに解析します。この関数は、内部的に newAddrParser(address).parseAddress()
を呼び出します。parseAddress()
は、パーサーオブジェクトが持つ文字列から単一のアドレスを解析するロジックを実装しています。
3. ParseAddressList
関数の追加
ParseAddressList(list string) ([]*Address, error)
は、RFC 5322形式のメールアドレスリスト文字列(例: "A <a@example.com>, B <b@example.com>"
)を受け取り、それを []*mail.Address
型のスライスに解析します。この関数は、内部的に newAddrParser(list).parseAddressList()
を呼び出します。parseAddressList()
は、パーサーオブジェクトが持つ文字列から複数のアドレスを解析するロジックを実装しています。
4. Header.AddressList
の変更
既存の Header.AddressList
メソッドも変更され、直接 newAddrParser(hdr).parseAddressList()
を呼び出す代わりに、新しく公開された ParseAddressList(hdr)
を呼び出すように修正されています。これは、コードの重複を避け、新しい公開関数を内部的にも利用することで、一貫性と保守性を高めるためのリファクタリングです。
5. テストの追加と修正
新しい公開関数が正しく動作することを保証するために、message_test.go
にテストが追加されています。特に、単一アドレスのパース (ParseAddress
) とリストのパース (ParseAddressList
) の両方について、様々な有効なアドレス形式と無効なアドレス形式をテストケースとして含んでいます。既存のテストも、新しい公開関数を利用するように修正されています。
この変更は、net/mail
パッケージの内部実装に大きな変更を加えることなく、既存の堅牢なパースロジックを再利用しつつ、APIの使いやすさと汎用性を向上させるという、効率的かつ効果的なアプローチを採用しています。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、src/pkg/net/mail/message.go
と src/pkg/net/mail/message_test.go
の2つのファイルに集中しています。
src/pkg/net/mail/message.go
--- a/src/pkg/net/mail/message.go
+++ b/src/pkg/net/mail/message.go
@@ -127,7 +127,7 @@ func (h Header) AddressList(key string) ([]*Address, error) {
if hdr == "" {
return nil, ErrHeaderNotPresent
}
- return newAddrParser(hdr).parseAddressList()
+ return ParseAddressList(hdr)
}
// Address represents a single mail address.
@@ -138,6 +138,16 @@ type Address struct {
Address string // user@domain
}
+// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
+func ParseAddress(address string) (*Address, error) {
+ return newAddrParser(address).parseAddress()
+}
+
+// ParseAddressList parses the given string as a list of addresses.
+func ParseAddressList(list string) ([]*Address, error) {
+ return newAddrParser(list).parseAddressList()
+}
+
// String formats the address as a valid RFC 5322 address.
// If the address's name contains non-ASCII characters
// the name will be rendered according to RFC 2047.
src/pkg/net/mail/message_test.go
--- a/src/pkg/net/mail/message_test.go
+++ b/src/pkg/net/mail/message_test.go
@@ -227,13 +227,24 @@ func TestAddressParsing(t *testing.T) {
},
}
for _, test := range tests {
- addrs, err := newAddrParser(test.addrsStr).parseAddressList()
+ if len(test.exp) == 1 {
+ addr, err := ParseAddress(test.addrsStr)
+ if err != nil {
+ t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
+ continue
+ }
+ if !reflect.DeepEqual([]*Address{addr}, test.exp) {
+ t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
+ }
+ }
+
+ addrs, err := ParseAddressList(test.addrsStr)
if err != nil {
- t.Errorf("Failed parsing %q: %v", test.addrsStr, err)
+ t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
continue
}
if !reflect.DeepEqual(addrs, test.exp) {
- t.Errorf("Parse of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
+ t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
}
}
}
コアとなるコードの解説
src/pkg/net/mail/message.go
の変更点
-
Header.AddressList
メソッドの変更:- 変更前:
return newAddrParser(hdr).parseAddressList()
- 変更後:
return ParseAddressList(hdr)
- この変更は、
Header.AddressList
が内部的に非公開のnewAddrParser
を直接呼び出す代わりに、新しく公開されたParseAddressList
関数を呼び出すようにリファクタリングされたことを示しています。これにより、コードの重複が排除され、ParseAddressList
がアドレスリストパースの単一のエントリポイントとなります。
- 変更前:
-
ParseAddress
関数の追加:// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>" func ParseAddress(address string) (*Address, error) { return newAddrParser(address).parseAddress() }
- この新しい公開関数は、単一のメールアドレス文字列を引数にとり、
*mail.Address
型のポインタとエラーを返します。 - 内部的には、既存の非公開パーサー生成関数
newAddrParser
を使用してパーサーオブジェクトを作成し、そのparseAddress()
メソッドを呼び出して単一のアドレスを解析します。これにより、net/mail
パッケージの外部から単一のメールアドレスを直接パースできるようになります。
- この新しい公開関数は、単一のメールアドレス文字列を引数にとり、
-
ParseAddressList
関数の追加:// ParseAddressList parses the given string as a list of addresses. func ParseAddressList(list string) ([]*Address, error) { return newAddrParser(list).parseAddressList() }
- この新しい公開関数は、メールアドレスのリストを含む文字列を引数にとり、
[]*mail.Address
型のスライスとエラーを返します。 ParseAddress
と同様に、newAddrParser
を使用してパーサーオブジェクトを作成し、そのparseAddressList()
メソッドを呼び出してアドレスリストを解析します。これにより、net/mail
パッケージの外部から複数のメールアドレスを直接パースできるようになります。
- この新しい公開関数は、メールアドレスのリストを含む文字列を引数にとり、
src/pkg/net/mail/message_test.go
の変更点
- テストロジックの拡張:
- 変更前は、
TestAddressParsing
関数内のループでnewAddrParser(test.addrsStr).parseAddressList()
を呼び出してアドレスリストのパースのみをテストしていました。 - 変更後、テストケース
test.exp
の長さが1の場合(つまり、単一のアドレスを期待する場合)に、新しく追加されたParseAddress
関数をテストするブロックが追加されました。if len(test.exp) == 1 { addr, err := ParseAddress(test.addrsStr) // ... エラーチェックと結果の比較 ... }
- その後、既存のテストロジックは
ParseAddressList(test.addrsStr)
を呼び出すように変更され、アドレスリストのパースをテストします。 - エラーメッセージも、
"Failed parsing (single) %q: %v"
や"Failed parsing (list) %q: %v"
のように、単一アドレスとリストのパースを区別するように修正されています。
- 変更前は、
これらの変更により、net/mail
パッケージはより柔軟なアドレスパース機能を提供し、その機能が適切にテストされるようになりました。
関連リンク
- Go言語
net/mail
パッケージのドキュメント: https://pkg.go.dev/net/mail - RFC 5322 - Internet Message Format: https://datatracker.ietf.org/doc/html/rfc5322
参考にした情報源リンク
- Go言語
net/mail
パッケージのRFC 5322準拠に関する情報 (Stack Overflow): https://stackoverflow.com/questions/30900040/go-net-mail-package-rfc-5322 - Go言語
net/mail
パッケージのドキュメント (go.dev): https://pkg.go.dev/net/mail - Go言語の変更リスト (CL) 5676067 (これは今回のコミットとは直接関係ないが、検索結果として得られた情報): https://golang.org/cl/5676067 (このCLはGo 1.22.4へのアップデートに関するもので、今回のコミットとは無関係でした。)
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語の
net/mail
パッケージの内部実装に関する議論 (GitHub Issuesなど) - RFC 2047 - MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text (メールアドレスの表示名に非ASCII文字が含まれる場合のエンコーディングについて関連): https://datatracker.ietf.org/doc/html/rfc2047
- RFC 4155 - The
From
Header Field in RFC 2822 (mbox形式の"From "行に関する関連): https://datatracker.ietf.org/doc/html/rfc4155 - RFC 6532 - Internationalized Email Headers (RFC 5322の拡張に関する関連): https://datatracker.ietf.org/doc/html/rfc6532