Skip to content

Commit a08cb9f

Browse files
iwdgoianlancetaylor
authored andcommitted
net/mail: added support to trailing CFWS in date
RFC 5322 date format allows CFWS after the timezone. If CFWS is valid, it is discarded and parsing is done as before using time.Parse(). Existing test is extended with limit cases and invalid strings. Fixes #22661 Change-Id: I54b96d7bc384b751962a76690e7e4786217a7941 Reviewed-on: https://go-review.googlesource.com/c/go/+/117596 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent ba108c9 commit a08cb9f

File tree

2 files changed

+169
-1
lines changed

2 files changed

+169
-1
lines changed

src/net/mail/message.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func buildDateLayouts() {
7979
years := [...]string{"2006", "06"} // year = 4*DIGIT / 2*DIGIT
8080
seconds := [...]string{":05", ""} // second
8181
// "-0700 (MST)" is not in RFC 5322, but is common.
82-
zones := [...]string{"-0700", "MST", "-0700 (MST)"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ...
82+
zones := [...]string{"-0700", "MST"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ...
8383

8484
for _, dow := range dows {
8585
for _, day := range days {
@@ -98,6 +98,29 @@ func buildDateLayouts() {
9898
// ParseDate parses an RFC 5322 date string.
9999
func ParseDate(date string) (time.Time, error) {
100100
dateLayoutsBuildOnce.Do(buildDateLayouts)
101+
// CR and LF must match and are tolerated anywhere in the date field.
102+
date = strings.ReplaceAll(date, "\r\n", "")
103+
if strings.Index(date, "\r") != -1 {
104+
return time.Time{}, errors.New("mail: header has a CR without LF")
105+
}
106+
// Re-using some addrParser methods which support obsolete text, i.e. non-printable ASCII
107+
p := addrParser{date, nil}
108+
p.skipSpace()
109+
110+
// RFC 5322: zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone
111+
// zone length is always 5 chars unless obsolete (obs-zone)
112+
if ind := strings.IndexAny(p.s, "+-"); ind != -1 && len(p.s) >= ind+5 {
113+
date = p.s[:ind+5]
114+
p.s = p.s[ind+5:]
115+
} else if ind := strings.Index(p.s, "T"); ind != -1 && len(p.s) >= ind+1 {
116+
// The last letter T of the obsolete time zone is checked when no standard time zone is found.
117+
// If T is misplaced, the date to parse is garbage.
118+
date = p.s[:ind+1]
119+
p.s = p.s[ind+1:]
120+
}
121+
if !p.skipCFWS() {
122+
return time.Time{}, errors.New("mail: misformatted parenthetical comment")
123+
}
101124
for _, layout := range dateLayouts {
102125
t, err := time.Parse(layout, date)
103126
if err == nil {

src/net/mail/message_test.go

+145
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,151 @@ func TestDateParsing(t *testing.T) {
124124
}
125125
}
126126

127+
func TestDateParsingCFWS(t *testing.T) {
128+
tests := []struct {
129+
dateStr string
130+
exp time.Time
131+
valid bool
132+
}{
133+
// FWS-only. No date.
134+
{
135+
" ",
136+
// nil is not allowed
137+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
138+
false,
139+
},
140+
// FWS is allowed before optional day of week.
141+
{
142+
" Fri, 21 Nov 1997 09:55:06 -0600",
143+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
144+
true,
145+
},
146+
{
147+
"21 Nov 1997 09:55:06 -0600",
148+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
149+
true,
150+
},
151+
{
152+
"Fri 21 Nov 1997 09:55:06 -0600",
153+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
154+
false, // missing ,
155+
},
156+
// FWS is allowed before day of month but HTAB fails.
157+
{
158+
"Fri, 21 Nov 1997 09:55:06 -0600",
159+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
160+
true,
161+
},
162+
// FWS is allowed before and after year but HTAB fails.
163+
{
164+
"Fri, 21 Nov 1997 09:55:06 -0600",
165+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
166+
true,
167+
},
168+
// FWS is allowed before zone but HTAB is not handled. Obsolete timezone is handled.
169+
{
170+
"Fri, 21 Nov 1997 09:55:06 CST",
171+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("CST", 0)),
172+
true,
173+
},
174+
// FWS is allowed after date and a CRLF is already replaced.
175+
{
176+
"Fri, 21 Nov 1997 09:55:06 CST (no leading FWS and a trailing CRLF) \r\n",
177+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("CST", 0)),
178+
true,
179+
},
180+
// CFWS is a reduced set of US-ASCII where space and accentuated are obsolete. No error.
181+
{
182+
"Fri, 21 Nov 1997 09:55:06 -0600 (MDT and non-US-ASCII signs éèç )",
183+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
184+
true,
185+
},
186+
// CFWS is allowed after zone including a nested comment.
187+
// Trailing FWS is allowed.
188+
{
189+
"Fri, 21 Nov 1997 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
190+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
191+
true,
192+
},
193+
// CRLF is incomplete and misplaced.
194+
{
195+
"Fri, 21 Nov 1997 \r 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
196+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
197+
false,
198+
},
199+
// CRLF is complete but misplaced. No error is returned.
200+
{
201+
"Fri, 21 Nov 199\r\n7 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
202+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
203+
true, // should be false in the strict interpretation of RFC 5322.
204+
},
205+
// Invalid ASCII in date.
206+
{
207+
"Fri, 21 Nov 1997 ù 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
208+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
209+
false,
210+
},
211+
// CFWS chars () in date.
212+
{
213+
"Fri, 21 Nov () 1997 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
214+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
215+
false,
216+
},
217+
// Timezone is invalid but T is found in comment.
218+
{
219+
"Fri, 21 Nov 1997 09:55:06 -060 \r\n (Thisisa(valid)cfws) \t ",
220+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
221+
false,
222+
},
223+
// Date has no month.
224+
{
225+
"Fri, 21 1997 09:55:06 -0600",
226+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
227+
false,
228+
},
229+
// Invalid month : OCT iso Oct
230+
{
231+
"Fri, 21 OCT 1997 09:55:06 CST",
232+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
233+
false,
234+
},
235+
// A too short time zone.
236+
{
237+
"Fri, 21 Nov 1997 09:55:06 -060",
238+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
239+
false,
240+
},
241+
// A too short obsolete time zone.
242+
{
243+
"Fri, 21 1997 09:55:06 GT",
244+
time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
245+
false,
246+
},
247+
}
248+
for _, test := range tests {
249+
hdr := Header{
250+
"Date": []string{test.dateStr},
251+
}
252+
date, err := hdr.Date()
253+
if err != nil && test.valid {
254+
t.Errorf("Header(Date: %s).Date(): %v", test.dateStr, err)
255+
} else if err == nil && !date.Equal(test.exp) && test.valid {
256+
t.Errorf("Header(Date: %s).Date() = %+v, want %+v", test.dateStr, date, test.exp)
257+
} else if err == nil && !test.valid { // an invalid expression was tested
258+
t.Errorf("Header(Date: %s).Date() did not return an error but %v", test.dateStr, date)
259+
}
260+
261+
date, err = ParseDate(test.dateStr)
262+
if err != nil && test.valid {
263+
t.Errorf("ParseDate(%s): %v", test.dateStr, err)
264+
} else if err == nil && !test.valid { // an invalid expression was tested
265+
t.Errorf("ParseDate(%s) did not return an error but %v", test.dateStr, date)
266+
} else if err == nil && test.valid && !date.Equal(test.exp) {
267+
t.Errorf("ParseDate(%s) = %+v, want %+v", test.dateStr, date, test.exp)
268+
}
269+
}
270+
}
271+
127272
func TestAddressParsingError(t *testing.T) {
128273
mustErrTestCases := [...]struct {
129274
text string

0 commit comments

Comments
 (0)