header.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // headers.go implements "Q" encoding as specified by RFC 2047.
  2. //Modified from https://github.com/joegrasse/mime to use with Go Simple Mail
  3. package mail
  4. import (
  5. "bufio"
  6. "bytes"
  7. "fmt"
  8. "io"
  9. "strings"
  10. "unicode/utf8"
  11. )
  12. type encoder struct {
  13. w *bufio.Writer
  14. charset string
  15. usedChars int
  16. }
  17. // newEncoder returns a new mime header encoder that writes to w. The c
  18. // parameter specifies the name of the character set of the text that will be
  19. // encoded. The u parameter indicates how many characters have been used
  20. // already.
  21. func newEncoder(w io.Writer, c string, u int) *encoder {
  22. return &encoder{bufio.NewWriter(w), strings.ToUpper(c), u}
  23. }
  24. // encode encodes p using the "Q" encoding and writes it to the underlying
  25. // io.Writer. It limits line length to 75 characters.
  26. func (e *encoder) encode(p []byte) (n int, err error) {
  27. var output bytes.Buffer
  28. allPrintable := true
  29. // some lines we encode end in "
  30. //maxLineLength := 75 - 1
  31. maxLineLength := 76
  32. // prevent header injection
  33. p = secureHeader(p)
  34. // check to see if we have all printable characters
  35. for _, c := range p {
  36. if !isVchar(c) && !isWSP(c) {
  37. allPrintable = false
  38. break
  39. }
  40. }
  41. // all characters are printable. just do line folding
  42. if allPrintable {
  43. text := string(p)
  44. words := strings.Split(text, " ")
  45. lineBuffer := ""
  46. firstWord := true
  47. // split the line where necessary
  48. for _, word := range words {
  49. /*fmt.Println("Current Line:",lineBuffer)
  50. fmt.Println("Here: Max:", maxLineLength ,"Buffer Length:", len(lineBuffer), "Used Chars:", e.usedChars, "Length Encoded Char:",len(word))
  51. fmt.Println("----------")*/
  52. newWord := ""
  53. if !firstWord {
  54. newWord += " "
  55. }
  56. newWord += word
  57. // check line length
  58. if (e.usedChars+len(lineBuffer)+len(newWord) /*+len(" ")+len(word)*/) > maxLineLength && (lineBuffer != "" || e.usedChars != 0) {
  59. output.WriteString(lineBuffer + "\r\n")
  60. // first word on newline needs a space in front
  61. if !firstWord {
  62. lineBuffer = ""
  63. } else {
  64. lineBuffer = " "
  65. }
  66. //firstLine = false
  67. //firstWord = true
  68. // reset since not on the first line anymore
  69. e.usedChars = 0
  70. }
  71. /*if !firstWord {
  72. lineBuffer += " "
  73. }*/
  74. lineBuffer += newWord /*word*/
  75. firstWord = false
  76. // reset since not on the first line anymore
  77. /*if !firstLine {
  78. e.usedChars = 0
  79. }*/
  80. }
  81. output.WriteString(lineBuffer)
  82. } else {
  83. firstLine := true
  84. // A single encoded word can not be longer than 75 characters
  85. if e.usedChars == 0 {
  86. maxLineLength = 75
  87. }
  88. wordBegin := "=?" + e.charset + "?Q?"
  89. wordEnd := "?="
  90. lineBuffer := wordBegin
  91. for i := 0; i < len(p); {
  92. // encode the character
  93. encodedChar, runeLength := encode(p, i)
  94. /*fmt.Println("Current Line:",lineBuffer)
  95. fmt.Println("Here: Max:", maxLineLength ,"Buffer Length:", len(lineBuffer), "Used Chars:", e.usedChars, "Length Encoded Char:",len(encodedChar))
  96. fmt.Println("----------")*/
  97. // Check line length
  98. if len(lineBuffer)+e.usedChars+len(encodedChar) > (maxLineLength - len(wordEnd)) {
  99. output.WriteString(lineBuffer + wordEnd + "\r\n")
  100. lineBuffer = " " + wordBegin
  101. firstLine = false
  102. }
  103. lineBuffer += encodedChar
  104. i = i + runeLength
  105. // reset since not on the first line anymore
  106. if !firstLine {
  107. e.usedChars = 0
  108. maxLineLength = 76
  109. }
  110. }
  111. output.WriteString(lineBuffer + wordEnd)
  112. }
  113. e.w.Write(output.Bytes())
  114. e.w.Flush()
  115. n = output.Len()
  116. return n, nil
  117. }
  118. // encode takes a string and position in that string and encodes one utf-8
  119. // character. It then returns the encoded string and number of runes in the
  120. // character.
  121. func encode(text []byte, i int) (encodedString string, runeLength int) {
  122. started := false
  123. for ; i < len(text) && (!utf8.RuneStart(text[i]) || !started); i++ {
  124. switch c := text[i]; {
  125. case c == ' ':
  126. encodedString += "_"
  127. case isVchar(c) && c != '=' && c != '?' && c != '_':
  128. encodedString += string(c)
  129. default:
  130. encodedString += fmt.Sprintf("=%02X", c)
  131. }
  132. runeLength++
  133. started = true
  134. }
  135. return
  136. }
  137. // secureHeader removes all unnecessary values to prevent
  138. // header injection
  139. func secureHeader(text []byte) []byte {
  140. secureValue := strings.TrimSpace(string(text))
  141. secureValue = strings.Replace(secureValue, "\r", "", -1)
  142. secureValue = strings.Replace(secureValue, "\n", "", -1)
  143. secureValue = strings.Replace(secureValue, "\t", "", -1)
  144. return []byte(secureValue)
  145. }
  146. // isQtext returns true if c is an RFC 5322 qtext character.
  147. func isQtext(c byte) bool {
  148. // Printable US-ASCII, excluding backslash or quote.
  149. if c == '\\' || c == '"' {
  150. return false
  151. }
  152. return '!' <= c && c <= '~'
  153. }
  154. // isVchar returns true if c is an RFC 5322 VCHAR character.
  155. func isVchar(c byte) bool {
  156. // Visible (printing) characters.
  157. return '!' <= c && c <= '~'
  158. }
  159. // isWSP returns true if c is a WSP (white space).
  160. // WSP is a space or horizontal tab (RFC5234 Appendix B).
  161. func isWSP(c byte) bool {
  162. return c == ' ' || c == '\t'
  163. }