func writeJSONPart(writer *multipart.Writer, contentType string, body Body, compressed bool) (err error) { bytes, err := json.Marshal(body) if err != nil { return err } if len(bytes) < kMinCompressedJSONSize { compressed = false } partHeaders := textproto.MIMEHeader{} partHeaders.Set("Content-Type", contentType) if compressed { partHeaders.Set("Content-Encoding", "gzip") } part, err := writer.CreatePart(partHeaders) if err != nil { return err } if compressed { gz := gzip.NewWriter(part) _, err = gz.Write(bytes) gz.Close() } else { _, err = part.Write(bytes) } return }
// Write the attachment to the specified multipart writer. func (a Attachment) Write(w *multipart.Writer) error { headers := make(textproto.MIMEHeader) if len(a.Filename) != 0 { headers.Add("Content-Type", fmt.Sprintf("%s; name=%s", a.ContentType, a.Filename)) } else { headers.Add("Content-Type", a.ContentType) } if a.Encoded { headers.Add("Content-Transfer-Encoding", "base64") } else { headers.Add("Content-Transfer-Encoding", "quoted-printable") } p, err := w.CreatePart(headers) if err != nil { return err } if a.Encoded { if _, err := p.Write([]byte(a.Content)); err != nil { return err } } else { q := quotedprintable.NewWriter(p) if _, err := q.Write([]byte(a.Content)); err != nil { return err } return q.Close() } return nil }
// Adds a new part to the given multipart writer, containing the given revision. // The revision will be written as a nested multipart body if it has attachments. func (db *Database) WriteRevisionAsPart(revBody Body, isError bool, compressPart bool, writer *multipart.Writer) error { partHeaders := textproto.MIMEHeader{} docID, _ := revBody["_id"].(string) revID, _ := revBody["_rev"].(string) if len(docID) > 0 { partHeaders.Set("X-Doc-ID", docID) partHeaders.Set("X-Rev-ID", revID) } if hasInlineAttachments(revBody) { // Write as multipart, including attachments: // OPT: Find a way to do this w/o having to buffer the MIME body in memory! var buffer bytes.Buffer docWriter := multipart.NewWriter(&buffer) contentType := fmt.Sprintf("multipart/related; boundary=%q", docWriter.Boundary()) partHeaders.Set("Content-Type", contentType) db.WriteMultipartDocument(revBody, docWriter, compressPart) docWriter.Close() content := bytes.TrimRight(buffer.Bytes(), "\r\n") part, err := writer.CreatePart(partHeaders) if err == nil { _, err = part.Write(content) } return err } else { // Write as JSON: contentType := "application/json" if isError { contentType += `; error="true"` } return writeJSONPart(writer, contentType, revBody, compressPart) } }
// Writes a revision to a MIME multipart writer, encoding large attachments as separate parts. func (db *Database) WriteMultipartDocument(body Body, writer *multipart.Writer) { // First extract the attachments that should follow: following := []attInfo{} for name, value := range BodyAttachments(body) { meta := value.(map[string]interface{}) var info attInfo info.contentType, _ = meta["content_type"].(string) info.data, _ = decodeAttachment(meta["data"]) if info.data != nil && len(info.data) > kMaxInlineAttachmentSize { info.name = name following = append(following, info) delete(meta, "data") meta["follows"] = true } } // Write the main JSON body: jsonOut, _ := json.Marshal(body) partHeaders := textproto.MIMEHeader{} partHeaders.Set("Content-Type", "application/json") part, _ := writer.CreatePart(partHeaders) part.Write(jsonOut) // Write the following attachments for _, info := range following { partHeaders := textproto.MIMEHeader{} if info.contentType != "" { partHeaders.Set("Content-Type", info.contentType) } partHeaders.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", info.name)) part, _ := writer.CreatePart(partHeaders) part.Write(info.data) } }
func createFormFile(writer *multipart.Writer, fieldname, filename string) { // Try to open the file. file, err := os.Open(filename) if err != nil { panic(err) } defer file.Close() // Create a new form-data header with the provided field name and file name. // Determine Content-Type of the file by its extension. h := textproto.MIMEHeader{} h.Set("Content-Disposition", fmt.Sprintf( `form-data; name="%s"; filename="%s"`, escapeQuotes(fieldname), escapeQuotes(filepath.Base(filename)), )) h.Set("Content-Type", "application/octet-stream") if ct := mime.TypeByExtension(filepath.Ext(filename)); ct != "" { h.Set("Content-Type", ct) } part, err := writer.CreatePart(h) if err != nil { panic(err) } // Copy the content of the file we have opened not reading the whole // file into memory. _, err = io.Copy(part, file) if err != nil { panic(err) } }
func createBlobletPartWriter(i int, path string, size int64, hash string, writer *multipart.Writer) (io.Writer, error) { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="bloblet-%d"; path="%s"; filename="%s"; hash="%s"`, i, path, bloblet.BlobletFileName, hash)) h.Set("Content-Type", "application/zip") h.Set("Content-Length", fmt.Sprintf("%d", size)) h.Set("Content-Transfer-Encoding", "binary") return writer.CreatePart(h) }
func createZipPartWriter(zipStats os.FileInfo, writer *multipart.Writer) (io.Writer, error) { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", `form-data; name="application"; filename="application.zip"`) h.Set("Content-Type", "application/zip") h.Set("Content-Length", fmt.Sprintf("%d", zipStats.Size())) h.Set("Content-Transfer-Encoding", "binary") return writer.CreatePart(h) }
func createJpegFormFile(w *multipart.Writer, fieldname, filename string) (io.Writer, error) { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(fieldname), escapeQuotes(filename))) // Major change from the stock function is to declare this with // type == image/jpeg, otherwise Shutterfly's API will choke on // the request: h.Set("Content-Type", "image/jpeg") return w.CreatePart(h) }
/* * extra.CheckCrc: * 0: 不进行crc32校验 * 1: 以writeMultipart自动生成crc32的值,进行校验 * 2: 以extra.Crc32的值,进行校验 * other: 和2一样, 以 extra.Crc32的值,进行校验 */ func writeMultipart(writer *multipart.Writer, uptoken, key string, hasKey bool, extra *PutExtra) (err error) { if extra == nil { extra = &PutExtra{} } //token if err = writer.WriteField("token", uptoken); err != nil { return } //key if hasKey { if err = writer.WriteField("key", key); err != nil { return } } // extra.Params if extra.Params != nil { for k, v := range extra.Params { err = writer.WriteField(k, v) if err != nil { return } } } //extra.CheckCrc if extra.CheckCrc != 0 { err = writer.WriteField("crc32", strconv.FormatInt(int64(extra.Crc32), 10)) if err != nil { return } } //file head := make(textproto.MIMEHeader) // default the filename is same as key , but "" var fileName = key if fileName == "" { fileName = "filename" } head.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, escapeQuotes(fileName))) if extra.MimeType != "" { head.Set("Content-Type", extra.MimeType) } _, err = writer.CreatePart(head) return err }
// CreateFormFile creates a form file func CreateFormFile(w *multipart.Writer, fieldname, filename, contentType string) (io.Writer, error) { h := make(textproto.MIMEHeader) h.Set("Content-Type", contentType) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(fieldname), escapeQuotes(filename))) if contentType == "" { contentType = "application/octet-stream" } return w.CreatePart(h) }
func multipartMIME( writer *multipart.Writer, msg string, attachments []*Attachment, ) error { // write message mh := make(textproto.MIMEHeader) mh.Add("Content-Type", "text/plain") mh.Add("Content-Transfer-Encoding", "base64") msgWriter, err := writer.CreatePart(mh) if err != nil { return log.Error(err) } _, err = io.WriteString(msgWriter, base64.Encode([]byte(msg))) if err != nil { return log.Error(err) } // write attachments for _, attachment := range attachments { mh = make(textproto.MIMEHeader) base := filepath.Base(attachment.Filename) if attachment.ContentType != "" { mh.Add("Content-Type", attachment.ContentType) } else { ct := mime.TypeByExtension(filepath.Ext(base)) if ct != "" { mh.Add("Content-Type", ct) } else { mh.Add("Content-Type", "application/octet-stream") } } mh.Add("Content-Transfer-Encoding", "base64") mh.Add("Content-Disposition", "attachment; filename="+base) if attachment.Inline { mh.Add("Content-Disposition", "inline") } attachmentWriter, err := writer.CreatePart(mh) if err != nil { return log.Error(err) } encoder := base64.NewEncoder(attachmentWriter) if _, err := io.Copy(encoder, attachment.Reader); err != nil { return log.Error(err) } if err := encoder.Close(); err != nil { return log.Error(err) } } return nil }
func appendPart(w *multipart.Writer, headers func(h textproto.MIMEHeader), body string) { if body == "" { return } h := textproto.MIMEHeader{} h.Set("Content-Transfer-Encoding", "quoted-printable") headers(h) partW, err := w.CreatePart(h) log.Panice(err, "create MIME part") quoW := quotedprintable.NewWriter(partW) defer quoW.Close() io.WriteString(quoW, body) }
// Write the body of the email to the specified writer. func (e *Email) writeBody(w *multipart.Writer) error { var ( buff = &bytes.Buffer{} altWriter = multipart.NewWriter(buff) ) p, err := w.CreatePart(textproto.MIMEHeader{ "Content-Type": []string{ fmt.Sprintf("multipart/alternative; boundary=%s", altWriter.Boundary()), }, }) if err != nil { return err } if e.Text == "" { e.Text = sanitize.HTML(e.Html) } if e.Html == "" { e.Html = toHTML(e.Text) } if err := (Attachment{ ContentType: "text/plain; charset=utf-8", Content: e.Text, }.Write(altWriter)); err != nil { return err } if err := (Attachment{ ContentType: "text/html; charset=utf-8", Content: e.Html, }.Write(altWriter)); err != nil { return err } if err := altWriter.Close(); err != nil { return err } if _, err := io.Copy(p, buff); err != nil { return err } return nil }
// Writes a revision to a MIME multipart writer, encoding large attachments as separate parts. func (db *Database) WriteMultipartDocument(body Body, writer *multipart.Writer, compress bool) { // First extract the attachments that should follow: following := []attInfo{} for name, value := range BodyAttachments(body) { meta := value.(map[string]interface{}) if meta["stub"] != true { var err error var info attInfo info.contentType, _ = meta["content_type"].(string) info.data, err = decodeAttachment(meta["data"]) if info.data == nil { base.Warn("Couldn't decode attachment %q of doc %q: %v", name, body["_id"], err) meta["stub"] = true delete(meta, "data") } else if len(info.data) > kMaxInlineAttachmentSize { info.name = name following = append(following, info) meta["follows"] = true delete(meta, "data") } } } // Write the main JSON body: writeJSONPart(writer, "application/json", body, compress) // Write the following attachments for _, info := range following { partHeaders := textproto.MIMEHeader{} if info.contentType != "" { partHeaders.Set("Content-Type", info.contentType) } partHeaders.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", info.name)) part, _ := writer.CreatePart(partHeaders) part.Write(info.data) } }
// Bytes gets the encoded MIME message. func (m *Message) Bytes() ([]byte, error) { var buffer = &bytes.Buffer{} header := textproto.MIMEHeader{} var err error // Require To, Cc, or Bcc // We'll parse the slices into a list of addresses // and then make sure that list isn't empty. toAddrs := getAddressListString(m.To) ccAddrs := getAddressListString(m.Cc) bccAddrs := getAddressListString(m.Bcc) var hasTo = toAddrs != "" var hasCc = ccAddrs != "" var hasBcc = bccAddrs != "" if !hasTo && !hasCc && !hasBcc { return nil, ErrMissingRecipient } if hasTo { header.Add("To", toAddrs) } if hasCc { header.Add("Cc", ccAddrs) } // BCC header is excluded on purpose. // BCC recipients aren't included in the message // headers and are only used at the SMTP level. var emptyAddress mail.Address // Require From address if m.From == emptyAddress { return nil, ErrMissingFromAddress } header.Add("From", m.From.String()) // Optional ReplyTo if m.ReplyTo != emptyAddress { header.Add("Reply-To", m.ReplyTo.String()) } // Optional Subject if m.Subject != "" { quotedSubject := qEncodeAndWrap(m.Subject, 9 /* len("Subject: ") */) if quotedSubject[0] == '"' { // qEncode used simple quoting, which adds quote // characters to email subjects. quotedSubject = quotedSubject[1 : len(quotedSubject)-1] } header.Add("Subject", quotedSubject) } // Date if _, ok := m.Headers["Date"]; !ok { header.Add("Date", time.Now().UTC().Format(time.RFC822)) } for k, v := range m.Headers { header[k] = v } header.Add("MIME-Version", "1.0") var mixedw *multipart.Writer var hasAttachments = m.Attachments != nil && len(m.Attachments) > 0 if hasAttachments { // Top level multipart writer for our `multipart/mixed` body. // only needed if we have attachments mixedw = multipart.NewWriter(buffer) header.Add("Content-Type", fmt.Sprintf("multipart/mixed;%s boundary=%s", crlf, mixedw.Boundary())) err = writeHeader(buffer, header) if err != nil { return nil, err } header = textproto.MIMEHeader{} // Write the start of our `multipart/mixed` body. _, err = fmt.Fprintf(buffer, "--%s%s", mixedw.Boundary(), crlf) if err != nil { return nil, err } } var altw *multipart.Writer if m.Body != "" && m.HTMLBody != "" { // Nested multipart writer for our `multipart/alternative` body. altw = multipart.NewWriter(buffer) header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary())) err := writeHeader(buffer, header) if err != nil { return nil, err } } // Only include an empty plain text body if the html body is also empty. if m.Body != "" || m.Body == "" && m.HTMLBody == "" { if altw != nil { header = textproto.MIMEHeader{} } header.Add("Content-Type", "text/plain; charset=utf-8") header.Add("Content-Transfer-Encoding", "quoted-printable") //header.Add("Content-Transfer-Encoding", "base64") var writer io.Writer if altw != nil { partw, err := altw.CreatePart(header) if err != nil { return nil, err } writer = partw } else { writer = buffer err = writeHeader(buffer, header) if err != nil { return nil, err } } bodyBytes := []byte(m.Body) //encoder := NewBase64MimeEncoder(writer) encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), writer) _, err = encoder.Write(bodyBytes) if err != nil { return nil, err } err = encoder.Close() if err != nil { return nil, err } } if m.HTMLBody != "" { if altw != nil { header = textproto.MIMEHeader{} } header.Add("Content-Type", "text/html; charset=utf-8") //header.Add("Content-Transfer-Encoding", "quoted-printable") header.Add("Content-Transfer-Encoding", "base64") var writer io.Writer if altw != nil { partw, err := altw.CreatePart(header) if err != nil { return nil, err } writer = partw } else { writer = buffer err = writeHeader(buffer, header) if err != nil { return nil, err } } htmlBodyBytes := []byte(m.HTMLBody) encoder := NewBase64MimeEncoder(writer) //encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), writer) _, err = encoder.Write(htmlBodyBytes) if err != nil { return nil, err } err = encoder.Close() if err != nil { return nil, err } } if altw != nil { altw.Close() } else { _, err = fmt.Fprintf(buffer, "%s", crlf) if err != nil { return nil, err } } if hasAttachments { for _, attachment := range m.Attachments { contentType := attachment.ContentType if contentType == "" { contentType = mime.TypeByExtension(filepath.Ext(attachment.Name)) if contentType == "" { contentType = "application/octet-stream" } } header := textproto.MIMEHeader{} header.Add("Content-Type", contentType) header.Add("Content-Disposition", fmt.Sprintf(`attachment;%s filename="%s"`, crlf, attachment.Name)) header.Add("Content-Transfer-Encoding", "base64") attachmentPart, err := mixedw.CreatePart(header) if err != nil { return nil, err } if attachment.Data != nil { encoder := NewBase64MimeEncoder(attachmentPart) _, err = io.Copy(encoder, attachment.Data) if err != nil { return nil, err } err = encoder.Close() if err != nil { return nil, err } } } mixedw.Close() } return buffer.Bytes(), nil }
func sendMail(to []string, cc []string, bcc []string, msg *Message) error { from := Config.DefaultFrom server := Config.MailServer if msg.Server != "" { server = msg.Server } if msg.From != "" { from = msg.From } if from == "" { return errNoFrom } var auth smtp.Auth cram, username, password, server := parseServer(server) if username != "" || password != "" { if cram { auth = smtp.CRAMMD5Auth(username, password) } else { auth = smtp.PlainAuth("", username, password, server) } } var buf bytes.Buffer headers := msg.Headers if headers == nil { headers = make(Headers) } if msg.Subject != "" { headers["Subject"] = msg.Subject } headers["From"] = from if msg.ReplyTo != "" { headers["Reply-To"] = msg.ReplyTo } var err error if len(to) > 0 { headers["To"], err = joinAddrs(to) if err != nil { return err } for ii, v := range to { if v == Admin { if Config.AdminEmail == "" { return errNoAdminEmail } to[ii] = Config.AdminEmail } } } if len(cc) > 0 { headers["Cc"], err = joinAddrs(cc) if err != nil { return err } } if len(bcc) > 0 { headers["Bcc"], err = joinAddrs(bcc) if err != nil { return err } } hk := make([]string, 0, len(headers)) for k := range headers { hk = append(hk, k) } sort.Strings(hk) for _, k := range hk { buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, headers[k])) } buf.WriteString("MIME-Version: 1.0\r\n") mw := multipart.NewWriter(&buf) mw.SetBoundary(makeBoundary()) // Create a multipart mixed first fmt.Fprintf(&buf, "Content-Type: multipart/mixed;\r\n\tboundary=%q\r\n\r\n", mw.Boundary()) var bodyWriter *multipart.Writer if msg.TextBody != "" && msg.HTMLBody != "" { boundary := makeBoundary() outerHeader := make(textproto.MIMEHeader) // First part is a multipart/alternative, which contains the text and html bodies outerHeader.Set("Content-Type", fmt.Sprintf("multipart/alternative; boundary=%q", boundary)) iw, err := mw.CreatePart(outerHeader) if err != nil { return err } bodyWriter = multipart.NewWriter(iw) bodyWriter.SetBoundary(boundary) } else { bodyWriter = mw } if msg.TextBody != "" { textHeader := make(textproto.MIMEHeader) textHeader.Set("Content-Type", "text/plain; charset=UTF-8") tpw, err := bodyWriter.CreatePart(textHeader) if err != nil { return err } if _, err := io.WriteString(tpw, msg.TextBody); err != nil { return err } tpw.Write(crlf) tpw.Write(crlf) } attached := make(map[*Attachment]bool) if msg.HTMLBody != "" { var htmlAttachments []*Attachment for _, v := range msg.Attachments { if v.ContentID != "" && strings.Contains(msg.HTMLBody, fmt.Sprintf("cid:%s", v.ContentID)) { htmlAttachments = append(htmlAttachments, v) attached[v] = true } } var htmlWriter *multipart.Writer if len(htmlAttachments) > 0 { relatedHeader := make(textproto.MIMEHeader) relatedBoundary := makeBoundary() relatedHeader.Set("Content-Type", fmt.Sprintf("multipart/related; boundary=%q; type=\"text/html\"", relatedBoundary)) rw, err := bodyWriter.CreatePart(relatedHeader) if err != nil { return err } htmlWriter = multipart.NewWriter(rw) htmlWriter.SetBoundary(relatedBoundary) } else { htmlWriter = bodyWriter } htmlHeader := make(textproto.MIMEHeader) htmlHeader.Set("Content-Type", "text/html; charset=UTF-8") thw, err := htmlWriter.CreatePart(htmlHeader) if err != nil { return err } if _, err := io.WriteString(thw, msg.HTMLBody); err != nil { return err } thw.Write(crlf) thw.Write(crlf) for _, v := range htmlAttachments { attachmentHeader := make(textproto.MIMEHeader) attachmentHeader.Set("Content-Disposition", "inline") attachmentHeader.Set("Content-Id", fmt.Sprintf("<%s>", v.ContentID)) attachmentHeader.Set("Content-Transfer-Encoding", "base64") attachmentHeader.Set("Content-Type", v.ContentType) aw, err := htmlWriter.CreatePart(attachmentHeader) if err != nil { return err } b := make([]byte, base64.StdEncoding.EncodedLen(len(v.Data))) base64.StdEncoding.Encode(b, v.Data) aw.Write(b) } if htmlWriter != bodyWriter { if err := htmlWriter.Close(); err != nil { return err } } } if bodyWriter != mw { if err := bodyWriter.Close(); err != nil { return err } } for _, v := range msg.Attachments { if attached[v] { continue } attachmentHeader := make(textproto.MIMEHeader) attachmentHeader.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", v.Name)) attachmentHeader.Set("Content-Transfer-Encoding", "base64") attachmentHeader.Set("Content-Type", v.ContentType) aw, err := mw.CreatePart(attachmentHeader) if err != nil { return err } b := make([]byte, base64.StdEncoding.EncodedLen(len(v.Data))) base64.StdEncoding.Encode(b, v.Data) aw.Write(b) } if err := mw.Close(); err != nil { return err } if server == "echo" { printer(buf.String()) return nil } return smtp.SendMail(server, auth, from, to, buf.Bytes()) }
func CreateFormFile(w *multipart.Writer, filename string) (io.Writer, error) { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "file", filename)) h.Set("Content-Type", "application/zip") return w.CreatePart(h) }