shizaru/httphandlers.go

144 lines
4.4 KiB
Go

package main
import (
// "fmt"
"golang.org/x/net/html"
"mime"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
func LoggingWrapper(log *os.File, handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handler(w, r)
t := time.Now()
log.WriteString(r.RemoteAddr + " " + t.Format(time.UnixDate) + " " + r.RequestURI + "\n")
}
}
func GetHandler(config Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Security first!
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
// Default to text/plain, as used for errors
w.Header().Set("Content-Type", "text/plain")
// Fail if request method is not GET or HEAD
if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Unimplemented HTTP method: use HEAD or GET.", http.StatusNotImplemented)
return
}
// Fail if there are dots in the path
if strings.Contains(r.URL.Path, "..") {
http.Error(w, "Your directory traversal technique has been defeated!", http.StatusForbidden)
return
}
// Resolve URI path to actual filesystem path
path := r.URL.Path
if strings.HasPrefix(path, "/~") {
bits := strings.Split(r.URL.Path, "/")
username := bits[1][1:]
//strings.Replace(bits[1], "~", "", 1)
new_prefix := filepath.Join("home", username, config.HomeDocBase)
path = strings.Replace(path, bits[1], new_prefix, 1)
} else {
path = filepath.Join(config.DocBase, r.URL.Path)
}
//fmt.Println(path)
// Fail if file does not exist
info, err := os.Stat(path)
if os.IsNotExist(err) {
http.Error(w, "Not found.", http.StatusNotFound)
return
}
if os.IsPermission(err) {
http.Error(w, "Permission denied.", http.StatusForbidden)
return
}
// Handle URLS which map to a directory
if info.IsDir() {
// Redirect to add trailing slash if missing
// (otherwise relative links don't work properly)
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, r.URL.String()+"/", http.StatusMovedPermanently)
return
}
// Add index.html to directory paths, if it exists
index_path := filepath.Join(path, "index.html")
index_info, err := os.Stat(index_path)
if !os.IsNotExist(err) {
path = index_path
info = index_info
}
}
// Fail if file is not world readable
if uint64(info.Mode().Perm())&0444 != 0444 {
http.Error(w, "File not world-readable.", http.StatusForbidden)
return
}
// If this is a directory, nothing below makes sense, so just
// serve it!
if info.IsDir() {
http.ServeFile(w, r, path)
return
}
// Get MIME type
ext := filepath.Ext(path)
mime := mime.TypeByExtension(ext)
if mime == "" {
http.Error(w, "MIME type detection error.", http.StatusInternalServerError)
return
}
// Fail if MIME type is forbidden
_, ok := config.BadMimesMap[mime]
if ok {
http.Error(w, "Forbidden MIME type: "+mime, http.StatusForbidden)
return
}
// Fail if file is too large
if !strings.HasPrefix(mime, "text/plain") && ((strings.HasPrefix(mime, "text/") && info.Size() > 1024*1024) ||
info.Size() > 32*1024) {
http.Error(w, "File too big: text/plain files have no limit, other text/* files may be up to 1 MiB, non-text files may be up to 32 KiB.", http.StatusForbidden)
return
}
// Fail if a HTML file doesn't measure up
if strings.HasPrefix(mime, "text/html") {
reader, err := os.Open(path)
if err != nil {
http.Error(w, "Some kind of file error!", http.StatusInternalServerError)
return
}
doc, err := html.Parse(reader)
if err != nil {
http.Error(w, "Some kind of HTML parsing error!", http.StatusInternalServerError)
return
}
var imgcount int
ok, msg := ValidateHtml(config, doc, 0, &imgcount)
if ok == false {
http.Error(w, "HTML document contains forbidden tag: "+msg, http.StatusForbidden)
return
}
}
// At this point everything is fine and we'll actually try to serve
// the file.
w.Header().Set("Content-Type", mime)
http.ServeFile(w, r, path)
}
}
func GetRedirectTLSHandler(config Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Don't redirect Let's Encrypt requests
if strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
path := filepath.Join(config.DocBase, r.URL.Path)
http.ServeFile(w, r, path)
// But redirect everything else
} else {
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
}
} }