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.
 
 
 
 
 

585 lines
17 KiB

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