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

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

このコミットは、Go言語の標準ライブラリである net/mail パッケージにおける日付ヘッダーのパース処理を改善するものです。具体的には、RFC 5322で厳密には定義されていないものの、広く使用されている日付フォーマット(例: タイムゾーン情報に括弧で囲まれた略称が含まれる形式)を net/mail が正しく解釈できるように、パースロジックをより柔軟にしています。これにより、様々なメールクライアントやシステムから送信されるメールの Date ヘッダーを、より堅牢に処理できるようになります。

コミット

commit 132dbb61aadf2eaa0718e388b2bb7e3f6e42f734
Author: Bill Thiede <couchmoney@gmail.com>
Date:   Fri May 25 09:19:21 2012 +1000

    net/mail: more liberal parsing of Date headers.
    Fixes #3639.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/6243045

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

https://github.com/golang/go/commit/132dbb61aadf2eaa0718e388b2bb7e3f6e42f734

元コミット内容

net/mail パッケージにおいて、Date ヘッダーのパース処理をより寛容にする変更です。Go issue #3639 を修正します。

変更の背景

メールの Date ヘッダーは、RFC 5322 (Internet Message Format) によってそのフォーマットが規定されています。しかし、現実の世界では、様々なメールクライアントやシステムがこのRFCの規定に厳密に従わない、あるいはRFCで明示的に定義されていないが一般的に使用されている形式で Date ヘッダーを生成することがあります。

