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

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

このコミットは、Go言語の標準ライブラリである net/mail パッケージにおいて、メールアドレスのパース機能の一部をより公開されたAPIとして提供するように変更するものです。具体的には、既存のメールヘッダーからアドレスリストをパースする機能が Header.AddressList メソッドとして提供されていましたが、このコミットにより、任意の文字列から単一のメールアドレスまたはメールアドレスのリストをパースするための新しい公開関数 ParseAddressParseAddressList が追加されます。これにより、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.AddressListHeader 型に依存しており、汎用的な文字列パースには不向きでした。

この変更により、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 のように、NameAddress という複数のフィールドを持つことができます。
  • メソッド (Method): 特定の型(構造体など)に関連付けられた関数です。レシーバ引数(例: (h Header))を持ち、その型のインスタンスのデータにアクセスしたり操作したりできます。Header.AddressListHeader 型のメソッドです。
  • 関数 (Function): どの型にも関連付けられていない独立したコードブロックです。引数を取り、値を返すことができます。このコミットで追加される ParseAddressParseAddressList は関数です。

このコミットは、既存のメソッドが提供していた機能を、より汎用的な関数として公開することで、利用者がより柔軟にメールアドレスのパース機能を利用できるようにすることを目的としています。

技術的詳細

このコミットの技術的な核心は、net/mail パッケージ内部でメールアドレスのパースに使用されていた非公開のパーサーロジックを、新しい公開関数 ParseAddressParseAddressList を介して外部に公開することにあります。

1. newAddrParser の再利用

コミット前の Header.AddressList メソッドは、内部で newAddrParser(hdr).parseAddressList() を呼び出してアドレスリストを解析していました。newAddrParser は、解析対象の文字列を受け取り、アドレス解析を行うための内部的なパーサーオブジェクトを生成する役割を担っていました。

このコミットでは、この既存の newAddrParser とそれに続く parseAddress() および parseAddressList() という内部的なパースロジックをそのまま利用しています。新しい公開関数 ParseAddressParseAddressList は、単にこれらの内部関数をラップし、外部から呼び出せるようにする「薄いラッパー」として機能します。

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.gosrc/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 の変更点

  1. Header.AddressList メソッドの変更:

    • 変更前: return newAddrParser(hdr).parseAddressList()
    • 変更後: return ParseAddressList(hdr)
    • この変更は、Header.AddressList が内部的に非公開の newAddrParser を直接呼び出す代わりに、新しく公開された ParseAddressList 関数を呼び出すようにリファクタリングされたことを示しています。これにより、コードの重複が排除され、ParseAddressList がアドレスリストパースの単一のエントリポイントとなります。
  2. 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 パッケージの外部から単一のメールアドレスを直接パースできるようになります。
  3. 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 の変更点

  1. テストロジックの拡張:
    • 変更前は、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 パッケージはより柔軟なアドレスパース機能を提供し、その機能が適切にテストされるようになりました。

関連リンク

参考にした情報源リンク