mailbox.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
  5. *
  6. * This file is part of gostfix project https://git.semlanik.org/semlanik/gostfix
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy of this
  9. * software and associated documentation files (the "Software"), to deal in the Software
  10. * without restriction, including without limitation the rights to use, copy, modify,
  11. * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
  12. * to permit persons to whom the Software is furnished to do so, subject to the following
  13. * conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in all copies
  16. * or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  19. * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  20. * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  21. * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  22. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. * DEALINGS IN THE SOFTWARE.
  24. */
  25. package web
  26. import (
  27. "crypto/md5"
  28. "crypto/tls"
  29. "encoding/hex"
  30. "encoding/json"
  31. "fmt"
  32. "html"
  33. template "html/template"
  34. "log"
  35. "net/http"
  36. "net/smtp"
  37. "strconv"
  38. "strings"
  39. "time"
  40. common "git.semlanik.org/semlanik/gostfix/common"
  41. "git.semlanik.org/semlanik/gostfix/config"
  42. "git.semlanik.org/semlanik/gostfix/utils"
  43. )
  44. func (s *Server) handleMailbox(w http.ResponseWriter, user, email string) {
  45. fmt.Fprint(w, s.templater.ExecuteIndex(&struct {
  46. Folders template.HTML
  47. MailNew template.HTML
  48. Version template.HTML
  49. }{
  50. MailNew: template.HTML(s.templater.ExecuteNewMail("")),
  51. Folders: "Folders",
  52. Version: common.Version,
  53. }))
  54. }
  55. func (s *Server) handleMailboxRequest(path, user string, mailbox int, w http.ResponseWriter, r *http.Request) {
  56. log.Printf("Handle mailbox %s", path)
  57. emails, err := s.storage.GetEmails(user)
  58. if err != nil || len(emails) <= 0 {
  59. s.error(http.StatusInternalServerError, "Unable to access mailbox", w)
  60. return
  61. }
  62. if len(emails) <= mailbox {
  63. if path == "" {
  64. http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
  65. } else {
  66. s.error(http.StatusInternalServerError, "Unable to access mailbox", w)
  67. }
  68. return
  69. }
  70. switch path {
  71. case "":
  72. s.handleMailbox(w, user, emails[mailbox])
  73. case "folders":
  74. s.handleFolders(w, user, emails[mailbox])
  75. case "folderStat":
  76. s.handleFolderStat(w, r, user, emails[mailbox])
  77. case "statusLine":
  78. s.handleStatusLine(w, user, emails[mailbox])
  79. case "mailList":
  80. s.handleMailList(w, r, user, emails[mailbox])
  81. case "sendNewMail":
  82. s.handleNewMail(w, r, user, emails[mailbox])
  83. case "notifierSubscribe":
  84. s.Notifier.handleNotifierRequest(w, r, emails[mailbox])
  85. default:
  86. http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
  87. }
  88. }
  89. func (s *Server) handleFolders(w http.ResponseWriter, user, email string) {
  90. folders := s.storage.GetFolders(email)
  91. out, err := json.Marshal(&struct {
  92. Folders []*common.Folder `json:"folders"`
  93. Html string `json:"html"`
  94. }{
  95. Folders: folders,
  96. Html: s.templater.ExecuteFolders(s.storage.GetFolders(email)),
  97. })
  98. if err != nil {
  99. s.error(http.StatusInternalServerError, "Could not fetch folder list", w)
  100. }
  101. w.Write(out)
  102. }
  103. func (s *Server) handleFolderStat(w http.ResponseWriter, r *http.Request, user, email string) {
  104. unread, total, err := s.storage.GetEmailStats(user, email, s.extractFolder(email, r))
  105. if err != nil {
  106. s.error(http.StatusInternalServerError, "Couldn't read mailbox stat", w)
  107. return
  108. }
  109. out, err := json.Marshal(&struct {
  110. Total int `json:"total"`
  111. Unread int `json:"unread"`
  112. }{
  113. Total: total,
  114. Unread: unread,
  115. })
  116. if err != nil {
  117. s.error(http.StatusInternalServerError, "Couldn't parse mailbox stat", w)
  118. return
  119. }
  120. w.Write(out)
  121. }
  122. func (s *Server) handleMailList(w http.ResponseWriter, r *http.Request, user, email string) {
  123. folder := s.extractFolder(email, r)
  124. page, err := strconv.Atoi(r.FormValue("page"))
  125. if err != nil {
  126. page = 0
  127. }
  128. _, total, err := s.storage.GetEmailStats(user, email, folder)
  129. if err != nil {
  130. s.error(http.StatusInternalServerError, "Couldn't read email database", w)
  131. return
  132. }
  133. mailList, err := s.storage.GetMailList(user, email, folder, common.Frame{Skip: int32(50 * page), Limit: 50})
  134. if err != nil {
  135. s.error(http.StatusInternalServerError, "Couldn't read email database", w)
  136. return
  137. }
  138. out, err := json.Marshal(&struct {
  139. Total int `json:"total"`
  140. Html string `json:"html"`
  141. }{
  142. Total: total,
  143. Html: s.templater.ExecuteMailList(mailList),
  144. })
  145. if err != nil {
  146. s.error(http.StatusInternalServerError, "Could not perform maillist", w)
  147. return
  148. }
  149. w.Write(out)
  150. }
  151. func (s *Server) handleStatusLine(w http.ResponseWriter, user, email string) {
  152. info, err := s.storage.GetUserInfo(user)
  153. if err != nil {
  154. s.error(http.StatusInternalServerError, "Could not read user info", w)
  155. return
  156. }
  157. type EmailIndexes struct {
  158. Index int
  159. Email string
  160. }
  161. emails, err := s.storage.GetEmails(user)
  162. emailsIndexes := []EmailIndexes{}
  163. k := 0
  164. for i, existingEmail := range emails {
  165. emailsIndexes = append(emailsIndexes, EmailIndexes{i, existingEmail})
  166. if existingEmail == email {
  167. k = i
  168. }
  169. }
  170. emailsIndexes = emailsIndexes[:k+copy(emailsIndexes[k:], emailsIndexes[k+1:])]
  171. if err != nil {
  172. s.error(http.StatusInternalServerError, "Could not read user info", w)
  173. return
  174. }
  175. emailHash := md5.Sum([]byte(strings.Trim(email, "\t ")))
  176. fmt.Fprint(w, s.templater.ExecuteStatusLine(&struct {
  177. Name string
  178. Email string
  179. EmailHash string
  180. EmailsIndexes []EmailIndexes
  181. }{
  182. Name: info.FullName,
  183. Email: email,
  184. EmailHash: hex.EncodeToString(emailHash[:]),
  185. EmailsIndexes: emailsIndexes,
  186. }))
  187. }
  188. func (s *Server) extractFolder(email string, r *http.Request) string {
  189. folder := r.FormValue("folder")
  190. folders := s.storage.GetFolders(email)
  191. ok := false
  192. for _, existFolder := range folders {
  193. if folder == existFolder.Name {
  194. ok = true
  195. break
  196. }
  197. }
  198. if !ok {
  199. folder = common.Inbox
  200. }
  201. return folder
  202. }
  203. func (s *Server) handleNewMail(w http.ResponseWriter, r *http.Request, user, email string) {
  204. rawMail := &common.Mail{
  205. Header: &common.MailHeader{
  206. From: email,
  207. To: r.FormValue("to"),
  208. Cc: r.FormValue("cc"),
  209. Bcc: r.FormValue("bcc"),
  210. Date: time.Now().Unix(),
  211. Subject: r.FormValue("subject"),
  212. },
  213. Body: &common.MailBody{
  214. PlainText: html.EscapeString(r.FormValue("body")),
  215. },
  216. }
  217. resultEmail := s.templater.ExecuteMail(&struct {
  218. From string
  219. Subject string
  220. Date template.HTML
  221. To string
  222. Body template.HTML
  223. }{
  224. From: rawMail.Header.From,
  225. To: rawMail.Header.To,
  226. Subject: rawMail.Header.Subject,
  227. Date: template.HTML(time.Unix(rawMail.Header.Date, 0).Format(time.RFC1123Z)),
  228. Body: template.HTML(rawMail.Body.PlainText),
  229. })
  230. host := config.ConfigInstance().MyDomain
  231. server := host + ":25"
  232. _, token := s.extractAuth(w, r)
  233. auth := smtp.PlainAuth("token", user, token, host)
  234. tlsconfig := &tls.Config{
  235. InsecureSkipVerify: true,
  236. ServerName: host,
  237. }
  238. client, err := smtp.Dial(server)
  239. if err != nil {
  240. s.error(http.StatusInternalServerError, "Unable to send message", w)
  241. log.Printf("Dial %s \n", err)
  242. return
  243. }
  244. err = client.StartTLS(tlsconfig)
  245. if err != nil {
  246. s.error(http.StatusInternalServerError, "Unable to send message", w)
  247. log.Printf("StartTLS %s \n", err)
  248. return
  249. }
  250. err = client.Auth(auth)
  251. if err != nil {
  252. s.error(http.StatusInternalServerError, "Unable to send message", w)
  253. log.Printf("Auth %s \n", err)
  254. return
  255. }
  256. err = client.Mail(email)
  257. if err != nil {
  258. s.error(http.StatusInternalServerError, "Unable to send message", w)
  259. log.Printf("Mail %s \n", err)
  260. return
  261. }
  262. toList := strings.Split(rawMail.Header.To, ",")
  263. for _, to := range toList {
  264. if !utils.RegExpUtilsInstance().EmailChecker.MatchString(to) {
  265. log.Println("Skip email " + to)
  266. continue
  267. }
  268. err = client.Rcpt(to)
  269. if err != nil {
  270. // s.error(http.StatusInternalServerError, "Unable to send message", w)
  271. log.Println(err)
  272. continue
  273. }
  274. }
  275. mailWriter, err := client.Data()
  276. if err != nil {
  277. s.error(http.StatusInternalServerError, "Unable to send message", w)
  278. log.Println(err)
  279. return
  280. }
  281. _, err = mailWriter.Write([]byte(resultEmail))
  282. if err != nil {
  283. s.error(http.StatusInternalServerError, "Unable to send message", w)
  284. log.Println(err)
  285. return
  286. }
  287. err = mailWriter.Close()
  288. if err != nil {
  289. s.error(http.StatusInternalServerError, "Unable to send message", w)
  290. log.Println(err)
  291. return
  292. }
  293. client.Quit()
  294. s.storage.SaveMail(email, common.Sent, rawMail, true)
  295. w.WriteHeader(http.StatusOK)
  296. w.Write([]byte{0})
  297. }