Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/linux-port/threat-model-launching.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Attacker: controls a file's name, content, mode bits and symlinks (downloaded ar
| 9 | Defense in depth inside the launcher: `OpenAsync` refuses executable MIME types (x-executable, x-pie-executable, x-sharedlib, appimage, x-desktop); no xdg-open fallback for files with an execute bit (also via symlink); `LaunchUriAsync` refuses `file:` URIs | `LinuxLauncherService` | `Open_ExecutableMimeTypes...`, `Open_ExecBit...`, `LaunchUri_RefusesFileUris` |
| 10 | More than 5 files opened at once asks first; files needing a gate are processed one by one, never in the bulk default-app launch | `OpenFilesLinuxAsync` | (UI path, covered by 1-9) |
| 11 | Dry-run seam (`FILES_LAUNCH_DRYRUN`) so automated runs spawn nothing | `DryRunProcessStarter` | n/a |
| 12 | Drop items onto an executable: same plan as gates 1-2 (only confirmable binaries/scripts); the dialog shows the full argv (target plus every dropped path, `DisplaySanitizer.FullArguments`, refused if too large) and exactly that argv is run after the identity re-check | `NavigationHelpers.RunWithItemsLinuxAsync` | `OnlyConfirmedActionsMayRunAFile`, `DisplaySanitizerTests` |

## Review of default-open paths that could execute

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Files.Shared.Helpers;