このコミットは、特にタイムゾーンの表記に関して、RFC 5322では定義されていない "-0700 (MST)" のような形式(タイムゾーンのオフセットの後に括弧で囲まれたタイムゾーンの略称が続く形式)が原因で、net/mail パッケージが Date ヘッダーを正しくパースできない問題(Go issue #3639)を解決するために導入されました。

net/mail パッケージは、メールのヘッダーを解析し、構造化されたデータとして提供する役割を担っています。Date ヘッダーのパースに失敗すると、メールの送信日時を正確に取得できず、アプリケーションの動作に影響を与える可能性があります。この変更は、net/mail パッケージの堅牢性を高め、より多くの種類のメールを適切に処理できるようにすることを目的としています。

前提知識の解説

RFC 5322 (Internet Message Format)

RFC 5322は、インターネットメッセージ(主に電子メール)の標準フォーマットを定義する仕様です。このRFCは、メッセージのヘッダーフィールド(From, To, Subject, Date など)の構文と意味、およびメッセージ本文の構造を詳細に規定しています。

特に Date ヘッダーに関しては、以下のようなフォーマットが推奨されています。

date-time = [ day-of-week "," ] day month year hour ":" minute [ ":" second ] FWS zone

例: Fri, 21 Nov 1997 09:55:06 -0600

ここで zone は、UTCからのオフセット(例: -0600)または特定のタイムゾーン略称(例: GMT, EST)で表されます。RFC 5322は、タイムゾーンの略称を括弧で囲む形式については明示的に言及していませんが、一部のシステムでは慣習的に使用されていました。

Go言語の time パッケージと日付フォーマット

Go言語の time パッケージは、日付と時刻を扱うための強力な機能を提供します。特に、time.Parse 関数は、指定されたレイアウト文字列に基づいて文字列から time.Time オブジェクトをパースするために使用されます。

Go言語の time パッケージにおけるレイアウト文字列は、特殊な参照時刻 Mon Jan 2 15:04:05 MST 2006 を使用して定義されます。この参照時刻の各要素が、パースまたはフォーマットしたい日付/時刻文字列の対応する要素にマッピングされます。

  • Mon: 曜日 (例: Mon, Tue)
  • Jan: 月 (例: Jan, Feb)
  • 2: 日 (例: 1, 02)
  • 15: 時 (24時間形式)
  • 04: 分
  • 05: 秒
  • MST: タイムゾーン略称 (例: MST, GMT)
  • 2006: 年 (4桁)
  • -0700: タイムゾーンオフセット (例: -0700, +0800)

net/mail パッケージは、Date ヘッダーをパースするために、RFC 5322で定義されている様々な日付フォーマットに対応する複数のレイアウト文字列を内部的に保持しています。これらのレイアウト文字列を順に試行し、最初にマッチしたものでパースを試みます。

技術的詳細

このコミットの技術的な核心は、src/pkg/net/mail/message.go ファイル内の dateLayouts スライスを生成するロジックに、新しいタイムゾーンのフォーマットを追加した点です。

元のコードでは、タイムゾーンの表現として "-0700" (UTCからのオフセット) と "MST" (タイムゾーン略称) の2種類を考慮していました。しかし、Go issue #3639 で報告された問題は、"-0600 (MDT)" のように、オフセットの後に括弧で囲まれたタイムゾーン略称が続く形式がパースできないことでした。

このコミットでは、zones 配列に "-0700 (MST)" という新しい要素を追加しています。これにより、dateLayouts が生成される際に、この新しいタイムゾーン形式を含む日付レイアウト文字列も含まれるようになります。

具体的には、init() 関数内で dateLayouts を生成する際に、dows, days, months, years, hours, minutes, seconds, zones の各要素を組み合わせて可能な日付レイマットを網羅的に生成しています。zones"-0700 (MST)" が追加されたことで、net/mail はこの形式の Date ヘッダーも認識し、正しくパースできるようになります。

また、src/pkg/net/mail/message_test.go には、この新しいフォーマットをテストするための新しいテストケースが追加されています。これは、"Fri, 21 Nov 1997 09:55:06 -0600 (MDT)" という文字列が、期待される time.Time オブジェクトに正しくパースされることを検証します。このテストケースの追加は、変更が意図通りに機能し、将来のリグレッションを防ぐ上で非常に重要です。

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

diff --git a/src/pkg/net/mail/message.go b/src/pkg/net/mail/message.go
index 0917bbedf1..b610ccf3f0 100644
--- a/src/pkg/net/mail/message.go
+++ b/src/pkg/net/mail/message.go
@@ -69,11 +69,12 @@ var dateLayouts []string
 func init() {
 	// Generate layouts based on RFC 5322, section 3.3.
 
-\tdows := [...]string{\"\", \"Mon, \"}     // day-of-week
-\tdays := [...]string{\"2\", \"02\"}       // day = 1*2DIGIT
-\tyears := [...]string{\"2006\", \"06\"}   // year = 4*DIGIT / 2*DIGIT
-\tseconds := [...]string{\":05\", \"\"}    // second
-\tzones := [...]string{\"-0700\", \"MST\"} // zone = ((\"+\" / \"-\") 4DIGIT) / \"GMT\" / ...
+\tdows := [...]string{\"\", \"Mon, \"}   // day-of-week
+\tdays := [...]string{\"2\", \"02\"}     // day = 1*2DIGIT
+\tyears := [...]string{\"2006\", \"06\"} // year = 4*DIGIT / 2*DIGIT
+\tseconds := [...]string{\":05\", \"\"}  // second
+\t// \"-0700 (MST)\" is not in RFC 5322, but is common.
+\tzones := [...]string{\"-0700\", \"MST\", \"-0700 (MST)\"} // zone = ((\"+\" / \"-\") 4DIGIT) / \"GMT\" / ...
 
 	for _, dow := range dows {
 	\tfor _, day := range days {
diff --git a/src/pkg/net/mail/message_test.go b/src/pkg/net/mail/message_test.go
index 671ff2efac..fd17eb414a 100644
--- a/src/pkg/net/mail/message_test.go
+++ b/src/pkg/net/mail/message_test.go
@@ -95,6 +95,11 @@ func TestDateParsing(t *testing.T) {\n \t\t\t\"21 Nov 97 09:55:06 GMT\",\n \t\t\ttime.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone(\"GMT\", 0)),\n \t\t},\n+\t\t// Commonly found format not specified by RFC 5322.\n+\t\t{\n+\t\t\t\"Fri, 21 Nov 1997 09:55:06 -0600 (MDT)\",\n+\t\t\ttime.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone(\"\", -6*60*60)),\n+\t\t},\n \t}\n \tfor _, test := range tests {\n \t\thdr := Header{\n```

## コアとなるコードの解説

### `src/pkg/net/mail/message.go` の変更

`zones` 配列の定義が変更されました。

```go
-	zones := [...]string{"-0700", "MST"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ...
+	// "-0700 (MST)" is not in RFC 5322, but is common.
+	zones := [...]string{"-0700", "MST", "-0700 (MST)"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ...

この変更により、zones 配列に "-0700 (MST)" という新しい文字列が追加されました。これは、タイムゾーンのオフセットの後に括弧で囲まれたタイムゾーン略称が続く形式を表しています。init() 関数内で dateLayouts が生成される際、この新しい zones の要素が他の日付/時刻要素と組み合わされ、net/mail がパースを試みる日付レイアウトの候補に追加されます。これにより、RFC 5322では厳密に定義されていないが、実世界で広く使用されている日付フォーマットに対応できるようになります。コメントで「RFC 5322にはないが一般的」と明記されている点が、この変更の意図を明確に示しています。

src/pkg/net/mail/message_test.go の変更

TestDateParsing 関数に新しいテストケースが追加されました。

+		// Commonly found format not specified by RFC 5322.
+		{
+			"Fri, 21 Nov 1997 09:55:06 -0600 (MDT)",
+			time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
+		},

このテストケースは、"Fri, 21 Nov 1997 09:55:06 -0600 (MDT)" という文字列が、net/mail パッケージによって正しくパースされることを検証します。期待される結果は、time.Date 関数で生成された time.Time オブジェクトであり、タイムゾーンは -0600 (MDT) に対応する固定ゾーンとして設定されています。このテストの追加は、message.go で行われた変更が期待通りに機能することを確認し、将来のコード変更によってこの機能が損なわれないようにするための重要なステップです。コメントで「RFC 5322で指定されていないが、一般的に見られるフォーマット」と説明されており、このテストケースがカバーする具体的なシナリオを明確にしています。

関連リンク

参考にした情報源リンク

  • Go issue #3639 の内容 (コミットメッセージから参照)
  • RFC 5322 の日付フォーマットに関する一般的な知識
  • Go言語の time パッケージのドキュメントに関する一般的な知識
  • コミットの差分情報