getPathsForPermissionCheck function
Gets all paths that should be checked for permissions. This includes the original path, all intermediate symlink targets in the chain, and the final resolved path.
For example, if test.txt -> /etc/passwd -> /private/etc/passwd:
- test.txt (original path)
- /etc/passwd (intermediate symlink target)
- /private/etc/passwd (final resolved path)
This is important for security: a deny rule for /etc/passwd should block access even if the file is actually at /private/etc/passwd (as on macOS).
Implementation
List<String> getPathsForPermissionCheck(String inputPath) {
// Expand tilde notation defensively
String path = inputPath;
final homeDir = Platform.environment['HOME'] ?? '';
if (path == '~') {
path = homeDir;
} else if (path.startsWith('~/')) {
path = p.join(homeDir, path.substring(2));
}
final pathSet = <String>{};
final fsImpl = getFsImplementation();
// Always check the original path
pathSet.add(path);
// Block UNC paths before any filesystem access to prevent network
// requests (DNS/SMB) during validation on Windows
if (path.startsWith('//') || path.startsWith('\\\\')) {
return pathSet.toList();
}
// Follow the symlink chain, collecting ALL intermediate targets
try {
String currentPath = path;
final visited = <String>{};
const maxDepth = 40; // Prevent runaway loops, matches typical SYMLOOP_MAX
for (int depth = 0; depth < maxDepth; depth++) {
// Prevent infinite loops from circular symlinks
if (visited.contains(currentPath)) {
break;
}
visited.add(currentPath);
if (!fsImpl.existsSync(currentPath)) {
// Path doesn't exist (new file case). existsSync follows symlinks,
// so this is also reached for DANGLING symlinks.
if (currentPath == path) {
final resolved = resolveDeepestExistingAncestorSync(fsImpl, path);
if (resolved != null) {
pathSet.add(resolved);
}
}
break;
}
final stats = fsImpl.lstatSync(currentPath);
// Skip special file types that can cause issues
if (stats.isFIFO() ||
stats.isSocket() ||
stats.isCharacterDevice() ||
stats.isBlockDevice()) {
break;
}
if (!stats.isSymbolicLink) {
break;
}
// Get the immediate symlink target
final target = fsImpl.readlinkSync(currentPath);
// If target is relative, resolve it relative to the symlink's directory
final absoluteTarget = p.isAbsolute(target)
? target
: p.join(p.dirname(currentPath), target);
// Add this intermediate target to the set
pathSet.add(absoluteTarget);
currentPath = absoluteTarget;
}
} catch (_) {
// If anything fails during chain traversal, continue with what we have
}
// Also add the final resolved path using realpathSync for completeness
final safeResult = safeResolvePath(fsImpl, path);
if (safeResult.isSymlink && safeResult.resolvedPath != path) {
pathSet.add(safeResult.resolvedPath);
}
return pathSet.toList();
}