email.go 9.9 KB

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