package handler import ( "fmt" "html/template" "io/ioutil" "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) { switch { case os.IsNotExist(err): return "404 page not found", http.StatusNotFound case 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, filePath string) { f, err := fs.Open(filePath) 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, fs, filePath) 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, fs http.FileSystem, filePath 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("static/main.html") if err != nil { fmt.Printf(err.Error()) return } data := &Data{ Title: filePath, IsRoot: filePath == "/", Files: make([]*File, 0, len(dirs)), } for _, d := range dirs { fileName := d.Name() icon := "file" size := byteCountBinary(d.Size()) if d.IsDir() { fileName += "/" icon = "folder" size = "-" } if !d.IsDir() && strings.HasPrefix(strings.ToLower(fileName), "readme.") { data.Readme = loadReadmeFile(fs, filePath+"/"+fileName) } url := url.URL{Path: fileName} data.Files = append(data.Files, &File{ Icon: icon, URL: url.String(), Name: htmlReplacer.Replace(fileName), Size: size, Date: d.ModTime().Format("02.01.2006 15:04:05"), }) } err = t.Execute(w, data) if err != nil { fmt.Printf(err.Error()) } } func loadReadmeFile(fs http.FileSystem, fileName string) string { f, err := fs.Open(fileName) if err != nil { return "" } defer f.Close() data, err := ioutil.ReadAll(f) return string(data) } type fileHandler struct { rootPath string } func NewHandler(rootPath string) http.Handler { return &fileHandler{ rootPath: rootPath, } } 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.rootPath), path.Clean(upath)) }