util.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package main
  2. import (
  3. "bytes"
  4. "compress/zlib"
  5. "crypto/md5"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "errors"
  9. "github.com/sloonz/go-qprintable"
  10. "gopkg.in/iconv.v1"
  11. "io/ioutil"
  12. "regexp"
  13. "strings"
  14. )
  15. func validateEmailData(client *Client) (user string, host string, addr_err error) {
  16. if user, host, addr_err = extractEmail(client.mail_from); addr_err != nil {
  17. return user, host, addr_err
  18. }
  19. client.mail_from = user + "@" + host
  20. if user, host, addr_err = extractEmail(client.rcpt_to); addr_err != nil {
  21. return user, host, addr_err
  22. }
  23. client.rcpt_to = user + "@" + host
  24. // check if on allowed hosts
  25. if allowed := allowedHosts[strings.ToLower(host)]; !allowed {
  26. return user, host, errors.New("invalid host:" + host)
  27. }
  28. return user, host, addr_err
  29. }
  30. func extractEmail(str string) (name string, host string, err error) {
  31. re, _ := regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you're drunk!
  32. if matched := re.FindStringSubmatch(str); len(matched) > 2 {
  33. host = validHost(matched[2])
  34. name = matched[1]
  35. } else {
  36. if res := strings.Split(str, "@"); len(res) > 1 {
  37. name = res[0]
  38. host = validHost(res[1])
  39. }
  40. }
  41. if host == "" || name == "" {
  42. err = errors.New("Invalid address, [" + name + "@" + host + "] address:" + str)
  43. }
  44. return name, host, err
  45. }
  46. // Decode strings in Mime header format
  47. // eg. =?ISO-2022-JP?B?GyRCIVo9dztSOWJAOCVBJWMbKEI=?=
  48. func mimeHeaderDecode(str string) string {
  49. reg, _ := regexp.Compile(`=\?(.+?)\?([QBqp])\?(.+?)\?=`)
  50. matched := reg.FindAllStringSubmatch(str, -1)
  51. var charset, encoding, payload string
  52. if matched != nil {
  53. for i := 0; i < len(matched); i++ {
  54. if len(matched[i]) > 2 {
  55. charset = matched[i][1]
  56. encoding = strings.ToUpper(matched[i][2])
  57. payload = matched[i][3]
  58. switch encoding {
  59. case "B":
  60. str = strings.Replace(
  61. str,
  62. matched[i][0],
  63. mailTransportDecode(payload, "base64", charset),
  64. 1)
  65. case "Q":
  66. str = strings.Replace(
  67. str,
  68. matched[i][0],
  69. mailTransportDecode(payload, "quoted-printable", charset),
  70. 1)
  71. }
  72. }
  73. }
  74. }
  75. return str
  76. }
  77. func validHost(host string) string {
  78. host = strings.Trim(host, " ")
  79. re, _ := 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])$`)
  80. if re.MatchString(host) {
  81. return host
  82. }
  83. return ""
  84. }
  85. // decode from 7bit to 8bit UTF-8
  86. // encoding_type can be "base64" or "quoted-printable"
  87. func mailTransportDecode(str string, encoding_type string, charset string) string {
  88. if charset == "" {
  89. charset = "UTF-8"
  90. } else {
  91. charset = strings.ToUpper(charset)
  92. }
  93. if encoding_type == "base64" {
  94. str = fromBase64(str)
  95. } else if encoding_type == "quoted-printable" {
  96. str = fromQuotedP(str)
  97. }
  98. if charset != "UTF-8" {
  99. charset = fixCharset(charset)
  100. if cd, err := iconv.Open("UTF-8", charset); err == nil {
  101. defer func() {
  102. cd.Close()
  103. if r := recover(); r != nil {
  104. //logln(1, fmt.Sprintf("Recovered in %v", r))
  105. }
  106. }()
  107. // eg. charset can be "ISO-2022-JP"
  108. return cd.ConvString(str)
  109. }
  110. }
  111. return str
  112. }
  113. func fromBase64(data string) string {
  114. buf := bytes.NewBufferString(data)
  115. decoder := base64.NewDecoder(base64.StdEncoding, buf)
  116. res, _ := ioutil.ReadAll(decoder)
  117. return string(res)
  118. }
  119. func fromQuotedP(data string) string {
  120. buf := bytes.NewBufferString(data)
  121. decoder := qprintable.NewDecoder(qprintable.BinaryEncoding, buf)
  122. res, _ := ioutil.ReadAll(decoder)
  123. return string(res)
  124. }
  125. func compress(s string) string {
  126. var b bytes.Buffer
  127. w, _ := zlib.NewWriterLevel(&b, zlib.BestSpeed) // flate.BestCompression
  128. w.Write([]byte(s))
  129. w.Close()
  130. return b.String()
  131. }
  132. func fixCharset(charset string) string {
  133. reg, _ := regexp.Compile(`[_:.\/\\]`)
  134. fixed_charset := reg.ReplaceAllString(charset, "-")
  135. // Fix charset
  136. // borrowed from http://squirrelmail.svn.sourceforge.net/viewvc/squirrelmail/trunk/squirrelmail/include/languages.php?revision=13765&view=markup
  137. // OE ks_c_5601_1987 > cp949
  138. fixed_charset = strings.Replace(fixed_charset, "ks-c-5601-1987", "cp949", -1)
  139. // Moz x-euc-tw > euc-tw
  140. fixed_charset = strings.Replace(fixed_charset, "x-euc", "euc", -1)
  141. // Moz x-windows-949 > cp949
  142. fixed_charset = strings.Replace(fixed_charset, "x-windows_", "cp", -1)
  143. // windows-125x and cp125x charsets
  144. fixed_charset = strings.Replace(fixed_charset, "windows-", "cp", -1)
  145. // ibm > cp
  146. fixed_charset = strings.Replace(fixed_charset, "ibm", "cp", -1)
  147. // iso-8859-8-i -> iso-8859-8
  148. fixed_charset = strings.Replace(fixed_charset, "iso-8859-8-i", "iso-8859-8", -1)
  149. if charset != fixed_charset {
  150. return fixed_charset
  151. }
  152. return charset
  153. }
  154. func md5hex(str string) string {
  155. h := md5.New()
  156. h.Write([]byte(str))
  157. sum := h.Sum([]byte{})
  158. return hex.EncodeToString(sum)
  159. }