2017-08-05 18:42:14 +02:00
|
|
|
package unreadMail
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"git.gutmet.org/libUnreadMail/imap"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"mime"
|
|
|
|
"mime/multipart"
|
|
|
|
"mime/quotedprintable"
|
|
|
|
"net/mail"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Parameters struct {
|
|
|
|
Server string
|
|
|
|
Port int
|
|
|
|
Cert string
|
|
|
|
User string
|
|
|
|
Passwd string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Error string
|
|
|
|
|
|
|
|
func (err Error) Error() string {
|
|
|
|
return string(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateParameters(p *Parameters) []error {
|
|
|
|
var err []error
|
|
|
|
p.Cert = strings.TrimSpace(p.Cert)
|
|
|
|
p.User = strings.TrimSpace(p.User)
|
|
|
|
p.Passwd = strings.TrimSpace(p.Passwd)
|
|
|
|
|
|
|
|
if p.Server == "" {
|
|
|
|
err = append(err, Error("Empty server address"))
|
|
|
|
}
|
|
|
|
if p.Port <= 0 || p.Port > 65535 {
|
|
|
|
err = append(err, Error("Invalid port number: "+strconv.Itoa(p.Port)))
|
|
|
|
}
|
|
|
|
if p.Cert == "" {
|
|
|
|
err = append(err, Error("Empty server cert"))
|
|
|
|
}
|
|
|
|
if p.User == "" {
|
|
|
|
err = append(err, Error("Empty user"))
|
|
|
|
}
|
|
|
|
if p.Passwd == "" {
|
|
|
|
err = append(err, Error("Empty passwd"))
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func tlsConnection(p *Parameters) (*tls.Conn, error) {
|
|
|
|
roots := x509.NewCertPool()
|
|
|
|
ok := roots.AppendCertsFromPEM([]byte(p.Cert))
|
|
|
|
if !ok {
|
|
|
|
return nil, Error("Failed to set up root cert")
|
|
|
|
}
|
|
|
|
conf := &tls.Config{RootCAs: roots}
|
|
|
|
c, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", p.Server, p.Port), conf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error("Failed to dial")
|
|
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalize(m []byte, encoding string) ([]byte, error) {
|
|
|
|
var s []byte
|
|
|
|
var err error
|
|
|
|
if encoding == "quoted-printable" {
|
|
|
|
s, err = ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(string(m))))
|
|
|
|
} else if encoding == "base64" {
|
|
|
|
s, err = ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, strings.NewReader(string(m))))
|
2017-08-05 18:58:58 +02:00
|
|
|
} else {
|
|
|
|
s = m
|
2017-08-05 18:42:14 +02:00
|
|
|
}
|
|
|
|
if err == io.ErrUnexpectedEOF {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:58:04 +02:00
|
|
|
func decodeSubject(subject string) (string, error) {
|
|
|
|
dec := new(mime.WordDecoder)
|
|
|
|
return dec.DecodeHeader(subject)
|
|
|
|
}
|
|
|
|
|
2017-08-05 18:42:14 +02:00
|
|
|
func getPlainParts(mail *mail.Message) ([]string, []error) {
|
|
|
|
parts := make([]string, 0)
|
|
|
|
errs := make([]error, 0)
|
|
|
|
mediaType, params, err := mime.ParseMediaType(mail.Header.Get("Content-Type"))
|
2017-08-16 17:04:56 +02:00
|
|
|
_, hasBoundary := params["boundary"]
|
|
|
|
if err == nil && strings.HasPrefix(mediaType, "multipart/") && hasBoundary {
|
2017-08-05 18:42:14 +02:00
|
|
|
mr := multipart.NewReader(mail.Body, params["boundary"])
|
|
|
|
for {
|
|
|
|
p, err := mr.NextPart()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
if contentType := p.Header.Get("Content-Type"); strings.HasPrefix(contentType, "text/plain") {
|
2017-08-16 17:04:56 +02:00
|
|
|
if contentDisposition := p.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") {
|
|
|
|
body, _ := ioutil.ReadAll(p)
|
|
|
|
encoding := p.Header.Get("Content-Transfer-Encoding")
|
|
|
|
if encoding != "" {
|
|
|
|
body, err = normalize(body, encoding)
|
|
|
|
if err != nil {
|
|
|
|
body = nil
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
parts = append(parts, string(body))
|
|
|
|
} else {
|
|
|
|
_, params, errDispo := mime.ParseMediaType(contentDisposition)
|
|
|
|
if filename, ok := params["filename"]; errDispo == nil && ok {
|
|
|
|
parts = append(parts, "Plain attachment: "+filename)
|
2017-08-05 18:42:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
parts = append(parts, "Non-plain part: "+contentType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// not a multipart message
|
|
|
|
if strings.HasPrefix(mediaType, "text/plain") {
|
|
|
|
encoding := mail.Header.Get("Content-Transfer-Encoding")
|
|
|
|
body, _ := ioutil.ReadAll(mail.Body)
|
|
|
|
if encoding != "" {
|
|
|
|
body, err = normalize(body, encoding)
|
|
|
|
if err != nil {
|
|
|
|
body = nil
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
parts = append(parts, string(body))
|
|
|
|
} else if contentName := params["name"]; contentName != "" {
|
|
|
|
parts = append(parts, contentName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parts, errs
|
|
|
|
}
|
|
|
|
|
|
|
|
func Fetch(p *Parameters) ([]*mail.Message, []error) {
|
|
|
|
errs := validateParameters(p)
|
|
|
|
if len(errs) == 0 {
|
|
|
|
conn, err := tlsConnection(p)
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
} else {
|
|
|
|
return imap.Fetch(conn, p.User, p.Passwd)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, errs
|
|
|
|
}
|
|
|
|
|
|
|
|
func FetchPlaintext(p *Parameters) ([]string, []error) {
|
|
|
|
mails, errs := Fetch(p)
|
|
|
|
mailsPlain := make([]string, 0)
|
2017-08-08 23:48:56 +02:00
|
|
|
for _, mail := range mails {
|
|
|
|
// check for mime parts
|
|
|
|
parts, pErrs := getPlainParts(mail)
|
|
|
|
errs = append(errs, pErrs...)
|
|
|
|
date := mail.Header.Get("Date")
|
|
|
|
from := mail.Header.Get("From")
|
2017-08-09 16:36:59 +02:00
|
|
|
to := mail.Header.Get("To")
|
2017-08-15 16:58:04 +02:00
|
|
|
subject, sErr := decodeSubject(mail.Header.Get("Subject"))
|
|
|
|
if sErr != nil {
|
|
|
|
errs = append(errs, sErr)
|
|
|
|
}
|
2017-08-09 16:36:59 +02:00
|
|
|
m := fmt.Sprintf("Date: %s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s", date, from, to, subject, strings.Join(parts, "\n\n"))
|
2017-08-08 23:48:56 +02:00
|
|
|
mailsPlain = append(mailsPlain, m)
|
2017-08-05 18:42:14 +02:00
|
|
|
}
|
|
|
|
return mailsPlain, errs
|
|
|
|
}
|