/* Serve is a very simple static file server in go Usage: -p="8100": port to serve on -d=".": the directory of static files to host Navigating to http://localhost:8100 will display the index.html or directory listing file. */ package main import ( "bytes" "errors" "fmt" "io" "local/args" "local/gziphttp" "log" "net/http" "net/http/httptest" "os" "path" "regexp" "strings" ) const ( ENDPOINT_UPLOAD = "__upload__" ENDPOINT_DELETE = "__delete__" ) var ( fs *args.ArgSet ) func main() { fs = args.NewArgSet() fs.Append(args.STRING, "p", "port to serve", "8100") fs.Append(args.STRING, "d", "static path to serve", "./public") if err := fs.Parse(); err != nil { panic(err) } d := fs.Get("d").GetString() p := strings.TrimPrefix(fs.Get("p").GetString(), ":") http.Handle("/", http.HandlerFunc(handler(d))) log.Printf("Serving %s on HTTP port: %s\n", d, p) log.Fatal(http.ListenAndServe(":"+p, nil)) } func handler(d string) http.HandlerFunc { return gzip(endpoints(withDel(fserve(d)))) } func writeMeta(w http.ResponseWriter) { fmt.Fprintf(w, `
`) } func writeForm(w http.ResponseWriter) { fmt.Fprintf(w, ` `, ENDPOINT_UPLOAD) } func gzip(foo http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if gziphttp.Can(r) { gz := gziphttp.New(w) defer gz.Close() w = gz } foo(w, r) } } func endpoints(foo http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if isDir(r) { writeMeta(w) writeForm(w) } if isUploaded(r) { if err := upload(w, r); err != nil { fmt.Fprintln(w, err.Error()) } } else if isDeleted(r) { if err := del(w, r); err != nil { fmt.Fprintln(w, err.Error()) } } else { setContentTypeIfMedia(w, r) foo(w, r) } } } func isUploaded(r *http.Request) bool { return path.Base(r.URL.Path) == ENDPOINT_UPLOAD } func isDeleted(r *http.Request) bool { return path.Base(r.URL.Path) == ENDPOINT_DELETE } func isDir(r *http.Request) bool { d := toRealPath(r.URL.Path) fi, err := os.Stat(d) if err != nil { return false } if !fi.IsDir() { return false } if _, err := os.Stat(path.Join(d, "index.html")); err == nil { return false } return true } func withDel(foo http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !isDir(r) { foo(w, r) return } fmt.Fprintln(w, ``+"\n") w2 := httptest.NewRecorder() foo(w2, r) b := bytes.Split(w2.Body.Bytes(), []byte("\n")) buff := bytes.NewBuffer(nil) for i := range b { if bytes.Contains(b[i], []byte(" 0 { match = bytes.Split(match, []byte(`href="`))[1] match = match[:len(match)-1] b[i] = []byte(fmt.Sprintf(` %s`, match, ENDPOINT_DELETE, match, b[i])) } } buff.Write(b[i]) buff.Write([]byte("\n")) } io.Copy(w, buff) } } func fserve(d string) http.HandlerFunc { h := http.FileServer(http.Dir(d)) return h.ServeHTTP } func upload(w http.ResponseWriter, r *http.Request) error { r.ParseMultipartForm(100 << 20) file, handler, err := r.FormFile("file") if err != nil { return err } defer file.Close() p := toRealPath(path.Join(path.Dir(r.URL.Path), handler.Filename)) if fi, err := os.Stat(p); err == nil && !fi.IsDir() { return errors.New("already exists") } f, err := os.Create(p) if err != nil { return err } defer f.Close() if _, err := io.Copy(f, file); err != nil { return err } http.Redirect(w, r, path.Dir(r.URL.Path)+"/", http.StatusSeeOther) return nil } func del(w http.ResponseWriter, r *http.Request) error { p := toRealPath(path.Dir(r.URL.Path)) _, err := os.Stat(p) if err != nil { return err } err = os.RemoveAll(p) if err != nil { return err } http.Redirect(w, r, path.Dir(path.Dir(r.URL.Path))+"/", http.StatusSeeOther) return nil } func toRealPath(p string) string { d := path.Join(fs.Get("d").GetString()) return path.Join(d, p) } func setContentTypeIfMedia(w http.ResponseWriter, r *http.Request) { ext := strings.ToLower(path.Ext(r.URL.Path)) if i := strings.LastIndex(ext, "."); i != -1 { ext = ext[i:] } k := "Content-Type" v := "" switch ext { case ".mp4": v = "video/mp4" case ".mkv": v = "video/x-matroska" case ".mp3": v = "audio/mpeg3" case ".epub", ".mobi": k = "Content-Disposition" v = "attachment" case ".jpg", ".jpeg": v = "image/jpeg" case ".gif": v = "image/gif" case ".png": v = "image/png" case ".ico": v = "image/x-icon" case ".svg": v = "image/svg+xml" case ".css": v = "text/css" case ".js": v = "text/javascript" case ".json": v = "application/json" case ".html", ".htm": v = "text/html" case ".pdf": w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", path.Base(r.URL.Path))) v = "application/pdf" case ".webm": v = "video/webm" case ".weba": v = "audio/webm" case ".webp": v = "image/webp" case ".zip": v = "application/zip" case ".7z": v = "application/x-7z-compressed" case ".tar": v = "application/x-tar" default: return } w.Header().Set(k, v) }