mirror of https://github.com/cloudreve/Cloudreve
feat(storage policy): set deny/allow list for file extension and custom regexp (#2695)
parent
60bf0e02b3
commit
c8c2a60adb
|
@ -41,6 +41,12 @@ type (
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
// 允许的文件扩展名
|
// 允许的文件扩展名
|
||||||
FileType []string `json:"file_type"`
|
FileType []string `json:"file_type"`
|
||||||
|
// IsFileTypeDenyList Whether above list is a deny list.
|
||||||
|
IsFileTypeDenyList bool `json:"is_file_type_deny_list,omitempty"`
|
||||||
|
// FileRegexp 文件扩展名正则表达式
|
||||||
|
NameRegexp string `json:"file_regexp,omitempty"`
|
||||||
|
// IsNameRegexp Whether above regexp is a deny list.
|
||||||
|
IsNameRegexpDenyList bool `json:"is_name_regexp_deny_list,omitempty"`
|
||||||
// OauthRedirect Oauth 重定向地址
|
// OauthRedirect Oauth 重定向地址
|
||||||
OauthRedirect string `json:"od_redirect,omitempty"`
|
OauthRedirect string `json:"od_redirect,omitempty"`
|
||||||
// CustomProxy whether to use custom-proxy to get file content
|
// CustomProxy whether to use custom-proxy to get file content
|
||||||
|
|
|
@ -120,6 +120,20 @@ func (f *DBFS) Create(ctx context.Context, path *fs.URI, fileType types.FileType
|
||||||
|
|
||||||
ancestor = newFile(ancestor, newFolder)
|
ancestor = newFile(ancestor, newFolder)
|
||||||
} else {
|
} else {
|
||||||
|
// valide file name
|
||||||
|
policy, err := f.getPreferredPolicy(ctx, ancestor, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateExtension(desired[i], policy); err != nil {
|
||||||
|
return nil, fs.ErrIllegalObjectName.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateFileNameRegexp(desired[i], policy); err != nil {
|
||||||
|
return nil, fs.ErrIllegalObjectName.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
file, err := f.createFile(ctx, ancestor, desired[i], fileType, o)
|
file, err := f.createFile(ctx, ancestor, desired[i], fileType, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -170,6 +184,10 @@ func (f *DBFS) Rename(ctx context.Context, path *fs.URI, newName string) (fs.Fil
|
||||||
if err := validateExtension(newName, policy); err != nil {
|
if err := validateExtension(newName, policy); err != nil {
|
||||||
return nil, fs.ErrIllegalObjectName.WithError(err)
|
return nil, fs.ErrIllegalObjectName.WithError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateFileNameRegexp(newName, policy); err != nil {
|
||||||
|
return nil, fs.ErrIllegalObjectName.WithError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock target
|
// Lock target
|
||||||
|
|
|
@ -3,10 +3,12 @@ package dbfs
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxFileNameLength = 256
|
const MaxFileNameLength = 256
|
||||||
|
@ -30,18 +32,35 @@ func validateFileName(name string) error {
|
||||||
|
|
||||||
// validateExtension validates the file extension.
|
// validateExtension validates the file extension.
|
||||||
func validateExtension(name string, policy *ent.StoragePolicy) error {
|
func validateExtension(name string, policy *ent.StoragePolicy) error {
|
||||||
// 不需要验证
|
|
||||||
if len(policy.Settings.FileType) == 0 {
|
if len(policy.Settings.FileType) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.IsInExtensionList(policy.Settings.FileType, name) {
|
inList := util.IsInExtensionList(policy.Settings.FileType, name)
|
||||||
|
if (policy.Settings.IsFileTypeDenyList && inList) || (!policy.Settings.IsFileTypeDenyList && !inList) {
|
||||||
return fmt.Errorf("file extension is not allowed")
|
return fmt.Errorf("file extension is not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateFileNameRegexp(name string, policy *ent.StoragePolicy) error {
|
||||||
|
if policy.Settings.NameRegexp == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
match, err := regexp.MatchString(policy.Settings.NameRegexp, name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid file name regexp: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (policy.Settings.IsNameRegexpDenyList && match) || (!policy.Settings.IsNameRegexpDenyList && !match) {
|
||||||
|
return fmt.Errorf("file name is not allowed by regexp")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateFileSize validates the file size.
|
// validateFileSize validates the file size.
|
||||||
func validateFileSize(size int64, policy *ent.StoragePolicy) error {
|
func validateFileSize(size int64, policy *ent.StoragePolicy) error {
|
||||||
if policy.MaxSize == 0 {
|
if policy.MaxSize == 0 {
|
||||||
|
@ -56,11 +75,15 @@ func validateFileSize(size int64, policy *ent.StoragePolicy) error {
|
||||||
// validateNewFile validates the upload request.
|
// validateNewFile validates the upload request.
|
||||||
func validateNewFile(fileName string, size int64, policy *ent.StoragePolicy) error {
|
func validateNewFile(fileName string, size int64, policy *ent.StoragePolicy) error {
|
||||||
if err := validateFileName(fileName); err != nil {
|
if err := validateFileName(fileName); err != nil {
|
||||||
return err
|
return fs.ErrIllegalObjectName.WithError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateExtension(fileName, policy); err != nil {
|
if err := validateExtension(fileName, policy); err != nil {
|
||||||
return err
|
return fs.ErrIllegalObjectName.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateFileNameRegexp(fileName, policy); err != nil {
|
||||||
|
return fs.ErrIllegalObjectName.WithError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateFileSize(size, policy); err != nil {
|
if err := validateFileSize(size, policy); err != nil {
|
||||||
|
|
|
@ -250,12 +250,15 @@ type DirectLink struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoragePolicy struct {
|
type StoragePolicy struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
AllowedSuffix []string `json:"allowed_suffix,omitempty"`
|
AllowedSuffix []string `json:"allowed_suffix,omitempty"`
|
||||||
Type types.PolicyType `json:"type"`
|
DeniedSuffix []string `json:"denied_suffix,omitempty"`
|
||||||
MaxSize int64 `json:"max_size"`
|
AllowedNameRegexp string `json:"allowed_name_regexp,omitempty"`
|
||||||
Relay bool `json:"relay,omitempty"`
|
DeniedNameRegexp string `json:"denied_name_regexp,omitempty"`
|
||||||
|
Type types.PolicyType `json:"type"`
|
||||||
|
MaxSize int64 `json:"max_size"`
|
||||||
|
Relay bool `json:"relay,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Entity struct {
|
type Entity struct {
|
||||||
|
@ -442,14 +445,30 @@ func BuildStoragePolicy(sp *ent.StoragePolicy, hasher hashid.Encoder) *StoragePo
|
||||||
if sp == nil {
|
if sp == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &StoragePolicy{
|
|
||||||
ID: hashid.EncodePolicyID(hasher, sp.ID),
|
res := &StoragePolicy{
|
||||||
Name: sp.Name,
|
ID: hashid.EncodePolicyID(hasher, sp.ID),
|
||||||
Type: types.PolicyType(sp.Type),
|
Name: sp.Name,
|
||||||
MaxSize: sp.MaxSize,
|
Type: types.PolicyType(sp.Type),
|
||||||
AllowedSuffix: sp.Settings.FileType,
|
MaxSize: sp.MaxSize,
|
||||||
Relay: sp.Settings.Relay,
|
Relay: sp.Settings.Relay,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sp.Settings.IsFileTypeDenyList {
|
||||||
|
res.DeniedSuffix = sp.Settings.FileType
|
||||||
|
} else {
|
||||||
|
res.AllowedSuffix = sp.Settings.FileType
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp.Settings.NameRegexp != "" {
|
||||||
|
if sp.Settings.IsNameRegexpDenyList {
|
||||||
|
res.DeniedNameRegexp = sp.Settings.NameRegexp
|
||||||
|
} else {
|
||||||
|
res.AllowedNameRegexp = sp.Settings.NameRegexp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteEventSourceHeader(c *gin.Context) {
|
func WriteEventSourceHeader(c *gin.Context) {
|
||||||
|
|
Loading…
Reference in New Issue