| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 | package mailimport (	"bytes"	"crypto/md5"	"crypto/tls"	"encoding/base64"	"encoding/hex"	"errors"	"fmt"	"io/ioutil"	"log"	"net/mail"	"net/smtp"	"path"	"path/filepath"	"regexp"	"strconv"	"strings"	"github.com/astaxie/beego")var (	imageRegex  = regexp.MustCompile(`(src|background)=["'](.*?)["']`)	schemeRegxp = regexp.MustCompile(`^[A-z]+://`))// Mail will represent a formatted emailtype Mail struct {	To         []string	ToName     []string	Subject    string	HTML       string	Text       string	From       string	Bcc        []string	FromName   string	ReplyTo    string	Date       string	Files      map[string]string	Headers    string	BaseDir    string //内容中图片路径	Charset    string //编码	RetReceipt string //回执地址,空白则禁用回执}// NewMail returns a new Mailfunc NewMail() Mail {	return Mail{}}// SMTPClient structtype SMTPClient struct {	smtpAuth smtp.Auth	host     string	port     string	user     string	secure   string}// SMTPConfig 配置结构体type SMTPConfig struct {	Username string	Password string	Host     string	Port     int	Secure   string	Identity string}func (s *SMTPConfig) Address() string {	if s.Port == 0 {		s.Port = 25	}	return s.Host + `:` + strconv.Itoa(s.Port)}func (s *SMTPConfig) Auth() smtp.Auth {	var auth smtp.Auth	s.Secure = strings.ToUpper(s.Secure)	switch s.Secure {	case "NONE":		auth = unencryptedAuth{smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)}	case "LOGIN":		auth = LoginAuth(s.Username, s.Password)	case "SSL":		fallthrough	default:		//auth = smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)		auth = unencryptedAuth{smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)}	}	return auth}func NewSMTPClient(conf *SMTPConfig) SMTPClient {	return SMTPClient{		smtpAuth: conf.Auth(),		host:     conf.Host,		port:     strconv.Itoa(conf.Port),		user:     conf.Username,		secure:   conf.Secure,	}}// NewMail returns a new Mailfunc (c *SMTPClient) NewMail() Mail {	return NewMail()}// Send - It can be used for generic SMTP stufffunc (c *SMTPClient) Send(m Mail) error {	length := 0	if len(m.Charset) == 0 {		m.Charset = "utf-8"	}	boundary := "COSCMSBOUNDARYFORSMTPGOLIB"	var message bytes.Buffer	message.WriteString(fmt.Sprintf("X-SMTPAPI: %s\r\n", m.Headers))	//回执	if len(m.RetReceipt) > 0 {		message.WriteString(fmt.Sprintf("Return-Receipt-To: %s\r\n", m.RetReceipt))		message.WriteString(fmt.Sprintf("Disposition-Notification-To: %s\r\n", m.RetReceipt))	}	message.WriteString(fmt.Sprintf("From: %s <%s>\r\n", m.FromName, m.From))	if len(m.ReplyTo) > 0 {		message.WriteString(fmt.Sprintf("Return-Path: %s\r\n", m.ReplyTo))	}	length = len(m.To)	if length > 0 {		nameLength := len(m.ToName)		if nameLength > 0 {			message.WriteString(fmt.Sprintf("To: %s <%s>", m.ToName[0], m.To[0]))		} else {			message.WriteString(fmt.Sprintf("To: <%s>", m.To[0]))		}		for i := 1; i < length; i++ {			if nameLength > i {				message.WriteString(fmt.Sprintf(", %s <%s>", m.ToName[i], m.To[i]))			} else {				message.WriteString(fmt.Sprintf(", <%s>", m.To[i]))			}		}	}	length = len(m.Bcc)	if length > 0 {		message.WriteString(fmt.Sprintf("Bcc: <%s>", m.Bcc[0]))		for i := 1; i < length; i++ {			message.WriteString(fmt.Sprintf(", <%s>", m.Bcc[i]))		}	}	message.WriteString("\r\n")	message.WriteString(fmt.Sprintf("Subject: %s\r\n", m.Subject))	message.WriteString("MIME-Version: 1.0\r\n")	if m.Files != nil {		message.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n\n--%s\r\n", boundary, boundary))	}	if len(m.HTML) > 0 {		//解析内容中的图片		rs := imageRegex.FindAllStringSubmatch(m.HTML, -1)		var embedImages string		for _, v := range rs {			surl := v[2]			if v2 := schemeRegxp.FindStringIndex(surl); v2 == nil {				filename := path.Base(surl)				directory := path.Dir(surl)				if directory == "." {					directory = ""				}				h := md5.New()				h.Write([]byte(surl + "@coscms.0"))				cid := hex.EncodeToString(h.Sum(nil))				if len(m.BaseDir) > 0 && !strings.HasSuffix(m.BaseDir, "/") {					m.BaseDir += "/"				}				if len(directory) > 0 && !strings.HasSuffix(directory, "/") {					directory += "/"				}				if str, err := m.ReadAttachment(m.BaseDir + directory + filename); err == nil {					re3 := regexp.MustCompile(v[1] + `=["']` + regexp.QuoteMeta(surl) + `["']`)					m.HTML = re3.ReplaceAllString(m.HTML, v[1]+`="cid:`+cid+`"`)					embedImages += fmt.Sprintf("--%s\r\n", boundary)					embedImages += fmt.Sprintf("Content-Type: application/octet-stream; name=\"%s\"; charset=\"%s\"\r\n", filename, m.Charset)					embedImages += fmt.Sprintf("Content-Description: %s\r\n", filename)					embedImages += fmt.Sprintf("Content-Disposition: inline; filename=\"%s\"; charset=\"%s\"\r\n", filename, m.Charset)					embedImages += fmt.Sprintf("Content-Transfer-Encoding: base64\r\nContent-ID: <%s>\r\n\r\n%s\r\n\n", cid, str)				}			}		}		part := fmt.Sprintf("Content-Type: text/html\r\n\n%s\r\n\n", m.HTML)		message.WriteString(part)		message.WriteString(embedImages)	} else {		part := fmt.Sprintf("Content-Type: text/plain\r\n\n%s\r\n\n", m.Text)		message.WriteString(part)	}	if m.Files != nil {		for key, value := range m.Files {			message.WriteString(fmt.Sprintf("--%s\r\n", boundary))			message.WriteString("Content-Type: application/octect-stream\r\n")			message.WriteString("Content-Transfer-Encoding:base64\r\n")			message.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"; charset=\"%s\"\r\n\r\n%s\r\n\n", key, m.Charset, value))		}		message.WriteString(fmt.Sprintf("--%s--", boundary))	}	if c.secure == "SSL" || c.secure == "TLS" {		return c.SendTLS(m, message)	}	return smtp.SendMail(c.host+":"+c.port, c.smtpAuth, m.From, m.To, message.Bytes())}//SendTLS 通过TLS发送func (c *SMTPClient) SendTLS(m Mail, message bytes.Buffer) error {	var ct *smtp.Client	var err error	// TLS config	tlsconfig := &tls.Config{		InsecureSkipVerify: true,		ServerName:         c.host,	}	// Here is the key, you need to call tls.Dial instead of smtp.Dial	// for smtp servers running on 465 that require an ssl connection	// from the very beginning (no starttls)	conn, err := tls.Dial("tcp", c.host+":"+c.port, tlsconfig)	if err != nil {		log.Println(err, c.host)		return err	}	ct, err = smtp.NewClient(conn, c.host)	if err != nil {		log.Println(err)		return err	}	//if err := ct.StartTLS(tlsconfig);err != nil {	//	log.Println("StartTLS Error:",err,c.host,c.port)	//	return err	//}	//if err := ct.StartTLS(tlsconfig);err != nil {	//	fmt.Println(err)	//	return err	//}	fmt.Println(c.smtpAuth)	if ok,s := ct.Extension("AUTH"); ok {		beego.Info(s)		// Auth		if err = ct.Auth(c.smtpAuth); err != nil {			log.Println("Auth Error:",				err,				c.user,			)			return err		}	}	// To && From	if err = ct.Mail(m.From); err != nil {		log.Println("Mail Error:", err, m.From)		return err	}	for _, v := range m.To {		if err := ct.Rcpt(v); err != nil {			log.Println("Rcpt Error:", err, v)			return err		}	}	// Data	w, err := ct.Data()	if err != nil {		log.Println("Data Object Error:", err)		return err	}	_, err = w.Write(message.Bytes())	if err != nil {		log.Println("Write Data Object Error:", err)		return err	}	err = w.Close()	if err != nil {		log.Println("Data Object Close Error:", err)		return err	}	ct.Quit()	return nil}// AddTo will take a valid email address and store it in the mail.// It will return an error if the email is invalid.func (m *Mail) AddTo(email string) error {	//Parses a single RFC 5322 address, e.g. "Barry Gibbs <[email protected]>"	parsedAddess, e := mail.ParseAddress(email)	if e != nil {		return e	}	m.AddRecipient(parsedAddess)	return nil}// SetTos 设置收信人Email地址func (m *Mail) SetTos(emails []string) {	m.To = emails}// AddToName will add a new receipient name to mailfunc (m *Mail) AddToName(name string) {	m.ToName = append(m.ToName, name)}// AddRecipient will take an already parsed mail.Addressfunc (m *Mail) AddRecipient(receipient *mail.Address) {	m.To = append(m.To, receipient.Address)	if len(receipient.Name) > 0 {		m.ToName = append(m.ToName, receipient.Name)	}}// AddSubject will set the subject of the mailfunc (m *Mail) AddSubject(s string) {	m.Subject = s}// AddHTML will set the body of the mailfunc (m *Mail) AddHTML(html string) {	m.HTML = html}// AddText will set the body of the emailfunc (m *Mail) AddText(text string) {	m.Text = text}// AddFrom will set the senders emailfunc (m *Mail) AddFrom(from string) error {	//Parses a single RFC 5322 address, e.g. "Barry Gibbs <[email protected]>"	parsedAddess, e := mail.ParseAddress(from)	if e != nil {		return e	}	m.From = parsedAddess.Address	m.FromName = parsedAddess.Name	return nil}// AddBCC works like AddTo but for BCCfunc (m *Mail) AddBCC(email string) error {	parsedAddess, e := mail.ParseAddress(email)	if e != nil {		return e	}	m.Bcc = append(m.Bcc, parsedAddess.Address)	return nil}// AddRecipientBCC works like AddRecipient but for BCCfunc (m *Mail) AddRecipientBCC(email *mail.Address) {	m.Bcc = append(m.Bcc, email.Address)}// AddFromName will set the senders namefunc (m *Mail) AddFromName(name string) {	m.FromName = name}// AddReplyTo will set the return addressfunc (m *Mail) AddReplyTo(reply string) {	m.ReplyTo = reply}// AddDate specifies the datefunc (m *Mail) AddDate(date string) {	m.Date = date}// AddAttachment will include file/s in mailfunc (m *Mail) AddAttachment(filePath string) error {	if m.Files == nil {		m.Files = make(map[string]string)	}	str, err := m.ReadAttachment(filePath)	if err != nil {		return err	}	_, filename := filepath.Split(filePath)	m.Files[filename] = str	return nil}// ReadAttachment reading attachmentfunc (m *Mail) ReadAttachment(filePath string) (string, error) {	file, e := ioutil.ReadFile(filePath)	if e != nil {		return "", e	}	encoded := base64.StdEncoding.EncodeToString(file)	totalChars := len(encoded)	maxLength := 500 //每行最大长度	totalLines := totalChars / maxLength	var buf bytes.Buffer	for i := 0; i < totalLines; i++ {		buf.WriteString(encoded[i*maxLength:(i+1)*maxLength] + "\n")	}	buf.WriteString(encoded[totalLines*maxLength:])	return buf.String(), nil}// AddHeaders addding header stringfunc (m *Mail) AddHeaders(headers string) {	m.Headers = headers}// =======================================================// unencryptedAuth// =======================================================type unencryptedAuth struct {	smtp.Auth}func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {	s := *server	s.TLS = true	return a.Auth.Start(&s)}// ======================================================// loginAuth// ======================================================type loginAuth struct {	username, password string}// LoginAuth loginAuth方式认证func LoginAuth(username, password string) smtp.Auth {	return &loginAuth{username, password}}func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {	if !server.TLS {		return "", nil, errors.New("unencrypted connection")	}	return "LOGIN", []byte(a.username), nil}func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {	if more {		switch string(fromServer) {		case "Username:":			return []byte(a.username), nil		case "Password:":			return []byte(a.password), nil		default:			return nil, errors.New("Unkown fromServer")		}	}	return nil, nil}
 |