127 lines
3.0 KiB
Go
127 lines
3.0 KiB
Go
package pathvalidate
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
var (
|
|
DefaultFilenameSanitizer = FilenameSanitizer{}
|
|
DefaultFilepathSanitizer = FilepathSanitizer{}
|
|
)
|
|
|
|
type FilepathSanitizer struct {
|
|
FilenameSanitizer
|
|
}
|
|
|
|
func (fps FilepathSanitizer) Sanitize(path string, replacement rune) (string, error) {
|
|
var (
|
|
err error
|
|
)
|
|
cleaned := filepath.Clean(path)
|
|
split := strings.Split(cleaned, string(os.PathSeparator))
|
|
splitS := make([]string, 0, len(split))
|
|
for _, name := range split {
|
|
name, err = fps.FilenameSanitizer.Sanitize(name, replacement)
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
splitS = append(splitS, name)
|
|
}
|
|
return filepath.Join(splitS...), nil
|
|
}
|
|
|
|
func (fps FilepathSanitizer) Validate(path string) error {
|
|
cleaned := filepath.Clean(path)
|
|
split := strings.Split(cleaned, string(os.PathSeparator))
|
|
for _, name := range split {
|
|
if err := fps.FilenameSanitizer.Validate(name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type FilenameSanitizer struct {
|
|
BaseFile
|
|
}
|
|
|
|
func (f FilenameSanitizer) Sanitize(path string, replacement rune) (string, error) {
|
|
var (
|
|
err error
|
|
)
|
|
if f.BaseFile.MinLength == 0 {
|
|
f.BaseFile = DefaultBaseFile
|
|
}
|
|
replace := func(r rune) rune {
|
|
if unicode.Is(InvalidFilename, r) && r != ' ' {
|
|
return replacement
|
|
}
|
|
return r
|
|
}
|
|
sanitized := strings.Map(replace, path)
|
|
sanitized = f.UpdateReservedKeywords(sanitized, "_")
|
|
sanitized = strings.TrimSpace(sanitized)
|
|
err = f.Validate(sanitized)
|
|
if err != nil {
|
|
return path, fmt.Errorf("could not validate sanitized filename: %w", err)
|
|
}
|
|
return sanitized, nil
|
|
}
|
|
|
|
func (f FilenameSanitizer) Validate(path string) error {
|
|
if f.BaseFile.MinLength == 0 {
|
|
f.BaseFile = DefaultBaseFile
|
|
}
|
|
nameLen := utf8.RuneCountInString(path)
|
|
cleaned := filepath.Clean(path)
|
|
|
|
if nameLen > f.MaxLength {
|
|
return fmt.Errorf("%w: wanted <= %d, got = %d", ErrMaxLength, f.MaxLength, nameLen)
|
|
}
|
|
|
|
if nameLen < f.MinLength {
|
|
return fmt.Errorf("%w: wanted >= %d, got = %d", ErrMinLength, f.MinLength, nameLen)
|
|
}
|
|
|
|
err := f.validateReservedKeywords(cleaned)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
validate := func(r rune) bool {
|
|
return unicode.Is(InvalidFilename, r) && r != ' '
|
|
}
|
|
if n := strings.IndexFunc(cleaned, validate); n != -1 {
|
|
r, _ := utf8.DecodeRuneInString(cleaned[n:])
|
|
return fmt.Errorf("%w: '%s' (%#x)", ErrInvalidChar, string(r), r)
|
|
}
|
|
if cleaned[0] == ' ' {
|
|
return fmt.Errorf("%w: space at beginning of string", ErrInvalidChar)
|
|
}
|
|
if cleaned[len(cleaned)-1] == ' ' {
|
|
return fmt.Errorf("%w: space at end of string", ErrInvalidChar)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SanitizeFilename(path string, replacement rune) (string, error) {
|
|
return DefaultFilenameSanitizer.Sanitize(path, replacement)
|
|
}
|
|
|
|
func ValidateFilename(path string) error {
|
|
return DefaultFilenameSanitizer.Validate(path)
|
|
}
|
|
|
|
func SanitizeFilepath(path string, replacement rune) (string, error) {
|
|
return DefaultFilepathSanitizer.Sanitize(path, replacement)
|
|
}
|
|
|
|
func ValidateFilepath(path string) error {
|
|
return DefaultFilepathSanitizer.Validate(path)
|
|
}
|