fix(email): preserve timezone when rebuilding Date headers (#355)

This commit is contained in:
0clickjacking0 2026-08-17 10:39:30 +08:00 committed by GitHub
parent 430c9c6c10
commit cd8d549a90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 99 additions and 5 deletions

View File

@ -234,7 +234,7 @@ func NewEmailFromModel(d models.Email) *Email {
Bcc: Bcc,
Cc: Cc,
Attachments: Attachments,
Date: d.SendDate.Format("2006-01-02 15:04:05"),
Date: d.SendDate.Format(time.RFC3339),
}
if d.Type == 0 {
ret.Authentication = NewEmailAuthentication(d.SPFCheck == 1, d.DKIMCheck == 1)
@ -290,7 +290,7 @@ func NewEmailFromReader(to []string, r io.Reader, size int) *Email {
if err != nil {
sendTime = time.Now()
}
ret.Date = sendTime.Format(time.DateTime)
ret.Date = sendTime.Format(time.RFC3339)
m.Walk(func(path []int, entity *message.Entity, err error) error {
return formatContent(entity, ret)
})
@ -614,7 +614,11 @@ func (e *Email) BuildBytes(ctx *context.Context, dkim bool) []byte {
// Create our mail header
var h mail.Header
if e.Date != "" {
t, err := time.ParseInLocation("2006-01-02 15:04:05", e.Date, time.Local)
t, err := time.Parse(time.RFC3339, e.Date)
if err != nil {
// Keep compatibility with Date values stored before timezone offsets were preserved.
t, err = time.ParseInLocation(time.DateTime, e.Date, time.Local)
}
if err != nil {
log.WithContext(ctx).Errorf("Time Error ! Err:%+v", err)
h.SetDate(time.Now())

View File

@ -3,13 +3,103 @@ package parsemail
import (
"bytes"
"fmt"
"github.com/emersion/go-message"
"io"
stdmail "net/mail"
"strings"
"testing"
"time"
"github.com/Jinnrry/pmail/models"
"github.com/emersion/go-message"
)
func builtMessageDate(t *testing.T, raw []byte) time.Time {
t.Helper()
m, err := stdmail.ReadMessage(bytes.NewReader(raw))
if err != nil {
t.Fatalf("read built message: %v", err)
}
date, err := stdmail.ParseDate(m.Header.Get("Date"))
if err != nil {
t.Fatalf("parse built Date header %q: %v", m.Header.Get("Date"), err)
}
return date
}
func TestEmailDateFromReaderPreservesInstant(t *testing.T) {
const dateHeader = "Sun, 09 Aug 2026 11:51:08 +0000"
raw := []byte("From: sender@example.com\r\n" +
"To: recipient@example.com\r\n" +
"Subject: Date round trip\r\n" +
"Date: " + dateHeader + "\r\n" +
"Message-ID: <date-reader@example.com>\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n\r\n" +
"body")
expected, err := stdmail.ParseDate(dateHeader)
if err != nil {
t.Fatalf("parse fixture date: %v", err)
}
email := NewEmailFromReader([]string{"recipient@example.com"}, bytes.NewReader(raw), len(raw))
stored, err := time.Parse(time.RFC3339, email.Date)
if err != nil {
t.Fatalf("stored Date is not RFC3339: %q: %v", email.Date, err)
}
if !stored.Equal(expected) {
t.Fatalf("stored Date changed instant: got %s, want %s", stored, expected)
}
got := builtMessageDate(t, email.BuildBytes(nil, false))
if !got.Equal(expected) {
t.Fatalf("rebuilt Date changed instant: got %s, want %s", got, expected)
}
}
func TestEmailDateFromModelPreservesUTCInstant(t *testing.T) {
expected := time.Date(2026, time.August, 9, 11, 51, 8, 0, time.UTC)
email := NewEmailFromModel(models.Email{
Id: 1,
FromAddress: "sender@example.com",
To: `[{"EmailAddress":"recipient@example.com","Name":""}]`,
Subject: "Model date",
SendDate: expected,
MsgID: "date-model@example.com",
})
if email.Date != expected.Format(time.RFC3339) {
t.Fatalf("model Date = %q, want %q", email.Date, expected.Format(time.RFC3339))
}
got := builtMessageDate(t, email.BuildBytes(nil, false))
if !got.Equal(expected) {
t.Fatalf("rebuilt model Date changed instant: got %s, want %s", got, expected)
}
}
func TestEmailBuildBytesAcceptsLegacyDate(t *testing.T) {
const legacyDate = "2026-08-09 11:51:08"
expected, err := time.ParseInLocation(time.DateTime, legacyDate, time.Local)
if err != nil {
t.Fatalf("parse legacy fixture date: %v", err)
}
email := &Email{
From: buildUser("sender@example.com"),
To: buildUsers([]string{"recipient@example.com"}),
Subject: "Legacy date",
Text: []byte("body"),
Date: legacyDate,
MsgID: "date-legacy@example.com",
}
got := builtMessageDate(t, email.BuildBytes(nil, false))
if !got.Equal(expected) {
t.Fatalf("legacy Date changed instant: got %s, want %s", got, expected)
}
}
func TestHtmlTxtAttachment(t *testing.T) {
emailBytes := `From: "=?utf-8?B?amlubnJyeQ==?=" <ok@xjiangwei.cn>
To: "=?utf-8?B?YWRtaW4=?=" <admin@jinnrry.com>