Как создать вложенный многочастный / MIME-конверт для электронной почты в Go? - PullRequest
0 голосов
/ 13 декабря 2018

Я пытаюсь понять, как создать multipart / mime конверты для электронных писем в Go.Следующий код генерирует правильно вложенные тела, но границы вставляются неправильно.

Вы можете увидеть демо на 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) и избегать третьего вечеринки пытается его поднять.

1 Ответ

0 голосов
/ 16 декабря 2018

К сожалению, стандартная поддержка библиотек для написания многочастных сообщений MIME имеет плохой API для вложения.Проблема в том, что вы должны установить строку boundary в заголовке перед созданием модуля записи, но сгенерированная строка границы, очевидно, недоступна до создания модуля записи.Таким образом, вы должны установить граничные строки явно.

Вот мое решение ( работает на игровой площадке Go ), упрощенное для краткости.Я решил использовать внешнюю границу писателя, чтобы установить внутреннюю, и добавил метки, чтобы было легче отслеживать при чтении вывода.

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
}
...