aboutsummaryrefslogtreecommitdiffstats
path: root/worker/jmap/fetch.go
diff options
context:
space:
mode:
Diffstat (limited to 'worker/jmap/fetch.go')
-rw-r--r--worker/jmap/fetch.go196
1 files changed, 196 insertions, 0 deletions
diff --git a/worker/jmap/fetch.go b/worker/jmap/fetch.go
new file mode 100644
index 00000000..17b3fb2f
--- /dev/null
+++ b/worker/jmap/fetch.go
@@ -0,0 +1,196 @@
+package jmap
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+
+ "git.sr.ht/~rjarry/aerc/models"
+ "git.sr.ht/~rjarry/aerc/worker/types"
+ "git.sr.ht/~rockorager/go-jmap"
+ "git.sr.ht/~rockorager/go-jmap/mail/email"
+ "github.com/emersion/go-message/charset"
+)
+
+var headersProperties = []string{
+ "id",
+ "blobId",
+ "mailboxIds",
+ "keywords",
+ "size",
+ "receivedAt",
+ "headers",
+ "messageId",
+ "inReplyTo",
+ "references",
+ "from",
+ "to",
+ "cc",
+ "bcc",
+ "replyTo",
+ "subject",
+ "bodyStructure",
+}
+
+func (w *JMAPWorker) handleFetchMessageHeaders(msg *types.FetchMessageHeaders) error {
+ var req jmap.Request
+
+ ids := make([]jmap.ID, 0, len(msg.Uids))
+ for _, uid := range msg.Uids {
+ id, ok := w.uidStore.GetKey(uid)
+ if !ok {
+ return fmt.Errorf("bug: no jmap id for message uid: %v", uid)
+ }
+ jid := jmap.ID(id)
+ m, err := w.cache.GetEmail(jid)
+ if err == nil {
+ w.w.PostMessage(&types.MessageInfo{
+ Message: types.RespondTo(msg),
+ Info: w.translateMsgInfo(m),
+ }, nil)
+ continue
+ }
+ ids = append(ids, jid)
+ }
+
+ if len(ids) == 0 {
+ return nil
+ }
+
+ req.Invoke(&email.Get{
+ Account: w.accountId,
+ IDs: ids,
+ Properties: headersProperties,
+ })
+
+ resp, err := w.Do(&req)
+ if err != nil {
+ return err
+ }
+
+ for _, inv := range resp.Responses {
+ switch r := inv.Args.(type) {
+ case *email.GetResponse:
+ for _, m := range r.List {
+ w.w.PostMessage(&types.MessageInfo{
+ Message: types.RespondTo(msg),
+ Info: w.translateMsgInfo(m),
+ }, nil)
+ if err := w.cache.PutEmail(m.ID, m); err != nil {
+ w.w.Warnf("PutEmail: %s", err)
+ }
+ }
+ if err = w.cache.PutEmailState(r.State); err != nil {
+ w.w.Warnf("PutEmailState: %s", err)
+ }
+ case *jmap.MethodError:
+ return wrapMethodError(r)
+ }
+ }
+
+ return nil
+}
+
+func (w *JMAPWorker) handleFetchMessageBodyPart(msg *types.FetchMessageBodyPart) error {
+ id, ok := w.uidStore.GetKey(msg.Uid)
+ if !ok {
+ return fmt.Errorf("bug: unknown message uid %d", msg.Uid)
+ }
+ mail, err := w.cache.GetEmail(jmap.ID(id))
+ if err != nil {
+ return fmt.Errorf("bug: unknown message id %s: %w", id, err)
+ }
+
+ part := mail.BodyStructure
+ for i, index := range msg.Part {
+ index -= 1 // convert to zero based offset
+ if index < len(part.SubParts) {
+ part = part.SubParts[index]
+ } else {
+ return fmt.Errorf(
+ "bug: invalid part index[%d]: %v", i, msg.Part)
+ }
+ }
+
+ buf, err := w.cache.GetBlob(part.BlobID)
+ if err != nil {
+ rd, err := w.Download(part.BlobID)
+ if err != nil {
+ return w.wrapDownloadError("part", part.BlobID, err)
+ }
+ buf, err = io.ReadAll(rd)
+ rd.Close()
+ if err != nil {
+ return err
+ }
+ if err = w.cache.PutBlob(part.BlobID, buf); err != nil {
+ w.w.Warnf("PutBlob: %s", err)
+ }
+ }
+ var reader io.Reader = bytes.NewReader(buf)
+ if strings.HasPrefix(part.Type, "text/") && part.Charset != "" {
+ r, err := charset.Reader(part.Charset, reader)
+ if err != nil {
+ return fmt.Errorf("charset.Reader: %w", err)
+ }
+ reader = r
+ }
+ w.w.PostMessage(&types.MessageBodyPart{
+ Message: types.RespondTo(msg),
+ Part: &models.MessageBodyPart{
+ Reader: reader,
+ Uid: msg.Uid,
+ },
+ }, nil)
+
+ return nil
+}
+
+func (w *JMAPWorker) handleFetchFullMessages(msg *types.FetchFullMessages) error {
+ for _, uid := range msg.Uids {
+ id, ok := w.uidStore.GetKey(uid)
+ if !ok {
+ return fmt.Errorf("bug: unknown message uid %d", uid)
+ }
+ mail, err := w.cache.GetEmail(jmap.ID(id))
+ if err != nil {
+ return fmt.Errorf("bug: unknown message id %s: %w", id, err)
+ }
+ buf, err := w.cache.GetBlob(mail.BlobID)
+ if err != nil {
+ rd, err := w.Download(mail.BlobID)
+ if err != nil {
+ return w.wrapDownloadError("full", mail.BlobID, err)
+ }
+ buf, err = io.ReadAll(rd)
+ rd.Close()
+ if err != nil {
+ return err
+ }
+ if err = w.cache.PutBlob(mail.BlobID, buf); err != nil {
+ w.w.Warnf("PutBlob: %s", err)
+ }
+ }
+ w.w.PostMessage(&types.FullMessage{
+ Message: types.RespondTo(msg),
+ Content: &models.FullMessage{
+ Reader: bytes.NewReader(buf),
+ Uid: uid,
+ },
+ }, nil)
+ }
+
+ return nil
+}
+
+func (w *JMAPWorker) wrapDownloadError(prefix string, blobId jmap.ID, err error) error {
+ urlRepl := strings.NewReplacer(
+ "{accountId}", string(w.accountId),
+ "{blobId}", string(blobId),
+ "{type}", "application/octet-stream",
+ "{name}", "filename",
+ )
+ url := urlRepl.Replace(w.client.Session.DownloadURL)
+ return fmt.Errorf("%s: %q %w", prefix, url, err)
+}