pathvalidate/pathvalidate.go
2020-10-11 21:37:03 -07:00

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)
}