Golang1.7.3發送大附件郵件

package main

import (
    "bytes"
    "crypto/tls"
    "encoding/base64"
    "errors"
    "fmt"
    "io"
    "mime"
    "mime/multipart"
    "mime/quotedprintable"
    "net/smtp"
    "net/textproto"
    "os"
    "path/filepath"
    "strings"
)

const MemMaxSize = (1 << 20) * 10

func main() {
    var x = &Email{From: "[email protected]", To: "[email protected]",
        Subject: "Test", Content: "Hello world", Type: "plain"}
    size, err := x.Len()
    if err != nil {
        fmt.Println(err)
        return
    }
    var file io.ReadWriter
    if size >= MemMaxSize {
        temp := fmt.Sprintf(".%d.tmp", os.Getpid())
        file, err = os.Create()
        if err != nil {
            fmt.Println(err)
            return
        }
        defer os.Remove(temp)
    } else {
        file = bytes.NewBuffer(make([]byte, 0, size))
    }
    x.Writer(file)
    auth := smtp.PlainAuth("", "test", "123456", "smtp.163.com")
    err = Send(*x, "smtp.163.com:25", auth, file)
    if err != nil {
        fmt.Println(err)
    }
    if c, ok := file.(io.Closer); ok {
        c.Close()
    }
}

//發送消息
func Send(msg Email, addr string, auth smtp.Auth, body io.Reader) error {
    to := strings.Split(msg.To, ",")
    if msg.From == "" || len(to) == 0 {
        return errors.New("Must specify at least one From address and one To address")
    }
    client, err := smtp.Dial(addr)
    if err != nil {
        return err
    }
    defer client.Close()
    host := strings.Split(addr, ":")[0]
    if err = client.Hello(host); err != nil {
        return err
    }
    if ok, _ := client.Extension("STARTTLS"); ok {
        config := &tls.Config{ServerName: host}
        if err = client.StartTLS(config); err != nil {
            return err
        }
    }
    if err = client.Auth(auth); err != nil {
        return err
    }
    if err = client.Mail(msg.From); err != nil {
        return err
    }
    for _, addr := range to {
        if err = client.Rcpt(addr); err != nil {
            return err
        }
    }
    w, err := client.Data()
    if err != nil {
        return err
    }
    if value, ok := body.(io.Seeker); ok {
        value.Seek(0, 0)
    }
    _, err = io.Copy(w, body)
    if err != nil {
        return err
    }
    err = w.Close()
    if err != nil {
        return err
    }
    return client.Quit()
}

//添加附件
func Attach(w *multipart.Writer, filename string) (err error) {
    typ := mime.TypeByExtension(filepath.Ext(filename))
    var Header = make(textproto.MIMEHeader)
    if typ != "" {
        Header.Set("Content-Type", typ)
    } else {
        Header.Set("Content-Type", "application/octet-stream")
    }
    basename := filepath.Base(filename)
    Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", basename))
    Header.Set("Content-ID", fmt.Sprintf("<%s>", basename))
    Header.Set("Content-Transfer-Encoding", "base64")
    File, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer File.Close()

    mw, err := w.CreatePart(Header)
    if err != nil {
        return err
    }
    return base64Wrap(mw, File)
}

type Email struct {
    From        string
    To          string
    Subject     string
    Content     string
    ContentPath string
    Type        string
    Attachments string
}
//返回基礎的頭信息
func (e *Email) Headers() (textproto.MIMEHeader, error) {
    res := make(textproto.MIMEHeader)
    if _, ok := res["To"]; !ok && len(e.To) > 0 {
        res.Set("To", e.To)
    }

    if _, ok := res["Subject"]; !ok && e.Subject != "" {
        res.Set("Subject", e.Subject)
    }

    if _, ok := res["From"]; !ok {
        res.Set("From", e.From)
    }
    return res, nil
}

//編碼郵件內容
func (e *Email) Writer(datawriter io.Writer) error {
    headers, err := e.Headers()
    if err != nil {
        return err
    }
    w := multipart.NewWriter(datawriter)

    headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary())
    headerToBytes(datawriter, headers)
    io.WriteString(datawriter, "\r\n")

    fmt.Fprintf(datawriter, "--%s\r\n", w.Boundary())
    header := textproto.MIMEHeader{}
    if e.Content != "" || e.ContentPath != "" {
        subWriter := multipart.NewWriter(datawriter)
        header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
        headerToBytes(datawriter, header)
        if e.Content != "" {
            header.Set("Content-Type", fmt.Sprintf("text/%s; charset=UTF-8", e.Type))
            header.Set("Content-Transfer-Encoding", "quoted-printable")
            if _, err := subWriter.CreatePart(header); err != nil {
                return err
            }
            qp := quotedprintable.NewWriter(datawriter)
            if _, err := qp.Write([]byte(e.Content)); err != nil {
                return err
            }
            if err := qp.Close(); err != nil {
                return err
            }
        } else {
            header.Set("Content-Type", fmt.Sprintf("text/%s; charset=UTF-8", e.Type))
            header.Set("Content-Transfer-Encoding", "quoted-printable")
            if _, err := subWriter.CreatePart(header); err != nil {
                return err
            }
            qp := quotedprintable.NewWriter(datawriter)
            File, err := os.Open(e.ContentPath)
            if err != nil {
                return err
            }
            defer File.Close()

            _, err = io.Copy(qp, File)
            if err != nil {
                return err
            }
            if err := qp.Close(); err != nil {
                return err
            }
        }
        if err := subWriter.Close(); err != nil {
            return err
        }
    }
    if e.Attachments != "" {
        list := strings.Split(e.Attachments, ",")
        for _, path := range list {
            err = Attach(w, path)
            if err != nil {
                w.Close()
                return err
            }
        }
    }
    return nil
}

//查看一下發送的內容大小,如果過超過一定大小則,使用磁盤文件做臨時
func (e *Email) Len() (int64, error) {
    var l int64
    if e.Content != "" {
        l += int64(len(e.Content))
    } else {
        stat, err := os.Lstat(e.ContentPath)
        if err != nil {
            return 0, err
        }
        l += stat.Size()
    }
    if e.Attachments != "" {
        for _, path := range strings.Split(e.Attachments, ",") {
            stat, err := os.Lstat(path)
            if err != nil {
                return 0, err
            }
            l += stat.Size()
        }
    }
    return l, nil

}
//根據頭信息創建附件
func headerToBytes(w io.Writer, header textproto.MIMEHeader) {
    for field, vals := range header {
        for _, subval := range vals {
            io.WriteString(w, field)
            io.WriteString(w, ": ")
            switch {
            case field == "Content-Type" || field == "Content-Disposition":
                w.Write([]byte(subval))
            default:
                w.Write([]byte(mime.QEncoding.Encode("UTF-8", subval)))
            }
            io.WriteString(w, "\r\n")
        }
    }
}

//編碼成每行固定長度的base64消息
func base64Wrap(w io.Writer, r io.Reader) error {
    const maxRaw = 57
    const MaxLineLength = 76

    buffer := make([]byte, MaxLineLength+len("\r\n"))
    copy(buffer[MaxLineLength:], "\r\n")
    var b = make([]byte, maxRaw)
    for {
        n, err := r.Read(b)
        if err != nil {
            if err == io.EOF {
                return nil
            }
            return err
        }
        if n == maxRaw {
            base64.StdEncoding.Encode(buffer, b[:n])
            w.Write(buffer)
        } else {
            out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
            base64.StdEncoding.Encode(out, b)
            out = append(out, "\r\n"...)
            w.Write(out)
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章