package storage import ( "path" "strings" ) // ChunkDir 实现默认分片目录约定:<父目录>/chunks/。 // local/s3/webdav 三引擎共用,保持分片路径一致。 func ChunkDir(savePath, uploadID string) string { dir := path.Dir(savePath) name := path.Base(savePath) // 防御:savePath 非法时仍返回明确结构,具体引擎再做安全校验 if name == "." || name == "/" { name = "file" } return path.Join(dir, "chunks", uploadID) + "/" + name } // ChunkPartPath 分片对象完整路径(相对存储根)。 func ChunkPartPath(savePath, uploadID string, index int) string { dir := path.Dir(savePath) return path.Join(dir, "chunks", uploadID, itoa(index)+".part") } // SanitizePath 清理相对路径:统一斜杠、去首尾斜杠、拒绝 .. 穿越。 // 返回清理后的相对路径与是否合法。 func SanitizePath(p string) (string, bool) { raw := strings.ReplaceAll(strings.TrimSpace(p), "\\", "/") raw = strings.TrimPrefix(raw, "/") if raw == "" { return "", false } cleaned := path.Clean(raw) if cleaned == ".." || strings.HasPrefix(cleaned, "../") || path.IsAbs(cleaned) { return "", false } // 拒绝任何单独的 .. 段 for _, seg := range strings.Split(cleaned, "/") { if seg == ".." { return "", false } } return cleaned, true } // SanitizeFileName 清理文件名:剥离路径、替换非法字符、限制长度。 // 对齐参考 core/utils.py 的 sanitize_filename。 func SanitizeFileName(name string) string { // 剥离路径 if idx := strings.LastIndexAny(name, "/\\"); idx >= 0 { name = name[idx+1:] } var b strings.Builder for _, r := range name { switch { case r < 0x20 || r == 0x7f: b.WriteByte('_') case strings.ContainsRune(`\*?:"<>|`, r): b.WriteByte('_') case r == ' ': b.WriteByte('_') default: b.WriteRune(r) } } cleaned := b.String() // 压缩连续下划线 for strings.Contains(cleaned, "__") { cleaned = strings.ReplaceAll(cleaned, "__", "_") } cleaned = strings.Trim(cleaned, "._") if cleaned == "" { return "unnamed_file" } if len(cleaned) > 255 { cleaned = cleaned[:255] } return cleaned } // itoa 小整数转字符串。 func itoa(n int) string { if n == 0 { return "0" } neg := n < 0 if neg { n = -n } var buf [21]byte i := len(buf) for n > 0 { i-- buf[i] = byte('0' + n%10) n /= 10 } if neg { i-- buf[i] = '-' } return string(buf[i:]) }