server.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. "bufio"
  28. "fmt"
  29. template "html/template"
  30. ioutil "io/ioutil"
  31. "log"
  32. "net/http"
  33. "strings"
  34. common "../common"
  35. utils "../utils"
  36. "github.com/gorilla/sessions"
  37. )
  38. const (
  39. StateHeaderScan = iota
  40. StateBodyScan
  41. StateContentScan
  42. )
  43. const (
  44. AtLeastOneHeaderMask = 1 << iota
  45. FromHeaderMask
  46. DateHeaderMask
  47. ToHeaderMask
  48. AllHeaderMask = 15
  49. )
  50. func NewEmail() *common.Mail {
  51. return &common.Mail{
  52. Header: &common.MailHeader{},
  53. Body: &common.MailBody{
  54. ContentType: "plain/text",
  55. },
  56. }
  57. }
  58. type Server struct {
  59. fileServer http.Handler
  60. templater *Templater
  61. mailPath string
  62. sessionStore *sessions.CookieStore
  63. }
  64. func NewServer(mailPath string) *Server {
  65. return &Server{
  66. templater: NewTemplater("./data/templates"),
  67. fileServer: http.FileServer(http.Dir("./data")),
  68. mailPath: mailPath,
  69. sessionStore: sessions.NewCookieStore(make([]byte, 32)),
  70. }
  71. }
  72. func (s *Server) Run() {
  73. http.Handle("/", s)
  74. log.Fatal(http.ListenAndServe(":65200", nil))
  75. }
  76. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  77. fmt.Println(r.URL.Path)
  78. if utils.StartsWith(r.URL.Path, "/css/") ||
  79. utils.StartsWith(r.URL.Path, "/assets/") ||
  80. utils.StartsWith(r.URL.Path, "/js/") {
  81. s.fileServer.ServeHTTP(w, r)
  82. } else if r.URL.Path == "/login" {
  83. session, _ := s.sessionStore.Get(r, "token")
  84. user, ok := session.Values["user"].(string)
  85. if ok && utils.RegExpUtilsInstance().UserChecker.FindString(user) == user && user != "" {
  86. http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
  87. return
  88. }
  89. if err := r.ParseForm(); err == nil {
  90. user = r.FormValue("user")
  91. fmt.Printf("User form: %s\n", user)
  92. // password := r.FormValue("password")
  93. if user == "semlanik" {
  94. session.Values["user"] = "semlanik"
  95. session.Save(r, w)
  96. http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
  97. return
  98. }
  99. }
  100. data, _ := ioutil.ReadFile("./data/templates/login.html")
  101. w.Write(data)
  102. } else if r.URL.Path == "/logout" {
  103. fmt.Printf("logout")
  104. session, _ := s.sessionStore.Get(r, "token")
  105. session.Values["user"] = ""
  106. session.Save(r, w)
  107. fmt.Printf("Reset user session: %s\n", session.Values["user"])
  108. http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
  109. } else {
  110. session, _ := s.sessionStore.Get(r, "token")
  111. user, ok := session.Values["user"].(string)
  112. fmt.Printf("User session: %s\n", user)
  113. if !ok || utils.RegExpUtilsInstance().UserChecker.FindString(user) != user || user == "" {
  114. fmt.Print("Invalid user")
  115. http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
  116. return
  117. }
  118. // mailPath = config.mailPath + "/" + r.URL.Query().Get("user")
  119. mailPath := "tmp" + "/" + user
  120. if !utils.FileExists(mailPath) {
  121. w.WriteHeader(http.StatusForbidden)
  122. fmt.Fprint(w, "403 Unknown user")
  123. return
  124. }
  125. file, err := utils.OpenAndLockWait(mailPath)
  126. if err != nil {
  127. w.WriteHeader(http.StatusInternalServerError)
  128. fmt.Fprint(w, "500 Internal server error")
  129. return
  130. }
  131. defer file.CloseAndUnlock()
  132. scanner := bufio.NewScanner(file)
  133. activeBoundary := ""
  134. var previousHeader *string = nil
  135. var emails []*common.Mail
  136. mandatoryHeaders := 0
  137. email := NewEmail()
  138. state := StateHeaderScan
  139. for scanner.Scan() {
  140. if scanner.Text() == "" {
  141. if state == StateHeaderScan && mandatoryHeaders&AtLeastOneHeaderMask == AtLeastOneHeaderMask {
  142. boundaryCapture := utils.RegExpUtilsInstance().BoundaryFinder.FindStringSubmatch(email.Body.ContentType)
  143. if len(boundaryCapture) == 2 {
  144. activeBoundary = boundaryCapture[1]
  145. } else {
  146. activeBoundary = ""
  147. }
  148. state = StateBodyScan
  149. // fmt.Printf("--------------------------Start body scan content type:%s boundary: %s -------------------------\n", email.Body.ContentType, activeBoundary)
  150. } else if state == StateBodyScan {
  151. // fmt.Printf("--------------------------Previous email-------------------------\n%v\n", email)
  152. if activeBoundary == "" {
  153. previousHeader = nil
  154. activeBoundary = ""
  155. // fmt.Printf("Actual headers: %d\n", mandatoryHeaders)
  156. if mandatoryHeaders == AllHeaderMask {
  157. emails = append(emails, email)
  158. }
  159. email = NewEmail()
  160. state = StateHeaderScan
  161. mandatoryHeaders = 0
  162. } else {
  163. // fmt.Printf("Still in body scan\n")
  164. continue
  165. }
  166. } else {
  167. // fmt.Printf("Empty line in state %d\n", state)
  168. }
  169. }
  170. if state == StateHeaderScan {
  171. capture := utils.RegExpUtilsInstance().HeaderFinder.FindStringSubmatch(scanner.Text())
  172. if len(capture) == 3 {
  173. // fmt.Printf("capture Header %s : %s\n", strings.ToLower(capture[0]), strings.ToLower(capture[1]))
  174. header := strings.ToLower(capture[1])
  175. mandatoryHeaders |= AtLeastOneHeaderMask
  176. switch header {
  177. case "from":
  178. previousHeader = &email.Header.From
  179. mandatoryHeaders |= FromHeaderMask
  180. case "to":
  181. previousHeader = &email.Header.To
  182. mandatoryHeaders |= ToHeaderMask
  183. case "cc":
  184. previousHeader = &email.Header.Cc
  185. case "bcc":
  186. previousHeader = &email.Header.Bcc
  187. mandatoryHeaders |= ToHeaderMask
  188. case "subject":
  189. previousHeader = &email.Header.Subject
  190. case "date":
  191. previousHeader = &email.Header.Date
  192. mandatoryHeaders |= DateHeaderMask
  193. case "content-type":
  194. previousHeader = &email.Body.ContentType
  195. default:
  196. previousHeader = nil
  197. }
  198. if previousHeader != nil {
  199. *previousHeader += capture[2]
  200. }
  201. continue
  202. }
  203. capture = utils.RegExpUtilsInstance().FoldingFinder.FindStringSubmatch(scanner.Text())
  204. if len(capture) == 2 && previousHeader != nil {
  205. *previousHeader += capture[1]
  206. continue
  207. }
  208. } else {
  209. // email.Body.Content += scanner.Text() + "\n"
  210. if activeBoundary != "" {
  211. capture := utils.RegExpUtilsInstance().BoundaryEndFinder.FindStringSubmatch(scanner.Text())
  212. if len(capture) == 2 {
  213. // fmt.Printf("capture Boundary End %s\n", capture[1])
  214. if activeBoundary == capture[1] {
  215. state = StateBodyScan
  216. activeBoundary = ""
  217. }
  218. continue
  219. }
  220. // capture = boundaryStartFinder.FindStringSubmatch(scanner.Text())
  221. // if len(capture) == 2 && activeBoundary == capture[1] {
  222. // // fmt.Printf("capture Boundary Start %s\n", capture[1])
  223. // state = StateContentScan
  224. // continue
  225. // }
  226. }
  227. }
  228. }
  229. if state == StateBodyScan && mandatoryHeaders == AllHeaderMask { //Finalize if body read till EOF
  230. // fmt.Printf("--------------------------Previous email-------------------------\n%v\n", email)
  231. previousHeader = nil
  232. activeBoundary = ""
  233. emails = append(emails, email)
  234. state = StateHeaderScan
  235. }
  236. fmt.Fprint(w, s.templater.ExecuteIndex(&Index{
  237. MailList: template.HTML(s.templater.ExecuteMailList(emails)),
  238. Folders: "Folders",
  239. Version: common.Version,
  240. }))
  241. }
  242. }