Browse Source

web admin: allow both allowed and denied extensions/patterns for a dir

this fix a regression introduced in the previous commit
Nicola Murino 4 năm trước cách đây
mục cha
commit
e3eca424f1
7 tập tin đã thay đổi với 110 bổ sung49 xóa
  1. 2 2
      dataprovider/dataprovider.go
  2. 2 2
      go.mod
  3. 3 3
      go.sum
  4. 23 5
      httpd/httpd_test.go
  5. 59 32
      httpd/web.go
  6. 5 5
      templates/user.html
  7. 16 0
      utils/utils.go

+ 2 - 2
dataprovider/dataprovider.go

@@ -927,14 +927,14 @@ func validateFiltersPatternExtensions(user *User) error {
 		for _, pattern := range f.AllowedPatterns {
 			_, err := path.Match(pattern, "abc")
 			if err != nil {
-				return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %v", pattern)}
+				return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %#v", pattern)}
 			}
 			allowed = append(allowed, strings.ToLower(pattern))
 		}
 		for _, pattern := range f.DeniedPatterns {
 			_, err := path.Match(pattern, "abc")
 			if err != nil {
-				return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %v", pattern)}
+				return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %#v", pattern)}
 			}
 			denied = append(denied, strings.ToLower(pattern))
 		}

+ 2 - 2
go.mod

@@ -26,7 +26,7 @@ require (
 	github.com/mitchellh/mapstructure v1.3.3 // indirect
 	github.com/otiai10/copy v1.2.0
 	github.com/pelletier/go-toml v1.8.1 // indirect
-	github.com/pires/go-proxyproto v0.3.1
+	github.com/pires/go-proxyproto v0.3.2
 	github.com/pkg/sftp v1.12.1-0.20201002132022-fcaa492add82
 	github.com/prometheus/client_golang v1.8.0
 	github.com/prometheus/common v0.15.0 // indirect
@@ -47,7 +47,7 @@ require (
 	golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
 	golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 // indirect
 	golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba
-	golang.org/x/tools v0.0.0-20201113202037-1643af1435f3 // indirect
+	golang.org/x/tools v0.0.0-20201116002733-ac45abd4c88c // indirect
 	google.golang.org/api v0.35.0
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20201113130914-ce600e9a6f9e // indirect

+ 3 - 3
go.sum

@@ -381,8 +381,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pires/go-proxyproto v0.3.1 h1:eWb52zeDUbSUDBV+8aVCfOy0pnEG6DrDW3cJ/WKdQsk=
-github.com/pires/go-proxyproto v0.3.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.3.2 h1:E5ig1h9SFGne7IWVY6yRu3UCzyAFkQIukXHMkdFUOCA=
+github.com/pires/go-proxyproto v0.3.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -677,7 +677,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
 golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201113202037-1643af1435f3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201116002733-ac45abd4c88c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

+ 23 - 5
httpd/httpd_test.go

@@ -2455,10 +2455,10 @@ func TestWebUserAddMock(t *testing.T) {
 	form.Set("permissions", "*")
 	form.Set("sub_dirs_permissions", " /subdir::list ,download ")
 	form.Set("virtual_folders", fmt.Sprintf(" /vdir:: %v :: 2 :: 1024", mappedDir))
-	form.Set("allowed_extensions", "/dir2::.jpg,.png\n/dir2::.ico")
-	form.Set("denied_extensions", "/dir1::.zip")
-	form.Set("allowed_patterns", "/dir2::*.jpg,*.png")
-	form.Set("denied_patterns", "/dir1::*.zip")
+	form.Set("allowed_extensions", "/dir2::.jpg,.png\n/dir2::.ico\n/dir1::.rar")
+	form.Set("denied_extensions", "/dir2::.webp,.webp\n/dir2::.tiff\n/dir1::.zip")
+	form.Set("allowed_patterns", "/dir2::*.jpg,*.png\n/dir1::*.png")
+	form.Set("denied_patterns", "/dir1::*.zip\n/dir3::*.rar\n/dir2::*.mkv")
 	b, contentType, _ := getMultipartFormData(form, "", "")
 	// test invalid url escape
 	req, _ := http.NewRequest(http.MethodPost, webUserPath+"?a=%2", &b)
@@ -2595,22 +2595,40 @@ func TestWebUserAddMock(t *testing.T) {
 	assert.Len(t, newUser.Filters.FileExtensions, 2)
 	for _, filter := range newUser.Filters.FileExtensions {
 		if filter.Path == "/dir1" {
+			assert.Len(t, filter.DeniedExtensions, 1)
+			assert.Len(t, filter.AllowedExtensions, 1)
 			assert.True(t, utils.IsStringInSlice(".zip", filter.DeniedExtensions))
+			assert.True(t, utils.IsStringInSlice(".rar", filter.AllowedExtensions))
 		}
 		if filter.Path == "/dir2" {
+			assert.Len(t, filter.DeniedExtensions, 2)
+			assert.Len(t, filter.AllowedExtensions, 3)
 			assert.True(t, utils.IsStringInSlice(".jpg", filter.AllowedExtensions))
 			assert.True(t, utils.IsStringInSlice(".png", filter.AllowedExtensions))
 			assert.True(t, utils.IsStringInSlice(".ico", filter.AllowedExtensions))
+			assert.True(t, utils.IsStringInSlice(".webp", filter.DeniedExtensions))
+			assert.True(t, utils.IsStringInSlice(".tiff", filter.DeniedExtensions))
 		}
 	}
-	assert.Len(t, newUser.Filters.FilePatterns, 2)
+	assert.Len(t, newUser.Filters.FilePatterns, 3)
 	for _, filter := range newUser.Filters.FilePatterns {
 		if filter.Path == "/dir1" {
+			assert.Len(t, filter.DeniedPatterns, 1)
+			assert.Len(t, filter.AllowedPatterns, 1)
+			assert.True(t, utils.IsStringInSlice("*.png", filter.AllowedPatterns))
 			assert.True(t, utils.IsStringInSlice("*.zip", filter.DeniedPatterns))
 		}
 		if filter.Path == "/dir2" {
+			assert.Len(t, filter.DeniedPatterns, 1)
+			assert.Len(t, filter.AllowedPatterns, 2)
 			assert.True(t, utils.IsStringInSlice("*.jpg", filter.AllowedPatterns))
 			assert.True(t, utils.IsStringInSlice("*.png", filter.AllowedPatterns))
+			assert.True(t, utils.IsStringInSlice("*.mkv", filter.DeniedPatterns))
+		}
+		if filter.Path == "/dir3" {
+			assert.Len(t, filter.DeniedPatterns, 1)
+			assert.Len(t, filter.AllowedPatterns, 0)
+			assert.True(t, utils.IsStringInSlice("*.rar", filter.DeniedPatterns))
 		}
 	}
 	req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(newUser.ID, 10), nil)

+ 59 - 32
httpd/web.go

@@ -6,6 +6,7 @@ import (
 	"html/template"
 	"io/ioutil"
 	"net/http"
+	"path"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -314,7 +315,7 @@ func getListFromPostFields(value string) map[string][]string {
 			dirExts := strings.Split(cleaned, "::")
 			if len(dirExts) > 1 {
 				dir := dirExts[0]
-				dir = strings.TrimSpace(dir)
+				dir = path.Clean(strings.TrimSpace(dir))
 				exts := []string{}
 				for _, e := range strings.Split(dirExts[1], ",") {
 					cleanedExt := strings.TrimSpace(e)
@@ -328,6 +329,7 @@ func getListFromPostFields(value string) map[string][]string {
 					} else {
 						result[dir] = exts
 					}
+					result[dir] = utils.RemoveDuplicates(result[dir])
 				}
 			}
 		}
@@ -335,39 +337,75 @@ func getListFromPostFields(value string) map[string][]string {
 	return result
 }
 
-func getFilePatternsFromPostField(value string, extesionsType int) []dataprovider.PatternsFilter {
+func getFilePatternsFromPostField(valueAllowed, valuesDenied string) []dataprovider.PatternsFilter {
 	var result []dataprovider.PatternsFilter
-	for dir, values := range getListFromPostFields(value) {
+	allowedPatterns := getListFromPostFields(valueAllowed)
+	deniedPatterns := getListFromPostFields(valuesDenied)
+
+	for dirAllowed, allowPatterns := range allowedPatterns {
 		filter := dataprovider.PatternsFilter{
-			Path: dir,
+			Path:            dirAllowed,
+			AllowedPatterns: allowPatterns,
 		}
-		if extesionsType == 1 {
-			filter.AllowedPatterns = values
-			filter.DeniedPatterns = []string{}
-		} else {
-			filter.DeniedPatterns = values
-			filter.AllowedPatterns = []string{}
+		for dirDenied, denPatterns := range deniedPatterns {
+			if dirAllowed == dirDenied {
+				filter.DeniedPatterns = denPatterns
+				break
+			}
 		}
 		result = append(result, filter)
 	}
+	for dirDenied, denPatterns := range deniedPatterns {
+		found := false
+		for _, res := range result {
+			if res.Path == dirDenied {
+				found = true
+				break
+			}
+		}
+		if !found {
+			result = append(result, dataprovider.PatternsFilter{
+				Path:           dirDenied,
+				DeniedPatterns: denPatterns,
+			})
+		}
+	}
 	return result
 }
 
-func getFileExtensionsFromPostField(value string, extesionsType int) []dataprovider.ExtensionsFilter {
+func getFileExtensionsFromPostField(valueAllowed, valuesDenied string) []dataprovider.ExtensionsFilter {
 	var result []dataprovider.ExtensionsFilter
-	for dir, values := range getListFromPostFields(value) {
+	allowedExtensions := getListFromPostFields(valueAllowed)
+	deniedExtensions := getListFromPostFields(valuesDenied)
+
+	for dirAllowed, allowedExts := range allowedExtensions {
 		filter := dataprovider.ExtensionsFilter{
-			Path: dir,
+			Path:              dirAllowed,
+			AllowedExtensions: allowedExts,
 		}
-		if extesionsType == 1 {
-			filter.AllowedExtensions = values
-			filter.DeniedExtensions = []string{}
-		} else {
-			filter.DeniedExtensions = values
-			filter.AllowedExtensions = []string{}
+		for dirDenied, deniedExts := range deniedExtensions {
+			if dirAllowed == dirDenied {
+				filter.DeniedExtensions = deniedExts
+				break
+			}
 		}
 		result = append(result, filter)
 	}
+	for dirDenied, deniedExts := range deniedExtensions {
+		found := false
+		for _, res := range result {
+			if res.Path == dirDenied {
+				found = true
+				break
+			}
+		}
+		if !found {
+			result = append(result, dataprovider.ExtensionsFilter{
+				Path:             dirDenied,
+				DeniedExtensions: deniedExts,
+			})
+		}
+	}
 	return result
 }
 
@@ -377,19 +415,8 @@ func getFiltersFromUserPostFields(r *http.Request) dataprovider.UserFilters {
 	filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
 	filters.DeniedLoginMethods = r.Form["ssh_login_methods"]
 	filters.DeniedProtocols = r.Form["denied_protocols"]
-	allowedExtensions := getFileExtensionsFromPostField(r.Form.Get("allowed_extensions"), 1)
-	deniedExtensions := getFileExtensionsFromPostField(r.Form.Get("denied_extensions"), 2)
-	extensions := []dataprovider.ExtensionsFilter{}
-	extensions = append(extensions, allowedExtensions...)
-	extensions = append(extensions, deniedExtensions...)
-	filters.FileExtensions = extensions
-	allowedPatterns := getFilePatternsFromPostField(r.Form.Get("allowed_patterns"), 1)
-	deniedPatterns := getFilePatternsFromPostField(r.Form.Get("denied_patterns"), 2)
-	patterns := []dataprovider.PatternsFilter{}
-	patterns = append(patterns, allowedPatterns...)
-	patterns = append(patterns, deniedPatterns...)
-	filters.FilePatterns = patterns
-
+	filters.FileExtensions = getFileExtensionsFromPostField(r.Form.Get("allowed_extensions"), r.Form.Get("denied_extensions"))
+	filters.FilePatterns = getFilePatternsFromPostField(r.Form.Get("allowed_patterns"), r.Form.Get("denied_patterns"))
 	return filters
 }
 

+ 5 - 5
templates/user.html

@@ -119,7 +119,7 @@
                 {{- end}}
                 {{- end}}</textarea>
             <small id="subDirsHelpBlock" class="form-text text-muted">
-                One exposed virtual directory path per line as dir::perms, for example /somedir::list,download
+                One exposed virtual directory path per line as /dir::perms, for example /somedir::list,download
             </small>
         </div>
     </div>
@@ -251,7 +251,7 @@
                 {{- end}}
                 {{- end}}</textarea>
             <small id="deniedPatternsHelpBlock" class="form-text text-muted">
-                One exposed virtual directory per line as dir::pattern1,pattern2, for example /subdir::*.zip,*.rar
+                One exposed virtual directory per line as /dir::pattern1,pattern2, for example /subdir::*.zip,*.rar
             </small>
         </div>
     </div>
@@ -266,7 +266,7 @@
                 {{- end}}
                 {{- end}}</textarea>
             <small id="allowedPatternsHelpBlock" class="form-text text-muted">
-                One exposed virtual directory per line as dir::pattern1,pattern2, for example /somedir::*.jpg,*.png
+                One exposed virtual directory per line as /dir::pattern1,pattern2, for example /somedir::*.jpg,*.png
             </small>
         </div>
     </div>
@@ -281,7 +281,7 @@
                 {{- end}}
                 {{- end}}</textarea>
             <small id="deniedExtensionsHelpBlock" class="form-text text-muted">
-                One exposed virtual directory per line as dir::extension1,extension2, for example /subdir::.zip,.rar. Deprecated, use file patterns
+                One exposed virtual directory per line as /dir::extension1,extension2, for example /subdir::.zip,.rar. Deprecated, use file patterns
             </small>
         </div>
     </div>
@@ -296,7 +296,7 @@
                 {{- end}}
                 {{- end}}</textarea>
             <small id="allowedExtensionsHelpBlock" class="form-text text-muted">
-                One exposed virtual directory per line as dir::extension1,extension2, for example /somedir::.jpg,.png. Deprecated, use file patterns
+                One exposed virtual directory per line as /dir::extension1,extension2, for example /somedir::.jpg,.png. Deprecated, use file patterns
             </small>
         </div>
     </div>

+ 16 - 0
utils/utils.go

@@ -53,6 +53,22 @@ func IsStringPrefixInSlice(obj string, list []string) bool {
 	return false
 }
 
+// RemoveDuplicates returns a new slice removing any duplicate element from the initial one
+func RemoveDuplicates(obj []string) []string {
+	if len(obj) == 0 {
+		return obj
+	}
+	result := make([]string, 0, len(obj))
+	seen := make(map[string]bool)
+	for _, item := range obj {
+		if _, ok := seen[item]; !ok {
+			result = append(result, item)
+		}
+		seen[item] = true
+	}
+	return result
+}
+
 // GetTimeAsMsSinceEpoch returns unix timestamp as milliseconds from a time struct
 func GetTimeAsMsSinceEpoch(t time.Time) int64 {
 	return t.UnixNano() / 1000000