2018-12-24 17:45:15 +00:00
/ *
2022-11-11 04:49:16 +00:00
* Copyright © 2018 - 2021 Musing Studio LLC .
2018-12-24 17:45:15 +00:00
*
* This file is part of WriteFreely .
*
* WriteFreely is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , included
* in the LICENSE file in this source code package .
* /
2018-12-31 06:05:26 +00:00
2018-11-08 04:50:50 +00:00
package writefreely
import (
"fmt"
"html/template"
"net/http"
"net/url"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/gorilla/sessions"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
2022-04-29 18:21:22 +00:00
"github.com/writefreely/go-gopher"
2021-04-06 21:24:07 +00:00
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page"
2018-11-08 04:50:50 +00:00
)
2019-06-16 22:55:50 +00:00
// UserLevel represents the required user level for accessing an endpoint
2018-11-08 04:50:50 +00:00
type UserLevel int
const (
2019-06-16 22:55:50 +00:00
UserLevelNoneType UserLevel = iota // user or not -- ignored
UserLevelOptionalType // user or not -- object fetched if user
UserLevelNoneRequiredType // non-user (required)
UserLevelUserType // user (required)
2018-11-08 04:50:50 +00:00
)
2019-06-16 22:55:50 +00:00
func UserLevelNone ( cfg * config . Config ) UserLevel {
return UserLevelNoneType
}
func UserLevelOptional ( cfg * config . Config ) UserLevel {
return UserLevelOptionalType
}
func UserLevelNoneRequired ( cfg * config . Config ) UserLevel {
return UserLevelNoneRequiredType
}
func UserLevelUser ( cfg * config . Config ) UserLevel {
return UserLevelUserType
}
// UserLevelReader returns the permission level required for any route where
// users can read published content.
func UserLevelReader ( cfg * config . Config ) UserLevel {
if cfg . App . Private {
return UserLevelUserType
}
return UserLevelOptionalType
}
2018-11-08 04:50:50 +00:00
type (
2019-06-14 01:56:13 +00:00
handlerFunc func ( app * App , w http . ResponseWriter , r * http . Request ) error
2020-03-02 01:12:47 +00:00
gopherFunc func ( app * App , w gopher . ResponseWriter , r * gopher . Request ) error
2019-06-14 01:56:13 +00:00
userHandlerFunc func ( app * App , u * User , w http . ResponseWriter , r * http . Request ) error
userApperHandlerFunc func ( apper Apper , u * User , w http . ResponseWriter , r * http . Request ) error
dataHandlerFunc func ( app * App , w http . ResponseWriter , r * http . Request ) ( [ ] byte , string , error )
authFunc func ( app * App , r * http . Request ) ( * User , error )
2019-06-16 22:55:50 +00:00
UserLevelFunc func ( cfg * config . Config ) UserLevel
2018-11-08 04:50:50 +00:00
)
type Handler struct {
errors * ErrorPages
2019-12-19 16:48:04 +00:00
sessionStore sessions . Store
2019-06-14 01:56:13 +00:00
app Apper
2018-11-08 04:50:50 +00:00
}
// ErrorPages hold template HTML error pages for displaying errors to the user.
// In each, there should be a defined template named "base".
type ErrorPages struct {
NotFound * template . Template
Gone * template . Template
InternalServerError * template . Template
2020-03-18 20:14:05 +00:00
UnavailableError * template . Template
2018-11-08 04:50:50 +00:00
Blank * template . Template
}
// NewHandler returns a new Handler instance, using the given StaticPage data,
// and saving alias to the application's CookieStore.
2019-06-14 01:56:13 +00:00
func NewHandler ( apper Apper ) * Handler {
2018-11-08 04:50:50 +00:00
h := & Handler {
errors : & ErrorPages {
NotFound : template . Must ( template . New ( "" ) . Parse ( "{{define \"base\"}}<html><head><title>404</title></head><body><p>Not found.</p></body></html>{{end}}" ) ) ,
Gone : template . Must ( template . New ( "" ) . Parse ( "{{define \"base\"}}<html><head><title>410</title></head><body><p>Gone.</p></body></html>{{end}}" ) ) ,
InternalServerError : template . Must ( template . New ( "" ) . Parse ( "{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}" ) ) ,
2020-03-18 20:14:05 +00:00
UnavailableError : template . Must ( template . New ( "" ) . Parse ( "{{define \"base\"}}<html><head><title>503</title></head><body><p>Service is temporarily unavailable.</p></body></html>{{end}}" ) ) ,
2018-11-08 04:50:50 +00:00
Blank : template . Must ( template . New ( "" ) . Parse ( "{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}" ) ) ,
} ,
2019-12-19 16:48:04 +00:00
sessionStore : apper . App ( ) . SessionStore ( ) ,
2019-06-14 01:56:13 +00:00
app : apper ,
2018-11-08 04:50:50 +00:00
}
return h
}
2019-06-13 22:50:23 +00:00
// NewWFHandler returns a new Handler instance, using WriteFreely template files.
// You MUST call writefreely.InitTemplates() before this.
2019-06-14 01:56:13 +00:00
func NewWFHandler ( apper Apper ) * Handler {
h := NewHandler ( apper )
2019-06-13 22:50:23 +00:00
h . SetErrorPages ( & ErrorPages {
NotFound : pages [ "404-general.tmpl" ] ,
Gone : pages [ "410.tmpl" ] ,
InternalServerError : pages [ "500.tmpl" ] ,
2020-03-18 20:14:05 +00:00
UnavailableError : pages [ "503.tmpl" ] ,
2019-06-13 22:50:23 +00:00
Blank : pages [ "blank.tmpl" ] ,
} )
return h
}
2018-11-08 04:50:50 +00:00
// SetErrorPages sets the given set of ErrorPages as templates for any errors
// that come up.
func ( h * Handler ) SetErrorPages ( e * ErrorPages ) {
h . errors = e
}
// User handles requests made in the web application by the authenticated user.
// This provides user-friendly HTML pages and actions that work in the browser.
func ( h * Handler ) User ( f userHandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s: %s" , e , debug . Stack ( ) )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = http . StatusInternalServerError
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
2019-06-14 01:56:13 +00:00
u := getUserSession ( h . app . App ( ) , r )
2018-11-08 04:50:50 +00:00
if u == nil {
err := ErrNotLoggedIn
status = err . Status
return err
}
2019-06-14 01:56:13 +00:00
err := f ( h . app . App ( ) , u , w , r )
2018-11-08 04:50:50 +00:00
if err == nil {
status = http . StatusOK
2021-06-27 21:57:07 +00:00
} else if impErr , ok := err . ( impart . HTTPError ) ; ok {
status = impErr . Status
if impErr == ErrUserNotFound {
log . Info ( "Logged-in user not found. Logging out." )
sendRedirect ( w , http . StatusFound , "/me/logout?to=" + h . app . App ( ) . cfg . App . LandingPath ( ) )
// Reset err so handleHTTPError does nothing
err = nil
}
2018-11-08 04:50:50 +00:00
} else {
status = http . StatusInternalServerError
}
return err
} ( ) )
}
}
2018-11-19 01:18:22 +00:00
// Admin handles requests on /admin routes
func ( h * Handler ) Admin ( f userHandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s: %s" , e , debug . Stack ( ) )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
status = http . StatusInternalServerError
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2019-06-14 01:56:13 +00:00
} ( )
u := getUserSession ( h . app . App ( ) , r )
if u == nil || ! u . IsAdmin ( ) {
err := impart . HTTPError { http . StatusNotFound , "" }
status = err . Status
return err
}
err := f ( h . app . App ( ) , u , w , r )
if err == nil {
status = http . StatusOK
} else if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = http . StatusInternalServerError
}
return err
} ( ) )
}
}
// AdminApper handles requests on /admin routes that require an Apper.
func ( h * Handler ) AdminApper ( f userApperHandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s: %s" , e , debug . Stack ( ) )
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-19 01:18:22 +00:00
status = http . StatusInternalServerError
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-19 01:18:22 +00:00
} ( )
2019-06-14 01:56:13 +00:00
u := getUserSession ( h . app . App ( ) , r )
2018-11-19 01:18:22 +00:00
if u == nil || ! u . IsAdmin ( ) {
err := impart . HTTPError { http . StatusNotFound , "" }
status = err . Status
return err
}
err := f ( h . app , u , w , r )
if err == nil {
status = http . StatusOK
} else if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = http . StatusInternalServerError
}
return err
} ( ) )
}
}
2019-06-17 00:24:47 +00:00
func apiAuth ( app * App , r * http . Request ) ( * User , error ) {
// Authorize user from Authorization header
t := r . Header . Get ( "Authorization" )
if t == "" {
return nil , ErrNoAccessToken
}
u := & User { ID : app . db . GetUserID ( t ) }
if u . ID == - 1 {
return nil , ErrBadAccessToken
}
return u , nil
}
// optionaAPIAuth is used for endpoints that accept authenticated requests via
// Authorization header or cookie, unlike apiAuth. It returns a different err
// in the case where no Authorization header is present.
func optionalAPIAuth ( app * App , r * http . Request ) ( * User , error ) {
// Authorize user from Authorization header
t := r . Header . Get ( "Authorization" )
if t == "" {
return nil , ErrNotLoggedIn
}
u := & User { ID : app . db . GetUserID ( t ) }
if u . ID == - 1 {
return nil , ErrBadAccessToken
}
return u , nil
}
func webAuth ( app * App , r * http . Request ) ( * User , error ) {
u := getUserSession ( app , r )
if u == nil {
return nil , ErrNotLoggedIn
}
return u , nil
}
2018-11-08 04:50:50 +00:00
// UserAPI handles requests made in the API by the authenticated user.
// This provides user-friendly HTML pages and actions that work in the browser.
func ( h * Handler ) UserAPI ( f userHandlerFunc ) http . HandlerFunc {
2019-06-17 00:24:47 +00:00
return h . UserAll ( false , f , apiAuth )
2018-11-08 04:50:50 +00:00
}
2020-07-30 20:46:01 +00:00
// UserWebAPI handles endpoints that accept a user authorized either via the web (cookies) or an Authorization header.
func ( h * Handler ) UserWebAPI ( f userHandlerFunc ) http . HandlerFunc {
return h . UserAll ( false , f , func ( app * App , r * http . Request ) ( * User , error ) {
// Authorize user via cookies
u := getUserSession ( app , r )
if u != nil {
return u , nil
}
// Fall back to access token, since user isn't logged in via web
var err error
u , err = apiAuth ( app , r )
if err != nil {
return nil , err
}
return u , nil
} )
}
2018-11-08 04:50:50 +00:00
func ( h * Handler ) UserAll ( web bool , f userHandlerFunc , a authFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
handleFunc := func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s: %s" , e , debug . Stack ( ) )
impart . WriteError ( w , impart . HTTPError { http . StatusInternalServerError , "Something didn't work quite right." } )
status = 500
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
2019-06-14 01:56:13 +00:00
u , err := a ( h . app . App ( ) , r )
2018-11-08 04:50:50 +00:00
if err != nil {
if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
return err
}
2019-06-14 01:56:13 +00:00
err = f ( h . app . App ( ) , u , w , r )
2018-11-08 04:50:50 +00:00
if err == nil {
status = 200
} else if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
return err
}
if web {
h . handleHTTPError ( w , r , handleFunc ( ) )
} else {
h . handleError ( w , r , handleFunc ( ) )
}
}
}
func ( h * Handler ) RedirectOnErr ( f handlerFunc , loc string ) handlerFunc {
2019-05-12 20:55:30 +00:00
return func ( app * App , w http . ResponseWriter , r * http . Request ) error {
2018-11-08 04:50:50 +00:00
err := f ( app , w , r )
if err != nil {
if ie , ok := err . ( impart . HTTPError ) ; ok {
// Override default redirect with returned error's, if it's a
// redirect error.
if ie . Status == http . StatusFound {
return ie
}
}
return impart . HTTPError { http . StatusFound , loc }
}
return nil
}
}
func ( h * Handler ) Page ( n string ) http . HandlerFunc {
2019-05-12 20:55:30 +00:00
return h . Web ( func ( app * App , w http . ResponseWriter , r * http . Request ) error {
2018-11-08 04:50:50 +00:00
t , ok := pages [ n ]
if ! ok {
return impart . HTTPError { http . StatusNotFound , "Page not found." }
}
sp := pageForReq ( app , r )
err := t . ExecuteTemplate ( w , "base" , sp )
if err != nil {
log . Error ( "Unable to render page: %v" , err )
}
return err
} , UserLevelOptional )
}
2019-06-16 22:55:50 +00:00
func ( h * Handler ) WebErrors ( f handlerFunc , ul UserLevelFunc ) http . HandlerFunc {
2018-11-08 04:50:50 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
// TODO: factor out this logic shared with Web()
h . handleHTTPError ( w , r , func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
2019-06-14 01:56:13 +00:00
u := getUserSession ( h . app . App ( ) , r )
2018-11-08 04:50:50 +00:00
username := "None"
if u != nil {
username = u . Username
}
log . Error ( "User: %s\n\n%s: %s" , username , e , debug . Stack ( ) )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = 500
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
var session * sessions . Session
var err error
2019-06-16 22:55:50 +00:00
if ul ( h . app . App ( ) . cfg ) != UserLevelNoneType {
2018-11-08 04:50:50 +00:00
session , err = h . sessionStore . Get ( r , cookieName )
2019-06-16 22:55:50 +00:00
if err != nil && ( ul ( h . app . App ( ) . cfg ) == UserLevelNoneRequiredType || ul ( h . app . App ( ) . cfg ) == UserLevelUserType ) {
2018-11-08 04:50:50 +00:00
// Cookie is required, but we can ignore this error
2019-07-01 20:45:35 +00:00
log . Error ( "Handler: Unable to get session (for user permission %d); ignoring: %v" , ul ( h . app . App ( ) . cfg ) , err )
2018-11-08 04:50:50 +00:00
}
_ , gotUser := session . Values [ cookieUserVal ] . ( * User )
2019-06-16 22:55:50 +00:00
if ul ( h . app . App ( ) . cfg ) == UserLevelNoneRequiredType && gotUser {
2018-11-08 04:50:50 +00:00
to := correctPageFromLoginAttempt ( r )
log . Info ( "Handler: Required NO user, but got one. Redirecting to %s" , to )
err := impart . HTTPError { http . StatusFound , to }
status = err . Status
return err
2019-06-16 22:55:50 +00:00
} else if ul ( h . app . App ( ) . cfg ) == UserLevelUserType && ! gotUser {
2018-11-08 04:50:50 +00:00
log . Info ( "Handler: Required a user, but DIDN'T get one. Sending not logged in." )
err := ErrNotLoggedIn
status = err . Status
return err
}
}
// TODO: pass User object to function
2019-06-14 01:56:13 +00:00
err = f ( h . app . App ( ) , w , r )
2018-11-08 04:50:50 +00:00
if err == nil {
status = 200
} else if httpErr , ok := err . ( impart . HTTPError ) ; ok {
status = httpErr . Status
if status < 300 || status > 399 {
2019-06-14 01:56:13 +00:00
addSessionFlash ( h . app . App ( ) , w , r , httpErr . Message , session )
2018-11-08 04:50:50 +00:00
return impart . HTTPError { http . StatusFound , r . Referer ( ) }
}
} else {
e := fmt . Sprintf ( "[Web handler] 500: %v" , err )
if ! strings . HasSuffix ( e , "write: broken pipe" ) {
log . Error ( e )
} else {
log . Error ( e )
}
log . Info ( "Web handler internal error render" )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = 500
}
return err
} ( ) )
}
}
2019-06-16 22:55:50 +00:00
func ( h * Handler ) CollectionPostOrStatic ( w http . ResponseWriter , r * http . Request ) {
if strings . Contains ( r . URL . Path , "." ) && ! isRaw ( r ) {
2019-06-19 23:17:45 +00:00
start := time . Now ( )
status := 200
defer func ( ) {
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2019-06-19 23:17:45 +00:00
} ( )
2019-06-16 22:55:50 +00:00
// Serve static file
h . app . App ( ) . shttp . ServeHTTP ( w , r )
2019-06-19 23:17:45 +00:00
return
2019-06-16 22:55:50 +00:00
}
h . Web ( viewCollectionPost , UserLevelReader ) ( w , r )
}
2018-11-08 04:50:50 +00:00
// Web handles requests made in the web application. This provides user-
// friendly HTML pages and actions that work in the browser.
2019-06-16 22:55:50 +00:00
func ( h * Handler ) Web ( f handlerFunc , ul UserLevelFunc ) http . HandlerFunc {
2018-11-08 04:50:50 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
2019-06-14 01:56:13 +00:00
u := getUserSession ( h . app . App ( ) , r )
2018-11-08 04:50:50 +00:00
username := "None"
if u != nil {
username = u . Username
}
log . Error ( "User: %s\n\n%s: %s" , username , e , debug . Stack ( ) )
log . Info ( "Web deferred internal error render" )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = 500
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
2019-06-16 22:55:50 +00:00
if ul ( h . app . App ( ) . cfg ) != UserLevelNoneType {
2018-11-08 04:50:50 +00:00
session , err := h . sessionStore . Get ( r , cookieName )
2019-06-16 22:55:50 +00:00
if err != nil && ( ul ( h . app . App ( ) . cfg ) == UserLevelNoneRequiredType || ul ( h . app . App ( ) . cfg ) == UserLevelUserType ) {
2018-11-08 04:50:50 +00:00
// Cookie is required, but we can ignore this error
2019-07-01 20:45:35 +00:00
log . Error ( "Handler: Unable to get session (for user permission %d); ignoring: %v" , ul ( h . app . App ( ) . cfg ) , err )
2018-11-08 04:50:50 +00:00
}
_ , gotUser := session . Values [ cookieUserVal ] . ( * User )
2019-06-16 22:55:50 +00:00
if ul ( h . app . App ( ) . cfg ) == UserLevelNoneRequiredType && gotUser {
2018-11-08 04:50:50 +00:00
to := correctPageFromLoginAttempt ( r )
log . Info ( "Handler: Required NO user, but got one. Redirecting to %s" , to )
err := impart . HTTPError { http . StatusFound , to }
status = err . Status
return err
2019-06-16 22:55:50 +00:00
} else if ul ( h . app . App ( ) . cfg ) == UserLevelUserType && ! gotUser {
2018-11-08 04:50:50 +00:00
log . Info ( "Handler: Required a user, but DIDN'T get one. Sending not logged in." )
err := ErrNotLoggedIn
status = err . Status
return err
}
}
// TODO: pass User object to function
2019-06-14 01:56:13 +00:00
err := f ( h . app . App ( ) , w , r )
2018-11-08 04:50:50 +00:00
if err == nil {
status = 200
} else if httpErr , ok := err . ( impart . HTTPError ) ; ok {
status = httpErr . Status
} else {
e := fmt . Sprintf ( "[Web handler] 500: %v" , err )
log . Error ( e )
log . Info ( "Web internal error render" )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = 500
}
return err
} ( ) )
}
}
func ( h * Handler ) All ( f handlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleError ( w , r , func ( ) error {
// TODO: return correct "success" status
status := 200
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s:\n%s" , e , debug . Stack ( ) )
impart . WriteError ( w , impart . HTTPError { http . StatusInternalServerError , "Something didn't work quite right." } )
status = 500
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
// TODO: do any needed authentication
2019-06-17 00:24:47 +00:00
err := f ( h . app . App ( ) , w , r )
if err != nil {
if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
}
return err
} ( ) )
}
}
2021-06-07 19:52:24 +00:00
func ( h * Handler ) PlainTextAPI ( f handlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleTextError ( w , r , func ( ) error {
// TODO: return correct "success" status
status := 200
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s:\n%s" , e , debug . Stack ( ) )
status = http . StatusInternalServerError
w . WriteHeader ( status )
fmt . Fprintf ( w , "Something didn't work quite right. The robots have alerted the humans." )
}
log . Info ( fmt . Sprintf ( "\"%s %s\" %d %s \"%s\" \"%s\"" , r . Method , r . RequestURI , status , time . Since ( start ) , r . UserAgent ( ) , r . Host ) )
} ( )
err := f ( h . app . App ( ) , w , r )
if err != nil {
if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = http . StatusInternalServerError
}
}
return err
} ( ) )
}
}
2019-12-30 23:14:01 +00:00
func ( h * Handler ) OAuth ( f handlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleOAuthError ( w , r , func ( ) error {
// TODO: return correct "success" status
status := 200
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s:\n%s" , e , debug . Stack ( ) )
impart . WriteError ( w , impart . HTTPError { http . StatusInternalServerError , "Something didn't work quite right." } )
status = 500
}
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
} ( )
err := f ( h . app . App ( ) , w , r )
if err != nil {
if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
}
return err
} ( ) )
}
}
2019-06-17 00:24:47 +00:00
func ( h * Handler ) AllReader ( f handlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleError ( w , r , func ( ) error {
status := 200
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s:\n%s" , e , debug . Stack ( ) )
impart . WriteError ( w , impart . HTTPError { http . StatusInternalServerError , "Something didn't work quite right." } )
status = 500
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2019-06-17 00:24:47 +00:00
} ( )
2020-06-21 03:18:30 +00:00
// Allow any origin, as public endpoints are handled in here
2021-04-06 21:24:07 +00:00
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
2020-06-21 03:18:30 +00:00
2019-06-17 00:24:47 +00:00
if h . app . App ( ) . cfg . App . Private {
// This instance is private, so ensure it's being accessed by a valid user
// Check if authenticated with an access token
_ , apiErr := optionalAPIAuth ( h . app . App ( ) , r )
if apiErr != nil {
if err , ok := apiErr . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
if apiErr == ErrNotLoggedIn {
// Fall back to web auth since there was no access token given
_ , err := webAuth ( h . app . App ( ) , r )
if err != nil {
if err , ok := apiErr . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
return err
}
} else {
return apiErr
}
}
}
2019-06-14 01:56:13 +00:00
err := f ( h . app . App ( ) , w , r )
2018-11-08 04:50:50 +00:00
if err != nil {
if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
}
return err
} ( ) )
}
}
2019-06-16 22:55:50 +00:00
func ( h * Handler ) Download ( f dataHandlerFunc , ul UserLevelFunc ) http . HandlerFunc {
2018-11-08 04:50:50 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
var status int
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s: %s" , e , debug . Stack ( ) )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = 500
}
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
2019-06-14 01:56:13 +00:00
data , filename , err := f ( h . app . App ( ) , w , r )
2018-11-08 04:50:50 +00:00
if err != nil {
if err , ok := err . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
return err
}
ext := ".json"
ct := "application/json"
if strings . HasSuffix ( r . URL . Path , ".csv" ) {
ext = ".csv"
ct = "text/csv"
} else if strings . HasSuffix ( r . URL . Path , ".zip" ) {
ext = ".zip"
ct = "application/zip"
}
w . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( "attachment; filename=%s%s" , filename , ext ) )
w . Header ( ) . Set ( "Content-Type" , ct )
w . Header ( ) . Set ( "Content-Length" , strconv . Itoa ( len ( data ) ) )
fmt . Fprint ( w , string ( data ) )
status = 200
return nil
} ( ) )
}
}
2019-06-16 22:55:50 +00:00
func ( h * Handler ) Redirect ( url string , ul UserLevelFunc ) http . HandlerFunc {
2018-11-08 04:50:50 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
start := time . Now ( )
var status int
2019-06-16 22:55:50 +00:00
if ul ( h . app . App ( ) . cfg ) != UserLevelNoneType {
2018-11-08 04:50:50 +00:00
session , err := h . sessionStore . Get ( r , cookieName )
2019-06-16 22:55:50 +00:00
if err != nil && ( ul ( h . app . App ( ) . cfg ) == UserLevelNoneRequiredType || ul ( h . app . App ( ) . cfg ) == UserLevelUserType ) {
2018-11-08 04:50:50 +00:00
// Cookie is required, but we can ignore this error
2019-07-01 20:45:35 +00:00
log . Error ( "Handler: Unable to get session (for user permission %d); ignoring: %v" , ul ( h . app . App ( ) . cfg ) , err )
2018-11-08 04:50:50 +00:00
}
_ , gotUser := session . Values [ cookieUserVal ] . ( * User )
2019-06-16 22:55:50 +00:00
if ul ( h . app . App ( ) . cfg ) == UserLevelNoneRequiredType && gotUser {
2018-11-08 04:50:50 +00:00
to := correctPageFromLoginAttempt ( r )
log . Info ( "Handler: Required NO user, but got one. Redirecting to %s" , to )
err := impart . HTTPError { http . StatusFound , to }
status = err . Status
return err
2019-06-16 22:55:50 +00:00
} else if ul ( h . app . App ( ) . cfg ) == UserLevelUserType && ! gotUser {
2018-11-08 04:50:50 +00:00
log . Info ( "Handler: Required a user, but DIDN'T get one. Sending not logged in." )
err := ErrNotLoggedIn
status = err . Status
return err
}
}
status = sendRedirect ( w , http . StatusFound , url )
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
return nil
} ( ) )
}
}
func ( h * Handler ) handleHTTPError ( w http . ResponseWriter , r * http . Request , err error ) {
if err == nil {
return
}
if err , ok := err . ( impart . HTTPError ) ; ok {
if err . Status >= 300 && err . Status < 400 {
sendRedirect ( w , err . Status , err . Message )
return
} else if err . Status == http . StatusUnauthorized {
q := ""
if r . URL . RawQuery != "" {
q = url . QueryEscape ( "?" + r . URL . RawQuery )
}
sendRedirect ( w , http . StatusFound , "/login?to=" + r . URL . Path + q )
return
} else if err . Status == http . StatusGone {
2019-01-03 20:43:44 +00:00
w . WriteHeader ( err . Status )
2018-11-08 04:50:50 +00:00
p := & struct {
page . StaticPage
Content * template . HTML
} {
2019-06-14 01:56:13 +00:00
StaticPage : pageForReq ( h . app . App ( ) , r ) ,
2018-11-08 04:50:50 +00:00
}
if err . Message != "" {
co := template . HTML ( err . Message )
p . Content = & co
}
h . errors . Gone . ExecuteTemplate ( w , "base" , p )
return
} else if err . Status == http . StatusNotFound {
2019-01-03 17:27:52 +00:00
w . WriteHeader ( err . Status )
2019-08-07 14:18:40 +00:00
if strings . Contains ( r . Header . Get ( "Accept" ) , "application/activity+json" ) {
// This is a fediverse request; simply return the header
return
}
2019-06-14 01:56:13 +00:00
h . errors . NotFound . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
return
} else if err . Status == http . StatusInternalServerError {
2019-01-03 20:43:44 +00:00
w . WriteHeader ( err . Status )
2018-11-08 04:50:50 +00:00
log . Info ( "handleHTTPErorr internal error render" )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
return
2020-03-18 20:14:05 +00:00
} else if err . Status == http . StatusServiceUnavailable {
w . WriteHeader ( err . Status )
h . errors . UnavailableError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
return
2018-11-08 04:50:50 +00:00
} else if err . Status == http . StatusAccepted {
impart . WriteSuccess ( w , "" , err . Status )
return
} else {
p := & struct {
page . StaticPage
Title string
Content template . HTML
} {
2019-06-14 01:56:13 +00:00
pageForReq ( h . app . App ( ) , r ) ,
2018-11-08 04:50:50 +00:00
fmt . Sprintf ( "Uh oh (%d)" , err . Status ) ,
template . HTML ( fmt . Sprintf ( "<p style=\"text-align: center\" class=\"introduction\">%s</p>" , err . Message ) ) ,
}
h . errors . Blank . ExecuteTemplate ( w , "base" , p )
return
}
impart . WriteError ( w , err )
return
}
impart . WriteError ( w , impart . HTTPError { http . StatusInternalServerError , "This is an unhelpful error message for a miscellaneous internal error." } )
}
func ( h * Handler ) handleError ( w http . ResponseWriter , r * http . Request , err error ) {
if err == nil {
return
}
if err , ok := err . ( impart . HTTPError ) ; ok {
if err . Status >= 300 && err . Status < 400 {
sendRedirect ( w , err . Status , err . Message )
return
}
// if strings.Contains(r.Header.Get("Accept"), "text/html") {
impart . WriteError ( w , err )
// }
return
}
2019-09-18 19:39:53 +00:00
if IsJSON ( r ) {
2018-11-08 04:50:50 +00:00
impart . WriteError ( w , impart . HTTPError { http . StatusInternalServerError , "This is an unhelpful error message for a miscellaneous internal error." } )
return
}
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
}
2021-06-07 19:52:24 +00:00
func ( h * Handler ) handleTextError ( w http . ResponseWriter , r * http . Request , err error ) {
if err == nil {
return
}
if err , ok := err . ( impart . HTTPError ) ; ok {
if err . Status >= 300 && err . Status < 400 {
sendRedirect ( w , err . Status , err . Message )
return
}
w . WriteHeader ( err . Status )
fmt . Fprintf ( w , http . StatusText ( err . Status ) )
return
}
w . WriteHeader ( http . StatusInternalServerError )
fmt . Fprintf ( w , "This is an unhelpful error message for a miscellaneous internal error." )
}
2019-12-30 23:14:01 +00:00
func ( h * Handler ) handleOAuthError ( w http . ResponseWriter , r * http . Request , err error ) {
if err == nil {
return
}
if err , ok := err . ( impart . HTTPError ) ; ok {
if err . Status >= 300 && err . Status < 400 {
sendRedirect ( w , err . Status , err . Message )
return
}
impart . WriteOAuthError ( w , err )
return
}
impart . WriteOAuthError ( w , impart . HTTPError { http . StatusInternalServerError , "This is an unhelpful error message for a miscellaneous internal error." } )
return
}
2018-11-08 04:50:50 +00:00
func correctPageFromLoginAttempt ( r * http . Request ) string {
to := r . FormValue ( "to" )
if to == "" {
to = "/"
} else if ! strings . HasPrefix ( to , "/" ) {
to = "/" + to
}
return to
}
func ( h * Handler ) LogHandlerFunc ( f http . HandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
h . handleHTTPError ( w , r , func ( ) error {
status := 200
start := time . Now ( )
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "Handler.LogHandlerFunc\n\n%s: %s" , e , debug . Stack ( ) )
2019-06-14 01:56:13 +00:00
h . errors . InternalServerError . ExecuteTemplate ( w , "base" , pageForReq ( h . app . App ( ) , r ) )
2018-11-08 04:50:50 +00:00
status = 500
}
// TODO: log actual status code returned
2019-08-01 20:12:22 +00:00
log . Info ( h . app . ReqLog ( r , status , time . Since ( start ) ) )
2018-11-08 04:50:50 +00:00
} ( )
2019-06-17 01:22:56 +00:00
if h . app . App ( ) . cfg . App . Private {
// This instance is private, so ensure it's being accessed by a valid user
// Check if authenticated with an access token
_ , apiErr := optionalAPIAuth ( h . app . App ( ) , r )
if apiErr != nil {
if err , ok := apiErr . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
if apiErr == ErrNotLoggedIn {
// Fall back to web auth since there was no access token given
_ , err := webAuth ( h . app . App ( ) , r )
if err != nil {
if err , ok := apiErr . ( impart . HTTPError ) ; ok {
status = err . Status
} else {
status = 500
}
return err
}
} else {
return apiErr
}
}
}
2018-11-08 04:50:50 +00:00
f ( w , r )
return nil
} ( ) )
}
}
2020-03-02 01:12:47 +00:00
func ( h * Handler ) Gopher ( f gopherFunc ) gopher . HandlerFunc {
return func ( w gopher . ResponseWriter , r * gopher . Request ) {
defer func ( ) {
if e := recover ( ) ; e != nil {
log . Error ( "%s: %s" , e , debug . Stack ( ) )
w . WriteError ( "An internal error occurred" )
}
log . Info ( "gopher: %s" , r . Selector )
} ( )
err := f ( h . app . App ( ) , w , r )
if err != nil {
log . Error ( "failed: %s" , err )
w . WriteError ( "the page failed for some reason (see logs)" )
}
}
}
2018-11-08 04:50:50 +00:00
func sendRedirect ( w http . ResponseWriter , code int , location string ) int {
w . Header ( ) . Set ( "Location" , location )
w . WriteHeader ( code )
return code
}
2020-09-04 19:59:07 +00:00
func cacheControl ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Cache-Control" , "public, max-age=604800, immutable" )
next . ServeHTTP ( w , r )
} )
}