Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/scripts/linux/build-avalonia-packages.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
PROJECT_PATH="$REPO_ROOT/src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj"
OUTPUT_ROOT="${1:-$REPO_ROOT/artifacts/avalonia-linux}"
VERSION="${2:-1.1.0}"
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.313",
"version": "10.0.103",
"rollForward": "minor"
}
}
2 changes: 1 addition & 1 deletion src/GregModmanager.Avalonia/Views/SettingsPage.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ private static void OnRestartApp(object? sender, RoutedEventArgs e)
{
var exe = Environment.ProcessPath;
if (string.IsNullOrEmpty(exe)) return;
SafeProcess.LaunchApp(exe);
SafeProcess.LaunchApp(exe, null);
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.Shutdown();
}
Expand Down
255 changes: 137 additions & 118 deletions src/GregModmanager.Core/Services/SafeProcess.cs
Original file line number Diff line number Diff line change
@@ -1,118 +1,137 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace GregModmanager.Services;

public static class SafeProcess
{
/// <summary>
/// Opens a URL in the default browser safely, ensuring only http and https schemes are allowed.
/// </summary>
public static Task OpenUrlAsync(string url)
{
if (string.IsNullOrWhiteSpace(url)) return Task.CompletedTask;

try
{
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
{
Process.Start(new ProcessStartInfo
{
FileName = uri.ToString(),
UseShellExecute = true
});
}
else
{
AppFileLog.Warn($"Blocked attempt to open insecure or invalid URL: {url}");
}
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to open URL: {url}", ex);
}

return Task.CompletedTask;
}

/// <summary>
/// Opens a folder in the system's file explorer.
/// </summary>
public static void OpenFolder(string path)
{
if (string.IsNullOrWhiteSpace(path)) return;

try
{
if (OperatingSystem.IsWindows())
{
Process.Start(new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"\"{path}\"",
UseShellExecute = false
});
}
else
{
// For other OS, we might still need UseShellExecute for some scenarios,
// but we should be careful. MAUI doesn't have a direct "OpenFolder" that works everywhere.
Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = true
});
}
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to open folder: {path}", ex);
}
}

/// <summary>
/// Specifically for Windows, opens explorer and selects a file.
/// </summary>
public static void OpenExplorerAndSelect(string filePath)
{
if (!OperatingSystem.IsWindows() || string.IsNullOrWhiteSpace(filePath)) return;

try
{
Process.Start(new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{filePath}\"",
UseShellExecute = false
});
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to open explorer and select: {filePath}", ex);
}
}

/// <summary>
/// Launches an executable with UseShellExecute = false.
/// </summary>
public static void LaunchApp(string exePath, string arguments = "")
{
if (string.IsNullOrWhiteSpace(exePath)) return;

try
{
Process.Start(new ProcessStartInfo
{
FileName = exePath,
Arguments = arguments,
UseShellExecute = false
});
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to launch app: {exePath}", ex);
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace GregModmanager.Services;

public static class SafeProcess
{
/// <summary>
/// Opens a URL in the default browser safely, ensuring only http and https schemes are allowed.
/// </summary>
public static Task OpenUrlAsync(string url)
{
if (string.IsNullOrWhiteSpace(url)) return Task.CompletedTask;

try
{
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
{
Process.Start(new ProcessStartInfo
{
FileName = uri.ToString(),
UseShellExecute = true
});
}
else
{
AppFileLog.Warn($"Blocked attempt to open insecure or invalid URL: {url}");
}
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to open URL: {url}", ex);
}

return Task.CompletedTask;
}

/// <summary>
/// Opens a folder in the system's file explorer.
/// </summary>
public static void OpenFolder(string path)
{
if (string.IsNullOrWhiteSpace(path)) return;

try
{
if (OperatingSystem.IsWindows())
{
var startInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
UseShellExecute = false
};
startInfo.ArgumentList.Add(path);
Process.Start(startInfo);
}
else
{
// For other OS, we might still need UseShellExecute for some scenarios,
// but we should be careful. MAUI doesn't have a direct "OpenFolder" that works everywhere.
Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = true
});
}
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to open folder: {path}", ex);
}
}

/// <summary>
/// Specifically for Windows, opens explorer and selects a file.
/// </summary>
public static void OpenExplorerAndSelect(string filePath)
{
if (!OperatingSystem.IsWindows() || string.IsNullOrWhiteSpace(filePath)) return;

try
{
var startInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
UseShellExecute = false
};
startInfo.ArgumentList.Add("/select," + filePath);
Process.Start(startInfo);
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to open explorer and select: {filePath}", ex);
}
}

/// <summary>
/// Launches an executable with UseShellExecute = false.
/// </summary>
public static void LaunchApp(string exePath, IEnumerable<string>? arguments = null)
{
if (string.IsNullOrWhiteSpace(exePath)) return;

if (!Path.IsPathRooted(exePath) || !File.Exists(exePath))
{
AppFileLog.Error($"Blocked attempt to launch unverified or non-absolute executable: {exePath}");
return;
}

try
{
var startInfo = new ProcessStartInfo
{
FileName = exePath,
UseShellExecute = false
};

if (arguments != null)
{
foreach (var arg in arguments)
{
startInfo.ArgumentList.Add(arg);
}
}

Process.Start(startInfo);
}
catch (Exception ex)
{
AppFileLog.Error($"Failed to launch app: {exePath}", ex);
}
}
}
Loading