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 } // 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) }