v1.3: Open websites, folders and programs without a confirm box
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8c09e56982
commit
aecdc732b9
+147
@@ -0,0 +1,147 @@
|
||||
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+)?" +
|
||||
@"(?:""(?<t>[^""'`$%\r\n;&|<>^]+)""|'(?<t>[^""'`$%\r\n;&|<>^]+)'|(?<t>[^\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<string>(); } })
|
||||
.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user