107 lines
3.2 KiB
Go
107 lines
3.2 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
|
||
|
// fmt.Println(r.URL.Path)
|
||
|
bits := filepath.SplitList(r.URL.Path)
|
||
|
// fmt.Println(bits[0])
|
||
|
path := r.URL.Path
|
||
|
if strings.HasPrefix(bits[0], "/~") {
|
||
|
bits[0] = strings.Replace(bits[0], "/~", "/home/", 1)
|
||
|
bits[0] += config.HomeDocBase
|
||
|
path = filepath.Join(bits...)
|
||
|
} 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
|
||
|
}
|
||
|
// Fail if file is not world readable
|
||
|
if uint64(info.Mode().Perm())&0444 != 0444 {
|
||
|
http.Error(w, "File not world-readable.", http.StatusForbidden)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Get MIME type
|
||
|
ext := filepath.Ext(path)
|
||
|
mime := mime.TypeByExtension(ext)
|
||
|
// 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 redirectTLS(w http.ResponseWriter, r *http.Request) {
|
||
|
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
|
||
|
}
|