Skip to content

Commit 548734c

Browse files
committed
add more imap commands
1 parent 4a9c782 commit 548734c

24 files changed

+1578
-218
lines changed

config/dynamic/mail/fetch.go

Lines changed: 163 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ package mail
33
import (
44
"context"
55
"fmt"
6-
"math"
76
"mokapi/imap"
7+
"mokapi/media"
8+
"mokapi/smtp"
89
"strings"
910
)
1011

1112
func (h *Handler) Fetch(req *imap.FetchRequest, res imap.FetchResponse, ctx context.Context) error {
1213
c := imap.ClientFromContext(ctx)
1314
mb := c.Session["mailbox"].(*Mailbox)
1415
selected := c.Session["selected"].(string)
15-
f, ok := mb.Folders[selected]
16-
if !ok {
16+
f := mb.Select(selected)
17+
if f == nil {
1718
return fmt.Errorf("mailbox not found")
1819
}
1920

@@ -61,47 +62,176 @@ func writeMessage(msg *Mail, opt imap.FetchOptions, w imap.MessageWriter) {
6162
if opt.Flags {
6263
w.WriteFlags(msg.Flags...)
6364
}
65+
if opt.Envelope {
66+
env := &imap.Envelope{
67+
Date: msg.Time,
68+
Subject: msg.Subject,
69+
From: toEnvelopeAddressList(msg.From),
70+
ReplyTo: toEnvelopeAddressList(msg.ReplyTo),
71+
To: toEnvelopeAddressList(msg.To),
72+
Cc: toEnvelopeAddressList(msg.Cc),
73+
Bcc: toEnvelopeAddressList(msg.Bcc),
74+
InReplyTo: msg.InReplyTo,
75+
MessageId: msg.MessageId,
76+
}
77+
78+
sender := toEnvelopeAddress(msg.Sender)
79+
if sender != nil {
80+
env.Sender = []imap.Address{*sender}
81+
}
82+
83+
w.WriteEnvelope(env)
84+
}
85+
if opt.BodyStructure {
86+
ct := media.ParseContentType(msg.ContentType)
87+
88+
bs := &imap.BodyStructure{
89+
Type: ct.Type,
90+
Subtype: ct.Subtype,
91+
Params: ct.Parameters,
92+
Encoding: msg.ContentTransferEncoding,
93+
Size: uint32(msg.Size),
94+
}
95+
96+
for _, part := range msg.Attachments {
97+
partType := media.ParseContentType(part.ContentType)
98+
p := imap.BodyStructure{
99+
Type: partType.Type,
100+
Subtype: partType.Subtype,
101+
Params: partType.Parameters,
102+
Encoding: part.Header["Content-Transfer-Encoding"],
103+
Size: uint32(len(part.Data)),
104+
Disposition: part.Disposition,
105+
}
106+
if part.ContentId != "" {
107+
p.ContentId = &part.ContentId
108+
}
109+
if part.Disposition != "" && part.Disposition != "inline" {
110+
p.Disposition = part.Disposition
111+
}
112+
bs.Parts = append(bs.Parts, p)
113+
114+
}
115+
116+
w.WriteBodyStructure(bs)
117+
}
64118

65119
for _, body := range opt.Body {
66120
bw := w.WriteBody(body)
67-
if body.Type == "header" {
68-
for _, field := range body.Fields {
69-
switch strings.ToLower(field) {
70-
case "date":
71-
bw.WriteHeader("date", msg.Time.Format(imap.DateTimeLayout))
72-
case "subject":
73-
bw.WriteHeader("subject", msg.Subject)
74-
case "from":
75-
bw.WriteHeader("from", addressListToString(msg.From))
76-
case "to":
77-
bw.WriteHeader("to", addressListToString(msg.To))
78-
case "cc":
79-
if msg.Cc != nil {
80-
bw.WriteHeader("cc", addressListToString(msg.Cc))
121+
122+
if body.Specifier == "header" {
123+
if len(body.Fields) == 0 {
124+
bw.WriteHeader("date", msg.Time.Format(imap.DateTimeLayout))
125+
bw.WriteHeader("subject", msg.Subject)
126+
bw.WriteHeader("from", addressListToString(msg.From))
127+
bw.WriteHeader("to", addressListToString(msg.To))
128+
if msg.Cc != nil {
129+
bw.WriteHeader("cc", addressListToString(msg.Cc))
130+
}
131+
bw.WriteHeader("message-id", msg.MessageId)
132+
bw.WriteHeader("content-type", msg.ContentType)
133+
// Add any additional headers
134+
for name, value := range msg.Headers {
135+
// avoid writing duplicates
136+
if !isStandardHeader(name) {
137+
bw.WriteHeader(name, value)
81138
}
82-
case "message-id":
83-
bw.WriteHeader("message-id", msg.MessageId)
84-
case "content-type":
85-
bw.WriteHeader("content-type", msg.ContentType)
86-
default:
87-
if v, ok := msg.Headers[field]; ok {
88-
bw.WriteHeader(field, v)
139+
}
140+
} else {
141+
for _, field := range body.Fields {
142+
switch strings.ToLower(field) {
143+
case "date":
144+
bw.WriteHeader("date", msg.Time.Format(imap.DateTimeLayout))
145+
case "subject":
146+
bw.WriteHeader("subject", msg.Subject)
147+
case "from":
148+
bw.WriteHeader("from", addressListToString(msg.From))
149+
case "to":
150+
bw.WriteHeader("to", addressListToString(msg.To))
151+
case "cc":
152+
if msg.Cc != nil {
153+
bw.WriteHeader("cc", addressListToString(msg.Cc))
154+
}
155+
case "message-id":
156+
bw.WriteHeader("message-id", msg.MessageId)
157+
case "content-type":
158+
bw.WriteHeader("content-type", msg.ContentType)
159+
default:
160+
if v, ok := msg.Headers[field]; ok {
161+
bw.WriteHeader(field, v)
162+
}
89163
}
90164
}
91165
}
92-
} else if body.Type == "text" {
93-
if body.Partially != nil {
94-
n := int(math.Min(float64(len(msg.Body)), float64(body.Partially.Limit)))
95-
bw.WriteBody(msg.Body[body.Partially.Offset:n])
96-
} else {
166+
} else if body.Specifier == "text" {
167+
if strings.HasPrefix(msg.ContentType, "multipart/") && len(body.Parts) == 0 {
168+
// https://datatracker.ietf.org/doc/html/rfc3501#section-7.4.2
169+
continue
170+
}
171+
if len(body.Parts) == 0 || len(msg.Attachments) == 0 {
97172
bw.WriteBody(msg.Body)
173+
} else {
174+
for _, part := range body.Parts {
175+
index := part - 1
176+
if index < 0 || index > len(msg.Attachments) {
177+
continue
178+
}
179+
p := msg.Attachments[index]
180+
bw.WriteBody(string(p.Data))
181+
}
98182
}
99-
} else if body.Type == "" {
100-
for k, v := range msg.Headers {
101-
bw.WriteHeader(k, v)
183+
} else if body.Specifier == "" {
184+
if body.Parts == nil {
185+
for k, v := range msg.Headers {
186+
bw.WriteHeader(k, v)
187+
}
188+
bw.WriteBody(msg.Body)
189+
} else {
190+
index := body.Parts[0] - 1
191+
if index < 0 || index > len(msg.Attachments) {
192+
continue
193+
}
194+
att := msg.Attachments[index]
195+
for k, v := range att.Header {
196+
bw.WriteHeader(k, v)
197+
}
198+
bw.WriteBody(string(att.Data))
102199
}
103-
bw.WriteBody(msg.Body)
200+
104201
}
105202
bw.Close()
106203
}
107204
}
205+
206+
func isStandardHeader(name string) bool {
207+
switch strings.ToLower(name) {
208+
case "date", "subject", "from", "to", "cc", "message-id", "content-type":
209+
return true
210+
default:
211+
return false
212+
}
213+
}
214+
215+
func toEnvelopeAddressList(list []smtp.Address) []imap.Address {
216+
if len(list) == 0 {
217+
return nil
218+
}
219+
220+
result := make([]imap.Address, len(list))
221+
for i, addr := range list {
222+
result[i] = *toEnvelopeAddress(&addr)
223+
}
224+
return result
225+
}
226+
227+
func toEnvelopeAddress(addr *smtp.Address) *imap.Address {
228+
if addr == nil {
229+
return nil
230+
}
231+
parts := strings.Split(addr.Address, "@")
232+
return &imap.Address{
233+
Name: addr.Name,
234+
Mailbox: parts[0],
235+
Host: parts[1],
236+
}
237+
}

0 commit comments

Comments
 (0)