226 lines
5.2 KiB
Go
226 lines
5.2 KiB
Go
package server
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCalculateLocalPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
basePath string
|
|
want string
|
|
expectError bool
|
|
}{
|
|
// Basic path handling
|
|
{
|
|
name: "Simple valid path",
|
|
input: "folder/file.txt",
|
|
basePath: "/base",
|
|
want: "/base/folder/file.txt",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Empty path",
|
|
input: "",
|
|
basePath: "/base",
|
|
want: "/base",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Current directory",
|
|
input: ".",
|
|
basePath: "/base",
|
|
want: "/base",
|
|
expectError: false,
|
|
},
|
|
|
|
// Leading/trailing slash handling
|
|
{
|
|
name: "Path with leading slash",
|
|
input: "/folder/file.txt",
|
|
basePath: "/base",
|
|
want: "/base/folder/file.txt",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Path with trailing slash",
|
|
input: "folder/",
|
|
basePath: "/base",
|
|
want: "/base/folder",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Path with both leading and trailing slashes",
|
|
input: "/folder/",
|
|
basePath: "/base",
|
|
want: "/base/folder",
|
|
expectError: false,
|
|
},
|
|
|
|
// Path traversal attempts
|
|
{
|
|
name: "Simple path traversal attempt",
|
|
input: "../file.txt",
|
|
basePath: "/base",
|
|
want: "",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "Complex path traversal attempt",
|
|
input: "folder/../../../etc/passwd",
|
|
basePath: "/base",
|
|
want: "",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "Encoded path traversal attempt",
|
|
input: "folder/..%2F..%2F..%2Fetc%2Fpasswd",
|
|
basePath: "/base",
|
|
want: "/base/folder/..%2F..%2F..%2Fetc%2Fpasswd",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Double dot hidden in path",
|
|
input: "folder/.../.../etc/passwd",
|
|
basePath: "/base",
|
|
want: "/base/folder/.../.../etc/passwd",
|
|
expectError: false,
|
|
},
|
|
|
|
// Edge cases
|
|
{
|
|
name: "Multiple sequential slashes",
|
|
input: "folder///subfolder////file.txt",
|
|
basePath: "/base",
|
|
want: "",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "Unicode characters in path",
|
|
input: "фольдер/файл.txt",
|
|
basePath: "/base",
|
|
want: "/base/фольдер/файл.txt",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Path with spaces and special characters",
|
|
input: "my folder/my file!@#$%.txt",
|
|
basePath: "/base",
|
|
want: "/base/my folder/my file!@#$%.txt",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Very long path",
|
|
input: "a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/file.txt",
|
|
basePath: "/base",
|
|
want: "/base/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/file.txt",
|
|
expectError: false,
|
|
},
|
|
|
|
// Base path variations
|
|
{
|
|
name: "Empty base path",
|
|
input: "file.txt",
|
|
basePath: "",
|
|
want: "file.txt",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Relative base path",
|
|
input: "file.txt",
|
|
basePath: "base/folder",
|
|
want: "base/folder/file.txt",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Base path with trailing slash",
|
|
input: "file.txt",
|
|
basePath: "/base/",
|
|
want: "/base/file.txt",
|
|
expectError: false,
|
|
},
|
|
|
|
// Symbolic link-like paths (if supported)
|
|
{
|
|
name: "Path with symbolic link-like components",
|
|
input: "folder/symlink/../file.txt",
|
|
basePath: "/base",
|
|
want: "",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := calculateLocalPath(tt.input, tt.basePath)
|
|
|
|
// Check error expectation
|
|
if (err != nil) != tt.expectError {
|
|
t.Errorf("calculateLocalPath() error = %v, expectError = %v", err, tt.expectError)
|
|
return
|
|
}
|
|
|
|
// If we expect an error, don't check the returned path
|
|
if tt.expectError {
|
|
return
|
|
}
|
|
|
|
// Check if the returned path matches expected
|
|
if got != tt.want {
|
|
t.Errorf("calculateLocalPath() = %v, want %v", got, tt.want)
|
|
}
|
|
|
|
// Additional security checks for non-error cases
|
|
if !tt.expectError {
|
|
// Verify the returned path is within base path
|
|
if !isWithinBasePath(got, tt.basePath) {
|
|
t.Errorf("Result path %v escapes base path %v", got, tt.basePath)
|
|
}
|
|
|
|
// Verify no '..' components in final path
|
|
if containsParentRef(got) {
|
|
t.Errorf("Result path %v contains parent references", got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Helper function to check if a path is contained within the base path
|
|
func isWithinBasePath(path, basePath string) bool {
|
|
if basePath == "" {
|
|
return true
|
|
}
|
|
|
|
absBase, err := filepath.Abs(basePath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
rel, err := filepath.Rel(absBase, absPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return !strings.HasPrefix(rel, "..")
|
|
}
|
|
|
|
// Helper function to check if a path contains parent directory references
|
|
func containsParentRef(path string) bool {
|
|
parts := strings.Split(filepath.Clean(path), string(filepath.Separator))
|
|
for _, part := range parts {
|
|
if part == ".." {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|