aboutsummaryrefslogtreecommitdiffstats
path: root/models/models.go
blob: e05c81acf1661b06128a6a78f2f209b5f29b2cc6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
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
	Extensions []string
}

func (c *Capabilities) Has(s string) bool {
	for _, ext := range c.Extensions {
		if ext == s {
			return true
		}
	}
	return false
}

// 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
	Sender    []*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
	Folder        string
}

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
}