80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
|
|
package local
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
func (c *Client) resolveObjectPath(objectKey string) (string, error) {
|
||
|
|
if strings.TrimSpace(objectKey) == "" || filepath.IsAbs(objectKey) || strings.Contains(objectKey, "\\") {
|
||
|
|
return "", fmt.Errorf("对象键不是安全的相对路径")
|
||
|
|
}
|
||
|
|
for _, segment := range strings.Split(objectKey, "/") {
|
||
|
|
if segment == "" || segment == "." || segment == ".." {
|
||
|
|
return "", fmt.Errorf("对象键包含无效路径段")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
target := filepath.Join(c.objectsPath, filepath.FromSlash(objectKey))
|
||
|
|
relative, err := filepath.Rel(c.objectsPath, target)
|
||
|
|
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
||
|
|
return "", fmt.Errorf("对象路径超出存储目录")
|
||
|
|
}
|
||
|
|
if err := c.ensureNoSymlink(filepath.Dir(target), c.objectsPath); err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
return target, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) ensureDirectory(directory string) error {
|
||
|
|
if err := os.MkdirAll(directory, 0o750); err != nil {
|
||
|
|
return fmt.Errorf("创建本地存储目录 %s: %w", directory, err)
|
||
|
|
}
|
||
|
|
if err := c.ensureNoSymlink(directory, c.rootPath); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return os.Chmod(directory, 0o750)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) ensureNoSymlink(target, boundary string) error {
|
||
|
|
target = filepath.Clean(target)
|
||
|
|
boundary = filepath.Clean(boundary)
|
||
|
|
relative, err := filepath.Rel(boundary, target)
|
||
|
|
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
||
|
|
return fmt.Errorf("路径超出本地存储边界")
|
||
|
|
}
|
||
|
|
current := boundary
|
||
|
|
if relative == "." {
|
||
|
|
return rejectSymlink(current)
|
||
|
|
}
|
||
|
|
if err := rejectSymlink(current); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
for _, segment := range strings.Split(relative, string(filepath.Separator)) {
|
||
|
|
current = filepath.Join(current, segment)
|
||
|
|
info, statErr := os.Lstat(current)
|
||
|
|
if os.IsNotExist(statErr) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if statErr != nil {
|
||
|
|
return fmt.Errorf("检查路径 %s: %w", current, statErr)
|
||
|
|
}
|
||
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
||
|
|
return fmt.Errorf("本地存储路径不允许符号链接: %s", current)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func rejectSymlink(path string) error {
|
||
|
|
info, err := os.Lstat(path)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
||
|
|
return fmt.Errorf("本地存储路径不允许符号链接: %s", path)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|