1
0
Fork 0
mirror of https://git.sr.ht/~rjarry/aerc synced 2025-07-12 03:00:21 +02:00
aerc/worker/jmap/threads.go
Robin Jarry 985ce7a92b jmap: explicitly fetch body structure subparts
When requesting the bodyStructure email property, the spec says that the
default returned body part properties are: ["partId", "blobId", "size",
"name", "type", "charset", "disposition", "cid", "language",
"location"].

Specifically, the "subParts" property is *NOT* expected by default.

Fastmail servers seem to use a different default "bodyProperties" list
and implicitly return "subParts" even if not requested.

Other JMAP server implementations (e.g. Apache James) do use the RFC
default "bodyProperties" and therefore omit returning "subParts" unless
explicitly asked to.

Change the requested "bodyProperties" to include "subParts" as well.
Aerc needs them to display messages.

NB: for later, we should probably change our message abstraction not to
include any body structure. This makes very little sense to expose that
to users. In fact, aerc has code to explicitly prevent users from
selecting multipart/* parts. Not requesting that information to the
server would make it easier.

Link: https://datatracker.ietf.org/doc/html/rfc8621#section-4.2
Reported-by: Benoit Tellier <btellier@linagora.com>
Signed-off-by: Robin Jarry <robin@jarry.cc>
Reviewed-by: Tim Culverhouse <tim@timculverhouse.com>
2025-02-11 21:02:24 +01:00

64 lines
1.6 KiB
Go

package jmap
import (
"git.sr.ht/~rockorager/go-jmap"
"git.sr.ht/~rockorager/go-jmap/mail/email"
"git.sr.ht/~rockorager/go-jmap/mail/thread"
)
func (w *JMAPWorker) fetchEntireThreads(threads []jmap.ID) ([]*email.Email, error) {
var req jmap.Request
if len(threads) == 0 {
return []*email.Email{}, nil
}
threadGetId := req.Invoke(&thread.Get{
Account: w.AccountId(),
IDs: threads,
})
// Opportunistically fetch all emails in this thread. We could wait for
// the result, check which ones we don't have, then fetch only those.
// However we can do this all in a single request which ends up being
// faster than two requests for most contexts
req.Invoke(&email.Get{
Account: w.AccountId(),
ReferenceIDs: &jmap.ResultReference{
ResultOf: threadGetId,
Name: "Thread/get",
Path: "/list/*/emailIds",
},
Properties: emailProperties,
BodyProperties: bodyProperties,
})
resp, err := w.Do(&req)
if err != nil {
return nil, err
}
emailsToReturn := make([]*email.Email, 0)
for _, inv := range resp.Responses {
switch r := inv.Args.(type) {
case *thread.GetResponse:
if err = w.cache.PutThreadState(r.State); err != nil {
w.w.Warnf("PutThreadState: %s", err)
}
for _, thread := range r.List {
if err = w.cache.PutThread(thread.ID, thread.EmailIDs); err != nil {
w.w.Warnf("PutThread: %s", err)
}
}
case *email.GetResponse:
emailsToReturn = append(emailsToReturn, r.List...)
if err = w.cache.PutEmailState(r.State); err != nil {
w.w.Warnf("PutEmailState: %s", err)
}
case *jmap.MethodError:
return nil, wrapMethodError(r)
}
}
return emailsToReturn, nil
}