const path = require('path');
const os = require('os');
const fs = require('fs');
const platformUtils = require('./platformUtils');
/**
* 跨平台路径处理工具
* 支持Windows UNC路径、长路径、符号链接等复杂场景
*/
/**
* 解析用户路径,支持跨平台路径处理
* @param {string} input - 输入路径
* @param {Object} options - 选项
* @param {string} options.workingDir - 工作目录
* @param {boolean} options.allowUNC - 是否允许UNC路径(Windows)
* @param {boolean} options.allowLongPath - 是否允许长路径(Windows)
* @returns {string} 解析后的绝对路径
*/
function resolveUserPath(input, { workingDir, allowUNC = false, allowLongPath = false } = {}) {
if (!input || typeof input !== 'string') {
throw new Error('路径参数无效');
}
const home = platformUtils.getHomeDirectory();
let base;
// 处理绝对路径
if (path.isAbsolute(input)) {
base = input;
} else if (workingDir) {
// 相对于工作目录
base = path.join(workingDir, input);
} else {
// 相对于家目录
base = path.join(home, input);
}
// 使用平台工具规范化路径
const normalizedPath = platformUtils.normalizePath(base);
// Windows特殊路径检查
if (platformUtils.isWindows) {
// UNC路径检查
if (platformUtils.isUNCPath(normalizedPath) && !allowUNC) {
const err = new Error('不允许UNC路径');
err.code = 'E_UNC_PATH_DENIED';
throw err;
}
// 长路径检查
if (platformUtils.isLongPath(normalizedPath) && !allowLongPath) {
const err = new Error('不允许长路径');
err.code = 'E_LONG_PATH_DENIED';
throw err;
}
// Windows路径长度检查
const pathConfig = platformUtils.getPathConfig();
if (normalizedPath.length > pathConfig.maxLength) {
const err = new Error(`路径长度超过限制 (${pathConfig.maxLength})`);
err.code = 'E_PATH_TOO_LONG';
throw err;
}
}
return path.resolve(normalizedPath);
}
/**
* 增强的家目录验证,支持跨平台和特殊路径
* @param {string} absPath - 绝对路径
* @param {Object} options - 选项
* @param {boolean} options.allowSymlinks - 是否允许符号链接
* @param {boolean} options.strictMode - 严格模式
* @returns {string} 验证后的路径
*/
function assertInHome(absPath, { allowSymlinks = false, strictMode = true } = {}) {
if (!absPath || typeof absPath !== 'string') {
const err = new Error('路径参数无效');
err.code = 'E_INVALID_PATH';
throw err;
}
const home = platformUtils.getHomeDirectory();
const homeSeparator = path.resolve(home) + path.sep;
let targetPath = path.resolve(absPath);
// 处理符号链接
if (platformUtils.isUnix && fs.existsSync(targetPath)) {
try {
const stats = fs.lstatSync(targetPath);
if (stats.isSymbolicLink()) {
if (!allowSymlinks) {
const err = new Error('不允许符号链接');
err.code = 'E_SYMLINK_DENIED';
throw err;
}
// 解析符号链接的真实路径
targetPath = fs.realpathSync(targetPath);
}
} catch (error) {
if (error.code !== 'ENOENT' && strictMode) {
throw error;
}
}
}
const targetSeparator = path.resolve(targetPath) + path.sep;
// Windows特殊处理
if (platformUtils.isWindows) {
// Windows路径不区分大小写
const homeNorm = homeSeparator.toLowerCase();
const targetNorm = targetSeparator.toLowerCase();
if (!targetNorm.startsWith(homeNorm)) {
const err = new Error('路径不允许');
err.code = 'E_PATH_DENIED';
throw err;
}
} else {
// Unix系统区分大小写
if (!targetSeparator.startsWith(homeSeparator)) {
const err = new Error('路径不允许');
err.code = 'E_PATH_DENIED';
throw err;
}
}
return targetPath;
}
/**
* 检查路径是否安全
* @param {string} inputPath - 输入路径
* @param {Object} options - 选项
* @returns {Object} 安全检查结果
*/
function validatePathSafety(inputPath, options = {}) {
const result = {
safe: true,
warnings: [],
errors: [],
normalizedPath: null
};
try {
// 解析路径
const resolvedPath = resolveUserPath(inputPath, options);
// 验证家目录
const validatedPath = assertInHome(resolvedPath, options);
result.normalizedPath = validatedPath;
// 平台特定检查
if (platformUtils.isWindows) {
// Windows特殊字符检查
const invalidChars = /[<>:"|?*]/;
if (invalidChars.test(path.basename(inputPath))) {
result.warnings.push('路径包含Windows保留字符');
}
// 保留名称检查
const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
if (reservedNames.test(path.basename(inputPath, path.extname(inputPath)))) {
result.warnings.push('路径包含Windows保留名称');
}
}
// 路径遍历攻击检查
if (inputPath.includes('..')) {
result.warnings.push('路径包含相对路径符号');
}
} catch (error) {
result.safe = false;
result.errors.push(error.message);
}
return result;
}
/**
* 获取路径信息
* @param {string} inputPath - 输入路径
* @returns {Object} 路径信息
*/
function getPathInfo(inputPath) {
const info = {
original: inputPath,
resolved: null,
platform: platformUtils.getPlatformInfo(),
type: null,
features: {}
};
try {
info.resolved = resolveUserPath(inputPath);
if (platformUtils.isWindows) {
info.type = 'windows';
info.features = {
isUNC: platformUtils.isUNCPath(inputPath),
isLong: platformUtils.isLongPath(inputPath),
driveLetter: path.parse(inputPath).root
};
} else {
info.type = 'unix';
info.features = {
isHidden: path.basename(inputPath).startsWith('.'),
isAbsolute: path.isAbsolute(inputPath)
};
}
// 检查文件存在性和类型
if (fs.existsSync(info.resolved)) {
const stats = fs.statSync(info.resolved);
info.exists = true;
info.isFile = stats.isFile();
info.isDirectory = stats.isDirectory();
info.isSymbolicLink = stats.isSymbolicLink();
} else {
info.exists = false;
}
} catch (error) {
info.error = error.message;
}
return info;
}
module.exports = {
resolveUserPath,
assertInHome,
validatePathSafety,
getPathInfo,
// 向后兼容的导出
platformUtils
};