mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 09:06:36 +08:00
fix(objectstore): decouple WebDAV targetPath from logical storage key and fix basePath duplication
- WebDAV Put now returns PutResult with driver-agnostic logical relative key (relKey), keeping database records decoupled from mount basePath. - targetPath mounts logical keys to the remote WebDAV server path, and transparently handles legacy database records containing basePath or duplicate basePath prefixes. - Make localBackend path resolution resilient to keys with leading slashes or legacy absolute paths outside local root by safely mounting them as relative paths. - Add comprehensive unit tests for WebDAV targetPath, relKey, end-to-end roundtrip with in-memory WebDAV server, and local storage leading slash handling.
This commit is contained in:
@@ -32,11 +32,15 @@ type webDAVBackend struct {
|
||||
}
|
||||
|
||||
func newWebDAVBackend(cfg WebDAVConfig) (*webDAVBackend, error) {
|
||||
basePath := strings.Trim(path.Clean("/"+cfg.BasePath), "/")
|
||||
if basePath == "." {
|
||||
basePath = ""
|
||||
}
|
||||
return &webDAVBackend{
|
||||
endpoint: strings.TrimRight(cfg.Endpoint, "/"),
|
||||
username: cfg.Username,
|
||||
password: cfg.Password,
|
||||
basePath: strings.Trim(cfg.BasePath, "/"),
|
||||
basePath: basePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -50,27 +54,27 @@ func (b *webDAVBackend) newClient(ctx context.Context) *gowebdav.Client {
|
||||
}
|
||||
|
||||
func (b *webDAVBackend) Put(ctx context.Context, key string, body io.Reader, size int64, _ string) (PutResult, error) {
|
||||
key = b.key(key)
|
||||
target := b.targetPath(key)
|
||||
client := b.newClient(ctx)
|
||||
if dir := path.Dir(key); dir != "." && dir != "/" {
|
||||
if dir := path.Dir(target); dir != "." && dir != "/" {
|
||||
if err := client.MkdirAll(dir, storageDirPerm); err != nil {
|
||||
return PutResult{}, fmt.Errorf("create WebDAV directory: %w", err)
|
||||
}
|
||||
}
|
||||
if err := client.WriteStreamWithLength(key, body, size, storageFilePerm); err != nil {
|
||||
if err := client.WriteStreamWithLength(target, body, size, storageFilePerm); err != nil {
|
||||
return PutResult{}, fmt.Errorf("put WebDAV object: %w", err)
|
||||
}
|
||||
return PutResult{Key: key}, nil
|
||||
return PutResult{Key: b.relKey(key)}, nil
|
||||
}
|
||||
|
||||
func (b *webDAVBackend) Get(ctx context.Context, key string) (*Object, error) {
|
||||
key = b.key(key)
|
||||
target := b.targetPath(key)
|
||||
client := b.newClient(ctx)
|
||||
info, err := client.Stat(key)
|
||||
info, err := client.Stat(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat WebDAV object: %w", err)
|
||||
}
|
||||
body, err := client.ReadStream(key)
|
||||
body, err := client.ReadStream(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get WebDAV object: %w", err)
|
||||
}
|
||||
@@ -83,7 +87,7 @@ func (b *webDAVBackend) Get(ctx context.Context, key string) (*Object, error) {
|
||||
|
||||
func (b *webDAVBackend) Delete(ctx context.Context, key string) error {
|
||||
client := b.newClient(ctx)
|
||||
if err := client.Remove(b.key(key)); err != nil {
|
||||
if err := client.Remove(b.targetPath(key)); err != nil {
|
||||
return fmt.Errorf("delete WebDAV object: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -97,6 +101,34 @@ func (b *webDAVBackend) Test(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *webDAVBackend) key(key string) string {
|
||||
return "/" + path.Join(b.basePath, strings.TrimLeft(key, "/"))
|
||||
// relKey extracts the clean, normalized, relative logical key (e.g. "uploads/2026/09/17/xxx.jpg")
|
||||
// to be persisted in the database, stripping any driver-specific basePath and leading slashes.
|
||||
func (b *webDAVBackend) relKey(key string) string {
|
||||
cleanKey := strings.Trim(path.Clean("/"+strings.ReplaceAll(key, "\\", "/")), "/")
|
||||
if cleanKey == "." {
|
||||
return ""
|
||||
}
|
||||
if b.basePath != "" {
|
||||
for cleanKey == b.basePath || strings.HasPrefix(cleanKey, b.basePath+"/") {
|
||||
cleanKey = strings.TrimPrefix(cleanKey, b.basePath)
|
||||
cleanKey = strings.TrimPrefix(cleanKey, "/")
|
||||
}
|
||||
}
|
||||
return cleanKey
|
||||
}
|
||||
|
||||
// targetPath resolves any key (relative, legacy with basePath, or corrupted with duplicate basePath)
|
||||
// into the absolute path used to access the object on the WebDAV server.
|
||||
func (b *webDAVBackend) targetPath(key string) string {
|
||||
rel := b.relKey(key)
|
||||
if b.basePath == "" {
|
||||
if rel == "" {
|
||||
return "/"
|
||||
}
|
||||
return "/" + rel
|
||||
}
|
||||
if rel == "" {
|
||||
return "/" + b.basePath
|
||||
}
|
||||
return "/" + b.basePath + "/" + rel
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user