Exemple #1
0
func HandleGetBeacon(w http.ResponseWriter, r *http.Request, id uint64, db *DBClient) {
	var viewerID int64
	viewerID, err := OptionalAuthenticate(w, r, db)
	beacon, err := db.GetThread(id)
	if err != nil {
		WriteErrorResp(w, err.Error(), DatabaseError)
		return
	}
	respBeaconMsg, err := ToRespBeaconMsg(w, beacon, viewerID, db)
	if err != nil {
		return
	}
	respJson, err := json.Marshal(respBeaconMsg)
	respBody := &bytes.Buffer{}
	partWriter := multipart.NewWriter(respBody)
	jsonHeader := textproto.MIMEHeader{}
	jsonHeader.Add("Content-Type", "application/json")
	jsonWriter, err := partWriter.CreatePart(jsonHeader)
	if err != nil {
		WriteErrorResp(w, err.Error(), ServerError)
		return
	}
	jsonWriter.Write(respJson)
	imgHeader := textproto.MIMEHeader{}
	imgHeader.Add("Content-Type", "img/jpeg")
	imgWriter, err := partWriter.CreatePart(imgHeader)
	if err != nil {
		WriteErrorResp(w, err.Error(), ServerError)
		return
	}
	imgWriter.Write(beacon.Image)
	partWriter.Close()
	w.Header().Add("Content-Type", partWriter.FormDataContentType())
	w.Write(respBody.Bytes())
}
Exemple #2
0
// Very Useful for AJAX Validater that require server-side validation
func (f *Form) ValidateSingle(name, value, mime string) error {
	values := Values{
		name: []string{value},
	}
	mimeheader := textproto.MIMEHeader{}
	mimeheader.Add("Content-Type", mime)
	files := FileHeaders{
		name: []*multipart.FileHeader{&multipart.FileHeader{
			Header: mimeheader}},
	}

	val := &Validation{values, files, true, CurVal("")}

	for _, field := range f.fields {
		switch t := field.(type) {
		case Label:
			continue
		default:
			if t.GetName() == name {
				return t.Validate(val)
			}
		}
	}
	return FormError(f.lang["ErrFieldDoesNotExist"])
}
Exemple #3
0
func TestPostBeacon(t *testing.T) {
	imgData = strings.Replace(imgData, "\n", "", -1)
	imgBytes, err := hex.DecodeString(imgData)
	if err != nil {
		t.Fatalf("Unable to parse image data.")
	}
	body := &bytes.Buffer{}
	partWriter := multipart.NewWriter(body)
	jsonHeader := textproto.MIMEHeader{}
	jsonHeader.Add("Content-Type", "application/json")
	jsonWriter, err := partWriter.CreatePart(jsonHeader)
	io.WriteString(jsonWriter, jsonData)
	imgHeader := textproto.MIMEHeader{}
	imgHeader.Add("Content-Type", "img/jpeg")
	imgWriter, err := partWriter.CreatePart(imgHeader)
	imgWriter.Write(imgBytes)
	partWriter.Close()
	req, _ := http.NewRequest("POST", "http://localhost:8765/beacon", body)
	req.Header.Add("Content-Type", partWriter.FormDataContentType())
	req.SetBasicAuth("1", "0")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		t.Error(err.Error())
	}
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err.Error())
	}
	if !strings.Contains(string(respBody), "id") {
		t.Fatalf("Response did not contain correct content: \n%s", string(respBody))
	}
}
Exemple #4
0
func (handler *HttpHandler) ServeDataObj(obj *DataObj) {

	rng := handler.request.Header["Range"]
	objMime := handler.getObjMime(obj)

	lenStr := strconv.FormatInt(obj.Size(), 10)

	if len(rng) > 0 {
		rangeHeader := rng[0]

		byteRange := strings.Split(rangeHeader, "bytes=")

		if len(byteRange) == 2 && byteRange[0] == "" && byteRange[1] != "" {

			var outputBuffer RangeOutput

			byteRangeSplit := strings.Split(byteRange[1], ",")

			for _, rangeStr := range byteRangeSplit {

				rangeStrSplit := strings.Split(rangeStr, "-")
				if len(rangeStrSplit) == 2 {

					var convErr error
					var firstByteN int64
					var lastByteN int64

					firstByte := rangeStrSplit[0]
					lastByte := rangeStrSplit[1]

					// should we only get last bytes
					if firstByte == "" {
						if lastByteN, convErr = strconv.ParseInt(lastByte, 10, 64); convErr != nil {
							log.Print("Error parsing byte range")
							return
						}

						firstByteN = obj.Size() - lastByteN
						lastByteN = obj.Size() - 1
					} else if lastByte == "" {
						if firstByteN, convErr = strconv.ParseInt(firstByte, 10, 64); convErr != nil {
							log.Print("Error parsing byte range")
							return
						}

						lastByteN = obj.Size() - 1
					} else {
						if firstByteN, convErr = strconv.ParseInt(firstByte, 10, 64); convErr != nil {
							log.Print("Error parsing byte range")
							return
						}

						if lastByteN, convErr = strconv.ParseInt(lastByte, 10, 64); convErr != nil {
							log.Print("Error parsing byte range")
							return
						}
					}

					if byteData, err := obj.ReadBytes(firstByteN, int(lastByteN-firstByteN)+1); err == nil {

						outputBuffer = append(outputBuffer, RangeSegmentOutput{
							ContentRange: "bytes " + strconv.FormatInt(firstByteN, 10) + "-" + strconv.FormatInt(lastByteN, 10) + "/" + lenStr,
							ContentType:  objMime,
							ByteContent:  byteData,
						})

					} else {
						log.Print(err)
					}

				} else {
					log.Print("Error parsing byte range")
				}
			}

			if len(outputBuffer) > 1 {

				handler.response.Header().Set("Accept-Ranges", "bytes")
				//handler.response.Header().Set("Content-Length", outputBuffer.TotalLength())

				mpWriter := multipart.NewWriter(handler.response)

				handler.response.Header().Set("Content-Type", "multipart/byteranges; boundary="+mpWriter.Boundary())

				handler.response.WriteHeader(http.StatusPartialContent)

				for _, outputSegment := range outputBuffer {

					var headers textproto.MIMEHeader = make(textproto.MIMEHeader)

					headers.Add("Content-Type", outputSegment.ContentType)
					headers.Add("Content-Range", outputSegment.ContentRange)

					if writer, err := mpWriter.CreatePart(headers); err != nil {
						log.Print(err)
						continue
					} else {
						writer.Write(outputSegment.ByteContent)
					}
				}

			} else if len(outputBuffer) == 1 {
				handler.response.Header().Set("Content-Range", outputBuffer[0].ContentRange)
				handler.response.Header().Set("Accept-Ranges", "bytes")
				handler.response.Header().Set("Content-Length", strconv.Itoa(len(outputBuffer[0].ByteContent)))
				handler.response.Header().Set("Content-Type", outputBuffer[0].ContentType)

				handler.response.WriteHeader(http.StatusPartialContent)

				handler.response.Write(outputBuffer[0].ByteContent)
			}
		}

	} else {

		if handler.opts.Download || handler.query.Get("download") != "" {
			handler.response.Header().Set("Content-Disposition", "attachment; filename="+obj.Name())
			handler.response.Header().Set("Content-type", "application/octet-stream")
		} else {
			handler.response.Header().Set("Content-type", objMime)
		}

		handler.response.Header().Set("Accept-Ranges", "bytes")
		handler.response.Header().Set("Content-Length", lenStr)

		if readEr := obj.ReadChunk(1024000, func(chunk []byte) {
			handler.response.Write(chunk)
		}); readEr != nil {
			log.Print(readEr)

			handler.response.WriteHeader(http.StatusInternalServerError)
			handler.response.Write([]byte("Error: " + readEr.Error()))

		}
	}

}
Exemple #5
0
func SendMailWithAttachments(host string, auth *smtp.Auth, from, subject string, to []string, msg []byte, atch Attachments) error {
	c, err := SMTPConnection(host, auth)
	if err != nil {
		return err
	}
	defer c.Quit()
	if err := c.Mail(from); err != nil {
		return err
	}
	for _, addr := range to {
		if err := c.Rcpt(addr); err != nil {
			return err
		}
	}
	w, err := c.Data()
	if err != nil {
		return err
	}
	multiw := multipart.NewWriter(w)
	err = write(
		w,
		fmt.Sprintf("From: %s%s", from, CRLF),
		fmt.Sprintf("Subject: %s%s", subject, CRLF),
		fmt.Sprintf("To: %s%s", strings.Join(to, ","), CRLF),
	)
	if err != nil {
		return err
	}
	if atch != nil {
		err = write(
			w,
			fmt.Sprintf(`Content-Type: multipart/mixed; boundary="%s"%s`, multiw.Boundary(), CRLF),
			"--"+multiw.Boundary()+CRLF,
			"Content-Transfer-Encoding: quoted-printable",
		)
	} else {
		return write(w, strings.Repeat(CRLF, 4), string(msg), strings.Repeat(CRLF, 4))
	}
	// We write either the message, or 4*CRLF since SMTP supports files
	// being sent without an actual body.
	if msg != nil {
		err = write(w,
			fmt.Sprintf(
				"%s%s%s",
				strings.Repeat(CRLF, 2),
				msg,
				strings.Repeat(CRLF, 2),
			),
		)
		if err != nil {
			return err
		}
	} else {
		if err := write(w, strings.Repeat(CRLF, 4)); err != nil {
			return err
		}
	}
	for filename, file := range atch {
		ext := mime.TypeByExtension(filepath.Ext(filename))
		if ext == "" {
			ext = "text/plain"
		}

		h := textproto.MIMEHeader{}
		h.Add("Content-Type", ext)
		h.Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
		h.Add("Content-Transfer-Encoding", "base64")
		newpart, err := multiw.CreatePart(h)
		if err != nil {
			return err
		}
		buf := bytes.NewBuffer([]byte{})
		bcdr := NewBase64Email(buf, base64.StdEncoding)
		if _, err = io.Copy(bcdr, file); err != nil {
			return err
		}
		if err = bcdr.Close(); err != nil {
			return err
		}
		if _, err = io.Copy(newpart, buf); err != nil {
			return err
		}
	}
	if err = multiw.Close(); err != nil {
		return err
	}
	return w.Close()
}
Exemple #6
0
func (s *Mailer) SendMail(email *shared.Email) error {

	if s.Config.DevOverrideAddress != nil && len(*s.Config.DevOverrideAddress) > 0 {
		email.HTML = email.Recipient + "<hr>" + email.HTML
		email.Recipient = *s.Config.DevOverrideAddress
	}

	log.Printf("Dial %s:%s", s.Config.ServerAddress, s.Config.ServerPort)

	c, err := smtp.Dial(s.Config.ServerAddress + ":" + s.Config.ServerPort)
	if err != nil {
		if s.Config.ServerPort == "9999" {
			log.Println("No dev email server is active")
			log.Printf(`
			MAILTO: %s
			SUBJECT: %s
			%s`, email.Recipient, email.Subject, email.HTML)
			return nil
		}
		log.Println(err)
		return err
	}
	defer c.Quit()

	// For Testing

	log.Println("Connected")

	log.Println("EHLO")

	if err = c.Hello(s.Config.EhloAddress); err != nil {
		log.Println(err)
		return err
	}
	log.Println("EHLO Done")

	if s.Config.ServerPort != "9999" {
		log.Println("Start TLS")
		tlsConfig := tls.Config{
			ServerName: s.Config.ServerAddress,
		}
		if err = c.StartTLS(&tlsConfig); err != nil {
			err = fmt.Errorf("SMTP TLS Error: %s", err.Error())
			log.Println(err)
			return err
		}

		auth := smtp.PlainAuth("", s.Config.Username, s.Config.Password, s.Config.ServerAddress)

		if err = c.Auth(auth); err != nil {
			err = fmt.Errorf("SMTP Auth error: %s", err.Error())
			log.Println(err)
			return err
		}

	}

	log.Printf("Sender %s", email.Sender)
	if err = c.Mail(email.Sender); err != nil {
		c.Reset()
		log.Println(err)
		return err
	}

	log.Printf("Recipient %s", email.Recipient)
	if err = c.Rcpt(email.Recipient); err != nil {
		c.Reset()
		log.Println(err)
		return err
	}

	log.Println("Data")

	writer, err := c.Data()
	if err != nil {
		return fmt.Errorf("SMPT Data Error: %s", err.Error())
	}

	mw := multipart.NewWriter(writer)

	headers := map[string]string{
		"From":         email.Sender,
		"To":           email.Recipient,
		"Subject":      email.Subject,
		"MIME-Version": "1.0",
		"Content-Type": `multipart/mixed; boundary="` + mw.Boundary() + `"`,
	}

	for key, val := range headers {
		fmt.Fprintf(writer, "%s: %s\n", key, val)
	}
	fmt.Fprintln(writer, "")

	htmlHeader := textproto.MIMEHeader{}
	htmlHeader.Add("Content-Type", "text/html")
	htmlPart, err := mw.CreatePart(htmlHeader)
	if err != nil {
		return err
	}
	fmt.Fprintln(htmlPart, email.HTML)

	mw.Close()
	writer.Close()
	c.Reset()
	log.Println("DONE")

	return nil
}
Exemple #7
0
// 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)
	}

	for k, v := range m.Headers {
		header[k] = v
	}

	// Top level multipart writer for our `multipart/mixed` body.
	mixedw := multipart.NewWriter(buffer)

	header.Add("MIME-Version", "1.0")
	header.Add("Content-Type", fmt.Sprintf("multipart/mixed;%s boundary=%s", crlf, mixedw.Boundary()))

	err = writeHeader(buffer, header)
	if err != nil {
		return nil, err
	}

	// Write the start of our `multipart/mixed` body.
	_, err = fmt.Fprintf(buffer, "--%s%s", mixedw.Boundary(), crlf)
	if err != nil {
		return nil, err
	}

	// Does the message have a body?
	if m.Body != "" || m.HTMLBody != "" {

		// Nested multipart writer for our `multipart/alternative` body.
		altw := multipart.NewWriter(buffer)

		header = textproto.MIMEHeader{}
		header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary()))
		err := writeHeader(buffer, header)
		if err != nil {
			return nil, err
		}

		if m.Body != "" {
			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")

			partw, err := altw.CreatePart(header)
			if err != nil {
				return nil, err
			}

			bodyBytes := []byte(m.Body)
			//encoder := NewBase64MimeEncoder(partw)
			encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), partw)
			_, err = encoder.Write(bodyBytes)
			if err != nil {
				return nil, err
			}
			err = encoder.Close()
			if err != nil {
				return nil, err
			}
		}

		if m.HTMLBody != "" {
			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")

			partw, err := altw.CreatePart(header)
			if err != nil {
				return nil, err
			}

			htmlBodyBytes := []byte(m.HTMLBody)
			encoder := NewBase64MimeEncoder(partw)
			//encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), partw)
			_, err = encoder.Write(htmlBodyBytes)
			if err != nil {
				return nil, err
			}
			err = encoder.Close()
			if err != nil {
				return nil, err
			}
		}

		altw.Close()
	}

	if m.Attachments != nil && len(m.Attachments) > 0 {

		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
}
Exemple #8
0
func HandleGetLocal(w http.ResponseWriter, r *http.Request, db *DBClient) {
	viewerID, err := OptionalAuthenticate(w, r, db)
	if err != nil {
		WriteErrorResp(w, err.Error(), DatabaseError)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		WriteErrorResp(w, err.Error(), ServerError)
		return
	}
	var searchMsg LocalSearchMsg
	err = json.Unmarshal(body, &searchMsg)
	if err != nil {
		WriteErrorResp(w, err.Error(), JsonError)
		return
	}
	loc := Geotag{
		Latitude:  searchMsg.Latitude,
		Longitude: searchMsg.Longitude,
	}
	beaconList, err := db.GetLocal(loc, searchMsg.Radius)
	if err != nil {
		WriteErrorResp(w, err.Error(), DatabaseError)
		return
	}
	respMsg := LocalSearchRespMsg{}
	for _, post := range beaconList {
		username, err := db.GetUsername(post.PosterID)
		if err != nil {
			WriteErrorResp(w, err.Error(), DatabaseError)
			return
		}
		var hearted bool
		if viewerID >= 0 {
			hearted, err = db.HasHearted(post.ID, uint64(viewerID))
			if err != nil {
				WriteErrorResp(w, err.Error(), DatabaseError)
				return
			}
		} else {
			hearted = false
		}
		commentCount, err := db.GetCommentCount(post.ID)
		if err != nil {
			WriteErrorResp(w, err.Error(), DatabaseError)
			return
		}
		nextPost := RespThumbnailMsg{
			SubmitBeaconMsg: SubmitBeaconMsg{
				SubmitPostMsg: SubmitPostMsg{
					Id:     post.ID,
					Poster: post.PosterID,
					Text:   post.Description,
				},
				LocationMsg: LocationMsg{
					Latitude:  post.Location.Latitude,
					Longitude: post.Location.Longitude,
				},
			},
			RespPostMsg: RespPostMsg{
				Hearts:   post.Hearts,
				Time:     FormatTime(post.Time),
				Username: username,
				Hearted:  hearted,
			},
			CommentCount: commentCount,
		}
		respMsg.Beacons = append(respMsg.Beacons, nextPost)
	}
	respJson, err := json.Marshal(respMsg)
	if err != nil {
		WriteErrorResp(w, err.Error(), ServerError)
		return
	}
	respBody := &bytes.Buffer{}
	partWriter := multipart.NewWriter(respBody)
	jsonHeader := textproto.MIMEHeader{}
	jsonHeader.Add("Content-Type", "application/json")
	jsonWriter, err := partWriter.CreatePart(jsonHeader)
	if err != nil {
		WriteErrorResp(w, err.Error(), ServerError)
		return
	}
	jsonWriter.Write(respJson)
	for _, post := range beaconList {
		imgHeader := textproto.MIMEHeader{}
		imgHeader.Add("Content-Type", "img/jpeg")
		imgWriter, err := partWriter.CreatePart(imgHeader)
		if err != nil {
			WriteErrorResp(w, err.Error(), ServerError)
			return
		}
		imgWriter.Write(post.Thumbnail)
	}
	partWriter.Close()
	w.Header().Add("Content-Type", partWriter.FormDataContentType())
	w.Write(respBody.Bytes())
}
Exemple #9
0
func (c *Client) UploadJSON(parent, filename string, payload []byte) (*Asset,
	error) {

	c.Log(DebugTrace, "[TRC] UploadJSON %v %v", filename, len(payload))

	t, err := c.ts.Token()
	if err != nil {
		return nil, err
	}

	url := contentURL
	c.Log(DebugURL, "[URL] %v", url)

	// create body
	j := NodeJSON{
		Name:    filename,
		Kind:    AssetFile,
		Parents: []string{parent},
	}
	jj, err := json.Marshal(j)
	if err != nil {
		return nil, err
	}

	// metadata
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	mh := textproto.MIMEHeader{}
	mh.Add("Content-Disposition", `form-data; name="metadata"`)
	mh.Add("Content-Type", "application/json")
	part, err := writer.CreatePart(mh)
	if err != nil {
		return nil, err
	}
	part.Write(jj)

	// content
	mh = textproto.MIMEHeader{}
	mh.Add("Content-Disposition", `form-data; name="content"; filename="`+
		filename+`"`)
	mh.Add("Content-Type", http.DetectContentType(payload))
	part, err = writer.CreatePart(mh)
	if err != nil {
		return nil, err
	}
	part.Write(payload)

	// flush
	writer.Close()

	// create http request
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+t.AccessToken)
	req.Header.Add("Content-Type", "multipart/form-data; boundary="+
		writer.Boundary())

	// dump body
	if c.GetMask()&DebugBody == DebugBody {
		x, _ := httputil.DumpRequestOut(req, true)
		c.Log(DebugBody, "BDY: %s", x)
	}

	// execute request
	clt := &http.Client{}
	res, err := clt.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	c.Log(DebugHTTP, "[HTP] %v", res.Status)

	// obtain body
	rbody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	c.Log(DebugBody, "[BDY] %v", string(rbody))

	switch res.StatusCode {
	case http.StatusCreated:
		// success
	default:
		return nil, NewCombinedError(res.StatusCode, res.Status, rbody)
	}

	var asset Asset
	err = json.Unmarshal(rbody, &asset)
	if err != nil {
		return nil, err
	}

	return &asset, nil
}
Exemple #10
0
// Bytes gets the encoded MIME message.
func (m *Message) Bytes() ([]byte, error) {
	var buffer = &bytes.Buffer{}

	header := textproto.MIMEHeader{}

	// Require To, Cc, or Bcc
	var hasTo = m.To != nil && len(m.To) > 0
	var hasCc = m.Cc != nil && len(m.Cc) > 0
	var hasBcc = m.Bcc != nil && len(m.Bcc) > 0

	if !hasTo && !hasCc && !hasBcc {
		return nil, ErrMissingRecipient
	} else {
		if hasTo {
			toAddrs, err := getAddressListString(m.To)
			if err != nil {
				return nil, err
			}
			header.Add("To", toAddrs)
		}
		if hasCc {
			ccAddrs, err := getAddressListString(m.Cc)
			if err != nil {
				return nil, err
			}
			header.Add("Cc", ccAddrs)
		}
	}

	// Require From address
	if m.From == "" {
		return nil, ErrMissingFromAddress
	} else {
		parsedAddy, err := mail.ParseAddress(m.From)
		if err != nil {
			return nil, err
		}

		header.Add("From", parsedAddy.String())
	}

	// Optional ReplyTo
	if m.ReplyTo != "" {
		parsedAddy, err := mail.ParseAddress(m.ReplyTo)
		if err != nil {
			return nil, err
		}

		header.Add("Reply-To", parsedAddy.String())
	}

	// Optional Subject
	if m.Subject != "" {
		header.Add("Subject", qEncodeAndWrap(m.Subject, 9 /* len("Subject: ") */))
	}

	// Top level multipart writer for our `multipart/mixed` body.
	mixedw := multipart.NewWriter(buffer)

	var err error

	header.Add("MIME-Version", "1.0")
	header.Add("Content-Type", fmt.Sprintf("multipart/mixed;%s boundary=%s", crlf, mixedw.Boundary()))

	err = writeHeader(buffer, header)
	if err != nil {
		return nil, err
	}

	// Write the start of our `multipart/mixed` body.
	_, err = fmt.Fprintf(buffer, "--%s%s", mixedw.Boundary(), crlf)
	if err != nil {
		return nil, err
	}

	// Does the message have a body?
	if m.Body != "" || m.HTMLBody != "" {

		// Nested multipart writer for our `multipart/alternative` body.
		altw := multipart.NewWriter(buffer)

		header = textproto.MIMEHeader{}
		header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary()))
		err := writeHeader(buffer, header)
		if err != nil {
			return nil, err
		}

		if m.Body != "" {
			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")

			partw, err := altw.CreatePart(header)
			if err != nil {
				return nil, err
			}

			bodyBytes := []byte(m.Body)
			encoder := NewBase64MimeEncoder(partw)
			//encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), partw)
			_, err = encoder.Write(bodyBytes)
			if err != nil {
				return nil, err
			}
			err = encoder.Close()
			if err != nil {
				return nil, err
			}
		}

		if m.HTMLBody != "" {
			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")

			partw, err := altw.CreatePart(header)
			if err != nil {
				return nil, err
			}

			htmlBodyBytes := []byte(m.HTMLBody)
			encoder := NewBase64MimeEncoder(partw)
			//encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), partw)
			_, err = encoder.Write(htmlBodyBytes)
			if err != nil {
				return nil, err
			}
			err = encoder.Close()
			if err != nil {
				return nil, err
			}
		}

		altw.Close()
	}

	if m.Attachments != nil && len(m.Attachments) > 0 {

		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
			}

			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 postStatementWithFile(t *testing.T, mart *martini.ClassicMartini, stmt, id string) (contentSHA2sum string) {
	// construct content
	sha2 := sha256.New()
	content := bytes.NewBuffer(nil)

	// write content
	fmt.Fprintln(io.MultiWriter(content, sha2), "example content text")
	contentSHA2sum = fmt.Sprintf("%x", sha2.Sum(nil))

	// update statement
	var statement map[string]interface{}
	json.Unmarshal([]byte(stmt), &statement)
	statement["id"] = id
	statement["attachments"] = []map[string]interface{}{
		{
			"usageType": "http://example.com/attachment-usage/test",
			"display": map[string]interface{}{
				"en-US": "A test attachment",
			},
			"description": map[string]interface{}{
				"en-US": "A test attachment (description)",
			},
			"contentType": "text/plain; charset=ascii",
			"length":      content.Len(),
			"sha2":        contentSHA2sum,
		},
	}
	ustmt, _ := json.Marshal(statement)

	//
	// create multipart/form-data
	var header textproto.MIMEHeader
	buffer := bytes.NewBuffer(nil)
	encoder := multipart.NewWriter(buffer)

	// json field
	header = make(textproto.MIMEHeader)
	header.Add("Content-Type", "application/json")
	jsonfield, err := encoder.CreatePart(header)
	if err != nil {
		t.Fatal(err)
	}
	fmt.Fprintln(jsonfield, string(ustmt))

	// text (content) field
	header = make(textproto.MIMEHeader)
	header.Add("Content-Type", "text/plain")
	header.Add("Content-Transfer-Encoding", "binary")
	header.Add("X-Experience-API-Hash", contentSHA2sum)
	textfield, err := encoder.CreatePart(header)
	if err != nil {
		t.Fatal(err)
	}
	io.Copy(textfield, content)

	// finish writing
	encoder.Close()

	resp := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/test/test/statements", buffer)
	req.Header.Add("Content-Type", "multipart/mixed; boundary="+encoder.Boundary())
	req.Header.Add("X-Experience-API-Version", "1.0.2")

	mart.ServeHTTP(resp, req)

	if got, expected := resp.Code, http.StatusOK; got != expected {
		r, _ := ioutil.ReadAll(resp.Body)
		t.Fatalf("Expected %v response code from post single statement with file; got %d, %v", expected, got, string(r))
	}

	return
}