diff --git a/CHANGELOG.md b/CHANGELOG.md index dc6f696..2845672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Backup: add Gmail message-list checkpoints, streaming shard construction, and stderr progress counters so full-mailbox backups can resume cleanly after interruption without keeping every raw message in RAM. ### Fixed +- Gmail: build outbound `Date` headers with the configured timezone so replies do not inherit a wrong host-local offset. (#514, #472) — thanks @dinakars777. - Gmail: preserve renewed watch expiration fields when a long-running `gmail watch serve` process records push delivery state after `gmail watch renew` runs separately. (#526) - Gmail: auto-fill draft reply subjects from the original message when `gmail drafts create --reply-to-message-id` omits `--subject`. (#488) — thanks @jbowerbir. - Drive: include `hasThumbnail` and `thumbnailLink` in `drive ls`, `drive search`, and `drive get` JSON responses. (#486) — thanks @gtapps. diff --git a/internal/cmd/gmail_mime.go b/internal/cmd/gmail_mime.go index c37dfb6..958cbd9 100644 --- a/internal/cmd/gmail_mime.go +++ b/internal/cmd/gmail_mime.go @@ -86,7 +86,11 @@ func buildRFC822(opts mailOptions, cfg *rfc822Config) ([]byte, error) { return nil, fmt.Errorf("invalid Subject: %w", err) } writeHeader(&b, "Subject", encodeHeaderIfNeeded(opts.Subject)) - writeHeader(&b, "Date", time.Now().Format(time.RFC1123Z)) + dateLocation, err := mailDateLocation() + if err != nil { + return nil, err + } + writeHeader(&b, "Date", time.Now().In(dateLocation).Format(time.RFC1123Z)) if !hasHeader(opts.AdditionalHeaders, "Message-ID") && !hasHeader(opts.AdditionalHeaders, "Message-Id") { messageID, err := randomMessageID(opts.From) if err != nil { @@ -211,6 +215,17 @@ func buildRFC822(opts mailOptions, cfg *rfc822Config) ([]byte, error) { return b.Bytes(), nil } +func mailDateLocation() (*time.Location, error) { + loc, err := getConfiguredTimezone("") + if err != nil { + return nil, err + } + if loc != nil { + return loc, nil + } + return time.Local, nil +} + func writeHeader(b *bytes.Buffer, name, value string) { b.WriteString(name) b.WriteString(": ") diff --git a/internal/cmd/gmail_mime_more_test.go b/internal/cmd/gmail_mime_more_test.go index 902e7c4..21949aa 100644 --- a/internal/cmd/gmail_mime_more_test.go +++ b/internal/cmd/gmail_mime_more_test.go @@ -1,10 +1,12 @@ package cmd import ( + "net/mail" "os" "path/filepath" "strings" "testing" + "time" ) func TestBuildRFC822_MissingFields(t *testing.T) { @@ -33,6 +35,32 @@ func TestBuildRFC822_AllowMissingTo(t *testing.T) { } } +func TestBuildRFC822_DateHeaderUsesConfiguredTimezone(t *testing.T) { + oldLocal := time.Local + time.Local = time.FixedZone("JST", 9*60*60) + t.Cleanup(func() { time.Local = oldLocal }) + t.Setenv("GOG_TIMEZONE", "UTC") + + raw, err := buildRFC822(mailOptions{ + From: "a@b.com", + To: []string{"c@d.com"}, + Subject: "Hi", + Body: "Hello", + }, nil) + if err != nil { + t.Fatalf("buildRFC822: %v", err) + } + + msg, err := mail.ReadMessage(strings.NewReader(string(raw))) + if err != nil { + t.Fatalf("ReadMessage: %v", err) + } + date := msg.Header.Get("Date") + if !strings.HasSuffix(date, "+0000") { + t.Fatalf("expected UTC Date header, got %q", date) + } +} + func TestBuildRFC822_InvalidHeaders(t *testing.T) { if _, err := buildRFC822(mailOptions{ From: "a@b.com\r\nBcc: evil@evil.com",