isPathSafe($path, $base)) { Log::warning('Path traversal blocked', ['path' => $path, 'base' => $base]); return false; } if (!file_exists($path)) return true; if (!is_dir($path)) return File::delete($path); return File::deleteDirectory($path); } public function safeMkdir(string $path): bool { if (File::isDirectory($path)) return true; return File::makeDirectory($path, 0755, true, true); } public function replaceDirectory(string $source, string $target, string $base): bool { if (!$this->isPathSafe($source, $base) || !$this->isPathSafe($target, $base)) { return false; } if (!File::exists($source)) return false; if (File::exists($target)) return false; $parent = dirname($target); if (!is_writable($parent)) return false; if (!File::makeDirectory($target, 0755, true, true)) return false; foreach (File::allFiles($source) as $file) { $relative = ltrim(str_replace($source, '', $file->getPathname()), '/'); $dest = $target . '/' . $relative; $destDir = dirname($dest); if (!File::isDirectory($destDir)) { File::makeDirectory($destDir, 0755, true, true); } if (!copy($file->getPathname(), $dest)) return false; } return File::deleteDirectory($source); } public function validateFileTypes(string $directory): array { $blocked = [ 'php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phps', 'asp', 'aspx', 'jsp', 'jspx', 'cfm', 'pl', 'py', 'rb', 'cgi', 'sh', 'bash', 'bat', 'cmd', 'exe', 'ps1', 'htaccess', 'htpasswd', ]; $found = []; foreach (File::allFiles($directory) as $file) { $ext = strtolower($file->getExtension()); if (in_array($ext, $blocked, true)) { $found[] = str_replace(base_path() . '/', '', $file->getPathname()); } } return $found; } }