| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | package http | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2020-06-16 19:56:44 +00:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 	"github.com/spf13/afero" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	"github.com/filebrowser/filebrowser/v2/errors" | 
					
						
							| 
									
										
										
										
											2020-06-06 15:45:51 +00:00
										 |  |  | 	"github.com/filebrowser/filebrowser/v2/files" | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	"github.com/filebrowser/filebrowser/v2/fileutils" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { | 
					
						
							|  |  |  | 	file, err := files.NewFileInfo(files.FileOptions{ | 
					
						
							| 
									
										
										
										
											2021-01-07 10:30:17 +00:00
										 |  |  | 		Fs:         d.user.Fs, | 
					
						
							|  |  |  | 		Path:       r.URL.Path, | 
					
						
							|  |  |  | 		Modify:     d.user.Perm.Modify, | 
					
						
							|  |  |  | 		Expand:     true, | 
					
						
							|  |  |  | 		ReadHeader: d.server.TypeDetectionByHeader, | 
					
						
							|  |  |  | 		Checker:    d, | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return errToStatus(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if file.IsDir { | 
					
						
							|  |  |  | 		file.Listing.Sorting = d.user.Sorting | 
					
						
							|  |  |  | 		file.Listing.ApplySort() | 
					
						
							|  |  |  | 		return renderJSON(w, r, file) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if checksum := r.URL.Query().Get("checksum"); checksum != "" { | 
					
						
							|  |  |  | 		err := file.Checksum(checksum) | 
					
						
							|  |  |  | 		if err == errors.ErrInvalidOption { | 
					
						
							|  |  |  | 			return http.StatusBadRequest, nil | 
					
						
							|  |  |  | 		} else if err != nil { | 
					
						
							|  |  |  | 			return http.StatusInternalServerError, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// do not waste bandwidth if we just want the checksum
 | 
					
						
							|  |  |  | 		file.Content = "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return renderJSON(w, r, file) | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-28 09:57:26 +00:00
										 |  |  | func resourceDeleteHandler(fileCache FileCache) handleFunc { | 
					
						
							|  |  |  | 	return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { | 
					
						
							|  |  |  | 		if r.URL.Path == "/" || !d.user.Perm.Delete { | 
					
						
							|  |  |  | 			return http.StatusForbidden, nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-28 09:57:26 +00:00
										 |  |  | 		file, err := files.NewFileInfo(files.FileOptions{ | 
					
						
							| 
									
										
										
										
											2021-01-07 10:30:17 +00:00
										 |  |  | 			Fs:         d.user.Fs, | 
					
						
							|  |  |  | 			Path:       r.URL.Path, | 
					
						
							|  |  |  | 			Modify:     d.user.Perm.Modify, | 
					
						
							|  |  |  | 			Expand:     true, | 
					
						
							|  |  |  | 			ReadHeader: d.server.TypeDetectionByHeader, | 
					
						
							|  |  |  | 			Checker:    d, | 
					
						
							| 
									
										
										
										
											2020-07-28 09:57:26 +00:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return errToStatus(err), err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-28 09:57:26 +00:00
										 |  |  | 		// delete thumbnails
 | 
					
						
							|  |  |  | 		for _, previewSizeName := range PreviewSizeNames() { | 
					
						
							|  |  |  | 			size, _ := ParsePreviewSize(previewSizeName) | 
					
						
							| 
									
										
										
										
											2020-07-28 11:40:06 +00:00
										 |  |  | 			if err := fileCache.Delete(r.Context(), previewCacheKey(file.Path, size)); err != nil { //nolint:govet
 | 
					
						
							| 
									
										
										
										
											2020-07-28 09:57:26 +00:00
										 |  |  | 				return errToStatus(err), err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-28 09:57:26 +00:00
										 |  |  | 		err = d.RunHook(func() error { | 
					
						
							|  |  |  | 			return d.user.Fs.RemoveAll(r.URL.Path) | 
					
						
							|  |  |  | 		}, "delete", r.URL.Path, "", d.user) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return errToStatus(err), err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return http.StatusOK, nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { | 
					
						
							|  |  |  | 	if !d.user.Perm.Create && r.Method == http.MethodPost { | 
					
						
							|  |  |  | 		return http.StatusForbidden, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !d.user.Perm.Modify && r.Method == http.MethodPut { | 
					
						
							|  |  |  | 		return http.StatusForbidden, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							| 
									
										
										
										
											2020-05-31 23:12:36 +00:00
										 |  |  | 		_, _ = io.Copy(ioutil.Discard, r.Body) | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// For directories, only allow POST for creation.
 | 
					
						
							|  |  |  | 	if strings.HasSuffix(r.URL.Path, "/") { | 
					
						
							|  |  |  | 		if r.Method == http.MethodPut { | 
					
						
							|  |  |  | 			return http.StatusMethodNotAllowed, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := d.user.Fs.MkdirAll(r.URL.Path, 0775) | 
					
						
							|  |  |  | 		return errToStatus(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if r.Method == http.MethodPost && r.URL.Query().Get("override") != "true" { | 
					
						
							|  |  |  | 		if _, err := d.user.Fs.Stat(r.URL.Path); err == nil { | 
					
						
							|  |  |  | 			return http.StatusConflict, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-16 14:25:35 +00:00
										 |  |  | 	action := "upload" | 
					
						
							|  |  |  | 	if r.Method == http.MethodPut { | 
					
						
							|  |  |  | 		action = "save" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	err := d.RunHook(func() error { | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 		dir, _ := path.Split(r.URL.Path) | 
					
						
							| 
									
										
										
										
											2020-06-16 19:56:44 +00:00
										 |  |  | 		err := d.user.Fs.MkdirAll(dir, 0775) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 		file, err := d.user.Fs.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		defer file.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, err = io.Copy(file, r.Body) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Gets the info about the file.
 | 
					
						
							|  |  |  | 		info, err := file.Stat() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size()) | 
					
						
							|  |  |  | 		w.Header().Set("ETag", etag) | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2020-01-16 14:25:35 +00:00
										 |  |  | 	}, action, r.URL.Path, "", d.user) | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:48 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		_ = d.user.Fs.RemoveAll(r.URL.Path) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	return errToStatus(err), err | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { | 
					
						
							|  |  |  | 	src := r.URL.Path | 
					
						
							|  |  |  | 	dst := r.URL.Query().Get("destination") | 
					
						
							|  |  |  | 	action := r.URL.Query().Get("action") | 
					
						
							|  |  |  | 	dst, err := url.QueryUnescape(dst) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return errToStatus(err), err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if dst == "/" || src == "/" { | 
					
						
							|  |  |  | 		return http.StatusForbidden, nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 	if err = checkParent(src, dst); err != nil { | 
					
						
							|  |  |  | 		return http.StatusBadRequest, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-16 19:30:17 +00:00
										 |  |  | 	override := r.URL.Query().Get("override") == "true" | 
					
						
							|  |  |  | 	rename := r.URL.Query().Get("rename") == "true" | 
					
						
							|  |  |  | 	if !override && !rename { | 
					
						
							|  |  |  | 		if _, err = d.user.Fs.Stat(dst); err == nil { | 
					
						
							| 
									
										
										
										
											2020-07-15 15:12:13 +00:00
										 |  |  | 			return http.StatusConflict, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-16 19:30:17 +00:00
										 |  |  | 	if rename { | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 		dst = addVersionSuffix(dst, d.user.Fs) | 
					
						
							| 
									
										
										
										
											2020-07-16 19:30:17 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 	err = d.RunHook(func() error { | 
					
						
							| 
									
										
										
										
											2020-06-06 15:45:51 +00:00
										 |  |  | 		switch action { | 
					
						
							|  |  |  | 		// TODO: use enum
 | 
					
						
							|  |  |  | 		case "copy": | 
					
						
							|  |  |  | 			if !d.user.Perm.Create { | 
					
						
							|  |  |  | 				return errors.ErrPermissionDenied | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 			return fileutils.Copy(d.user.Fs, src, dst) | 
					
						
							| 
									
										
										
										
											2020-06-06 15:45:51 +00:00
										 |  |  | 		case "rename": | 
					
						
							|  |  |  | 			if !d.user.Perm.Rename { | 
					
						
							|  |  |  | 				return errors.ErrPermissionDenied | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 			src = path.Clean("/" + src) | 
					
						
							|  |  |  | 			dst = path.Clean("/" + dst) | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-24 16:50:27 +00:00
										 |  |  | 			return fileutils.MoveFile(d.user.Fs, src, dst) | 
					
						
							| 
									
										
										
										
											2020-06-06 15:45:51 +00:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams) | 
					
						
							| 
									
										
										
										
											2019-01-05 22:44:33 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}, action, src, dst, d.user) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return errToStatus(err), err | 
					
						
							|  |  |  | }) | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | func checkParent(src, dst string) error { | 
					
						
							|  |  |  | 	rel, err := filepath.Rel(src, dst) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rel = filepath.ToSlash(rel) | 
					
						
							|  |  |  | 	if !strings.HasPrefix(rel, "../") && rel != ".." && rel != "." { | 
					
						
							|  |  |  | 		return errors.ErrSourceIsParent | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | func addVersionSuffix(source string, fs afero.Fs) string { | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 	counter := 1 | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 	dir, name := path.Split(source) | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 	ext := filepath.Ext(name) | 
					
						
							|  |  |  | 	base := strings.TrimSuffix(name, ext) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 		if _, err := fs.Stat(source); err != nil { | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		renamed := fmt.Sprintf("%s(%d)%s", base, counter, ext) | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 		source = path.Join(dir, renamed) | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | 		counter++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-03 12:30:56 +00:00
										 |  |  | 	return source | 
					
						
							| 
									
										
										
										
											2020-07-20 17:38:43 +00:00
										 |  |  | } |