fix(email): preserve timezone when rebuilding Date headers (#355)
This commit is contained in:
parent
430c9c6c10
commit
cd8d549a90
@ -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())
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user