diff --git a/pkg/fsutil/file.go b/pkg/fsutil/file.go index e958397f6..a3ccfc2e6 100644 --- a/pkg/fsutil/file.go +++ b/pkg/fsutil/file.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "regexp" "strings" ) @@ -116,3 +117,25 @@ func Touch(path string) error { } return nil } + +var ( + replaceCharsRE = regexp.MustCompile(`[&=\\/:*"?_ ]`) + removeCharsRE = regexp.MustCompile(`[^[:alnum:]-.]`) + multiHyphenRE = regexp.MustCompile(`\-+`) +) + +// SanitiseBasename returns a file basename removing any characters that are illegal or problematic to use in the filesystem. +func SanitiseBasename(v string) string { + v = strings.TrimSpace(v) + + // replace illegal filename characters with - + v = replaceCharsRE.ReplaceAllString(v, "-") + + // remove other characters + v = removeCharsRE.ReplaceAllString(v, "") + + // remove multiple hyphens + v = multiHyphenRE.ReplaceAllString(v, "-") + + return strings.TrimSpace(v) +} diff --git a/pkg/fsutil/file_test.go b/pkg/fsutil/file_test.go new file mode 100644 index 000000000..393d3b420 --- /dev/null +++ b/pkg/fsutil/file_test.go @@ -0,0 +1,26 @@ +package fsutil + +import "testing" + +func TestSanitiseBasename(t *testing.T) { + tests := []struct { + name string + v string + want string + }{ + {"basic", "basic", "basic"}, + {"spaces", `spaced name`, "spaced-name"}, + {"leading/trailing spaces", ` spaced name `, "spaced-name"}, + {"hyphen name", `hyphened-name`, "hyphened-name"}, + {"multi-hyphen", `hyphened--name`, "hyphened-name"}, + {"replaced characters", `a&b=c\d/:e*"f?_ g`, "a-b-c-d-e-f-g"}, + {"removed characters", `foo!!bar@@and, more`, "foobarand-more"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SanitiseBasename(tt.v); got != tt.want { + t.Errorf("SanitiseBasename() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/models/jsonschema/gallery.go b/pkg/models/jsonschema/gallery.go index 92480c7ff..596e7c610 100644 --- a/pkg/models/jsonschema/gallery.go +++ b/pkg/models/jsonschema/gallery.go @@ -6,6 +6,7 @@ import ( "strings" jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/models/json" ) @@ -26,7 +27,7 @@ type Gallery struct { } func (s Gallery) Filename(basename string, hash string) string { - ret := basename + ret := fsutil.SanitiseBasename(basename) if ret != "" { ret += "." diff --git a/pkg/models/jsonschema/image.go b/pkg/models/jsonschema/image.go index 1b60d2061..364daa0cf 100644 --- a/pkg/models/jsonschema/image.go +++ b/pkg/models/jsonschema/image.go @@ -5,6 +5,7 @@ import ( "os" jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/models/json" ) @@ -23,7 +24,7 @@ type Image struct { } func (s Image) Filename(basename string, hash string) string { - ret := s.Title + ret := fsutil.SanitiseBasename(s.Title) if ret == "" { ret = basename } diff --git a/pkg/models/jsonschema/movie.go b/pkg/models/jsonschema/movie.go index d9cab5d46..d787f8288 100644 --- a/pkg/models/jsonschema/movie.go +++ b/pkg/models/jsonschema/movie.go @@ -6,6 +6,7 @@ import ( jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models/json" ) @@ -27,7 +28,7 @@ type Movie struct { } func (s Movie) Filename() string { - return s.Name + ".json" + return fsutil.SanitiseBasename(s.Name) + ".json" } // Backwards Compatible synopsis for the movie diff --git a/pkg/models/jsonschema/performer.go b/pkg/models/jsonschema/performer.go index fdca28974..ad33452f3 100644 --- a/pkg/models/jsonschema/performer.go +++ b/pkg/models/jsonschema/performer.go @@ -5,6 +5,7 @@ import ( "os" jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/json" ) @@ -41,7 +42,7 @@ type Performer struct { } func (s Performer) Filename() string { - return s.Name + ".json" + return fsutil.SanitiseBasename(s.Name) + ".json" } func LoadPerformerFile(filePath string) (*Performer, error) { diff --git a/pkg/models/jsonschema/scene.go b/pkg/models/jsonschema/scene.go index 94fea7d9e..425ca10e8 100644 --- a/pkg/models/jsonschema/scene.go +++ b/pkg/models/jsonschema/scene.go @@ -5,6 +5,7 @@ import ( "os" jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/json" ) @@ -58,7 +59,7 @@ type Scene struct { } func (s Scene) Filename(basename string, hash string) string { - ret := s.Title + ret := fsutil.SanitiseBasename(s.Title) if ret == "" { ret = basename } diff --git a/pkg/models/jsonschema/studio.go b/pkg/models/jsonschema/studio.go index e795e1e30..d6932a28c 100644 --- a/pkg/models/jsonschema/studio.go +++ b/pkg/models/jsonschema/studio.go @@ -5,6 +5,7 @@ import ( "os" jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/json" ) @@ -24,7 +25,7 @@ type Studio struct { } func (s Studio) Filename() string { - return s.Name + ".json" + return fsutil.SanitiseBasename(s.Name) + ".json" } func LoadStudioFile(filePath string) (*Studio, error) { diff --git a/pkg/models/jsonschema/tag.go b/pkg/models/jsonschema/tag.go index 53a3e49ba..5f7e0bfa7 100644 --- a/pkg/models/jsonschema/tag.go +++ b/pkg/models/jsonschema/tag.go @@ -5,6 +5,7 @@ import ( "os" jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/models/json" ) @@ -19,7 +20,7 @@ type Tag struct { } func (s Tag) Filename() string { - return s.Name + ".json" + return fsutil.SanitiseBasename(s.Name) + ".json" } func LoadTagFile(filePath string) (*Tag, error) {