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)))) } if err == io.ErrUnexpectedEOF { err = nil } return s, err } 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")) if err == nil && strings.HasPrefix(mediaType, "multipart/") { 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") { 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 { 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) if len(errs) == 0 { 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") subject := mail.Header.Get("Subject") m := fmt.Sprintf("Date: %s\nFrom: %s\nSubject: %s\n\n%s", date, from, subject, strings.Join(parts, "\n\n")) mailsPlain = append(mailsPlain, m) } } return mailsPlain, errs }