Initial commit
This commit is contained in:
225
server/server_test.go
Normal file
225
server/server_test.go
Normal file
@@ -0,0 +1,225 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user