package filestorage import ( "context" "errors" "regexp" "strings" "time" ) var ( ErrRelativePath = errors.New("path cant be relative") ErrNonCanonicalPath = errors.New("path must be canonical") ErrPathTooLong = errors.New("path is too long") ErrPathInvalid = errors.New("path is invalid") ErrPathEndsWithDelimiter = errors.New("path can not end with delimiter") Delimiter = "/" multipleDelimiters = regexp.MustCompile(`/+`) ) func Join(parts ...string) string { joinedPath := Delimiter + strings.Join(parts, Delimiter) // makes the API more forgiving for clients without compromising safety return multipleDelimiters.ReplaceAllString(joinedPath, Delimiter) } type File struct { Contents []byte FileMetadata } type FileMetadata struct { Name string FullPath string MimeType string Modified time.Time Created time.Time Size int64 Properties map[string]string } type ListFilesResponse struct { Files []FileMetadata HasMore bool LastPath string } type Paging struct { After string First int } type UpsertFileCommand struct { Path string MimeType string Contents *[]byte Properties map[string]string } type PathFilters struct { allowedPrefixes []string disallowedPrefixes []string allowedPaths []string disallowedPaths []string } func toLower(list []string) []string { if list == nil { return nil } lower := make([]string, 0) for _, el := range list { lower = append(lower, strings.ToLower(el)) } return lower } func allowAllPathFilters() *PathFilters { return NewPathFilters(nil, nil, nil, nil) } //nolint:deadcode,unused func denyAllPathFilters() *PathFilters { return NewPathFilters([]string{}, []string{}, nil, nil) } func NewPathFilters(allowedPrefixes []string, allowedPaths []string, disallowedPrefixes []string, disallowedPaths []string) *PathFilters { return &PathFilters{ allowedPrefixes: toLower(allowedPrefixes), allowedPaths: toLower(allowedPaths), disallowedPaths: toLower(disallowedPaths), disallowedPrefixes: toLower(disallowedPrefixes), } } func (f *PathFilters) isDenyAll() bool { return f.allowedPaths != nil && f.allowedPrefixes != nil && (len(f.allowedPaths)+len(f.allowedPrefixes) == 0) } func (f *PathFilters) IsAllowed(path string) bool { if f == nil { return true } path = strings.ToLower(path) for i := range f.disallowedPaths { if f.disallowedPaths[i] == path { return false } } for i := range f.disallowedPrefixes { if strings.HasPrefix(path, f.disallowedPrefixes[i]) { return false } } if f.allowedPrefixes == nil && f.allowedPaths == nil { return true } for i := range f.allowedPaths { if f.allowedPaths[i] == path { return true } } for i := range f.allowedPrefixes { if strings.HasPrefix(path, f.allowedPrefixes[i]) { return true } } return false } type ListOptions struct { Recursive bool *PathFilters } type FileStorage interface { Get(ctx context.Context, path string) (*File, error) Delete(ctx context.Context, path string) error Upsert(ctx context.Context, command *UpsertFileCommand) error ListFiles(ctx context.Context, folderPath string, paging *Paging, options *ListOptions) (*ListFilesResponse, error) ListFolders(ctx context.Context, folderPath string, options *ListOptions) ([]FileMetadata, error) CreateFolder(ctx context.Context, path string) error DeleteFolder(ctx context.Context, path string) error close() error }