util.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package util
  2. import (
  3. "bytes"
  4. "compress/zlib"
  5. "crypto/md5"
  6. "encoding/base64"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "regexp"
  12. "strings"
  13. "gopkg.in/iconv.v1"
  14. "github.com/sloonz/go-qprintable"
  15. guerrilla "github.com/flashmob/go-guerrilla"
  16. )
  17. var allowedHosts map[string]bool
  18. // map the allow hosts for easy lookup
  19. func prepareAllowedHosts(allowedHostsStr string) {
  20. allowedHosts = make(map[string]bool, 15)
  21. if arr := strings.Split(allowedHostsStr, ","); len(arr) > 0 {
  22. for i := 0; i < len(arr); i++ {
  23. allowedHosts[arr[i]] = true
  24. }
  25. }
  26. }
  27. // TODO: cleanup
  28. func ValidateEmailData(client *guerrilla.Client, allowedHostsStr string) (user string, host string, addr_err error) {
  29. if allowedHosts == nil {
  30. prepareAllowedHosts(allowedHostsStr)
  31. }
  32. if user, host, addr_err = extractEmail(client.MailFrom); addr_err != nil {
  33. return user, host, addr_err
  34. }
  35. client.MailFrom = user + "@" + host
  36. if user, host, addr_err = extractEmail(client.RcptTo); addr_err != nil {
  37. return user, host, addr_err
  38. }
  39. client.RcptTo = user + "@" + host
  40. // check if on allowed hosts
  41. if allowed := allowedHosts[strings.ToLower(host)]; !allowed {
  42. return user, host, errors.New("invalid host:" + host)
  43. }
  44. return user, host, addr_err
  45. }
  46. var extractEmailRegex, _ = regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you're drunk!
  47. func extractEmail(str string) (name string, host string, err error) {
  48. if matched := extractEmailRegex.FindStringSubmatch(str); len(matched) > 2 {
  49. host = validHost(matched[2])
  50. name = matched[1]
  51. } else {
  52. if res := strings.Split(str, "@"); len(res) > 1 {
  53. name = res[0]
  54. host = validHost(res[1])
  55. }
  56. }
  57. if host == "" || name == "" {
  58. err = errors.New("Invalid address, [" + name + "@" + host + "] address:" + str)
  59. }
  60. return name, host, err
  61. }
  62. var mimeRegex, _ = regexp.Compile(`=\?(.+?)\?([QBqp])\?(.+?)\?=`)
  63. // Decode strings in Mime header format
  64. // eg. =?ISO-2022-JP?B?GyRCIVo9dztSOWJAOCVBJWMbKEI=?=
  65. func MimeHeaderDecode(str string) string {
  66. matched := mimeRegex.FindAllStringSubmatch(str, -1)
  67. var charset, encoding, payload string
  68. if matched != nil {
  69. for i := 0; i < len(matched); i++ {
  70. if len(matched[i]) > 2 {
  71. charset = matched[i][1]
  72. encoding = strings.ToUpper(matched[i][2])
  73. payload = matched[i][3]
  74. switch encoding {
  75. case "B":
  76. str = strings.Replace(
  77. str,
  78. matched[i][0],
  79. MailTransportDecode(payload, "base64", charset),
  80. 1)
  81. case "Q":
  82. str = strings.Replace(
  83. str,
  84. matched[i][0],
  85. MailTransportDecode(payload, "quoted-printable", charset),
  86. 1)
  87. }
  88. }
  89. }
  90. }
  91. return str
  92. }
  93. var valihostRegex, _ = regexp.Compile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
  94. func validHost(host string) string {
  95. host = strings.Trim(host, " ")
  96. if valihostRegex.MatchString(host) {
  97. return host
  98. }
  99. return ""
  100. }
  101. // decode from 7bit to 8bit UTF-8
  102. // encodingType can be "base64" or "quoted-printable"
  103. func MailTransportDecode(str string, encodingType string, charset string) string {
  104. if charset == "" {
  105. charset = "UTF-8"
  106. } else {
  107. charset = strings.ToUpper(charset)
  108. }
  109. if encodingType == "base64" {
  110. str = fromBase64(str)
  111. } else if encodingType == "quoted-printable" {
  112. str = fromQuotedP(str)
  113. }
  114. if charset != "UTF-8" {
  115. charset = fixCharset(charset)
  116. if cd, err := iconv.Open("UTF-8", charset); err == nil {
  117. defer func() {
  118. cd.Close()
  119. if r := recover(); r != nil {
  120. //logln(1, fmt.Sprintf("Recovered in %v", r))
  121. }
  122. }()
  123. // eg. charset can be "ISO-2022-JP"
  124. return cd.ConvString(str)
  125. }
  126. }
  127. return str
  128. }
  129. func fromBase64(data string) string {
  130. buf := bytes.NewBufferString(data)
  131. decoder := base64.NewDecoder(base64.StdEncoding, buf)
  132. res, _ := ioutil.ReadAll(decoder)
  133. return string(res)
  134. }
  135. func fromQuotedP(data string) string {
  136. buf := bytes.NewBufferString(data)
  137. decoder := qprintable.NewDecoder(qprintable.BinaryEncoding, buf)
  138. res, _ := ioutil.ReadAll(decoder)
  139. return string(res)
  140. }
  141. var charsetRegex, _ = regexp.Compile(`[_:.\/\\]`)
  142. func fixCharset(charset string) string {
  143. fixed_charset := charsetRegex.ReplaceAllString(charset, "-")
  144. // Fix charset
  145. // borrowed from http://squirrelmail.svn.sourceforge.net/viewvc/squirrelmail/trunk/squirrelmail/include/languages.php?revision=13765&view=markup
  146. // OE ks_c_5601_1987 > cp949
  147. fixed_charset = strings.Replace(fixed_charset, "ks-c-5601-1987", "cp949", -1)
  148. // Moz x-euc-tw > euc-tw
  149. fixed_charset = strings.Replace(fixed_charset, "x-euc", "euc", -1)
  150. // Moz x-windows-949 > cp949
  151. fixed_charset = strings.Replace(fixed_charset, "x-windows_", "cp", -1)
  152. // windows-125x and cp125x charsets
  153. fixed_charset = strings.Replace(fixed_charset, "windows-", "cp", -1)
  154. // ibm > cp
  155. fixed_charset = strings.Replace(fixed_charset, "ibm", "cp", -1)
  156. // iso-8859-8-i -> iso-8859-8
  157. fixed_charset = strings.Replace(fixed_charset, "iso-8859-8-i", "iso-8859-8", -1)
  158. if charset != fixed_charset {
  159. return fixed_charset
  160. }
  161. return charset
  162. }
  163. // returns an md5 hash as string of hex characters
  164. func MD5Hex(stringArguments ...*string) string {
  165. h := md5.New()
  166. var r *strings.Reader
  167. for i := 0; i < len(stringArguments); i++ {
  168. r = strings.NewReader(*stringArguments[i])
  169. io.Copy(h, r)
  170. }
  171. sum := h.Sum([]byte{})
  172. return fmt.Sprintf("%x", sum)
  173. }
  174. // concatenate & compress all strings passed in
  175. func Compress(stringArguments ...*string) string {
  176. var b bytes.Buffer
  177. var r *strings.Reader
  178. w, _ := zlib.NewWriterLevel(&b, zlib.BestSpeed)
  179. for i := 0; i < len(stringArguments); i++ {
  180. r = strings.NewReader(*stringArguments[i])
  181. io.Copy(w, r)
  182. }
  183. w.Close()
  184. return b.String()
  185. }