| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- // headers.go implements "Q" encoding as specified by RFC 2047.
- //Modified from https://github.com/joegrasse/mime to use with Go Simple Mail
- package mail
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "strings"
- "unicode/utf8"
- )
- type encoder struct {
- w *bufio.Writer
- charset string
- usedChars int
- }
- // newEncoder returns a new mime header encoder that writes to w. The c
- // parameter specifies the name of the character set of the text that will be
- // encoded. The u parameter indicates how many characters have been used
- // already.
- func newEncoder(w io.Writer, c string, u int) *encoder {
- return &encoder{bufio.NewWriter(w), strings.ToUpper(c), u}
- }
- // encode encodes p using the "Q" encoding and writes it to the underlying
- // io.Writer. It limits line length to 75 characters.
- func (e *encoder) encode(p []byte) (n int, err error) {
- var output bytes.Buffer
- allPrintable := true
- // some lines we encode end in "
- //maxLineLength := 75 - 1
- maxLineLength := 76
- // prevent header injection
- p = secureHeader(p)
- // check to see if we have all printable characters
- for _, c := range p {
- if !isVchar(c) && !isWSP(c) {
- allPrintable = false
- break
- }
- }
- // all characters are printable. just do line folding
- if allPrintable {
- text := string(p)
- words := strings.Split(text, " ")
- lineBuffer := ""
- firstWord := true
- // split the line where necessary
- for _, word := range words {
- /*fmt.Println("Current Line:",lineBuffer)
- fmt.Println("Here: Max:", maxLineLength ,"Buffer Length:", len(lineBuffer), "Used Chars:", e.usedChars, "Length Encoded Char:",len(word))
- fmt.Println("----------")*/
- newWord := ""
- if !firstWord {
- newWord += " "
- }
- newWord += word
- // check line length
- if (e.usedChars+len(lineBuffer)+len(newWord) /*+len(" ")+len(word)*/) > maxLineLength && (lineBuffer != "" || e.usedChars != 0) {
- output.WriteString(lineBuffer + "\r\n")
- // first word on newline needs a space in front
- if !firstWord {
- lineBuffer = ""
- } else {
- lineBuffer = " "
- }
- //firstLine = false
- //firstWord = true
- // reset since not on the first line anymore
- e.usedChars = 0
- }
- /*if !firstWord {
- lineBuffer += " "
- }*/
- lineBuffer += newWord /*word*/
- firstWord = false
- // reset since not on the first line anymore
- /*if !firstLine {
- e.usedChars = 0
- }*/
- }
- output.WriteString(lineBuffer)
- } else {
- firstLine := true
- // A single encoded word can not be longer than 75 characters
- if e.usedChars == 0 {
- maxLineLength = 75
- }
- wordBegin := "=?" + e.charset + "?Q?"
- wordEnd := "?="
- lineBuffer := wordBegin
- for i := 0; i < len(p); {
- // encode the character
- encodedChar, runeLength := encode(p, i)
- /*fmt.Println("Current Line:",lineBuffer)
- fmt.Println("Here: Max:", maxLineLength ,"Buffer Length:", len(lineBuffer), "Used Chars:", e.usedChars, "Length Encoded Char:",len(encodedChar))
- fmt.Println("----------")*/
- // Check line length
- if len(lineBuffer)+e.usedChars+len(encodedChar) > (maxLineLength - len(wordEnd)) {
- output.WriteString(lineBuffer + wordEnd + "\r\n")
- lineBuffer = " " + wordBegin
- firstLine = false
- }
- lineBuffer += encodedChar
- i = i + runeLength
- // reset since not on the first line anymore
- if !firstLine {
- e.usedChars = 0
- maxLineLength = 76
- }
- }
- output.WriteString(lineBuffer + wordEnd)
- }
- e.w.Write(output.Bytes())
- e.w.Flush()
- n = output.Len()
- return n, nil
- }
- // encode takes a string and position in that string and encodes one utf-8
- // character. It then returns the encoded string and number of runes in the
- // character.
- func encode(text []byte, i int) (encodedString string, runeLength int) {
- started := false
- for ; i < len(text) && (!utf8.RuneStart(text[i]) || !started); i++ {
- switch c := text[i]; {
- case c == ' ':
- encodedString += "_"
- case isVchar(c) && c != '=' && c != '?' && c != '_':
- encodedString += string(c)
- default:
- encodedString += fmt.Sprintf("=%02X", c)
- }
- runeLength++
- started = true
- }
- return
- }
- // secureHeader removes all unnecessary values to prevent
- // header injection
- func secureHeader(text []byte) []byte {
- secureValue := strings.TrimSpace(string(text))
- secureValue = strings.Replace(secureValue, "\r", "", -1)
- secureValue = strings.Replace(secureValue, "\n", "", -1)
- secureValue = strings.Replace(secureValue, "\t", "", -1)
- return []byte(secureValue)
- }
- // isQtext returns true if c is an RFC 5322 qtext character.
- func isQtext(c byte) bool {
- // Printable US-ASCII, excluding backslash or quote.
- if c == '\\' || c == '"' {
- return false
- }
- return '!' <= c && c <= '~'
- }
- // isVchar returns true if c is an RFC 5322 VCHAR character.
- func isVchar(c byte) bool {
- // Visible (printing) characters.
- return '!' <= c && c <= '~'
- }
- // isWSP returns true if c is a WSP (white space).
- // WSP is a space or horizontal tab (RFC5234 Appendix B).
- func isWSP(c byte) bool {
- return c == ' ' || c == '\t'
- }
|