【问题标题】:How to create a nested multipart / MIME envelope for Email in Go?如何在 Go 中为电子邮件创建嵌套的多部分/MIME 信封?
【发布时间】:2019-05-14 03:16:50
【问题描述】:

我正在尝试弄清楚如何在 Go 中为电子邮件构建 multipart/mime envelopes。以下代码生成正确嵌套的主体 - 但边界未正确插入。

你可以在https://play.golang.org/p/XLc4DQFObRn看到一个演示

package main

import (
    "bytes"
    "fmt"
    "io"
    "log"
    "math/rand"
    "mime/multipart"
    "mime/quotedprintable"
    "net/textproto"
)

//  multipart/mixed
//  |- multipart/related
//  |  |- multipart/alternative
//  |  |  |- text/plain
//  |  |  `- text/html
//  |  `- inlines..
//  `- attachments..


func main() {

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    var part io.Writer
    var err error

    // Text Content
    part, err = writer.CreatePart(textproto.MIMEHeader{"Content-Type": {"multipart/alternative"}})
    if err != nil {
        log.Fatal(err)
    }

    childWriter := multipart.NewWriter(part)

    var subpart io.Writer
    for _, contentType := range []string{"text/plain", "text/html"} {
        subpart, err = CreateQuoteTypePart(childWriter, contentType)
        if err != nil {
            log.Fatal(err)
        }
        _, err := subpart.Write([]byte("This is a line of text that needs to be wrapped by quoted-printable before it goes to far.\r\n\r\n"))
        if err != nil {
            log.Fatal(err)
        }
    }

    // Attachments
    filename := fmt.Sprintf("File_%d.jpg", rand.Int31())
    part, err = writer.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/octet-stream"}, "Content-Disposition": {"attachment; filename=" + filename}})
    if err != nil {
        log.Fatal(err)
    }
    part.Write([]byte("AABBCCDDEEFF"))

    writer.Close()

    fmt.Print(`From: Bob <bob@example.com>
To: Alice <alias@example.com>
Subject: Formatted text mail
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=`)
    fmt.Println(writer.Boundary())
    fmt.Println(body.String())

}

// https://github.com/domodwyer/mailyak/blob/master/attachments.go#L142
func CreateQuoteTypePart(writer *multipart.Writer, contentType string) (part io.Writer, err error) {
    header := textproto.MIMEHeader{
        "Content-Type":              []string{contentType},
        "Content-Transfer-Encoding": []string{"quoted-printable"},
    }

    part, err = writer.CreatePart(header)
    if err != nil {
        return
    }
    part = quotedprintable.NewWriter(part)
    return
}

我想坚持使用标准库 (stdlib) 中的答案,避免 third 派对 attempts 插手它。

【问题讨论】:

    标签: email go mime multipart


    【解决方案1】:

    不幸的是,用于编写多部分 MIME 消息的标准库支持有一个糟糕的嵌套 API。问题是你必须在创建writer之前在header中设置boundary字符串,但是在创建writer之前生成的边界字符串显然是不可用的。所以你必须明确设置边界字符串。

    这是我的解决方案 (runnable in the Go Playground),为简洁起见进行了简化。我选择使用外部写入器的边界来设置内部写入器的边界,并添加标签以便在读取输出时更容易跟踪。

    package main
    
    import ("bytes"; "fmt"; "io"; "log"; "math/rand"; "mime/multipart"; "net/textproto")
    
    //  multipart/mixed
    //  |- multipart/related
    //  |  |- multipart/alternative
    //  |  |  |- text/plain
    //  |  |  `- text/html
    //  |  `- inlines..
    //  `- attachments..
    
    func main() {
        mixedContent := &bytes.Buffer{}
        mixedWriter := multipart.NewWriter(mixedContent)
    
        // related content, inside mixed
        var newBoundary = "RELATED-" + mixedWriter.Boundary()
        mixedWriter.SetBoundary(first70("MIXED-" + mixedWriter.Boundary()))
    
        relatedWriter, newBoundary := nestedMultipart(mixedWriter, "multipart/related", newBoundary)
        altWriter, newBoundary := nestedMultipart(relatedWriter, "mulitpart/alternative", "ALTERNATIVE-" + newBoundary)
    
        // Actual content alternatives (finally!)
        var childContent io.Writer
    
        childContent, _ = altWriter.CreatePart(textproto.MIMEHeader{"Content-Type": {"text/plain"}})
        childContent.Write([]byte("This is a line of text\r\n\r\n"))
        childContent, _ = altWriter.CreatePart(textproto.MIMEHeader{"Content-Type": {"text/html"}})
        childContent.Write([]byte("<html>HTML goes here\r\n</html>\r\n"))
        altWriter.Close()
    
        relatedWriter.Close()
    
        // Attachments
        filename := fmt.Sprintf("File_%d.jpg", rand.Int31())
        var fileContent io.Writer
    
        fileContent, _ = mixedWriter.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/octet-stream"}, "Content-Disposition": {"attachment; filename=" + filename}})
        fileContent.Write([]byte("AABBCCDDEEFF"))
    
        mixedWriter.Close()
    
        fmt.Print(`From: Bob <bob@example.com>
    To: Alice <alias@example.com>
    Subject: Formatted text mail
    MIME-Version: 1.0
    Content-Type: multipart/mixed; boundary=`)
        fmt.Print(mixedWriter.Boundary(), "\n\n")
        fmt.Println(mixedContent.String())
    
    }
    
    func nestedMultipart(enclosingWriter *multipart.Writer, contentType, boundary string) (nestedWriter *multipart.Writer, newBoundary string) {
    
        var contentBuffer io.Writer
        var err error
    
        boundary = first70(boundary)
        contentWithBoundary := contentType + "; boundary=\"" + boundary + "\""
        contentBuffer, err = enclosingWriter.CreatePart(textproto.MIMEHeader{"Content-Type": {contentWithBoundary}})
        if err != nil {
            log.Fatal(err)
        }
    
        nestedWriter = multipart.NewWriter(contentBuffer)
        newBoundary = nestedWriter.Boundary()
        nestedWriter.SetBoundary(boundary)
        return
    }
    
    func first70(str string) string {
        if len(str) > 70 {
            return string(str[0:69])
        }
        return str
    }
    

    【讨论】:

    • @ColinGodsey 如果您愿意,您绝对可以提供赏金积分。点击问题下方的“开始赏金”,然后选择“奖励现有答案”的原因。
    猜你喜欢
    • 2021-01-11
    • 2019-05-06
    • 2018-05-16
    • 2016-06-04
    • 2011-06-07
    • 2014-03-31
    • 2020-06-30
    • 2016-10-05
    • 2021-02-06
    相关资源
    最近更新 更多