A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

633 lines
18 KiB

  1. /*
  2. * Copyright © 2018 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "fmt"
  13. "html/template"
  14. "net/http"
  15. "net/url"
  16. "runtime/debug"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "github.com/gorilla/sessions"
  21. "github.com/writeas/impart"
  22. "github.com/writeas/web-core/log"
  23. "github.com/writeas/writefreely/page"
  24. )
  25. type UserLevel int
  26. const (
  27. UserLevelNone UserLevel = iota // user or not -- ignored
  28. UserLevelOptional // user or not -- object fetched if user
  29. UserLevelNoneRequired // non-user (required)
  30. UserLevelUser // user (required)
  31. )
  32. type (
  33. handlerFunc func(app *app, w http.ResponseWriter, r *http.Request) error
  34. userHandlerFunc func(app *app, u *User, w http.ResponseWriter, r *http.Request) error
  35. dataHandlerFunc func(app *app, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
  36. authFunc func(app *app, r *http.Request) (*User, error)
  37. )
  38. type Handler struct {
  39. errors *ErrorPages
  40. sessionStore *sessions.CookieStore
  41. app *app
  42. }
  43. // ErrorPages hold template HTML error pages for displaying errors to the user.
  44. // In each, there should be a defined template named "base".
  45. type ErrorPages struct {
  46. NotFound *template.Template
  47. Gone *template.Template
  48. InternalServerError *template.Template
  49. Blank *template.Template
  50. }
  51. // NewHandler returns a new Handler instance, using the given StaticPage data,
  52. // and saving alias to the application's CookieStore.
  53. func NewHandler(app *app) *Handler {
  54. h := &Handler{
  55. errors: &ErrorPages{
  56. NotFound: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>404</title></head><body><p>Not found.</p></body></html>{{end}}")),
  57. Gone: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>410</title></head><body><p>Gone.</p></body></html>{{end}}")),
  58. InternalServerError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}")),
  59. Blank: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}")),
  60. },
  61. sessionStore: app.sessionStore,
  62. app: app,
  63. }
  64. return h
  65. }
  66. // SetErrorPages sets the given set of ErrorPages as templates for any errors
  67. // that come up.
  68. func (h *Handler) SetErrorPages(e *ErrorPages) {
  69. h.errors = e
  70. }
  71. // User handles requests made in the web application by the authenticated user.
  72. // This provides user-friendly HTML pages and actions that work in the browser.
  73. func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
  74. return func(w http.ResponseWriter, r *http.Request) {
  75. h.handleHTTPError(w, r, func() error {
  76. var status int
  77. start := time.Now()
  78. defer func() {
  79. if e := recover(); e != nil {
  80. log.Error("%s: %s", e, debug.Stack())
  81. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  82. status = http.StatusInternalServerError
  83. }
  84. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  85. }()
  86. u := getUserSession(h.app, r)
  87. if u == nil {
  88. err := ErrNotLoggedIn
  89. status = err.Status
  90. return err
  91. }
  92. err := f(h.app, u, w, r)
  93. if err == nil {
  94. status = http.StatusOK
  95. } else if err, ok := err.(impart.HTTPError); ok {
  96. status = err.Status
  97. } else {
  98. status = http.StatusInternalServerError
  99. }
  100. return err
  101. }())
  102. }
  103. }
  104. // Admin handles requests on /admin routes
  105. func (h *Handler) Admin(f userHandlerFunc) http.HandlerFunc {
  106. return func(w http.ResponseWriter, r *http.Request) {
  107. h.handleHTTPError(w, r, func() error {
  108. var status int
  109. start := time.Now()
  110. defer func() {
  111. if e := recover(); e != nil {
  112. log.Error("%s: %s", e, debug.Stack())
  113. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  114. status = http.StatusInternalServerError
  115. }
  116. log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()))
  117. }()
  118. u := getUserSession(h.app, r)
  119. if u == nil || !u.IsAdmin() {
  120. err := impart.HTTPError{http.StatusNotFound, ""}
  121. status = err.Status
  122. return err
  123. }
  124. err := f(h.app, u, w, r)
  125. if err == nil {
  126. status = http.StatusOK
  127. } else if err, ok := err.(impart.HTTPError); ok {
  128. status = err.Status
  129. } else {
  130. status = http.StatusInternalServerError
  131. }
  132. return err
  133. }())
  134. }
  135. }
  136. // UserAPI handles requests made in the API by the authenticated user.
  137. // This provides user-friendly HTML pages and actions that work in the browser.
  138. func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc {
  139. return h.UserAll(false, f, func(app *app, r *http.Request) (*User, error) {
  140. // Authorize user from Authorization header
  141. t := r.Header.Get("Authorization")
  142. if t == "" {
  143. return nil, ErrNoAccessToken
  144. }
  145. u := &User{ID: app.db.GetUserID(t)}
  146. if u.ID == -1 {
  147. return nil, ErrBadAccessToken
  148. }
  149. return u, nil
  150. })
  151. }
  152. func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerFunc {
  153. return func(w http.ResponseWriter, r *http.Request) {
  154. handleFunc := func() error {
  155. var status int
  156. start := time.Now()
  157. defer func() {
  158. if e := recover(); e != nil {
  159. log.Error("%s: %s", e, debug.Stack())
  160. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
  161. status = 500
  162. }
  163. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  164. }()
  165. u, err := a(h.app, r)
  166. if err != nil {
  167. if err, ok := err.(impart.HTTPError); ok {
  168. status = err.Status
  169. } else {
  170. status = 500
  171. }
  172. return err
  173. }
  174. err = f(h.app, u, w, r)
  175. if err == nil {
  176. status = 200
  177. } else if err, ok := err.(impart.HTTPError); ok {
  178. status = err.Status
  179. } else {
  180. status = 500
  181. }
  182. return err
  183. }
  184. if web {
  185. h.handleHTTPError(w, r, handleFunc())
  186. } else {
  187. h.handleError(w, r, handleFunc())
  188. }
  189. }
  190. }
  191. func (h *Handler) RedirectOnErr(f handlerFunc, loc string) handlerFunc {
  192. return func(app *app, w http.ResponseWriter, r *http.Request) error {
  193. err := f(app, w, r)
  194. if err != nil {
  195. if ie, ok := err.(impart.HTTPError); ok {
  196. // Override default redirect with returned error's, if it's a
  197. // redirect error.
  198. if ie.Status == http.StatusFound {
  199. return ie
  200. }
  201. }
  202. return impart.HTTPError{http.StatusFound, loc}
  203. }
  204. return nil
  205. }
  206. }
  207. func (h *Handler) Page(n string) http.HandlerFunc {
  208. return h.Web(func(app *app, w http.ResponseWriter, r *http.Request) error {
  209. t, ok := pages[n]
  210. if !ok {
  211. return impart.HTTPError{http.StatusNotFound, "Page not found."}
  212. }
  213. sp := pageForReq(app, r)
  214. err := t.ExecuteTemplate(w, "base", sp)
  215. if err != nil {
  216. log.Error("Unable to render page: %v", err)
  217. }
  218. return err
  219. }, UserLevelOptional)
  220. }
  221. func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
  222. return func(w http.ResponseWriter, r *http.Request) {
  223. // TODO: factor out this logic shared with Web()
  224. h.handleHTTPError(w, r, func() error {
  225. var status int
  226. start := time.Now()
  227. defer func() {
  228. if e := recover(); e != nil {
  229. u := getUserSession(h.app, r)
  230. username := "None"
  231. if u != nil {
  232. username = u.Username
  233. }
  234. log.Error("User: %s\n\n%s: %s", username, e, debug.Stack())
  235. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  236. status = 500
  237. }
  238. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  239. }()
  240. var session *sessions.Session
  241. var err error
  242. if ul != UserLevelNone {
  243. session, err = h.sessionStore.Get(r, cookieName)
  244. if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
  245. // Cookie is required, but we can ignore this error
  246. log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
  247. }
  248. _, gotUser := session.Values[cookieUserVal].(*User)
  249. if ul == UserLevelNoneRequired && gotUser {
  250. to := correctPageFromLoginAttempt(r)
  251. log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
  252. err := impart.HTTPError{http.StatusFound, to}
  253. status = err.Status
  254. return err
  255. } else if ul == UserLevelUser && !gotUser {
  256. log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
  257. err := ErrNotLoggedIn
  258. status = err.Status
  259. return err
  260. }
  261. }
  262. // TODO: pass User object to function
  263. err = f(h.app, w, r)
  264. if err == nil {
  265. status = 200
  266. } else if httpErr, ok := err.(impart.HTTPError); ok {
  267. status = httpErr.Status
  268. if status < 300 || status > 399 {
  269. addSessionFlash(h.app, w, r, httpErr.Message, session)
  270. return impart.HTTPError{http.StatusFound, r.Referer()}
  271. }
  272. } else {
  273. e := fmt.Sprintf("[Web handler] 500: %v", err)
  274. if !strings.HasSuffix(e, "write: broken pipe") {
  275. log.Error(e)
  276. } else {
  277. log.Error(e)
  278. }
  279. log.Info("Web handler internal error render")
  280. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  281. status = 500
  282. }
  283. return err
  284. }())
  285. }
  286. }
  287. // Web handles requests made in the web application. This provides user-
  288. // friendly HTML pages and actions that work in the browser.
  289. func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
  290. return func(w http.ResponseWriter, r *http.Request) {
  291. h.handleHTTPError(w, r, func() error {
  292. var status int
  293. start := time.Now()
  294. defer func() {
  295. if e := recover(); e != nil {
  296. u := getUserSession(h.app, r)
  297. username := "None"
  298. if u != nil {
  299. username = u.Username
  300. }
  301. log.Error("User: %s\n\n%s: %s", username, e, debug.Stack())
  302. log.Info("Web deferred internal error render")
  303. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  304. status = 500
  305. }
  306. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  307. }()
  308. if ul != UserLevelNone {
  309. session, err := h.sessionStore.Get(r, cookieName)
  310. if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
  311. // Cookie is required, but we can ignore this error
  312. log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
  313. }
  314. _, gotUser := session.Values[cookieUserVal].(*User)
  315. if ul == UserLevelNoneRequired && gotUser {
  316. to := correctPageFromLoginAttempt(r)
  317. log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
  318. err := impart.HTTPError{http.StatusFound, to}
  319. status = err.Status
  320. return err
  321. } else if ul == UserLevelUser && !gotUser {
  322. log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
  323. err := ErrNotLoggedIn
  324. status = err.Status
  325. return err
  326. }
  327. }
  328. // TODO: pass User object to function
  329. err := f(h.app, w, r)
  330. if err == nil {
  331. status = 200
  332. } else if httpErr, ok := err.(impart.HTTPError); ok {
  333. status = httpErr.Status
  334. } else {
  335. e := fmt.Sprintf("[Web handler] 500: %v", err)
  336. log.Error(e)
  337. log.Info("Web internal error render")
  338. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  339. status = 500
  340. }
  341. return err
  342. }())
  343. }
  344. }
  345. func (h *Handler) All(f handlerFunc) http.HandlerFunc {
  346. return func(w http.ResponseWriter, r *http.Request) {
  347. h.handleError(w, r, func() error {
  348. // TODO: return correct "success" status
  349. status := 200
  350. start := time.Now()
  351. defer func() {
  352. if e := recover(); e != nil {
  353. log.Error("%s:\n%s", e, debug.Stack())
  354. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
  355. status = 500
  356. }
  357. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  358. }()
  359. // TODO: do any needed authentication
  360. err := f(h.app, w, r)
  361. if err != nil {
  362. if err, ok := err.(impart.HTTPError); ok {
  363. status = err.Status
  364. } else {
  365. status = 500
  366. }
  367. }
  368. return err
  369. }())
  370. }
  371. }
  372. func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
  373. return func(w http.ResponseWriter, r *http.Request) {
  374. h.handleHTTPError(w, r, func() error {
  375. var status int
  376. start := time.Now()
  377. defer func() {
  378. if e := recover(); e != nil {
  379. log.Error("%s: %s", e, debug.Stack())
  380. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  381. status = 500
  382. }
  383. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  384. }()
  385. data, filename, err := f(h.app, w, r)
  386. if err != nil {
  387. if err, ok := err.(impart.HTTPError); ok {
  388. status = err.Status
  389. } else {
  390. status = 500
  391. }
  392. return err
  393. }
  394. ext := ".json"
  395. ct := "application/json"
  396. if strings.HasSuffix(r.URL.Path, ".csv") {
  397. ext = ".csv"
  398. ct = "text/csv"
  399. } else if strings.HasSuffix(r.URL.Path, ".zip") {
  400. ext = ".zip"
  401. ct = "application/zip"
  402. }
  403. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s%s", filename, ext))
  404. w.Header().Set("Content-Type", ct)
  405. w.Header().Set("Content-Length", strconv.Itoa(len(data)))
  406. fmt.Fprint(w, string(data))
  407. status = 200
  408. return nil
  409. }())
  410. }
  411. }
  412. func (h *Handler) Redirect(url string, ul UserLevel) http.HandlerFunc {
  413. return func(w http.ResponseWriter, r *http.Request) {
  414. h.handleHTTPError(w, r, func() error {
  415. start := time.Now()
  416. var status int
  417. if ul != UserLevelNone {
  418. session, err := h.sessionStore.Get(r, cookieName)
  419. if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
  420. // Cookie is required, but we can ignore this error
  421. log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
  422. }
  423. _, gotUser := session.Values[cookieUserVal].(*User)
  424. if ul == UserLevelNoneRequired && gotUser {
  425. to := correctPageFromLoginAttempt(r)
  426. log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
  427. err := impart.HTTPError{http.StatusFound, to}
  428. status = err.Status
  429. return err
  430. } else if ul == UserLevelUser && !gotUser {
  431. log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
  432. err := ErrNotLoggedIn
  433. status = err.Status
  434. return err
  435. }
  436. }
  437. status = sendRedirect(w, http.StatusFound, url)
  438. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  439. return nil
  440. }())
  441. }
  442. }
  443. func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err error) {
  444. if err == nil {
  445. return
  446. }
  447. if err, ok := err.(impart.HTTPError); ok {
  448. if err.Status >= 300 && err.Status < 400 {
  449. sendRedirect(w, err.Status, err.Message)
  450. return
  451. } else if err.Status == http.StatusUnauthorized {
  452. q := ""
  453. if r.URL.RawQuery != "" {
  454. q = url.QueryEscape("?" + r.URL.RawQuery)
  455. }
  456. sendRedirect(w, http.StatusFound, "/login?to="+r.URL.Path+q)
  457. return
  458. } else if err.Status == http.StatusGone {
  459. p := &struct {
  460. page.StaticPage
  461. Content *template.HTML
  462. }{
  463. StaticPage: pageForReq(h.app, r),
  464. }
  465. if err.Message != "" {
  466. co := template.HTML(err.Message)
  467. p.Content = &co
  468. }
  469. h.errors.Gone.ExecuteTemplate(w, "base", p)
  470. return
  471. } else if err.Status == http.StatusNotFound {
  472. h.errors.NotFound.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  473. return
  474. } else if err.Status == http.StatusInternalServerError {
  475. log.Info("handleHTTPErorr internal error render")
  476. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  477. return
  478. } else if err.Status == http.StatusAccepted {
  479. impart.WriteSuccess(w, "", err.Status)
  480. return
  481. } else {
  482. p := &struct {
  483. page.StaticPage
  484. Title string
  485. Content template.HTML
  486. }{
  487. pageForReq(h.app, r),
  488. fmt.Sprintf("Uh oh (%d)", err.Status),
  489. template.HTML(fmt.Sprintf("<p style=\"text-align: center\" class=\"introduction\">%s</p>", err.Message)),
  490. }
  491. h.errors.Blank.ExecuteTemplate(w, "base", p)
  492. return
  493. }
  494. impart.WriteError(w, err)
  495. return
  496. }
  497. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."})
  498. }
  499. func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) {
  500. if err == nil {
  501. return
  502. }
  503. if err, ok := err.(impart.HTTPError); ok {
  504. if err.Status >= 300 && err.Status < 400 {
  505. sendRedirect(w, err.Status, err.Message)
  506. return
  507. }
  508. // if strings.Contains(r.Header.Get("Accept"), "text/html") {
  509. impart.WriteError(w, err)
  510. // }
  511. return
  512. }
  513. if IsJSON(r.Header.Get("Content-Type")) {
  514. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."})
  515. return
  516. }
  517. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  518. }
  519. func correctPageFromLoginAttempt(r *http.Request) string {
  520. to := r.FormValue("to")
  521. if to == "" {
  522. to = "/"
  523. } else if !strings.HasPrefix(to, "/") {
  524. to = "/" + to
  525. }
  526. return to
  527. }
  528. func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
  529. return func(w http.ResponseWriter, r *http.Request) {
  530. h.handleHTTPError(w, r, func() error {
  531. status := 200
  532. start := time.Now()
  533. defer func() {
  534. if e := recover(); e != nil {
  535. log.Error("Handler.LogHandlerFunc\n\n%s: %s", e, debug.Stack())
  536. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
  537. status = 500
  538. }
  539. // TODO: log actual status code returned
  540. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  541. }()
  542. f(w, r)
  543. return nil
  544. }())
  545. }
  546. }
  547. func sendRedirect(w http.ResponseWriter, code int, location string) int {
  548. w.Header().Set("Location", location)
  549. w.WriteHeader(code)
  550. return code
  551. }