package main import ( "fmt" "html/template" "net/http" "net/url" "os" "path" "sort" "strings" ) func renderError(w http.ResponseWriter, msg string, code int) { http.Error(w, msg, 404) } func byteCountBinary(b int64) string { const unit = 1024 if b < unit { return fmt.Sprintf("%d B", b) } div, exp := int64(unit), 0 for n := b / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) } var htmlReplacer = strings.NewReplacer( "&", "&", "<", "<", ">", ">", // """ is shorter than """. `"`, """, // "'" is shorter than "'" and apos was not in HTML until HTML5. "'", "'", ) func localRedirect(w http.ResponseWriter, r *http.Request, newPath string) { if q := r.URL.RawQuery; q != "" { newPath += "?" + q } w.Header().Set("Location", newPath) w.WriteHeader(http.StatusMovedPermanently) } func toHTTPError(err error) (msg string, httpStatus int) { if os.IsNotExist(err) { return "404 page not found", http.StatusNotFound } if os.IsPermission(err) { return "403 Forbidden", http.StatusForbidden } // Default: return "500 Internal Server Error", http.StatusInternalServerError } func serveFile(w http.ResponseWriter, r *http.Request, fs http.FileSystem, name string) { f, err := fs.Open(name) if err != nil { msg, code := toHTTPError(err) renderError(w, msg, code) return } defer f.Close() d, err := f.Stat() if err != nil { msg, code := toHTTPError(err) renderError(w, msg, code) return } // redirect if the directory name doesn't end in a slash if d.IsDir() { url := r.URL.Path if url[len(url)-1] != '/' { localRedirect(w, r, path.Base(url)+"/") return } } // Still a directory? (we didn't find an index.html file) if d.IsDir() { dirList(w, r, f, name) return } // serveContent will check modification time // sizeFunc := func() (int64, error) { return d.Size(), nil } http.ServeContent(w, r, d.Name(), d.ModTime(), f) } func dirList(w http.ResponseWriter, r *http.Request, f http.File, name string) { dirs, err := f.Readdir(-1) if err != nil { renderError(w, "Error reading directory", http.StatusInternalServerError) return } sort.Slice(dirs, func(i, j int) bool { if dirs[j].IsDir() && !dirs[i].IsDir() { return true } return dirs[i].Name() < dirs[j].Name() }) w.Header().Set("Content-Type", "text/html; charset=utf-8") t, err := template.New("main.html").ParseFiles("main.html") if err != nil { fmt.Printf(err.Error()) return } data := &Data{ Title: name, IsRoot: name == "/", Files: make([]*File, 0, len(dirs)), } for _, d := range dirs { name := d.Name() icon := "file" size := byteCountBinary(d.Size()) if d.IsDir() { name += "/" icon = "folder" size = "-" } url := url.URL{Path: name} data.Files = append(data.Files, &File{ Icon: icon, URL: url.String(), Name: htmlReplacer.Replace(name), Size: size, Date: d.ModTime().Format("02.01.2006 15:04:05"), }) } err = t.Execute(w, data) if err != nil { fmt.Printf(err.Error()) } } type fileHandler struct { root string } func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { upath := r.URL.Path if !strings.HasPrefix(upath, "/") { upath = "/" + upath r.URL.Path = upath } serveFile(w, r, http.Dir(fh.root), path.Clean(upath)) }