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.
 
 
 
 
 

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