| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | package filemanager | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"mime" | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/dustin/go-humanize" | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 	"github.com/mholt/caddy/caddyhttp/httpserver" | 
					
						
							| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | // FileInfo is the information about a particular file or directory
 | 
					
						
							| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | type FileInfo struct { | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | 	IsDir    bool | 
					
						
							|  |  |  | 	Name     string | 
					
						
							|  |  |  | 	Size     int64 | 
					
						
							|  |  |  | 	URL      string | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 	Path     string // The relative Path of the file/directory relative to Caddyfile.
 | 
					
						
							|  |  |  | 	RootPath string // The Path of the file/directory on http.FileSystem.
 | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | 	ModTime  time.Time | 
					
						
							|  |  |  | 	Mode     os.FileMode | 
					
						
							|  |  |  | 	Mimetype string | 
					
						
							|  |  |  | 	Content  string | 
					
						
							|  |  |  | 	Type     string | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 	rootPath := strings.Replace(url.Path, c.BaseURL, "", 1) | 
					
						
							|  |  |  | 	rootPath = strings.TrimPrefix(rootPath, "/") | 
					
						
							|  |  |  | 	rootPath = "/" + rootPath | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	path := c.PathScope + rootPath | 
					
						
							| 
									
										
										
										
											2016-06-11 21:07:55 +00:00
										 |  |  | 	path = strings.Replace(path, "\\", "/", -1) | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 	path = filepath.Clean(path) | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 	file := &FileInfo{ | 
					
						
							|  |  |  | 		URL:      url.Path, | 
					
						
							|  |  |  | 		RootPath: rootPath, | 
					
						
							|  |  |  | 		Path:     path, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	f, err := c.Root.Open(rootPath) | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | 	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() | 
					
						
							|  |  |  | 	return file, 0, nil | 
					
						
							| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-11 21:15:42 +00:00
										 |  |  | // GetExtendedFileInfo is used to get extra parameters for FileInfo struct
 | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | func (fi *FileInfo) GetExtendedFileInfo() error { | 
					
						
							|  |  |  | 	fi.Mimetype = mime.TypeByExtension(filepath.Ext(fi.Path)) | 
					
						
							|  |  |  | 	fi.Type = SimplifyMimeType(fi.Mimetype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fi.Type == "text" { | 
					
						
							|  |  |  | 		err := fi.Read() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Read is used to read a file and store its content
 | 
					
						
							|  |  |  | func (fi *FileInfo) Read() error { | 
					
						
							|  |  |  | 	raw, err := ioutil.ReadFile(fi.Path) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	fi.Content = string(raw) | 
					
						
							| 
									
										
										
										
											2016-06-11 21:15:42 +00:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-10 19:54:19 +00:00
										 |  |  | // HumanSize returns the size of the file as a human-readable string
 | 
					
						
							|  |  |  | // in IEC format (i.e. power of 2 or base 1024).
 | 
					
						
							|  |  |  | func (fi FileInfo) HumanSize() string { | 
					
						
							|  |  |  | 	return humanize.IBytes(uint64(fi.Size)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HumanModTime returns the modified time of the file as a human-readable string.
 | 
					
						
							|  |  |  | func (fi FileInfo) HumanModTime(format string) string { | 
					
						
							|  |  |  | 	return fi.ModTime.Format(format) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-06-11 20:20:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // 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 | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-06-11 20:34:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Rename function is used tor rename a file or a directory
 | 
					
						
							|  |  |  | func (fi FileInfo) Rename(w http.ResponseWriter, r *http.Request) (int, error) { | 
					
						
							|  |  |  | 	newname := r.Header.Get("Rename-To") | 
					
						
							|  |  |  | 	if newname == "" { | 
					
						
							|  |  |  | 		return http.StatusBadRequest, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	newpath := filepath.Clean(newname) | 
					
						
							|  |  |  | 	newpath = strings.Replace(fi.Path, fi.Name, newname, 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := os.Rename(fi.Path, newpath); err != nil { | 
					
						
							|  |  |  | 		return ErrorToHTTPCode(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-21 13:23:16 +00:00
										 |  |  | 	return http.StatusOK, nil | 
					
						
							| 
									
										
										
										
											2016-06-11 20:34:38 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ServeAsHTML is used to serve single file pages
 | 
					
						
							|  |  |  | func (fi FileInfo) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 	if fi.IsDir { | 
					
						
							|  |  |  | 		return fi.serveListing(w, r, c) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 17:19:10 +00:00
										 |  |  | 	return fi.serveSingleFile(w, r, c) | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 17:19:10 +00:00
										 |  |  | func (fi FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | 	err := fi.GetExtendedFileInfo() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return ErrorToHTTPCode(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	page := &Page{ | 
					
						
							|  |  |  | 		Info: &PageInfo{ | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 			Name:   fi.Name, | 
					
						
							|  |  |  | 			Path:   fi.RootPath, | 
					
						
							|  |  |  | 			IsDir:  false, | 
					
						
							| 
									
										
										
										
											2016-06-14 17:19:10 +00:00
										 |  |  | 			Data:   fi, | 
					
						
							|  |  |  | 			Config: c, | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-21 14:02:30 +00:00
										 |  |  | 	templates := []string{"single", "actions", "base"} | 
					
						
							|  |  |  | 	for _, t := range templates { | 
					
						
							| 
									
										
										
										
											2016-06-21 15:00:57 +00:00
										 |  |  | 		code, err := page.AddTemplate(t, Asset, nil) | 
					
						
							| 
									
										
										
										
											2016-06-21 14:02:30 +00:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return code, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return page.PrintAsHTML(w) | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | func (fi FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 	file, err := c.Root.Open(fi.RootPath) | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return ErrorToHTTPCode(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer file.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listing, err := fi.loadDirectoryContents(file, c) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		fmt.Println(err) | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case os.IsPermission(err): | 
					
						
							|  |  |  | 			return http.StatusForbidden, err | 
					
						
							|  |  |  | 		case os.IsExist(err): | 
					
						
							|  |  |  | 			return http.StatusGone, err | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return http.StatusInternalServerError, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listing.Context = httpserver.Context{ | 
					
						
							|  |  |  | 		Root: c.Root, | 
					
						
							|  |  |  | 		Req:  r, | 
					
						
							|  |  |  | 		URL:  r.URL, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Copy the query values into the Listing struct
 | 
					
						
							|  |  |  | 	var limit int | 
					
						
							|  |  |  | 	listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.PathScope) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return http.StatusBadRequest, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listing.applySort() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if limit > 0 && limit <= len(listing.Items) { | 
					
						
							|  |  |  | 		listing.Items = listing.Items[:limit] | 
					
						
							|  |  |  | 		listing.ItemsLimitedTo = limit | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	page := &Page{ | 
					
						
							|  |  |  | 		Info: &PageInfo{ | 
					
						
							| 
									
										
										
										
											2016-06-14 17:19:10 +00:00
										 |  |  | 			Name:   listing.Name, | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 			Path:   fi.RootPath, | 
					
						
							|  |  |  | 			IsDir:  true, | 
					
						
							| 
									
										
										
										
											2016-06-14 17:19:10 +00:00
										 |  |  | 			Config: c, | 
					
						
							|  |  |  | 			Data:   listing, | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-21 14:02:30 +00:00
										 |  |  | 	templates := []string{"listing", "actions", "base"} | 
					
						
							|  |  |  | 	for _, t := range templates { | 
					
						
							| 
									
										
										
										
											2016-06-21 15:00:57 +00:00
										 |  |  | 		code, err := page.AddTemplate(t, Asset, nil) | 
					
						
							| 
									
										
										
										
											2016-06-21 14:02:30 +00:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return code, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return page.PrintAsHTML(w) | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (fi FileInfo) loadDirectoryContents(file http.File, c *Config) (*Listing, error) { | 
					
						
							|  |  |  | 	files, err := file.Readdir(-1) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | 	listing := directoryListing(files, fi.RootPath) | 
					
						
							| 
									
										
										
										
											2016-06-11 22:01:24 +00:00
										 |  |  | 	return &listing, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func directoryListing(files []os.FileInfo, urlPath string) Listing { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		fileinfos           []FileInfo | 
					
						
							|  |  |  | 		dirCount, fileCount int | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, f := range files { | 
					
						
							|  |  |  | 		name := f.Name() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if f.IsDir() { | 
					
						
							|  |  |  | 			name += "/" | 
					
						
							|  |  |  | 			dirCount++ | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			fileCount++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fileinfos = append(fileinfos, FileInfo{ | 
					
						
							|  |  |  | 			IsDir:   f.IsDir(), | 
					
						
							|  |  |  | 			Name:    f.Name(), | 
					
						
							|  |  |  | 			Size:    f.Size(), | 
					
						
							|  |  |  | 			URL:     url.String(), | 
					
						
							|  |  |  | 			ModTime: f.ModTime().UTC(), | 
					
						
							|  |  |  | 			Mode:    f.Mode(), | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return Listing{ | 
					
						
							|  |  |  | 		Name:     path.Base(urlPath), | 
					
						
							|  |  |  | 		Path:     urlPath, | 
					
						
							|  |  |  | 		Items:    fileinfos, | 
					
						
							|  |  |  | 		NumDirs:  dirCount, | 
					
						
							|  |  |  | 		NumFiles: fileCount, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-14 19:33:59 +00:00
										 |  |  | // ServeRawFile serves raw files
 | 
					
						
							|  |  |  | func (fi *FileInfo) ServeRawFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { | 
					
						
							|  |  |  | 	err := fi.GetExtendedFileInfo() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return ErrorToHTTPCode(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fi.Type != "text" { | 
					
						
							|  |  |  | 		fi.Read() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w.Header().Set("Content-Type", fi.Mimetype) | 
					
						
							|  |  |  | 	w.Write([]byte(fi.Content)) | 
					
						
							|  |  |  | 	return 200, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-11 21:34:00 +00:00
										 |  |  | // SimplifyMimeType returns the base type of a file
 | 
					
						
							|  |  |  | func SimplifyMimeType(name string) string { | 
					
						
							|  |  |  | 	if strings.HasPrefix(name, "video") { | 
					
						
							|  |  |  | 		return "video" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if strings.HasPrefix(name, "audio") { | 
					
						
							|  |  |  | 		return "audio" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if strings.HasPrefix(name, "image") { | 
					
						
							|  |  |  | 		return "image" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return "text" | 
					
						
							|  |  |  | } |