using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Microsoft.Win32; namespace QuickClaude { // Decides which "open ..." shell commands skip the confirm box. Only a command that is a // single open of ONE target, with no arguments and nothing chained on, qualifies: // Start-Process "https://x.com" a website (http/https only) // Start-Process "C:\wallpapers" a folder that exists (no network paths) // Start-Process mspaint a program: an .exe in Program Files, Windows, // Start-Process "C:\...\mirc.exe" AppData\Local\Programs or the Store app aliases, // Start-Process "...\mIRC.lnk" or a shortcut in the Start Menu or on the Desktop // Scripts (.bat, .ps1 and so on) and programs anywhere else still get the confirm box. public static class OpenRules { public enum Verdict { Ask, Allow, Hint } // verb, optional "" title (cmd start), optional -FilePath/-Path, then ONE target. // Quoted targets may contain spaces and ( ) for "Program Files (x86)", but never quotes, // ` $ % or line breaks (PowerShell and cmd expand those). Unquoted targets are stricter. static readonly Regex OpenCmd = new Regex( @"^\s*(?:cmd(?:\.exe)?\s+/c\s+)?(?:Start-Process|saps|start|explorer(?:\.exe)?|Invoke-Item|ii)\s+" + @"(?:""""\s+)?(?:-(?:FilePath|Path|LiteralPath)\s+)?" + @"(?:""(?[^""'`$%\r\n;&|<>^]+)""|'(?[^""'`$%\r\n;&|<>^]+)'|(?[^\s""'`$%;&|<>()^]+))\s*$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); static readonly Regex Url = new Regex(@"^https?://[^\s""'`;&|<>$()^%]+$", RegexOptions.IgnoreCase); static readonly Regex BareName = new Regex(@"^[A-Za-z0-9_.\-]+$"); public static Verdict Check(string command, string cwd, out string hint) { hint = null; if (string.IsNullOrEmpty(command) || command.IndexOfAny(new[] { '\n', '\r' }) >= 0) return Verdict.Ask; var m = OpenCmd.Match(command); if (!m.Success) return Verdict.Ask; var target = m.Groups["t"].Value.Trim(); if (target.Length == 0) return Verdict.Ask; if (Url.IsMatch(target)) return Verdict.Allow; try { if (Path.IsPathFullyQualified(target)) { if (target.StartsWith(@"\\")) return Verdict.Ask; // no network paths var full = Path.GetFullPath(target); if (Directory.Exists(full)) return Verdict.Allow; if (File.Exists(full) && TrustedProgram(full)) return Verdict.Allow; return Verdict.Ask; } if (BareName.IsMatch(target)) { // A bare name could also pick up a file sitting in the work folder; never allow that if (!string.IsNullOrEmpty(cwd) && Directory.Exists(cwd) && (File.Exists(Path.Combine(cwd, target)) || Directory.EnumerateFiles(cwd, target + ".*").Any())) return Verdict.Ask; if (FindProgram(target) != null) return Verdict.Allow; var lnk = FindShortcut(target); if (lnk != null) { hint = "\"" + target + "\" isn't on the PATH, so that command wouldn't find it. " + "Its Start Menu shortcut is " + lnk + ". Run Start-Process \"" + lnk + "\" instead " + "(that one opens without a confirm box)."; return Verdict.Hint; } } } catch { } return Verdict.Ask; } static string Env(string name) => Environment.GetEnvironmentVariable(name) ?? ""; static string Special(Environment.SpecialFolder f) => Environment.GetFolderPath(f); static string[] ProgramDirs => new[] { Env("ProgramFiles"), Env("ProgramFiles(x86)"), Env("ProgramW6432"), Env("WINDIR"), Path.Combine(Special(Environment.SpecialFolder.LocalApplicationData), "Programs"), Path.Combine(Special(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WindowsApps"), }.Where(d => d.Length > 0).ToArray(); static string[] ShortcutDirs => new[] { Path.Combine(Special(Environment.SpecialFolder.CommonApplicationData), "Microsoft", "Windows", "Start Menu"), Path.Combine(Special(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Start Menu"), Special(Environment.SpecialFolder.DesktopDirectory), Special(Environment.SpecialFolder.CommonDesktopDirectory), }.Where(d => d.Length > 0).ToArray(); static bool Under(string path, string dir) { var d = Path.GetFullPath(dir).TrimEnd('\\') + "\\"; return path.StartsWith(d, StringComparison.OrdinalIgnoreCase); } static bool TrustedProgram(string full) { var ext = Path.GetExtension(full).ToLowerInvariant(); if (ext == ".exe") return ProgramDirs.Any(d => Under(full, d)); if (ext == ".lnk") return ShortcutDirs.Any(d => Under(full, d)); return false; } // Where Windows itself would find a bare program name: the Windows folders, the Store app // aliases, and the App Paths registry (how "mspaint" and friends resolve). static string FindProgram(string name) { var exe = name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? name : name + ".exe"; var win = Env("WINDIR"); foreach (var dir in new[] { Path.Combine(win, "System32"), win, Path.Combine(Special(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WindowsApps"), }) { var p = Path.Combine(dir, exe); if (File.Exists(p)) return p; } foreach (var hive in new[] { Registry.CurrentUser, Registry.LocalMachine }) { using var key = hive.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\App Paths\" + exe); var val = (key?.GetValue(null) as string)?.Trim('"'); if (!string.IsNullOrEmpty(val) && File.Exists(val) && TrustedProgram(Path.GetFullPath(val))) return val; } return null; } // A Start Menu or Desktop shortcut named like the program ("mirc" finds "mIRC.lnk"). // Exact name first, then the shortest name that starts with it. static string FindShortcut(string name) { var all = ShortcutDirs.Where(Directory.Exists) .SelectMany(d => { try { return Directory.EnumerateFiles(d, "*.lnk", SearchOption.AllDirectories); } catch { return Enumerable.Empty(); } }) .ToList(); var exact = all.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f), name, StringComparison.OrdinalIgnoreCase)); if (exact != null) return exact; return all.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(name, StringComparison.OrdinalIgnoreCase)) .OrderBy(f => Path.GetFileNameWithoutExtension(f).Length).FirstOrDefault(); } } }