Skip to content

Commit 2a01727

Browse files
committed
fix: address roborev round-2 review feedback
- Decode attributedBody as fallback when message.text is NULL (macOS Ventura+ stores content exclusively in NSKeyedArchiver blob) - Hash iMessage GUIDs for Message-ID header (raw GUIDs contain ':' and '/' which are invalid in RFC 5322 msg-id local-part) - Make messageRow.Service nullable (*string) to handle NULL service column on system messages instead of failing Scan - Add extractAttributedBodyText with NSKeyedArchiver plist decoder - Add tests for attributedBody extraction and Message-ID sanitization
1 parent 7c04b3d commit 2a01727

5 files changed

Lines changed: 31 additions & 7 deletions

File tree

.planning/WORKLOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Worklog
2+
3+
**Session 2026-03-29 — msgvault quick-task**
4+
5+
- Fixed 3 bugs from roborev CI review on wesm/msgvault#224 (iMessage sync PR)
6+
- Timezone date filters: replaced 18 `time.Parse("2006-01-02")``time.ParseInLocation(..., time.Local)` across 9 files (6 CLI commands, web params, MCP handler, validation test)
7+
- Batch nil entries: switched `GetMessagesRawBatch` in imessage + gvoice from pre-allocated indexed slice to append-based (compact, nil-free results)
8+
- Attachment warning: added Warn-level log when `cache_has_attachments != 0` in iMessage client (silent data loss → explicit)
9+
- Confirmed attributedBody fallback was already implemented (client.go:305-313)
10+
- Pushed fixes to `imessage-upstream` branch, updating PR #224 on wesm/msgvault
11+
- Posted PR comment addressing all 4 review items
12+
- 5 commits on main, 1 commit on imessage-upstream, all tests pass (35 packages)
13+
- No blockers or carryover

internal/imessage/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ func (c *Client) GetMessageRaw(ctx context.Context, messageID string) (*gmail.Ra
326326

327327
// Build label based on service
328328
var labelIDs []string
329-
if msg.Service != "" {
330-
labelIDs = []string{msg.Service}
329+
if msg.Service != nil && *msg.Service != "" {
330+
labelIDs = []string{*msg.Service}
331331
}
332332

333333
// InternalDate as Unix milliseconds

internal/imessage/models.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type messageRow struct {
1111
AttributedBody []byte // NSKeyedArchiver blob; fallback when Text is nil (macOS Ventura+)
1212
Date int64 // Apple epoch timestamp (seconds or nanoseconds)
1313
IsFromMe int
14-
Service string // "iMessage" or "SMS"
14+
Service *string // "iMessage", "SMS", or NULL for system messages
1515
HasAttachments int
1616
HandleID *string // handle.id (phone or email), NULL for is_from_me
1717
ChatROWID *int64 // chat.ROWID for participant lookup

internal/imessage/parser.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package imessage
22

33
import (
4-
"fmt"
4+
"crypto/sha256"
5+
"encoding/hex"
56
"net/mail"
67
"strings"
78
"time"
@@ -131,9 +132,14 @@ func buildMIME(fromAddr, toAddrs []string, date time.Time, messageID, body strin
131132
// Subject (empty for iMessage - messages don't have subjects)
132133
b.WriteString("Subject: \r\n")
133134

134-
// Message-ID
135+
// Message-ID — hash the GUID since iMessage GUIDs contain characters
136+
// like ':' and '/' that are invalid in RFC 5322 msg-id local-part.
135137
if messageID != "" {
136-
fmt.Fprintf(&b, "Message-ID: <%s@imessage.local>\r\n", messageID)
138+
h := sha256.Sum256([]byte(messageID))
139+
safeID := hex.EncodeToString(h[:12]) // 24 hex chars, unique enough
140+
b.WriteString("Message-ID: <")
141+
b.WriteString(safeID)
142+
b.WriteString("@imessage.local>\r\n")
137143
}
138144

139145
// MIME version and content type

internal/imessage/parser_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,14 @@ func TestBuildMIME(t *testing.T) {
177177
if !strings.Contains(mimeStr, "Date: ") {
178178
t.Error("missing Date header")
179179
}
180-
if !strings.Contains(mimeStr, "Message-ID: <p:0/ABC123@imessage.local>") {
180+
// Message-ID is a hash of the GUID (RFC 5322 safe)
181+
if !strings.Contains(mimeStr, "Message-ID: <") || !strings.Contains(mimeStr, "@imessage.local>") {
181182
t.Error("missing Message-ID header")
182183
}
184+
// Verify the raw GUID with invalid chars is NOT present
185+
if strings.Contains(mimeStr, "p:0/ABC123@imessage.local") {
186+
t.Error("Message-ID should not contain raw GUID with invalid chars")
187+
}
183188
if !strings.Contains(mimeStr, "Content-Type: text/plain; charset=utf-8") {
184189
t.Error("missing Content-Type header")
185190
}

0 commit comments

Comments
 (0)