email.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package parsemail
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/Jinnrry/pmail/config"
  7. "github.com/Jinnrry/pmail/models"
  8. "github.com/Jinnrry/pmail/utils/context"
  9. "github.com/emersion/go-message"
  10. _ "github.com/emersion/go-message/charset"
  11. "github.com/emersion/go-message/mail"
  12. log "github.com/sirupsen/logrus"
  13. "github.com/spf13/cast"
  14. "io"
  15. "net/textproto"
  16. "regexp"
  17. "strings"
  18. "time"
  19. )
  20. type User struct {
  21. EmailAddress string `json:"EmailAddress"`
  22. Name string `json:"Name"`
  23. }
  24. func (u User) GetDomainAccount() (string, string) {
  25. infos := strings.Split(u.EmailAddress, "@")
  26. if len(infos) >= 2 {
  27. return infos[0], infos[1]
  28. }
  29. return "", ""
  30. }
  31. type Attachment struct {
  32. Filename string
  33. ContentType string
  34. Content []byte
  35. ContentID string
  36. }
  37. // Email is the type used for email messages
  38. type Email struct {
  39. ReplyTo []*User
  40. From *User
  41. To []*User
  42. Bcc []*User
  43. Cc []*User
  44. Subject string
  45. Text []byte // Plaintext message (optional)
  46. HTML []byte // Html message (optional)
  47. Sender *User // override From as SMTP envelope sender (optional)
  48. Headers textproto.MIMEHeader
  49. Attachments []*Attachment
  50. ReadReceipt []string
  51. Date string
  52. Status int // 0未发送,1已发送,2发送失败,3删除
  53. MessageId int64
  54. Size int
  55. }
  56. func NewEmailFromModel(d models.Email) *Email {
  57. var To []*User
  58. json.Unmarshal([]byte(d.To), &To)
  59. var ReplyTo []*User
  60. json.Unmarshal([]byte(d.ReplyTo), &ReplyTo)
  61. var Sender *User
  62. json.Unmarshal([]byte(d.Sender), &Sender)
  63. var Bcc []*User
  64. json.Unmarshal([]byte(d.Bcc), &Bcc)
  65. var Cc []*User
  66. json.Unmarshal([]byte(d.Cc), &Cc)
  67. var Attachments []*Attachment
  68. json.Unmarshal([]byte(d.Attachments), &Attachments)
  69. return &Email{
  70. MessageId: cast.ToInt64(d.Id),
  71. From: &User{
  72. Name: d.FromName,
  73. EmailAddress: d.FromAddress,
  74. },
  75. To: To,
  76. Subject: d.Subject,
  77. Text: []byte(d.Text.String),
  78. HTML: []byte(d.Html.String),
  79. Sender: Sender,
  80. ReplyTo: ReplyTo,
  81. Bcc: Bcc,
  82. Cc: Cc,
  83. Attachments: Attachments,
  84. Date: d.SendDate.Format("2006-01-02 15:04:05"),
  85. }
  86. }
  87. func NewEmailFromReader(to []string, r io.Reader, size int) *Email {
  88. ret := &Email{}
  89. m, err := message.Read(r)
  90. if err != nil {
  91. log.Errorf("email解析错误! Error %+v", err)
  92. }
  93. ret.Size = size
  94. ret.From = buildUser(m.Header.Get("From"))
  95. smtpTo := buildUsers(to)
  96. ret.To = buildUsers(m.Header.Values("To"))
  97. ret.Bcc = []*User{}
  98. for _, user := range smtpTo {
  99. in := false
  100. for _, u := range ret.To {
  101. if u.EmailAddress == user.EmailAddress {
  102. in = true
  103. break
  104. }
  105. }
  106. if !in {
  107. ret.Bcc = append(ret.Bcc, user)
  108. }
  109. }
  110. ret.Cc = buildUsers(m.Header.Values("Cc"))
  111. ret.ReplyTo = buildUsers(m.Header.Values("ReplyTo"))
  112. ret.Sender = buildUser(m.Header.Get("Sender"))
  113. if ret.Sender == nil {
  114. ret.Sender = ret.From
  115. }
  116. ret.Subject, _ = m.Header.Text("Subject")
  117. sendTime, err := time.Parse(time.RFC1123Z, m.Header.Get("Date"))
  118. if err != nil {
  119. sendTime = time.Now()
  120. }
  121. ret.Date = sendTime.Format(time.DateTime)
  122. m.Walk(func(path []int, entity *message.Entity, err error) error {
  123. return formatContent(entity, ret)
  124. })
  125. return ret
  126. }
  127. func formatContent(entity *message.Entity, ret *Email) error {
  128. contentType, p, err := entity.Header.ContentType()
  129. if err != nil {
  130. log.Errorf("email read error! %+v", err)
  131. return err
  132. }
  133. switch contentType {
  134. case "multipart/alternative":
  135. case "multipart/mixed":
  136. case "text/plain":
  137. ret.Text, _ = io.ReadAll(entity.Body)
  138. case "text/html":
  139. ret.HTML, _ = io.ReadAll(entity.Body)
  140. case "multipart/related":
  141. entity.Walk(func(path []int, entity *message.Entity, err error) error {
  142. if t, _, _ := entity.Header.ContentType(); t == "multipart/related" {
  143. return nil
  144. }
  145. return formatContent(entity, ret)
  146. })
  147. default:
  148. c, _ := io.ReadAll(entity.Body)
  149. fileName := p["name"]
  150. if fileName == "" {
  151. contentDisposition := entity.Header.Get("Content-Disposition")
  152. r := regexp.MustCompile("filename=(.*)")
  153. matchs := r.FindStringSubmatch(contentDisposition)
  154. if len(matchs) == 2 {
  155. fileName = matchs[1]
  156. } else {
  157. fileName = "no_name_file"
  158. }
  159. }
  160. ret.Attachments = append(ret.Attachments, &Attachment{
  161. Filename: fileName,
  162. ContentType: contentType,
  163. Content: c,
  164. ContentID: strings.TrimPrefix(strings.TrimSuffix(entity.Header.Get("Content-Id"), ">"), "<"),
  165. })
  166. }
  167. return nil
  168. }
  169. func BuilderUser(str string) *User {
  170. return buildUser(str)
  171. }
  172. var emailAddressRe = regexp.MustCompile(`<(.*@.*)>`)
  173. func buildUser(str string) *User {
  174. if str == "" {
  175. return nil
  176. }
  177. ret := &User{}
  178. matched := emailAddressRe.FindStringSubmatch(str)
  179. if len(matched) == 2 {
  180. ret.EmailAddress = matched[1]
  181. } else {
  182. ret.EmailAddress = str
  183. return ret
  184. }
  185. str = strings.ReplaceAll(str, matched[0], "")
  186. str = strings.Trim(strings.TrimSpace(str), "\"")
  187. name, err := (&WordDecoder{}).Decode(strings.ReplaceAll(str, "\"", ""))
  188. if err == nil {
  189. ret.Name = strings.TrimSpace(name)
  190. } else {
  191. ret.Name = strings.TrimSpace(str)
  192. }
  193. return ret
  194. }
  195. func buildUsers(str []string) []*User {
  196. var ret []*User
  197. for _, s1 := range str {
  198. if s1 == "" {
  199. continue
  200. }
  201. for _, s := range strings.Split(s1, ",") {
  202. s = strings.TrimSpace(s)
  203. ret = append(ret, buildUser(s))
  204. }
  205. }
  206. return ret
  207. }
  208. func (e *Email) ForwardBuildBytes(ctx *context.Context, sender *models.User) []byte {
  209. var b bytes.Buffer
  210. from := []*mail.Address{{e.From.Name, e.From.EmailAddress}}
  211. to := []*mail.Address{}
  212. for _, user := range e.To {
  213. to = append(to, &mail.Address{
  214. Name: user.Name,
  215. Address: user.EmailAddress,
  216. })
  217. }
  218. senderAddress := []*mail.Address{{sender.Name, fmt.Sprintf("%s@%s", sender.Account, config.Instance.Domains[0])}}
  219. // Create our mail header
  220. var h mail.Header
  221. h.SetDate(time.Now())
  222. h.SetAddressList("From", from)
  223. h.SetAddressList("Sender", senderAddress)
  224. h.SetAddressList("To", to)
  225. h.SetText("Subject", e.Subject)
  226. h.SetMessageID(fmt.Sprintf("%d@%s", e.MessageId, config.Instance.Domain))
  227. if len(e.Cc) != 0 {
  228. cc := []*mail.Address{}
  229. for _, user := range e.Cc {
  230. cc = append(cc, &mail.Address{
  231. Name: user.Name,
  232. Address: user.EmailAddress,
  233. })
  234. }
  235. h.SetAddressList("Cc", cc)
  236. }
  237. // Create a new mail writer
  238. mw, err := mail.CreateWriter(&b, h)
  239. if err != nil {
  240. log.WithContext(ctx).Fatal(err)
  241. }
  242. // Create a text part
  243. tw, err := mw.CreateInline()
  244. if err != nil {
  245. log.WithContext(ctx).Fatal(err)
  246. }
  247. var th mail.InlineHeader
  248. th.Set("Content-Type", "text/plain")
  249. w, err := tw.CreatePart(th)
  250. if err != nil {
  251. log.Fatal(err)
  252. }
  253. io.WriteString(w, string(e.Text))
  254. w.Close()
  255. var html mail.InlineHeader
  256. html.Set("Content-Type", "text/html")
  257. w, err = tw.CreatePart(html)
  258. if err != nil {
  259. log.Fatal(err)
  260. }
  261. io.WriteString(w, string(e.HTML))
  262. w.Close()
  263. tw.Close()
  264. // Create an attachment
  265. for _, attachment := range e.Attachments {
  266. var ah mail.AttachmentHeader
  267. ah.Set("Content-Type", attachment.ContentType)
  268. ah.SetFilename(attachment.Filename)
  269. w, err = mw.CreateAttachment(ah)
  270. if err != nil {
  271. log.WithContext(ctx).Fatal(err)
  272. continue
  273. }
  274. w.Write(attachment.Content)
  275. w.Close()
  276. }
  277. mw.Close()
  278. // dkim 签名后返回
  279. return instance.Sign(b.String())
  280. }
  281. func (e *Email) BuildBytes(ctx *context.Context, dkim bool) []byte {
  282. var b bytes.Buffer
  283. from := []*mail.Address{{e.From.Name, e.From.EmailAddress}}
  284. to := []*mail.Address{}
  285. for _, user := range e.To {
  286. to = append(to, &mail.Address{
  287. Name: user.Name,
  288. Address: user.EmailAddress,
  289. })
  290. }
  291. // Create our mail header
  292. var h mail.Header
  293. if e.Date != "" {
  294. t, err := time.ParseInLocation("2006-01-02 15:04:05", e.Date, time.Local)
  295. if err != nil {
  296. log.WithContext(ctx).Errorf("Time Error ! Err:%+v", err)
  297. h.SetDate(time.Now())
  298. } else {
  299. h.SetDate(t)
  300. }
  301. } else {
  302. h.SetDate(time.Now())
  303. }
  304. h.SetMessageID(fmt.Sprintf("%d@%s", e.MessageId, config.Instance.Domain))
  305. h.SetAddressList("From", from)
  306. h.SetAddressList("Sender", from)
  307. h.SetAddressList("To", to)
  308. h.SetText("Subject", e.Subject)
  309. if len(e.Cc) != 0 {
  310. cc := []*mail.Address{}
  311. for _, user := range e.Cc {
  312. cc = append(cc, &mail.Address{
  313. Name: user.Name,
  314. Address: user.EmailAddress,
  315. })
  316. }
  317. h.SetAddressList("Cc", cc)
  318. }
  319. // Create a new mail writer
  320. mw, err := mail.CreateWriter(&b, h)
  321. if err != nil {
  322. log.WithContext(ctx).Fatal(err)
  323. }
  324. // Create a text part
  325. tw, err := mw.CreateInline()
  326. if err != nil {
  327. log.WithContext(ctx).Fatal(err)
  328. }
  329. var th mail.InlineHeader
  330. th.Header.Set("Content-Transfer-Encoding", "base64")
  331. th.SetContentType("text/plain", map[string]string{
  332. "charset": "UTF-8",
  333. })
  334. w, err := tw.CreatePart(th)
  335. if err != nil {
  336. log.Fatal(err)
  337. }
  338. io.WriteString(w, string(e.Text))
  339. w.Close()
  340. var html mail.InlineHeader
  341. html.SetContentType("text/html", map[string]string{
  342. "charset": "UTF-8",
  343. })
  344. html.Header.Set("Content-Transfer-Encoding", "base64")
  345. w, err = tw.CreatePart(html)
  346. if err != nil {
  347. log.Fatal(err)
  348. }
  349. if len(e.HTML) > 0 {
  350. io.WriteString(w, string(e.HTML))
  351. } else {
  352. io.WriteString(w, string(e.Text))
  353. }
  354. w.Close()
  355. tw.Close()
  356. // Create an attachment
  357. for _, attachment := range e.Attachments {
  358. var ah mail.AttachmentHeader
  359. ah.Set("Content-Type", attachment.ContentType)
  360. ah.SetFilename(attachment.Filename)
  361. w, err = mw.CreateAttachment(ah)
  362. if err != nil {
  363. log.WithContext(ctx).Fatal(err)
  364. continue
  365. }
  366. w.Write(attachment.Content)
  367. w.Close()
  368. }
  369. mw.Close()
  370. if dkim {
  371. // dkim 签名后返回
  372. return instance.Sign(b.String())
  373. }
  374. return b.Bytes()
  375. }