我正在尝试找出如何在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
}
答案 0 :(得分:5)
不幸的是,用于编写多部分MIME消息的标准库支持具有错误的嵌套API。问题在于,在创建编写器之前,必须在标头中设置boundary
字符串,但是在创建编写器之前,生成的边界字符串显然不可用。因此,您必须显式设置边界字符串。
这是我的解决方案(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
}