| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- package parsemail
- import (
- "bytes"
- "fmt"
- "github.com/Jinnrry/pmail/config"
- "github.com/Jinnrry/pmail/utils/context"
- "github.com/emersion/go-message"
- _ "github.com/emersion/go-message/charset"
- "github.com/emersion/go-message/mail"
- log "github.com/sirupsen/logrus"
- "io"
- "net/textproto"
- "regexp"
- "strings"
- "time"
- )
- type User struct {
- EmailAddress string `json:"EmailAddress"`
- Name string `json:"Name"`
- }
- func (u User) GetDomainAccount() (string, string) {
- infos := strings.Split(u.EmailAddress, "@")
- if len(infos) >= 2 {
- return infos[0], infos[1]
- }
- return "", ""
- }
- type Attachment struct {
- Filename string
- ContentType string
- Content []byte
- ContentID string
- }
- // Email is the type used for email messages
- type Email struct {
- ReplyTo []*User
- From *User
- To []*User
- Bcc []*User
- Cc []*User
- Subject string
- Text []byte // Plaintext message (optional)
- HTML []byte // Html message (optional)
- Sender *User // override From as SMTP envelope sender (optional)
- Headers textproto.MIMEHeader
- Attachments []*Attachment
- ReadReceipt []string
- Date string
- Status int // 0未发送,1已发送,2发送失败,3删除
- MessageId int64
- Size int
- }
- func NewEmailFromReader(to []string, r io.Reader, size int) *Email {
- ret := &Email{}
- m, err := message.Read(r)
- if err != nil {
- log.Errorf("email解析错误! Error %+v", err)
- }
- ret.Size = size
- ret.From = buildUser(m.Header.Get("From"))
- if len(to) > 0 {
- ret.To = buildUsers(to)
- } else {
- ret.To = buildUsers(m.Header.Values("To"))
- }
- ret.Cc = buildUsers(m.Header.Values("Cc"))
- ret.ReplyTo = buildUsers(m.Header.Values("ReplyTo"))
- ret.Sender = buildUser(m.Header.Get("Sender"))
- if ret.Sender == nil {
- ret.Sender = ret.From
- }
- ret.Subject, _ = m.Header.Text("Subject")
- sendTime, err := time.Parse(time.RFC1123Z, m.Header.Get("Date"))
- if err != nil {
- sendTime = time.Now()
- }
- ret.Date = sendTime.Format(time.DateTime)
- m.Walk(func(path []int, entity *message.Entity, err error) error {
- return formatContent(entity, ret)
- })
- return ret
- }
- func formatContent(entity *message.Entity, ret *Email) error {
- contentType, p, err := entity.Header.ContentType()
- if err != nil {
- log.Errorf("email read error! %+v", err)
- return err
- }
- switch contentType {
- case "multipart/alternative":
- case "multipart/mixed":
- case "text/plain":
- ret.Text, _ = io.ReadAll(entity.Body)
- case "text/html":
- ret.HTML, _ = io.ReadAll(entity.Body)
- case "multipart/related":
- entity.Walk(func(path []int, entity *message.Entity, err error) error {
- if t, _, _ := entity.Header.ContentType(); t == "multipart/related" {
- return nil
- }
- return formatContent(entity, ret)
- })
- default:
- c, _ := io.ReadAll(entity.Body)
- fileName := p["name"]
- if fileName == "" {
- contentDisposition := entity.Header.Get("Content-Disposition")
- r := regexp.MustCompile("filename=(.*)")
- matchs := r.FindStringSubmatch(contentDisposition)
- if len(matchs) == 2 {
- fileName = matchs[1]
- } else {
- fileName = "no_name_file"
- }
- }
- ret.Attachments = append(ret.Attachments, &Attachment{
- Filename: fileName,
- ContentType: contentType,
- Content: c,
- ContentID: strings.TrimPrefix(strings.TrimSuffix(entity.Header.Get("Content-Id"), ">"), "<"),
- })
- }
- return nil
- }
- func BuilderUser(str string) *User {
- return buildUser(str)
- }
- var emailAddressRe = regexp.MustCompile(`<(.*@.*)>`)
- func buildUser(str string) *User {
- if str == "" {
- return nil
- }
- ret := &User{}
- matched := emailAddressRe.FindStringSubmatch(str)
- if len(matched) == 2 {
- ret.EmailAddress = matched[1]
- } else {
- ret.EmailAddress = str
- return ret
- }
- str = strings.ReplaceAll(str, matched[0], "")
- str = strings.Trim(strings.TrimSpace(str), "\"")
- name, err := (&WordDecoder{}).Decode(strings.ReplaceAll(str, "\"", ""))
- if err == nil {
- ret.Name = strings.TrimSpace(name)
- } else {
- ret.Name = strings.TrimSpace(str)
- }
- return ret
- }
- func buildUsers(str []string) []*User {
- var ret []*User
- for _, s1 := range str {
- if s1 == "" {
- continue
- }
- for _, s := range strings.Split(s1, ",") {
- s = strings.TrimSpace(s)
- ret = append(ret, buildUser(s))
- }
- }
- return ret
- }
- func (e *Email) ForwardBuildBytes(ctx *context.Context, forwardAddress string) []byte {
- var b bytes.Buffer
- from := []*mail.Address{{e.From.Name, e.From.EmailAddress}}
- to := []*mail.Address{
- {
- Address: forwardAddress,
- },
- }
- // Create our mail header
- var h mail.Header
- h.SetDate(time.Now())
- h.SetAddressList("From", from)
- h.SetAddressList("To", to)
- h.SetText("Subject", e.Subject)
- h.SetMessageID(fmt.Sprintf("%d@%s", e.MessageId, config.Instance.Domain))
- if len(e.Cc) != 0 {
- cc := []*mail.Address{}
- for _, user := range e.Cc {
- cc = append(cc, &mail.Address{
- Name: user.Name,
- Address: user.EmailAddress,
- })
- }
- h.SetAddressList("Cc", cc)
- }
- // Create a new mail writer
- mw, err := mail.CreateWriter(&b, h)
- if err != nil {
- log.WithContext(ctx).Fatal(err)
- }
- // Create a text part
- tw, err := mw.CreateInline()
- if err != nil {
- log.WithContext(ctx).Fatal(err)
- }
- var th mail.InlineHeader
- th.Set("Content-Type", "text/plain")
- w, err := tw.CreatePart(th)
- if err != nil {
- log.Fatal(err)
- }
- io.WriteString(w, string(e.Text))
- w.Close()
- var html mail.InlineHeader
- html.Set("Content-Type", "text/html")
- w, err = tw.CreatePart(html)
- if err != nil {
- log.Fatal(err)
- }
- io.WriteString(w, string(e.HTML))
- w.Close()
- tw.Close()
- // Create an attachment
- for _, attachment := range e.Attachments {
- var ah mail.AttachmentHeader
- ah.Set("Content-Type", attachment.ContentType)
- ah.SetFilename(attachment.Filename)
- w, err = mw.CreateAttachment(ah)
- if err != nil {
- log.WithContext(ctx).Fatal(err)
- continue
- }
- w.Write(attachment.Content)
- w.Close()
- }
- mw.Close()
- // dkim 签名后返回
- return instance.Sign(b.String())
- }
- func (e *Email) BuildBytes(ctx *context.Context, dkim bool) []byte {
- var b bytes.Buffer
- from := []*mail.Address{{e.From.Name, e.From.EmailAddress}}
- to := []*mail.Address{}
- for _, user := range e.To {
- to = append(to, &mail.Address{
- Name: user.Name,
- Address: user.EmailAddress,
- })
- }
- // Create our mail header
- var h mail.Header
- if e.Date != "" {
- t, err := time.ParseInLocation("2006-01-02 15:04:05", e.Date, time.Local)
- if err != nil {
- log.WithContext(ctx).Errorf("Time Error ! Err:%+v", err)
- h.SetDate(time.Now())
- } else {
- h.SetDate(t)
- }
- } else {
- h.SetDate(time.Now())
- }
- h.SetMessageID(fmt.Sprintf("%d@%s", e.MessageId, config.Instance.Domain))
- h.SetAddressList("From", from)
- h.SetAddressList("To", to)
- h.SetText("Subject", e.Subject)
- if len(e.Cc) != 0 {
- cc := []*mail.Address{}
- for _, user := range e.Cc {
- cc = append(cc, &mail.Address{
- Name: user.Name,
- Address: user.EmailAddress,
- })
- }
- h.SetAddressList("Cc", cc)
- }
- // Create a new mail writer
- mw, err := mail.CreateWriter(&b, h)
- if err != nil {
- log.WithContext(ctx).Fatal(err)
- }
- // Create a text part
- tw, err := mw.CreateInline()
- if err != nil {
- log.WithContext(ctx).Fatal(err)
- }
- var th mail.InlineHeader
- th.SetContentType("text/plain", map[string]string{
- "charset": "UTF-8",
- })
- w, err := tw.CreatePart(th)
- if err != nil {
- log.Fatal(err)
- }
- io.WriteString(w, string(e.Text))
- w.Close()
- var html mail.InlineHeader
- html.SetContentType("text/html", map[string]string{
- "charset": "UTF-8",
- })
- w, err = tw.CreatePart(html)
- if err != nil {
- log.Fatal(err)
- }
- if len(e.HTML) > 0 {
- io.WriteString(w, string(e.HTML))
- } else {
- io.WriteString(w, string(e.Text))
- }
- w.Close()
- tw.Close()
- // Create an attachment
- for _, attachment := range e.Attachments {
- var ah mail.AttachmentHeader
- ah.Set("Content-Type", attachment.ContentType)
- ah.SetFilename(attachment.Filename)
- w, err = mw.CreateAttachment(ah)
- if err != nil {
- log.WithContext(ctx).Fatal(err)
- continue
- }
- w.Write(attachment.Content)
- w.Close()
- }
- mw.Close()
- if dkim {
- // dkim 签名后返回
- return instance.Sign(b.String())
- }
- return b.Bytes()
- }
|