namespace Files.App.Actions
{
internal sealed partial class InstallCertificateAction : ObservableObject, IAction
{
public async Task ExecuteAsync(object? parameter = null)
{
await ContextMenu.InvokeVerb("add", context.SelectedItems.Select(x => x.ItemPath).ToArray());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ public InstallCertificateAction()
context.PropertyChanged += Context_PropertyChanged;
}

public async Task ExecuteAsync(object? parameter = null)
{
await ContextMenu.InvokeVerb("add", context.SelectedItems.Select(x => x.ItemPath).ToArray());
}
#if !WINDOWS
// LINUX-TODO(install): this Windows command is hidden on Linux.
public Task ExecuteAsync(object? parameter = null)
=> Task.CompletedTask;
#endif

private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Expand Down
9 changes: 5 additions & 4 deletions src/Files.App/Actions/Content/Install/InstallFontAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ public async Task ExecuteAsync(object? parameter = null)
var paths = context.SelectedItems.Select(item => item.ItemPath!).ToArray();
var outcome = ReturnResult.Success;
var installed = (long)context.SelectedItems.Count;
if (OperatingSystem.IsWindows())
await Win32Helper.InstallFontsAsync(paths, false);
else
(outcome, installed) = await InstallForCurrentUserAsync(paths);
#if WINDOWS
await Win32Helper.InstallFontsAsync(paths, false);
#else
(outcome, installed) = await InstallForCurrentUserAsync(paths);
#endif

StatusCenterViewModel.RemoveItem(banner);
var currentWorkingDirectory = context.ShellPage.GetRequiredShellViewModel().WorkingDirectory!;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Files.Shared.Helpers;

namespace Files.App.Actions
{
internal sealed partial class InstallInfDriverAction : ObservableObject, IAction
{
public async Task ExecuteAsync(object? parameter = null)
{
await Task.WhenAll(context.SelectedItems.Select(selectedItem => Win32Helper.InstallInf(selectedItem.ItemPath)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ public InstallInfDriverAction()
context.PropertyChanged += Context_PropertyChanged;
}

public async Task ExecuteAsync(object? parameter = null)
{
await Task.WhenAll(context.SelectedItems.Select(selectedItem => Win32Helper.InstallInf(selectedItem.ItemPath)));
}
#if !WINDOWS
// LINUX-TODO(install): this Windows command is hidden on Linux.
public Task ExecuteAsync(object? parameter = null)
=> Task.CompletedTask;
#endif

public void Context_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Expand Down
15 changes: 15 additions & 0 deletions src/Files.App/Actions/Content/Run/BaseRunAsAction.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Files Community
// Licensed under the MIT License.



namespace Files.App.Actions
{
internal abstract partial class BaseRunAsAction : ObservableObject, IAction
{
public async Task ExecuteAsync(object? parameter = null)
{
await ContextMenu.InvokeVerb(_verb, _context.SelectedItem!.ItemPath);
}
}
}
11 changes: 6 additions & 5 deletions src/Files.App/Actions/Content/Run/BaseRunAsAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Files.App.Actions
{
internal abstract class BaseRunAsAction : ObservableObject, IAction
internal abstract partial class BaseRunAsAction : ObservableObject, IAction
{
private readonly IContentPageContext _context;

Expand All @@ -28,10 +28,11 @@ public BaseRunAsAction(string verb)
_context.PropertyChanged += Context_PropertyChanged;
}

public async Task ExecuteAsync(object? parameter = null)
{
await ContextMenu.InvokeVerb(_verb, _context.SelectedItem!.ItemPath);
}
#if !WINDOWS
// LINUX-TODO(launching): Windows shell elevation verbs are hidden on Linux.
public Task ExecuteAsync(object? parameter = null)
=> Task.CompletedTask;
#endif

public void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Files.Shared.Helpers;

namespace Files.App.Actions
{
internal sealed partial class RunWithPowershellAction : ObservableObject, IAction
{
public Task ExecuteAsync(object? parameter = null)
{
var itemPath = context.ShellPage?.SlimContentPage?.SelectedItem?.ItemPath;
return Win32Helper.RunPowershellCommandAsync(
$"& {Win32Helper.ToPowerShellStringLiteral(itemPath)}",
PowerShellExecutionOptions.None,
context.Folder?.ItemPath
);
}
}
}
12 changes: 4 additions & 8 deletions src/Files.App/Actions/Content/Run/RunWithPowershellAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,11 @@ public RunWithPowershellAction()
context.PropertyChanged += Context_PropertyChanged;
}

#if !WINDOWS
// LINUX-TODO(launching): this Windows command is hidden on Linux.
public Task ExecuteAsync(object? parameter = null)
{
var itemPath = context.ShellPage?.SlimContentPage?.SelectedItem?.ItemPath;
return Win32Helper.RunPowershellCommandAsync(
$"& {Win32Helper.ToPowerShellStringLiteral(itemPath)}",
PowerShellExecutionOptions.None,
context.Folder?.ItemPath
);
}
=> Task.CompletedTask;
#endif

private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Files.App/Actions/Content/Share/ShareItemAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public string AccessKey
=> "H";

public bool IsExecutable =>
OperatingSystem.IsWindows() &&
IsContextPageTypeAdaptedToCommand() &&
ShareItemHelpers.IsSupported() &&
context.SelectedItems.Any() &&
Expand All @@ -43,6 +44,9 @@ public ShareItemAction()

public Task ExecuteAsync(object? parameter = null)
{
if (!IsExecutable)
return Task.CompletedTask;

return ShareItemHelpers.ShareItemsAsync(context.SelectedItems);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Xaml.Controls;
using Windows.Foundation.Metadata;

namespace Files.App.Actions
{
internal sealed partial class CreateAlternateDataStreamAction : BaseUIAction, IAction
{
public async Task ExecuteAsync(object? parameter = null)
{
var nameDialog = DynamicDialogFactory.GetFor_CreateAlternateDataStreamDialog();
await nameDialog.TryShowAsync();

if (nameDialog.DynamicResult != DynamicDialogResult.Primary)
return;

var userInput = nameDialog.ViewModel.AdditionalData as string;
await Task.WhenAll(context.SelectedItems.Select(async selectedItem =>
{
var itemPath = selectedItem.ItemPath!;
var isDateOk = Win32Helper.GetFileDateModified(itemPath, out var dateModified);
var isReadOnly = Win32Helper.HasFileAttribute(itemPath, System.IO.FileAttributes.ReadOnly);

// Unset read-only attribute (#7534)
if (isReadOnly)
Win32Helper.UnsetFileAttribute(itemPath, System.IO.FileAttributes.ReadOnly);

if (!Win32Helper.WriteStringToFile($"{itemPath}:{userInput}", ""))
{
var dialog = new ContentDialog
{
Title = Strings.ErrorCreatingDataStreamTitle.GetLocalizedResource(),
Content = Strings.ErrorCreatingDataStreamDescription.GetLocalizedResource(),
PrimaryButtonText = "Ok".GetLocalizedResource()
};

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
dialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;

await dialog.TryShowAsync();
}

// Restore read-only attribute (#7534)
if (isReadOnly)
Win32Helper.SetFileAttribute(itemPath, System.IO.FileAttributes.ReadOnly);

// Restore date modified
if (isDateOk)
Win32Helper.SetFileDateModified(itemPath, dateModified);
}));

if (context.ShellPage is null)
return;

if (FoldersSettingsService.AreAlternateStreamsVisible)
await context.ShellPage.Refresh_Click();
else if (ApplicationSettingsService.ShowDataStreamsAreHiddenPrompt)
{
var dialog = new ContentDialog
{
Title = Strings.DataStreamsAreHiddenTitle.GetLocalizedResource(),
Content = Strings.DataStreamsAreHiddenDescription.GetLocalizedResource(),
PrimaryButtonText = Strings.Yes.GetLocalizedResource(),
SecondaryButtonText = Strings.DontShowAgain.GetLocalizedResource()
};

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
dialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;

var result = await dialog.TryShowAsync();
if (result == ContentDialogResult.Primary)
{
FoldersSettingsService.AreAlternateStreamsVisible = true;
await context.ShellPage.Refresh_Click();
}
else
ApplicationSettingsService.ShowDataStreamsAreHiddenPrompt = false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,77 +39,11 @@ public CreateAlternateDataStreamAction()
context.PropertyChanged += Context_PropertyChanged;
}

public async Task ExecuteAsync(object? parameter = null)
{
var nameDialog = DynamicDialogFactory.GetFor_CreateAlternateDataStreamDialog();
await nameDialog.TryShowAsync();

if (nameDialog.DynamicResult != DynamicDialogResult.Primary)
return;

var userInput = nameDialog.ViewModel.AdditionalData as string;
await Task.WhenAll(context.SelectedItems.Select(async selectedItem =>
{
var itemPath = selectedItem.ItemPath!;
var isDateOk = Win32Helper.GetFileDateModified(itemPath, out var dateModified);
var isReadOnly = Win32Helper.HasFileAttribute(itemPath, System.IO.FileAttributes.ReadOnly);

// Unset read-only attribute (#7534)
if (isReadOnly)
Win32Helper.UnsetFileAttribute(itemPath, System.IO.FileAttributes.ReadOnly);

if (!Win32Helper.WriteStringToFile($"{itemPath}:{userInput}", ""))
{
var dialog = new ContentDialog
{
Title = Strings.ErrorCreatingDataStreamTitle.GetLocalizedResource(),
Content = Strings.ErrorCreatingDataStreamDescription.GetLocalizedResource(),
PrimaryButtonText = "Ok".GetLocalizedResource()
};

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
dialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;

await dialog.TryShowAsync();
}

// Restore read-only attribute (#7534)
if (isReadOnly)
Win32Helper.SetFileAttribute(itemPath, System.IO.FileAttributes.ReadOnly);

// Restore date modified
if (isDateOk)
Win32Helper.SetFileDateModified(itemPath, dateModified);
}));

if (context.ShellPage is null)
return;

if (FoldersSettingsService.AreAlternateStreamsVisible)
await context.ShellPage.Refresh_Click();
else if (ApplicationSettingsService.ShowDataStreamsAreHiddenPrompt)
{
var dialog = new ContentDialog
{
Title = Strings.DataStreamsAreHiddenTitle.GetLocalizedResource(),
Content = Strings.DataStreamsAreHiddenDescription.GetLocalizedResource(),
PrimaryButtonText = Strings.Yes.GetLocalizedResource(),
SecondaryButtonText = Strings.DontShowAgain.GetLocalizedResource()
};

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
dialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;

var result = await dialog.TryShowAsync();
if (result == ContentDialogResult.Primary)
{
FoldersSettingsService.AreAlternateStreamsVisible = true;
await context.ShellPage.Refresh_Click();
}
else
ApplicationSettingsService.ShowDataStreamsAreHiddenPrompt = false;
}
}
#if !WINDOWS
// LINUX-TODO(streams): this Windows command is hidden on Linux.
public Task ExecuteAsync(object? parameter = null)
=> Task.CompletedTask;
#endif

private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Files.App/Actions/FileSystem/CreateShortcutAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public RichGlyph Glyph
=> new(themedIconStyle: "App.ThemedIcons.URL");

public override bool IsExecutable =>
Ioc.Default.GetRequiredService<Files.Platform.Abstractions.IPlatformCapabilities>().SupportsShortcutFiles &&
context.HasSelection &&
context.CanCreateItem &&
UIHelpers.CanShowDialog;
Expand All @@ -37,6 +38,9 @@ public CreateShortcutAction()

public Task ExecuteAsync(object? parameter = null)
{
if (!IsExecutable)
return Task.CompletedTask;

return UIFilesystemHelpers.CreateShortcutAsync(context.ShellPage, context.SelectedItems);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public RichGlyph Glyph
=> new("\uE71B");

public override bool IsExecutable =>
Ioc.Default.GetRequiredService<Files.Platform.Abstractions.IPlatformCapabilities>().SupportsShortcutFiles &&
context.CanCreateItem &&
UIHelpers.CanShowDialog;

Expand All @@ -42,6 +43,9 @@ public CreateShortcutFromDialogAction()

public Task ExecuteAsync(object? parameter = null)
{
if (!IsExecutable)
return Task.CompletedTask;

return UIFilesystemHelpers.CreateShortcutFromDialogAsync(context.ShellPage!);
}

Expand Down
Loading
Loading