smtp.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. package mail
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "errors"
  9. "fmt"
  10. "io/ioutil"
  11. "log"
  12. "net/mail"
  13. "net/smtp"
  14. "path"
  15. "path/filepath"
  16. "regexp"
  17. "strconv"
  18. "strings"
  19. )
  20. var (
  21. imageRegex = regexp.MustCompile(`(src|background)=["'](.*?)["']`)
  22. schemeRegxp = regexp.MustCompile(`^[A-z]+://`)
  23. )
  24. // Mail will represent a formatted email
  25. type Mail struct {
  26. To []string
  27. ToName []string
  28. Subject string
  29. HTML string
  30. Text string
  31. From string
  32. Bcc []string
  33. FromName string
  34. ReplyTo string
  35. Date string
  36. Files map[string]string
  37. Headers string
  38. BaseDir string //内容中图片路径
  39. Charset string //编码
  40. RetReceipt string //回执地址,空白则禁用回执
  41. }
  42. // NewMail returns a new Mail
  43. func NewMail() Mail {
  44. return Mail{}
  45. }
  46. // SMTPClient struct
  47. type SMTPClient struct {
  48. smtpAuth smtp.Auth
  49. host string
  50. port string
  51. user string
  52. secure string
  53. }
  54. // SMTPConfig 配置结构体
  55. type SMTPConfig struct {
  56. Username string
  57. Password string
  58. Host string
  59. Port int
  60. Secure string
  61. Identity string
  62. }
  63. func (s *SMTPConfig) Address() string {
  64. if s.Port == 0 {
  65. s.Port = 25
  66. }
  67. return s.Host + `:` + strconv.Itoa(s.Port)
  68. }
  69. func (s *SMTPConfig) Auth() smtp.Auth {
  70. var auth smtp.Auth
  71. s.Secure = strings.ToUpper(s.Secure)
  72. switch s.Secure {
  73. case "NONE":
  74. auth = unencryptedAuth{smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)}
  75. case "LOGIN":
  76. auth = LoginAuth(s.Username, s.Password)
  77. case "SSL":
  78. fallthrough
  79. default:
  80. auth = smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)
  81. //auth = unencryptedAuth{smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)}
  82. }
  83. return auth
  84. }
  85. func NewSMTPClient(conf *SMTPConfig) SMTPClient {
  86. return SMTPClient{
  87. smtpAuth: conf.Auth(),
  88. host: conf.Host,
  89. port: strconv.Itoa(conf.Port),
  90. user: conf.Username,
  91. secure: conf.Secure,
  92. }
  93. }
  94. // NewMail returns a new Mail
  95. func (c *SMTPClient) NewMail() Mail {
  96. return NewMail()
  97. }
  98. // Send - It can be used for generic SMTP stuff
  99. func (c *SMTPClient) Send(m Mail) error {
  100. length := 0
  101. if len(m.Charset) == 0 {
  102. m.Charset = "utf-8"
  103. }
  104. boundary := "COSCMSBOUNDARYFORSMTPGOLIB"
  105. var message bytes.Buffer
  106. message.WriteString(fmt.Sprintf("X-SMTPAPI: %s\r\n", m.Headers))
  107. //回执
  108. if len(m.RetReceipt) > 0 {
  109. message.WriteString(fmt.Sprintf("Return-Receipt-To: %s\r\n", m.RetReceipt))
  110. message.WriteString(fmt.Sprintf("Disposition-Notification-To: %s\r\n", m.RetReceipt))
  111. }
  112. message.WriteString(fmt.Sprintf("From: %s <%s>\r\n", m.FromName, m.From))
  113. if len(m.ReplyTo) > 0 {
  114. message.WriteString(fmt.Sprintf("Return-Path: %s\r\n", m.ReplyTo))
  115. }
  116. length = len(m.To)
  117. if length > 0 {
  118. nameLength := len(m.ToName)
  119. if nameLength > 0 {
  120. message.WriteString(fmt.Sprintf("To: %s <%s>", m.ToName[0], m.To[0]))
  121. } else {
  122. message.WriteString(fmt.Sprintf("To: <%s>", m.To[0]))
  123. }
  124. for i := 1; i < length; i++ {
  125. if nameLength > i {
  126. message.WriteString(fmt.Sprintf(", %s <%s>", m.ToName[i], m.To[i]))
  127. } else {
  128. message.WriteString(fmt.Sprintf(", <%s>", m.To[i]))
  129. }
  130. }
  131. }
  132. length = len(m.Bcc)
  133. if length > 0 {
  134. message.WriteString(fmt.Sprintf("Bcc: <%s>", m.Bcc[0]))
  135. for i := 1; i < length; i++ {
  136. message.WriteString(fmt.Sprintf(", <%s>", m.Bcc[i]))
  137. }
  138. }
  139. message.WriteString("\r\n")
  140. message.WriteString(fmt.Sprintf("Subject: %s\r\n", m.Subject))
  141. message.WriteString("MIME-Version: 1.0\r\n")
  142. if m.Files != nil {
  143. message.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n\n--%s\r\n", boundary, boundary))
  144. }
  145. if len(m.HTML) > 0 {
  146. //解析内容中的图片
  147. rs := imageRegex.FindAllStringSubmatch(m.HTML, -1)
  148. var embedImages string
  149. for _, v := range rs {
  150. surl := v[2]
  151. if v2 := schemeRegxp.FindStringIndex(surl); v2 == nil {
  152. filename := path.Base(surl)
  153. directory := path.Dir(surl)
  154. if directory == "." {
  155. directory = ""
  156. }
  157. h := md5.New()
  158. h.Write([]byte(surl + "@coscms.0"))
  159. cid := hex.EncodeToString(h.Sum(nil))
  160. if len(m.BaseDir) > 0 && !strings.HasSuffix(m.BaseDir, "/") {
  161. m.BaseDir += "/"
  162. }
  163. if len(directory) > 0 && !strings.HasSuffix(directory, "/") {
  164. directory += "/"
  165. }
  166. if str, err := m.ReadAttachment(m.BaseDir + directory + filename); err == nil {
  167. re3 := regexp.MustCompile(v[1] + `=["']` + regexp.QuoteMeta(surl) + `["']`)
  168. m.HTML = re3.ReplaceAllString(m.HTML, v[1]+`="cid:`+cid+`"`)
  169. embedImages += fmt.Sprintf("--%s\r\n", boundary)
  170. embedImages += fmt.Sprintf("Content-Type: application/octet-stream; name=\"%s\"; charset=\"%s\"\r\n", filename, m.Charset)
  171. embedImages += fmt.Sprintf("Content-Description: %s\r\n", filename)
  172. embedImages += fmt.Sprintf("Content-Disposition: inline; filename=\"%s\"; charset=\"%s\"\r\n", filename, m.Charset)
  173. embedImages += fmt.Sprintf("Content-Transfer-Encoding: base64\r\nContent-ID: <%s>\r\n\r\n%s\r\n\n", cid, str)
  174. }
  175. }
  176. }
  177. part := fmt.Sprintf("Content-Type: text/html\r\n\n%s\r\n\n", m.HTML)
  178. message.WriteString(part)
  179. message.WriteString(embedImages)
  180. } else {
  181. part := fmt.Sprintf("Content-Type: text/plain\r\n\n%s\r\n\n", m.Text)
  182. message.WriteString(part)
  183. }
  184. if m.Files != nil {
  185. for key, value := range m.Files {
  186. message.WriteString(fmt.Sprintf("--%s\r\n", boundary))
  187. message.WriteString("Content-Type: application/octect-stream\r\n")
  188. message.WriteString("Content-Transfer-Encoding:base64\r\n")
  189. message.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"; charset=\"%s\"\r\n\r\n%s\r\n\n", key, m.Charset, value))
  190. }
  191. message.WriteString(fmt.Sprintf("--%s--", boundary))
  192. }
  193. if c.secure == "SSL" || c.secure == "TLS" {
  194. return c.SendTLS(m, message)
  195. }
  196. return smtp.SendMail(c.host+":"+c.port, c.smtpAuth, m.From, m.To, message.Bytes())
  197. }
  198. //SendTLS 通过TLS发送
  199. func (c *SMTPClient) SendTLS(m Mail, message bytes.Buffer) error {
  200. var ct *smtp.Client
  201. var err error
  202. // TLS config
  203. tlsconfig := &tls.Config{
  204. InsecureSkipVerify: true,
  205. ServerName: c.host,
  206. }
  207. // Here is the key, you need to call tls.Dial instead of smtp.Dial
  208. // for smtp servers running on 465 that require an ssl connection
  209. // from the very beginning (no starttls)
  210. conn, err := tls.Dial("tcp", c.host+":"+c.port, tlsconfig)
  211. if err != nil {
  212. log.Println(err, c.host)
  213. return err
  214. }
  215. ct, err = smtp.NewClient(conn, c.host)
  216. if err != nil {
  217. log.Println(err)
  218. return err
  219. }
  220. //if err := ct.StartTLS(tlsconfig);err != nil {
  221. // fmt.Println(err)
  222. // return err
  223. //}
  224. fmt.Println(c.smtpAuth)
  225. if ok,_ := ct.Extension("AUTH"); ok {
  226. // Auth
  227. if err = ct.Auth(c.smtpAuth); err != nil {
  228. log.Println("Auth Error:",
  229. err,
  230. c.user,
  231. )
  232. return err
  233. }
  234. }
  235. // To && From
  236. if err = ct.Mail(m.From); err != nil {
  237. log.Println("Mail Error:", err, m.From)
  238. return err
  239. }
  240. for _, v := range m.To {
  241. if err := ct.Rcpt(v); err != nil {
  242. log.Println("Rcpt Error:", err, v)
  243. return err
  244. }
  245. }
  246. // Data
  247. w, err := ct.Data()
  248. if err != nil {
  249. log.Println("Data Object Error:", err)
  250. return err
  251. }
  252. _, err = w.Write(message.Bytes())
  253. if err != nil {
  254. log.Println("Write Data Object Error:", err)
  255. return err
  256. }
  257. err = w.Close()
  258. if err != nil {
  259. log.Println("Data Object Close Error:", err)
  260. return err
  261. }
  262. ct.Quit()
  263. return nil
  264. }
  265. // AddTo will take a valid email address and store it in the mail.
  266. // It will return an error if the email is invalid.
  267. func (m *Mail) AddTo(email string) error {
  268. //Parses a single RFC 5322 address, e.g. "Barry Gibbs <[email protected]>"
  269. parsedAddess, e := mail.ParseAddress(email)
  270. if e != nil {
  271. return e
  272. }
  273. m.AddRecipient(parsedAddess)
  274. return nil
  275. }
  276. // SetTos 设置收信人Email地址
  277. func (m *Mail) SetTos(emails []string) {
  278. m.To = emails
  279. }
  280. // AddToName will add a new receipient name to mail
  281. func (m *Mail) AddToName(name string) {
  282. m.ToName = append(m.ToName, name)
  283. }
  284. // AddRecipient will take an already parsed mail.Address
  285. func (m *Mail) AddRecipient(receipient *mail.Address) {
  286. m.To = append(m.To, receipient.Address)
  287. if len(receipient.Name) > 0 {
  288. m.ToName = append(m.ToName, receipient.Name)
  289. }
  290. }
  291. // AddSubject will set the subject of the mail
  292. func (m *Mail) AddSubject(s string) {
  293. m.Subject = s
  294. }
  295. // AddHTML will set the body of the mail
  296. func (m *Mail) AddHTML(html string) {
  297. m.HTML = html
  298. }
  299. // AddText will set the body of the email
  300. func (m *Mail) AddText(text string) {
  301. m.Text = text
  302. }
  303. // AddFrom will set the senders email
  304. func (m *Mail) AddFrom(from string) error {
  305. //Parses a single RFC 5322 address, e.g. "Barry Gibbs <[email protected]>"
  306. parsedAddess, e := mail.ParseAddress(from)
  307. if e != nil {
  308. return e
  309. }
  310. m.From = parsedAddess.Address
  311. m.FromName = parsedAddess.Name
  312. return nil
  313. }
  314. // AddBCC works like AddTo but for BCC
  315. func (m *Mail) AddBCC(email string) error {
  316. parsedAddess, e := mail.ParseAddress(email)
  317. if e != nil {
  318. return e
  319. }
  320. m.Bcc = append(m.Bcc, parsedAddess.Address)
  321. return nil
  322. }
  323. // AddRecipientBCC works like AddRecipient but for BCC
  324. func (m *Mail) AddRecipientBCC(email *mail.Address) {
  325. m.Bcc = append(m.Bcc, email.Address)
  326. }
  327. // AddFromName will set the senders name
  328. func (m *Mail) AddFromName(name string) {
  329. m.FromName = name
  330. }
  331. // AddReplyTo will set the return address
  332. func (m *Mail) AddReplyTo(reply string) {
  333. m.ReplyTo = reply
  334. }
  335. // AddDate specifies the date
  336. func (m *Mail) AddDate(date string) {
  337. m.Date = date
  338. }
  339. // AddAttachment will include file/s in mail
  340. func (m *Mail) AddAttachment(filePath string) error {
  341. if m.Files == nil {
  342. m.Files = make(map[string]string)
  343. }
  344. str, err := m.ReadAttachment(filePath)
  345. if err != nil {
  346. return err
  347. }
  348. _, filename := filepath.Split(filePath)
  349. m.Files[filename] = str
  350. return nil
  351. }
  352. // ReadAttachment reading attachment
  353. func (m *Mail) ReadAttachment(filePath string) (string, error) {
  354. file, e := ioutil.ReadFile(filePath)
  355. if e != nil {
  356. return "", e
  357. }
  358. encoded := base64.StdEncoding.EncodeToString(file)
  359. totalChars := len(encoded)
  360. maxLength := 500 //每行最大长度
  361. totalLines := totalChars / maxLength
  362. var buf bytes.Buffer
  363. for i := 0; i < totalLines; i++ {
  364. buf.WriteString(encoded[i*maxLength:(i+1)*maxLength] + "\n")
  365. }
  366. buf.WriteString(encoded[totalLines*maxLength:])
  367. return buf.String(), nil
  368. }
  369. // AddHeaders addding header string
  370. func (m *Mail) AddHeaders(headers string) {
  371. m.Headers = headers
  372. }
  373. // =======================================================
  374. // unencryptedAuth
  375. // =======================================================
  376. type unencryptedAuth struct {
  377. smtp.Auth
  378. }
  379. func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  380. s := *server
  381. s.TLS = true
  382. return a.Auth.Start(&s)
  383. }
  384. // ======================================================
  385. // loginAuth
  386. // ======================================================
  387. type loginAuth struct {
  388. username, password string
  389. }
  390. // LoginAuth loginAuth方式认证
  391. func LoginAuth(username, password string) smtp.Auth {
  392. return &loginAuth{username, password}
  393. }
  394. func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  395. if !server.TLS {
  396. return "", nil, errors.New("unencrypted connection")
  397. }
  398. return "LOGIN", []byte(a.username), nil
  399. }
  400. func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  401. if more {
  402. switch string(fromServer) {
  403. case "Username:":
  404. return []byte(a.username), nil
  405. case "Password:":
  406. return []byte(a.password), nil
  407. default:
  408. return nil, errors.New("Unkown fromServer")
  409. }
  410. }
  411. return nil, nil
  412. }