package models
import (
"errors"
"fmt"
"io"
"strings"
"time"
"git.sr.ht/~rjarry/aerc/lib/parse"
"github.com/emersion/go-message/mail"
)
// Flags is an abstraction around the different flags which can be present in
// different email backends and represents a flag that we use in the UI.
type Flags uint32
const (
// SeenFlag marks a message as having been seen previously
SeenFlag Flags = 1 << iota
// RecentFlag marks a message as being recent
RecentFlag
// AnsweredFlag marks a message as having been replied to
AnsweredFlag
// DeletedFlag marks a message as having been deleted
DeletedFlag
// FlaggedFlag marks a message with a user flag
FlaggedFlag
// DraftFlag marks a message as a draft
DraftFlag
)
func (f Flags) Has(flags Flags) bool {
return f&flags == flags
}
type Role string
var Roles = map[string]Role{
"all": AllRole,
"archive": ArchiveRole,
"drafts": DraftsRole,
"inbox": InboxRole,
"junk": JunkRole,
"sent": SentRole,
"trash": TrashRole,
"query": QueryRole,
}
const (
AllRole Role = "all"
ArchiveRole Role = "archive"
DraftsRole Role = "drafts"
InboxRole Role = "inbox"
JunkRole Role = "junk"
SentRole Role = "sent"
TrashRole Role = "trash"
// Custom aerc roles
QueryRole Role = "query"
// virtual node created by the directory tree
VirtualRole Role = "virtual"
)
type Directory struct {
Name string
// Exists messages in the Directory
Exists int
// Recent messages in the Directory
Recent int
// Unseen messages in the Directory
Unseen int
// IANA role
Role Role
}
type DirectoryInfo struct {
Name string
// The total number of messages in this mailbox.
Exists int
// The number of messages not seen since the last time the mailbox was opened.
Recent int
// The number of unread messages
Unseen int
}
// Capabilities provides the backend capabilities
type Capabilities struct {
Sort bool
Thread bool
}
// A MessageInfo holds information about the structure of a message
type MessageInfo struct {
BodyStructure *BodyStructure
Envelope *Envelope
Flags Flags
Labels []string
Filenames []string
InternalDate time.Time
RFC822Headers *mail.Header
Refs []string
Size uint32
Uid uint32
Error error
}
func (mi *MessageInfo) MsgId() (msgid string, err error) {
if mi == nil {
return "", errors.New("msg is nil")
}
if mi.Envelope == nil {
return "", errors.New("envelope is nil")
}
return mi.Envelope.MessageId, nil
}
func (mi *MessageInfo) InReplyTo() (msgid string, err error) {
if mi == nil {
return "", errors.New("msg is nil")
}
if mi.Envelope != nil && mi.Envelope.InReplyTo != "" {
return mi.Envelope.InReplyTo, nil
}
if mi.RFC822Headers == nil {
return "", errors.New("header is nil")
}
list := parse.MsgIDList(mi.RFC822Headers, "In-Reply-To")
if len(list) == 0 {
return "", errors.New("no results")
}
return list[0], err
}
func (mi *MessageInfo) References() ([]string, error) {
if mi == nil {
return []string{}, errors.New("msg is nil")
}
if mi.Refs != nil {
return mi.Refs, nil
}
if mi.RFC822Headers == nil {
return []string{}, errors.New("header is nil")
}
list := parse.MsgIDList(mi.RFC822Headers, "References")
if len(list) == 0 {
return []string{}, errors.New("no results")
}
return list, nil
}
// A MessageBodyPart can be displayed in the message viewer
type MessageBodyPart struct {
Reader io.Reader
Uid uint32
}
// A FullMessage is the entire message
type FullMessage struct {
Reader io.Reader
Uid uint32
}
type BodyStructure struct {
MIMEType string
MIMESubType string
Params map[string]string
Description string
Encoding string
Parts []*BodyStructure
Disposition string
DispositionParams map[string]string
}
// PartAtIndex returns the BodyStructure at the requested index
func (bs *BodyStructure) PartAtIndex(index []int) (*BodyStructure, error) {
if len(index) == 0 {
return bs, nil
}
cur := index[0]
rest := index[1:]
// passed indexes are 1 based, we need to convert back to actual indexes
curidx := cur - 1
if curidx < 0 {
return nil, fmt.Errorf("invalid index, expected 1 based input")
}
// no children, base case
if len(bs.Parts) == 0 {
if len(rest) != 0 {
return nil, fmt.Errorf("more index levels given than available")
}
if cur == 1 {
return bs, nil
} else {
return nil, fmt.Errorf("invalid index %v for non multipart", cur)
}
}
if cur > len(bs.Parts) {
return nil, fmt.Errorf("invalid index %v, only have %v children",
cur, len(bs.Parts))
}
return bs.Parts[curidx].PartAtIndex(rest)
}
func (bs *BodyStructure) FullMIMEType() string {
mime := fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
return strings.ToLower(mime)
}
func (bs *BodyStructure) FileName() string {
if filename, ok := bs.DispositionParams["filename"]; ok {
return filename
} else if filename, ok := bs.Params["name"]; ok {
// workaround golang not supporting RFC2231 besides ASCII and UTF8
return filename
}
return ""
}
type Envelope struct {
Date time.Time
Subject string
From []*mail.Address
ReplyTo []*mail.Address
To []*mail.Address
Cc []*mail.Address
Bcc []*mail.Address
MessageId string
InReplyTo string
}
// OriginalMail is helper struct used for reply/forward
type OriginalMail struct {
Date time.Time
From string
Text string
MIMEType string
RFC822Headers *mail.Header
}
type SignatureValidity int32
const (
UnknownValidity SignatureValidity = iota
Valid
InvalidSignature
UnknownEntity
UnsupportedMicalg
MicalgMismatch
)
type MessageDetails struct {
IsEncrypted bool
IsSigned bool
SignedBy string // Primary identity of signing key
SignedByKeyId uint64
SignatureValidity SignatureValidity
SignatureError string
DecryptedWith string // Primary Identity of decryption key
DecryptedWithKeyId uint64 // Public key id of decryption key
Body io.Reader
Micalg string
}