1package lib
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"io"
8	"regexp"
9	"strings"
10	"time"
11
12	"git.sr.ht/~sircmpwn/aerc/models"
13	"github.com/emersion/go-message"
14	_ "github.com/emersion/go-message/charset"
15	"github.com/emersion/go-message/mail"
16)
17
18// RFC 1123Z regexp
19var dateRe = regexp.MustCompile(`(((Mon|Tue|Wed|Thu|Fri|Sat|Sun))[,]?\s[0-9]{1,2})\s` +
20	`(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s` +
21	`([0-9]{4})\s([0-9]{2}):([0-9]{2})(:([0-9]{2}))?\s([\+|\-][0-9]{4})\s?`)
22
23func FetchEntityPartReader(e *message.Entity, index []int) (io.Reader, error) {
24	if len(index) < 1 {
25		return nil, fmt.Errorf("no part to read")
26	}
27	if mpr := e.MultipartReader(); mpr != nil {
28		idx := 0
29		for {
30			idx++
31			part, err := mpr.NextPart()
32			if err != nil {
33				return nil, err
34			}
35			if idx == index[0] {
36				rest := index[1:]
37				if len(rest) < 1 {
38					return bufReader(part)
39				}
40				return FetchEntityPartReader(part, index[1:])
41			}
42		}
43	}
44	if index[0] != 1 {
45		return nil, fmt.Errorf("cannont return non-first part of non-multipart")
46	}
47	return bufReader(e)
48}
49
50//TODO: the UI doesn't seem to like readers which aren't buffers
51func bufReader(e *message.Entity) (io.Reader, error) {
52	var buf bytes.Buffer
53	if _, err := io.Copy(&buf, e.Body); err != nil {
54		return nil, err
55	}
56	return &buf, nil
57}
58
59// split a MIME type into its major and minor parts
60func splitMIME(m string) (string, string) {
61	parts := strings.Split(m, "/")
62	if len(parts) != 2 {
63		return parts[0], ""
64	}
65	return parts[0], parts[1]
66}
67
68func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
69	var body models.BodyStructure
70	contentType, ctParams, err := e.Header.ContentType()
71	if err != nil {
72		return nil, fmt.Errorf("could not parse content type: %v", err)
73	}
74	mimeType, mimeSubType := splitMIME(contentType)
75	body.MIMEType = mimeType
76	body.MIMESubType = mimeSubType
77	body.Params = ctParams
78	body.Description = e.Header.Get("content-description")
79	body.Encoding = e.Header.Get("content-transfer-encoding")
80	if cd := e.Header.Get("content-disposition"); cd != "" {
81		contentDisposition, cdParams, err := e.Header.ContentDisposition()
82		if err != nil {
83			return nil, fmt.Errorf("could not parse content disposition: %v", err)
84		}
85		body.Disposition = contentDisposition
86		body.DispositionParams = cdParams
87	}
88	body.Parts = []*models.BodyStructure{}
89	if mpr := e.MultipartReader(); mpr != nil {
90		for {
91			part, err := mpr.NextPart()
92			if err == io.EOF {
93				return &body, nil
94			} else if err != nil {
95				return nil, err
96			}
97			ps, err := ParseEntityStructure(part)
98			if err != nil {
99				return nil, fmt.Errorf("could not parse child entity structure: %v", err)
100			}
101			body.Parts = append(body.Parts, ps)
102		}
103	}
104	return &body, nil
105}
106
107func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
108	date, err := parseDate(h)
109	if err != nil {
110		return nil, fmt.Errorf("could not parse date header: %v", err)
111	}
112	from, err := parseAddressList(h, "from")
113	if err != nil {
114		return nil, fmt.Errorf("could not read from address: %v", err)
115	}
116	to, err := parseAddressList(h, "to")
117	if err != nil {
118		return nil, fmt.Errorf("could not read to address: %v", err)
119	}
120	cc, err := parseAddressList(h, "cc")
121	if err != nil {
122		return nil, fmt.Errorf("could not read cc address: %v", err)
123	}
124	bcc, err := parseAddressList(h, "bcc")
125	if err != nil {
126		return nil, fmt.Errorf("could not read bcc address: %v", err)
127	}
128	replyTo, err := parseAddressList(h, "reply-to")
129	if err != nil {
130		return nil, fmt.Errorf("could not read reply-to address: %v", err)
131	}
132	subj, err := h.Subject()
133	if err != nil {
134		return nil, fmt.Errorf("could not read subject: %v", err)
135	}
136	msgID, err := h.Text("message-id")
137	if err != nil {
138		return nil, fmt.Errorf("could not read message id: %v", err)
139	}
140	return &models.Envelope{
141		Date:      date,
142		Subject:   subj,
143		MessageId: msgID,
144		From:      from,
145		ReplyTo:   replyTo,
146		To:        to,
147		Cc:        cc,
148		Bcc:       bcc,
149	}, nil
150}
151
152// parseDate extends the built-in date parser with additional layouts which are
153// non-conforming but appear in the wild.
154func parseDate(h *mail.Header) (time.Time, error) {
155	t, parseErr := h.Date()
156	if parseErr == nil {
157		return t, nil
158	}
159	text, err := h.Text("date")
160	if err != nil {
161		return time.Time{}, errors.New("no date header")
162	}
163	// sometimes, no error occurs but the date is empty. In this case, guess time from received header field
164	if text == "" {
165		guess, err := h.Text("received")
166		if err != nil {
167			return time.Time{}, errors.New("no received header")
168		}
169		t, _ := time.Parse(time.RFC1123Z, dateRe.FindString(guess))
170		return t, nil
171	}
172	layouts := []string{
173		// X-Mailer: EarthLink Zoo Mail 1.0
174		"Mon, _2 Jan 2006 15:04:05 -0700 (GMT-07:00)",
175	}
176	for _, layout := range layouts {
177		if t, err := time.Parse(layout, text); err == nil {
178			return t, nil
179		}
180	}
181	return time.Time{}, fmt.Errorf("unrecognized date format: %s", t)
182}
183
184func parseAddressList(h *mail.Header, key string) ([]*models.Address, error) {
185	var converted []*models.Address
186	addrs, err := h.AddressList(key)
187	if err != nil {
188		if hdr, err := h.Text(key); err == nil {
189			return []*models.Address{&models.Address{
190				Name: hdr,
191			}}, nil
192		}
193		return nil, err
194	}
195	for _, addr := range addrs {
196		parts := strings.Split(addr.Address, "@")
197		var mbox, host string
198		if len(parts) > 1 {
199			mbox = strings.Join(parts[0:len(parts)-1], "@")
200			host = parts[len(parts)-1]
201		} else {
202			mbox = addr.Address
203		}
204		converted = append(converted, &models.Address{
205			Name:    addr.Name,
206			Mailbox: mbox,
207			Host:    host,
208		})
209	}
210	return converted, nil
211}
212
213// RawMessage is an interface that describes a raw message
214type RawMessage interface {
215	NewReader() (io.Reader, error)
216	ModelFlags() ([]models.Flag, error)
217	Labels() ([]string, error)
218	UID() uint32
219}
220
221// MessageInfo populates a models.MessageInfo struct for the message.
222// based on the reader returned by NewReader
223func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
224	r, err := raw.NewReader()
225	if err != nil {
226		return nil, err
227	}
228	msg, err := message.Read(r)
229	if err != nil {
230		return nil, fmt.Errorf("could not read message: %v", err)
231	}
232	bs, err := ParseEntityStructure(msg)
233	if err != nil {
234		return nil, fmt.Errorf("could not get structure: %v", err)
235	}
236	env, err := parseEnvelope(&mail.Header{msg.Header})
237	if err != nil {
238		return nil, fmt.Errorf("could not get envelope: %v", err)
239	}
240	flags, err := raw.ModelFlags()
241	if err != nil {
242		return nil, err
243	}
244	labels, err := raw.Labels()
245	if err != nil {
246		return nil, err
247	}
248	return &models.MessageInfo{
249		BodyStructure: bs,
250		Envelope:      env,
251		Flags:         flags,
252		Labels:        labels,
253		InternalDate:  env.Date,
254		RFC822Headers: &mail.Header{msg.Header},
255		Size:          0,
256		Uid:           raw.UID(),
257	}, nil
258}
259