package main import ( "bufio" "errors" "io" "syscall" "time" "encoding/json" "github.com/emersion/go-imap" "github.com/emersion/go-imap/backend/backendutil" "github.com/emersion/go-message" "github.com/emersion/go-message/textproto" "github.com/google/uuid" ) type Message struct { Id uuid.UUID Uid uint32 Date time.Time Size uint32 Flags []string Body io.ReadCloser Mailbox *Mailbox } func (m *Message) Close() (err error) { err = m.Body.Close() if err != nil { if errors.Is(err, syscall.EINVAL) { err = nil delete(m.Mailbox.user.openMessages, m) } } else { delete(m.Mailbox.user.openMessages, m) } return err } type Literal struct { UnderlyingReader io.ReadCloser Message *Message } func (l *Literal) Len() int { return int(l.Message.Size) } func (l *Literal) Read(p []byte) (n int, err error) { return l.UnderlyingReader.Read(p) } func (m *Message) getBody() imap.Literal { return &Literal{ UnderlyingReader: m.Body, Message: m, } } func (m *Message) entity() (*message.Entity, error) { return message.Read(m.Body) } func (m *Message) header() (textproto.Header, error) { hdr, err := textproto.ReadHeader(bufio.NewReader(m.Body)) return hdr, err } func (m *Message) Fetch(seqNum uint32, items []imap.FetchItem) (*imap.Message, error) { fetched := imap.NewMessage(seqNum, items) for _, item := range items { switch item { case imap.FetchEnvelope: hdr, _ := m.header() fetched.Envelope, _ = backendutil.FetchEnvelope(hdr) case imap.FetchBody, imap.FetchBodyStructure: hdr, _ := m.header() fetched.BodyStructure, _ = backendutil.FetchBodyStructure(hdr, m.Body, item == imap.FetchBodyStructure) case imap.FetchFlags: fetched.Flags = m.Flags case imap.FetchInternalDate: fetched.InternalDate = m.Date case imap.FetchRFC822Size: fetched.Size = m.Size case imap.FetchUid: fetched.Uid = m.Uid default: section, err := imap.ParseBodySectionName(item) if err != nil { break } body := bufio.NewReader(m.Body) hdr, err := textproto.ReadHeader(body) if err != nil { return nil, err } l, _ := backendutil.FetchBodySection(hdr, m.Body, section) fetched.Body[section] = l } } return fetched, nil } func (m *Message) Match(seqNum uint32, c *imap.SearchCriteria) (bool, error) { e, _ := m.entity() return backendutil.Match(e, seqNum, m.Uid, m.Date, m.Flags, c) } func LoadRawMessage(idRaw []byte, uid uint32, date time.Time, size uint32, flagsRaw string, ownerRaw []byte, mailbox *Mailbox) (*Message, error) { msg := &Message{ Uid: uid, Date: date, Size: size, Mailbox: mailbox, } mailbox.user.openMessages[msg] = struct{}{} err := json.Unmarshal([]byte(flagsRaw), &msg.Flags) if err != nil { return nil, err } msg.Id, err = uuid.FromBytes(idRaw) if err != nil { return nil, err } owner, err := uuid.FromBytes(ownerRaw) if err != nil { return nil, err } file, err := GetFile(msg.Id.String(), owner) if err != nil { return nil, err } msg.Body = file return msg, nil }