diff --git a/listing.go b/OLD/listing.go similarity index 100% rename from listing.go rename to OLD/listing.go diff --git a/page.go b/OLD/page.go similarity index 100% rename from page.go rename to OLD/page.go diff --git a/single.go b/OLD/single.go similarity index 100% rename from single.go rename to OLD/single.go diff --git a/sort.go b/OLD/sort.go similarity index 100% rename from sort.go rename to OLD/sort.go diff --git a/assets.go b/assets.go deleted file mode 100644 index 4ac96a80..00000000 --- a/assets.go +++ /dev/null @@ -1,39 +0,0 @@ -package filemanager - -import ( - "errors" - "mime" - "net/http" - "path/filepath" - "strings" -) - -// ServeAssets redirects the request for the respective method -func ServeAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { - switch r.Method { - case "GET": - return serveAssetsGET(w, r, c) - default: - return http.StatusMethodNotAllowed, errors.New("Invalid method.") - } -} - -// serveAssetsGET provides the method for GET request on Assets page -func serveAssetsGET(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { - // gets the filename to be used with Assets function - filename := strings.Replace(r.URL.Path, c.BaseURL+"/_filemanagerinternal", "public", 1) - file, err := Asset(filename) - if err != nil { - return 404, nil - } - - // Get the file extension ant its mime type - extension := filepath.Ext(filename) - mediatype := mime.TypeByExtension(extension) - - // Write the header with the Content-Type and write the file - // content to the buffer - w.Header().Set("Content-Type", mediatype) - w.Write(file) - return 200, nil -} diff --git a/delete.go b/delete.go deleted file mode 100644 index 3fc6e654..00000000 --- a/delete.go +++ /dev/null @@ -1,31 +0,0 @@ -package filemanager - -import ( - "net/http" - "os" -) - -// Delete handles the delete requests -func Delete(path string, info os.FileInfo) (int, error) { - var err error - // If it's dir, remove all of the content inside - if info.IsDir() { - err = os.RemoveAll(path) - } else { - err = os.Remove(path) - } - - // Check for errors - if err != nil { - switch { - case os.IsPermission(err): - return http.StatusForbidden, err - case os.IsExist(err): - return http.StatusGone, err - default: - return http.StatusInternalServerError, err - } - } - - return http.StatusOK, nil -} diff --git a/fileinfo.go b/fileinfo.go index f4380144..d3972a75 100644 --- a/fileinfo.go +++ b/fileinfo.go @@ -1,13 +1,17 @@ package filemanager import ( + "net/http" + "net/url" "os" + "path/filepath" + "strings" "time" "github.com/dustin/go-humanize" ) -// FileInfo is the info about a particular file or directory +// FileInfo is the information about a particular file or directory type FileInfo struct { IsDir bool Name string @@ -15,6 +19,36 @@ type FileInfo struct { URL string ModTime time.Time Mode os.FileMode + Path string +} + +// GetFileInfo gets the file information and, in case of error, returns the +// respective HTTP error code +func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) { + var err error + + path := strings.Replace(url.Path, c.BaseURL, c.PathScope, 1) + path = filepath.Clean(path) + + file := &FileInfo{Path: path} + f, err := c.Root.Open(path) + if err != nil { + return file, ErrorToHTTPCode(err), err + } + defer f.Close() + + info, err := f.Stat() + if err != nil { + return file, ErrorToHTTPCode(err), err + } + + file.IsDir = info.IsDir() + file.ModTime = info.ModTime() + file.Name = info.Name() + file.Size = info.Size() + file.URL = url.Path + + return file, 0, nil } // HumanSize returns the size of the file as a human-readable string @@ -27,3 +61,21 @@ func (fi FileInfo) HumanSize() string { func (fi FileInfo) HumanModTime(format string) string { return fi.ModTime.Format(format) } + +// Delete handles the delete requests +func (fi FileInfo) Delete() (int, error) { + var err error + + // If it's a directory remove all the contents inside + if fi.IsDir { + err = os.RemoveAll(fi.Path) + } else { + err = os.Remove(fi.Path) + } + + if err != nil { + return ErrorToHTTPCode(err), err + } + + return http.StatusOK, nil +} diff --git a/filemanager.go b/filemanager.go index e8d2bf2f..0a0155ba 100644 --- a/filemanager.go +++ b/filemanager.go @@ -9,57 +9,46 @@ package filemanager import ( + "mime" "net/http" "os" + "path/filepath" "strings" "github.com/mholt/caddy/caddyhttp/httpserver" ) +const assetsURL = "/_filemanagerinternal" + // FileManager is an http.Handler that can show a file listing when // directories in the given paths are specified. type FileManager struct { - Next httpserver.Handler - Configs []Config - IgnoreIndexes bool -} - -// Config is a configuration for browsing in a particular path. -type Config struct { - PathScope string - Root http.FileSystem - BaseURL string - StyleSheet string - Variables interface{} + Next httpserver.Handler + Configs []Config } // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met. -// If so, control is handed over to ServeListing. func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - var c *Config - var file *InfoRequest + var ( + c *Config + fi *FileInfo + code int + err error + assets bool + ) - // Check if there is a FileManager configuration to match the path for i := range f.Configs { if httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) { c = &f.Configs[i] + assets = httpserver.Path(r.URL.Path).Matches(c.BaseURL + assetsURL) - // Serve assets - if httpserver.Path(r.URL.Path).Matches(c.BaseURL + "/_filemanagerinternal") { - return ServeAssets(w, r, c) - } - - // Gets the file path to be used within c.Root - filepath := strings.Replace(r.URL.Path, c.BaseURL, "", 1) - - if r.Method != http.MethodPost { - file = GetFileInfo(filepath, c) - if file.Err != nil { - defer file.File.Close() - return file.Code, file.Err + if r.Method != http.MethodPost && !assets { + fi, code, err = GetFileInfo(r.URL, c) + if err != nil { + return code, err } - if file.Info.IsDir() && !strings.HasSuffix(r.URL.Path, "/") { + if fi.IsDir && !strings.HasSuffix(r.URL.Path, "/") { http.Redirect(w, r, r.URL.Path+"/", http.StatusTemporaryRedirect) return 0, nil } @@ -69,17 +58,21 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err switch r.Method { case http.MethodGet: // Read and show directory or file - if file.Info.IsDir() { - return f.ServeListing(w, r, file.File, c) + if assets { + return ServeAssets(w, r, c) } - return f.ServeSingleFile(w, r, file, c) + + /* if file.Info.IsDir() { + return f.ServeListing(w, r, file.File, c) + } + return f.ServeSingleFile(w, r, file, c) */ case http.MethodPost: // Create new file or directory return http.StatusOK, nil case http.MethodDelete: // Delete a file or a directory - return Delete(filepath, file.Info) + return fi.Delete() case http.MethodPut: // Update/Modify a directory or file @@ -93,48 +86,38 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err } } } + return f.Next.ServeHTTP(w, r) } -// InfoRequest is the information given by a GetFileInfo function -type InfoRequest struct { - Info os.FileInfo - File http.File - Path string - Code int - Err error +// ErrorToHTTPCode gets the respective HTTP code for an error +func ErrorToHTTPCode(err error) int { + switch { + case os.IsPermission(err): + return http.StatusForbidden + case os.IsExist(err): + return http.StatusGone + default: + return http.StatusInternalServerError + } } -// GetFileInfo gets the file information and, in case of error, returns the -// respective HTTP error code -func GetFileInfo(path string, c *Config) *InfoRequest { - request := &InfoRequest{Path: path} - request.File, request.Err = c.Root.Open(path) - if request.Err != nil { - switch { - case os.IsPermission(request.Err): - request.Code = http.StatusForbidden - case os.IsExist(request.Err): - request.Code = http.StatusNotFound - default: - request.Code = http.StatusInternalServerError - } - - return request +// ServeAssets provides the needed assets for the front-end +func ServeAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { + // gets the filename to be used with Assets function + filename := strings.Replace(r.URL.Path, c.BaseURL+assetsURL, "public", 1) + file, err := Asset(filename) + if err != nil { + return http.StatusNotFound, nil } - request.Info, request.Err = request.File.Stat() + // Get the file extension and its mimetype + extension := filepath.Ext(filename) + mediatype := mime.TypeByExtension(extension) - if request.Err != nil { - switch { - case os.IsPermission(request.Err): - request.Code = http.StatusForbidden - case os.IsExist(request.Err): - request.Code = http.StatusGone - default: - request.Code = http.StatusInternalServerError - } - } - - return request + // Write the header with the Content-Type and write the file + // content to the buffer + w.Header().Set("Content-Type", mediatype) + w.Write(file) + return 200, nil } diff --git a/setup.go b/setup.go index ab43f8cc..a4270557 100644 --- a/setup.go +++ b/setup.go @@ -16,58 +16,57 @@ func init() { }) } -// setup configures a new Browse middleware instance. +// setup configures a new FileManager middleware instance. func setup(c *caddy.Controller) error { - configs, err := fileManagerParse(c) + configs, err := parseConfiguration(c) if err != nil { return err } - f := FileManager{ - Configs: configs, - IgnoreIndexes: false, - } - httpserver.GetConfig(c.Key).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - f.Next = next - return f + return FileManager{Configs: configs, Next: next} }) return nil } -func fileManagerParse(c *caddy.Controller) ([]Config, error) { +// Config is a configuration for browsing in a particualr path. +type Config struct { + PathScope string + Root http.FileSystem + BaseURL string + StyleSheet string +} + +// parseConfiguration parses the configuration set by the user so it can +// be used by the middleware +func parseConfiguration(c *caddy.Controller) ([]Config, error) { var configs []Config - appendCfg := func(fmc Config) error { + appendConfig := func(cfg Config) error { for _, c := range configs { - if c.PathScope == fmc.PathScope { + if c.PathScope == cfg.PathScope { return fmt.Errorf("duplicate file managing config for %s", c.PathScope) } } - configs = append(configs, fmc) + configs = append(configs, cfg) return nil } for c.Next() { - var fmc = Config{ - PathScope: ".", - BaseURL: "", - StyleSheet: "", - } - + var cfg = Config{PathScope: "."} for c.NextBlock() { switch c.Val() { case "show": if !c.NextArg() { return configs, c.ArgErr() } - fmc.PathScope = c.Val() + cfg.PathScope = c.Val() case "on": if !c.NextArg() { return configs, c.ArgErr() } - fmc.BaseURL = c.Val() + cfg.BaseURL = c.Val() case "styles": if !c.NextArg() { return configs, c.ArgErr() @@ -76,15 +75,12 @@ func fileManagerParse(c *caddy.Controller) ([]Config, error) { if err != nil { return configs, err } - fmc.StyleSheet = string(tplBytes) + cfg.StyleSheet = string(tplBytes) } } - fmc.Root = http.Dir(fmc.PathScope) - - // Save configuration - err := appendCfg(fmc) - if err != nil { + cfg.Root = http.Dir(cfg.PathScope) + if err := appendConfig(cfg); err != nil { return configs, err } }