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