2023-04-22 18:11:21 +01:00
|
|
|
package error_pages
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2023-06-20 16:48:04 +01:00
|
|
|
"github.com/MrMelon54/rescheduler"
|
2023-04-22 22:18:39 +01:00
|
|
|
"io/fs"
|
|
|
|
"log"
|
2023-04-22 18:11:21 +01:00
|
|
|
"net/http"
|
2023-04-22 22:18:39 +01:00
|
|
|
"path/filepath"
|
2023-04-23 04:25:43 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2023-04-22 18:11:21 +01:00
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrorPages stores the custom error pages and is called by the servers to
|
|
|
|
// output meaningful pages for HTTP error codes
|
|
|
|
type ErrorPages struct {
|
|
|
|
s *sync.RWMutex
|
|
|
|
m map[int]func(rw http.ResponseWriter)
|
|
|
|
generic func(rw http.ResponseWriter, code int)
|
2023-04-22 22:18:39 +01:00
|
|
|
dir fs.FS
|
2023-06-20 16:48:04 +01:00
|
|
|
r *rescheduler.Rescheduler
|
2023-04-22 18:11:21 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:35:23 +01:00
|
|
|
// New creates a new error pages generator
|
2023-04-22 22:18:39 +01:00
|
|
|
func New(dir fs.FS) *ErrorPages {
|
2023-06-20 16:48:04 +01:00
|
|
|
e := &ErrorPages{
|
2023-04-22 18:11:21 +01:00
|
|
|
s: &sync.RWMutex{},
|
|
|
|
m: make(map[int]func(rw http.ResponseWriter)),
|
2023-04-24 01:35:23 +01:00
|
|
|
// generic error page writer
|
2023-04-22 18:11:21 +01:00
|
|
|
generic: func(rw http.ResponseWriter, code int) {
|
2023-04-24 01:35:23 +01:00
|
|
|
// if status text is empty then the code is unknown
|
2023-04-22 18:11:21 +01:00
|
|
|
a := http.StatusText(code)
|
2023-06-03 19:33:06 +01:00
|
|
|
fmt.Printf("%d - %s\n", code, a)
|
2023-04-22 18:11:21 +01:00
|
|
|
if a != "" {
|
2023-04-24 01:35:23 +01:00
|
|
|
// output in "xxx Error Text" format
|
2023-04-22 18:11:21 +01:00
|
|
|
http.Error(rw, fmt.Sprintf("%d %s\n", code, a), code)
|
|
|
|
return
|
|
|
|
}
|
2023-04-24 01:35:23 +01:00
|
|
|
// output the code and generic unknown message
|
2023-04-22 18:11:21 +01:00
|
|
|
http.Error(rw, fmt.Sprintf("%d Unknown Error Code\n", code), code)
|
|
|
|
},
|
|
|
|
dir: dir,
|
|
|
|
}
|
2023-06-20 16:48:04 +01:00
|
|
|
e.r = rescheduler.NewRescheduler(e.threadCompile)
|
|
|
|
return e
|
2023-04-22 18:11:21 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:35:23 +01:00
|
|
|
// ServeError writes the error page for the given code to the response writer
|
2023-04-22 18:11:21 +01:00
|
|
|
func (e *ErrorPages) ServeError(rw http.ResponseWriter, code int) {
|
2023-04-24 01:35:23 +01:00
|
|
|
// read lock for safety
|
2023-04-22 18:11:21 +01:00
|
|
|
e.s.RLock()
|
|
|
|
defer e.s.RUnlock()
|
2023-04-24 01:35:23 +01:00
|
|
|
|
|
|
|
// use the custom error page if it exists
|
2023-04-22 18:11:21 +01:00
|
|
|
if p, ok := e.m[code]; ok {
|
|
|
|
p(rw)
|
|
|
|
return
|
|
|
|
}
|
2023-04-24 01:35:23 +01:00
|
|
|
|
|
|
|
// otherwise use the generic error page
|
2023-04-22 18:11:21 +01:00
|
|
|
e.generic(rw, code)
|
|
|
|
}
|
2023-04-22 22:18:39 +01:00
|
|
|
|
2023-06-20 16:48:04 +01:00
|
|
|
// Compile loads the error pages the certificates and keys from the directories.
|
|
|
|
//
|
|
|
|
// This method makes use of the rescheduler instead of just ignoring multiple
|
|
|
|
// calls.
|
2023-04-22 22:18:39 +01:00
|
|
|
func (e *ErrorPages) Compile() {
|
2023-06-20 16:48:04 +01:00
|
|
|
e.r.Run()
|
|
|
|
}
|
2023-04-22 22:18:39 +01:00
|
|
|
|
2023-06-20 16:48:04 +01:00
|
|
|
func (e *ErrorPages) threadCompile() {
|
|
|
|
// new map
|
|
|
|
errorPageMap := make(map[int]func(rw http.ResponseWriter))
|
|
|
|
|
|
|
|
// compile map and check errors
|
|
|
|
if e.dir != nil {
|
|
|
|
err := e.internalCompile(errorPageMap)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("[Certs] Compile failed: %s\n", err)
|
|
|
|
return
|
2023-04-22 22:18:39 +01:00
|
|
|
}
|
2023-06-20 16:48:04 +01:00
|
|
|
}
|
2023-04-24 01:35:23 +01:00
|
|
|
|
2023-06-20 16:48:04 +01:00
|
|
|
// lock while replacing the map
|
|
|
|
e.s.Lock()
|
|
|
|
e.m = errorPageMap
|
|
|
|
e.s.Unlock()
|
2023-04-22 22:18:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ErrorPages) internalCompile(m map[int]func(rw http.ResponseWriter)) error {
|
|
|
|
// try to read dir
|
2023-06-04 22:28:48 +01:00
|
|
|
files, err := fs.ReadDir(e.dir, ".")
|
2023-04-22 22:18:39 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read error pages dir: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[ErrorPages] Compiling lookup table for %d error pages\n", len(files))
|
|
|
|
|
|
|
|
// find and load error pages
|
|
|
|
for _, i := range files {
|
|
|
|
// skip dirs
|
|
|
|
if i.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// get file name and extension
|
|
|
|
name := i.Name()
|
|
|
|
ext := filepath.Ext(name)
|
2023-04-23 04:25:43 +01:00
|
|
|
|
|
|
|
// if the extension is not 'html' then ignore the file
|
2023-06-04 22:28:48 +01:00
|
|
|
if ext != ".html" {
|
2023-04-23 04:25:43 +01:00
|
|
|
log.Printf("[ErrorPages] WARNING: ignoring non '.html' file in error pages directory: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the name can't be
|
|
|
|
nameInt, err := strconv.Atoi(strings.TrimSuffix(name, ".html"))
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("[ErrorPages] WARNING: ignoring invalid error page in error pages directory: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
2023-04-24 01:35:23 +01:00
|
|
|
|
|
|
|
// check if code is in range 100-599
|
2023-04-23 04:25:43 +01:00
|
|
|
if nameInt < 100 || nameInt >= 600 {
|
|
|
|
log.Printf("[ErrorPages] WARNING: ignoring invalid error page in error pages directory must be 100-599: '%s'\n", name)
|
2023-04-22 22:18:39 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to read html file
|
|
|
|
htmlData, err := fs.ReadFile(e.dir, name)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read html file '%s': %w", name, err)
|
|
|
|
}
|
|
|
|
|
2023-04-24 01:35:23 +01:00
|
|
|
// create a callback function to write the page
|
2023-04-23 04:25:43 +01:00
|
|
|
m[nameInt] = func(rw http.ResponseWriter) {
|
|
|
|
rw.Header().Set("Content-Type", "text/html; encoding=utf-8")
|
|
|
|
rw.WriteHeader(nameInt)
|
|
|
|
_, _ = rw.Write(htmlData)
|
|
|
|
}
|
2023-04-22 22:18:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// well no errors happened
|
|
|
|
return nil
|
|
|
|
}
|