- Expand batch upload list area and optimize preview image layout - Improve upload speed tracking and display (server-side & client-side) - Reduce network detection log noise - Optimize Docker build process (caching, env validation) - Add shadow environment deployment support
147 lines
4.8 KiB
TypeScript
147 lines
4.8 KiB
TypeScript
/**
|
|
* R29 测试:路径安全工具
|
|
*
|
|
* 验证:
|
|
* 1. 路径遍历攻击检测
|
|
* 2. 路径边界检查
|
|
* 3. 文件名清理
|
|
*/
|
|
|
|
import path from 'path'
|
|
import {
|
|
isPathSafe,
|
|
resolveSafePath,
|
|
isPathInAllowedDirs,
|
|
sanitizeFileName,
|
|
getAllowedFileDirectories
|
|
} from '../path-security'
|
|
|
|
describe('R29: 路径安全工具', () => {
|
|
describe('isPathSafe - 路径遍历检测', () => {
|
|
it('应该拒绝包含 .. 的路径', () => {
|
|
expect(isPathSafe('../etc/passwd')).toBe(false)
|
|
expect(isPathSafe('../../etc/passwd')).toBe(false)
|
|
expect(isPathSafe('uploads/../../../etc/passwd')).toBe(false)
|
|
})
|
|
|
|
it('应该拒绝 URL 编码的遍历路径', () => {
|
|
expect(isPathSafe('%2e%2e/etc/passwd')).toBe(false)
|
|
expect(isPathSafe('..%2fetc%2fpasswd')).toBe(false)
|
|
})
|
|
|
|
it('应该拒绝 Windows 风格的遍历路径', () => {
|
|
expect(isPathSafe('..\\Windows\\System32')).toBe(false)
|
|
})
|
|
|
|
it('应该拒绝 null 字节注入', () => {
|
|
expect(isPathSafe('file.txt\x00.exe')).toBe(false)
|
|
})
|
|
|
|
it('应该接受正常路径', () => {
|
|
expect(isPathSafe('uploads/file.zip')).toBe(true)
|
|
expect(isPathSafe('uploads/2026/01/file.zip')).toBe(true)
|
|
expect(isPathSafe('/absolute/path/file.zip')).toBe(true)
|
|
})
|
|
|
|
it('应该拒绝空路径', () => {
|
|
expect(isPathSafe('')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('resolveSafePath - 安全路径解析', () => {
|
|
const basePath = '/var/www/uploads'
|
|
|
|
it('应该解析在基目录内的路径', () => {
|
|
const result = resolveSafePath(basePath, 'file.zip')
|
|
expect(result).toBe(path.normalize('/var/www/uploads/file.zip'))
|
|
})
|
|
|
|
it('应该解析子目录路径', () => {
|
|
const result = resolveSafePath(basePath, '2026/01/file.zip')
|
|
expect(result).toBe(path.normalize('/var/www/uploads/2026/01/file.zip'))
|
|
})
|
|
|
|
it('应该拒绝越界路径', () => {
|
|
const result = resolveSafePath(basePath, '../etc/passwd')
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it('应该拒绝空参数', () => {
|
|
expect(resolveSafePath('', 'file.zip')).toBeNull()
|
|
expect(resolveSafePath(basePath, '')).toBeNull()
|
|
})
|
|
|
|
it('应该拒绝相对基目录', () => {
|
|
const result = resolveSafePath('uploads', 'file.zip')
|
|
expect(result).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('isPathInAllowedDirs - 目录边界检查', () => {
|
|
const allowedDirs = ['/var/www/uploads', '/var/www/temp']
|
|
|
|
it('应该接受允许目录内的路径', () => {
|
|
expect(isPathInAllowedDirs('/var/www/uploads/file.zip', allowedDirs)).toBe(true)
|
|
expect(isPathInAllowedDirs('/var/www/temp/file.zip', allowedDirs)).toBe(true)
|
|
})
|
|
|
|
it('应该接受子目录路径', () => {
|
|
expect(isPathInAllowedDirs('/var/www/uploads/2026/01/file.zip', allowedDirs)).toBe(true)
|
|
})
|
|
|
|
it('应该拒绝不在允许目录的路径', () => {
|
|
expect(isPathInAllowedDirs('/etc/passwd', allowedDirs)).toBe(false)
|
|
expect(isPathInAllowedDirs('/var/www/other/file.zip', allowedDirs)).toBe(false)
|
|
})
|
|
|
|
it('应该拒绝前缀匹配但不是子目录的路径', () => {
|
|
// /var/www/uploads-backup 不应该匹配 /var/www/uploads
|
|
expect(isPathInAllowedDirs('/var/www/uploads-backup/file.zip', allowedDirs)).toBe(false)
|
|
})
|
|
|
|
it('应该拒绝空参数', () => {
|
|
expect(isPathInAllowedDirs('', allowedDirs)).toBe(false)
|
|
expect(isPathInAllowedDirs('/var/www/uploads/file.zip', [])).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('sanitizeFileName - 文件名清理', () => {
|
|
it('应该移除路径分隔符', () => {
|
|
expect(sanitizeFileName('path/to/file.txt')).toBe('path_to_file.txt')
|
|
expect(sanitizeFileName('path\\to\\file.txt')).toBe('path_to_file.txt')
|
|
})
|
|
|
|
it('应该移除路径遍历字符', () => {
|
|
expect(sanitizeFileName('../file.txt')).toBe('__file.txt')
|
|
expect(sanitizeFileName('../../file.txt')).toBe('____file.txt')
|
|
})
|
|
|
|
it('应该移除 Windows 不允许的字符', () => {
|
|
expect(sanitizeFileName('file<>:"|?*.txt')).toBe('file_______.txt')
|
|
})
|
|
|
|
it('应该处理空文件名', () => {
|
|
expect(sanitizeFileName('')).toBe('unnamed')
|
|
})
|
|
|
|
it('应该保留正常文件名', () => {
|
|
expect(sanitizeFileName('normal_file-name.txt')).toBe('normal_file-name.txt')
|
|
expect(sanitizeFileName('中文文件名.zip')).toBe('中文文件名.zip')
|
|
})
|
|
})
|
|
|
|
describe('getAllowedFileDirectories - 获取允许的目录', () => {
|
|
it('应该返回目录列表', () => {
|
|
const dirs = getAllowedFileDirectories()
|
|
expect(Array.isArray(dirs)).toBe(true)
|
|
expect(dirs.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('应该包含 uploads 目录', () => {
|
|
const dirs = getAllowedFileDirectories()
|
|
const hasUploads = dirs.some(dir => dir.includes('uploads'))
|
|
expect(hasUploads).toBe(true)
|
|
})
|
|
})
|
|
})
|