Skip to content

Commit 0b89468

Browse files
authored
Fix: Fixed issue where it didn't work to open password protected archives (#12708)
1 parent db70e42 commit 0b89468

File tree

18 files changed

+490
-272
lines changed

18 files changed

+490
-272
lines changed

src/Files.App/Data/Models/ItemViewModel.cs

Lines changed: 52 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,19 @@
77
using Files.App.Utils.StorageItems;
88
using Files.App.Helpers.StorageCache;
99
using Files.App.Utils.Shell;
10-
using Files.App.Storage.FtpStorage;
1110
using Files.App.ViewModels.Previews;
1211
using Files.Core.Services.SizeProvider;
1312
using Files.Shared.Cloud;
1413
using Files.Shared.EventArguments;
1514
using Files.Shared.Services;
16-
using FluentFTP;
1715
using Microsoft.Extensions.Logging;
1816
using Microsoft.UI.Xaml.Data;
1917
using Microsoft.UI.Xaml.Media;
2018
using Microsoft.UI.Xaml.Media.Imaging;
2119
using System.Collections.Concurrent;
2220
using System.IO;
23-
using System.Net;
2421
using System.Runtime.CompilerServices;
2522
using System.Runtime.InteropServices;
26-
using System.Text;
2723
using System.Text.Json;
2824
using Vanara.Windows.Shell;
2925
using Windows.Foundation;
@@ -144,20 +140,14 @@ public async Task SetWorkingDirectoryAsync(string? value)
144140

145141
WorkingDirectory = value;
146142

147-
string? pathRoot;
148-
if (FtpHelpers.IsFtpPath(WorkingDirectory))
149-
{
150-
var rootIndex = FtpHelpers.GetRootIndex(WorkingDirectory);
151-
pathRoot = rootIndex is -1
152-
? WorkingDirectory
153-
: WorkingDirectory.Substring(0, rootIndex);
154-
}
155-
else
143+
string? pathRoot = null;
144+
if (!FtpHelpers.IsFtpPath(WorkingDirectory))
156145
{
157146
pathRoot = Path.GetPathRoot(WorkingDirectory);
158147
}
159148

160149
GitDirectory = pathRoot is null ? null : GitHelpers.GetGitRepositoryPath(WorkingDirectory, pathRoot);
150+
161151
OnPropertyChanged(nameof(WorkingDirectory));
162152
}
163153

@@ -1380,55 +1370,46 @@ private async Task RapidAddItemsToCollection(string? path, LibraryItem? library
13801370
var stopwatch = new Stopwatch();
13811371
stopwatch.Start();
13821372

1383-
if (FtpHelpers.IsFtpPath(path))
1384-
{
1385-
// Recycle bin and network are enumerated by the fulltrust process
1386-
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false });
1387-
await EnumerateItemsFromSpecialFolderAsync(path);
1388-
}
1389-
else
1373+
var isRecycleBin = path.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal);
1374+
var enumerated = await EnumerateItemsFromStandardFolderAsync(path, addFilesCTS.Token, library);
1375+
1376+
// Hide progressbar after enumeration
1377+
IsLoadingItems = false;
1378+
1379+
switch (enumerated)
13901380
{
1391-
var isRecycleBin = path.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal);
1392-
var enumerated = await EnumerateItemsFromStandardFolderAsync(path, addFilesCTS.Token, library);
1381+
// Enumerated with FindFirstFileExFromApp
1382+
// Is folder synced to cloud storage?
1383+
case 0:
1384+
currentStorageFolder ??= await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path));
1385+
var syncStatus = await CheckCloudDriveSyncStatusAsync(currentStorageFolder?.Item);
1386+
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs()
1387+
{
1388+
IsTypeCloudDrive = syncStatus != CloudDriveSyncStatus.NotSynced && syncStatus != CloudDriveSyncStatus.Unknown,
1389+
IsTypeGitRepository = GitDirectory is not null
1390+
});
1391+
WatchForDirectoryChanges(path, syncStatus);
1392+
if (GitDirectory is not null)
1393+
WatchForGitChanges();
1394+
break;
13931395

1394-
// Hide progressbar after enumeration
1395-
IsLoadingItems = false;
1396+
// Enumerated with StorageFolder
1397+
case 1:
1398+
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false, IsTypeRecycleBin = isRecycleBin });
1399+
currentStorageFolder ??= await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path));
1400+
WatchForStorageFolderChanges(currentStorageFolder?.Item);
1401+
break;
13961402

1397-
switch (enumerated)
1398-
{
1399-
// Enumerated with FindFirstFileExFromApp
1400-
// Is folder synced to cloud storage?
1401-
case 0:
1402-
currentStorageFolder ??= await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path));
1403-
var syncStatus = await CheckCloudDriveSyncStatusAsync(currentStorageFolder?.Item);
1404-
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs()
1405-
{
1406-
IsTypeCloudDrive = syncStatus != CloudDriveSyncStatus.NotSynced && syncStatus != CloudDriveSyncStatus.Unknown,
1407-
IsTypeGitRepository = GitDirectory is not null
1408-
});
1409-
WatchForDirectoryChanges(path, syncStatus);
1410-
if (GitDirectory is not null)
1411-
WatchForGitChanges();
1412-
break;
1413-
1414-
// Enumerated with StorageFolder
1415-
case 1:
1416-
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false, IsTypeRecycleBin = isRecycleBin });
1417-
currentStorageFolder ??= await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path));
1418-
WatchForStorageFolderChanges(currentStorageFolder?.Item);
1419-
break;
1420-
1421-
// Watch for changes using FTP in Box Drive folder (#7428) and network drives (#5869)
1422-
case 2:
1423-
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false });
1424-
WatchForWin32FolderChanges(path);
1425-
break;
1426-
1427-
// Enumeration failed
1428-
case -1:
1429-
default:
1430-
break;
1431-
}
1403+
// Watch for changes using Win32 in Box Drive folder (#7428) and network drives (#5869)
1404+
case 2:
1405+
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false });
1406+
WatchForWin32FolderChanges(path);
1407+
break;
1408+
1409+
// Enumeration failed
1410+
case -1:
1411+
default:
1412+
break;
14321413
}
14331414

14341415
await GetDefaultItemIcons(folderSettings.GetIconSize());
@@ -1455,99 +1436,6 @@ public void CloseWatcher()
14551436
watcherCTS = new CancellationTokenSource();
14561437
}
14571438

1458-
public async Task EnumerateItemsFromSpecialFolderAsync(string path)
1459-
{
1460-
var isFtp = FtpHelpers.IsFtpPath(path);
1461-
1462-
CurrentFolder = new ListedItem(null!)
1463-
{
1464-
PrimaryItemAttribute = StorageItemTypes.Folder,
1465-
ItemPropertiesInitialized = true,
1466-
ItemNameRaw =
1467-
path.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) ? "RecycleBin".GetLocalizedResource() :
1468-
path.StartsWith(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) ? "Network".GetLocalizedResource() :
1469-
path.StartsWith(Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase) ? "ThisPC".GetLocalizedResource() :
1470-
isFtp ? "FTP" : "Unknown",
1471-
ItemDateModifiedReal = DateTimeOffset.Now, // Fake for now
1472-
ItemDateCreatedReal = DateTimeOffset.Now, // Fake for now
1473-
ItemType = "Folder".GetLocalizedResource(),
1474-
FileImage = null,
1475-
LoadFileIcon = false,
1476-
ItemPath = path,
1477-
FileSize = null,
1478-
FileSizeBytes = 0
1479-
};
1480-
1481-
if (!isFtp || !FtpHelpers.VerifyFtpPath(path))
1482-
return;
1483-
1484-
// TODO: Show invalid path dialog
1485-
1486-
using var client = new AsyncFtpClient();
1487-
client.Host = FtpHelpers.GetFtpHost(path);
1488-
client.Port = FtpHelpers.GetFtpPort(path);
1489-
client.Credentials = FtpManager.Credentials.Get(client.Host, FtpManager.Anonymous);
1490-
1491-
static async Task<FtpProfile?> WrappedAutoConnectFtpAsync(AsyncFtpClient client)
1492-
{
1493-
try
1494-
{
1495-
return await client.AutoConnect();
1496-
}
1497-
catch (FtpAuthenticationException)
1498-
{
1499-
return null;
1500-
}
1501-
1502-
throw new InvalidOperationException();
1503-
}
1504-
1505-
await Task.Run(async () =>
1506-
{
1507-
try
1508-
{
1509-
if (!client.IsConnected && await WrappedAutoConnectFtpAsync(client) is null)
1510-
{
1511-
await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
1512-
{
1513-
var credentialDialogViewModel = new CredentialDialogViewModel();
1514-
1515-
if (await dialogService.ShowDialogAsync(credentialDialogViewModel) != DialogResult.Primary)
1516-
return;
1517-
1518-
// Can't do more than that to mitigate immutability of strings. Perhaps convert DisposableArray to SecureString immediately?
1519-
if (!credentialDialogViewModel.IsAnonymous)
1520-
client.Credentials = new NetworkCredential(credentialDialogViewModel.UserName, Encoding.UTF8.GetString(credentialDialogViewModel.Password));
1521-
});
1522-
}
1523-
1524-
if (!client.IsConnected && await WrappedAutoConnectFtpAsync(client) is null)
1525-
throw new InvalidOperationException();
1526-
1527-
FtpManager.Credentials[client.Host] = client.Credentials;
1528-
1529-
var sampler = new IntervalSampler(500);
1530-
var list = await client.GetListing(FtpHelpers.GetFtpPath(path));
1531-
1532-
for (var i = 0; i < list.Length; i++)
1533-
{
1534-
filesAndFolders.Add(new FtpItem(list[i], path));
1535-
1536-
if (i == list.Length - 1 || sampler.CheckNow())
1537-
{
1538-
await OrderFilesAndFoldersAsync();
1539-
await ApplyFilesAndFoldersChangesAsync();
1540-
}
1541-
}
1542-
}
1543-
catch
1544-
{
1545-
// Network issue
1546-
FtpManager.Credentials.Remove(client.Host);
1547-
}
1548-
});
1549-
}
1550-
15511439
public async Task<int> EnumerateItemsFromStandardFolderAsync(string path, CancellationToken cancellationToken, LibraryItem? library = null)
15521440
{
15531441
// Flag to use FindFirstFileExFromApp or StorageFolder enumeration - Use storage folder for Box Drive (#4629)
@@ -1557,7 +1445,8 @@ public async Task<int> EnumerateItemsFromStandardFolderAsync(string path, Cancel
15571445
!path.StartsWith(@"\\?\", StringComparison.Ordinal) &&
15581446
!path.StartsWith(@"\\SHELL\", StringComparison.Ordinal) &&
15591447
!isWslDistro;
1560-
bool enumFromStorageFolder = isBoxFolder;
1448+
bool isFtp = FtpHelpers.IsFtpPath(path);
1449+
bool enumFromStorageFolder = isBoxFolder || isFtp;
15611450

15621451
BaseStorageFolder? rootFolder = null;
15631452

@@ -1585,7 +1474,7 @@ public async Task<int> EnumerateItemsFromStandardFolderAsync(string path, Cancel
15851474
}
15861475
else
15871476
{
1588-
var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path));
1477+
var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path, workingRoot, currentStorageFolder));
15891478
if (res)
15901479
{
15911480
currentStorageFolder = res.Result;
@@ -1756,12 +1645,15 @@ await Task.Run(async () =>
17561645
}
17571646
}
17581647

1759-
private Task EnumFromStorageFolderAsync(string path, BaseStorageFolder? rootFolder, StorageFolderWithPath currentStorageFolder, CancellationToken cancellationToken)
1648+
private async Task EnumFromStorageFolderAsync(string path, BaseStorageFolder? rootFolder, StorageFolderWithPath currentStorageFolder, CancellationToken cancellationToken)
17601649
{
17611650
if (rootFolder is null)
1762-
return Task.CompletedTask;
1651+
return;
17631652

1764-
return Task.Run(async () =>
1653+
if (rootFolder is IPasswordProtectedItem ppis)
1654+
ppis.PasswordRequestedCallback = UIFilesystemHelpers.RequestPassword;
1655+
1656+
await Task.Run(async () =>
17651657
{
17661658
List<ListedItem> finalList = await UniversalStorageEnumerator.ListEntries(
17671659
rootFolder,
@@ -1782,6 +1674,9 @@ private Task EnumFromStorageFolderAsync(string path, BaseStorageFolder? rootFold
17821674
await OrderFilesAndFoldersAsync();
17831675
await ApplyFilesAndFoldersChangesAsync();
17841676
}, cancellationToken);
1677+
1678+
if (rootFolder is IPasswordProtectedItem ppiu)
1679+
ppiu.PasswordRequestedCallback = null;
17851680
}
17861681

17871682
private void CheckForSolutionFile()

src/Files.App/Dialogs/CredentialDialog.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<TextBox
3737
x:Name="UserName"
3838
Margin="4"
39+
x:Load="{x:Bind ViewModel.PasswordOnly, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
3940
IsEnabled="{x:Bind ViewModel.IsAnonymous, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
4041
PlaceholderText="{helpers:ResourceString Name=CredentialDialogUserName/PlaceholderText}"
4142
Text="{x:Bind ViewModel.UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
@@ -47,6 +48,7 @@
4748
<CheckBox
4849
x:Name="Anonymous"
4950
Margin="4"
51+
x:Load="{x:Bind ViewModel.CanBeAnonymous, Mode=OneWay}"
5052
Content="{helpers:ResourceString Name=CredentialDialogAnonymous/Content}" />
5153
</StackPanel>
5254
</Grid>

src/Files.App/Helpers/Storage/StorageHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathA
144144
await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(item.Path, rootItem)));
145145
}
146146
if (returnedItem.Result is null && item.Item is not null)
147-
{
148147
returnedItem = new FilesystemResult<IStorageItem>(item.Item, FileSystemStatusCode.Success);
149-
}
148+
if (returnedItem.Result is IPasswordProtectedItem ppid && item.Item is IPasswordProtectedItem ppis)
149+
ppid.Credentials = ppis.Credentials;
150150
return returnedItem;
151151
}
152152

src/Files.App/Helpers/UI/UIFilesystemHelpers.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33

44
using Files.App.Dialogs;
55
using Files.App.Utils.StorageItems;
6+
using Files.App.Storage.FtpStorage;
67
using Files.App.ViewModels.Dialogs;
78
using Microsoft.Extensions.Logging;
89
using System.Collections.Concurrent;
910
using System.IO;
11+
using System.Net;
12+
using System.Text;
1013
using Windows.ApplicationModel.DataTransfer;
1114
using Windows.Storage;
1215
using Windows.System;
@@ -414,5 +417,31 @@ public static void UpdateShortcutItemProperties(ShortcutItem item, string target
414417
item.WorkingDirectory = workingDir;
415418
item.RunAsAdmin = runAsAdmin;
416419
}
420+
421+
public async static Task<StorageCredential> RequestPassword(IPasswordProtectedItem sender)
422+
{
423+
var path = ((IStorageItem)sender).Path;
424+
var isFtp = FtpHelpers.IsFtpPath(path);
425+
426+
var credentialDialogViewModel = new CredentialDialogViewModel() { CanBeAnonymous = isFtp, PasswordOnly = !isFtp };
427+
IDialogService dialogService = Ioc.Default.GetRequiredService<IDialogService>();
428+
var dialogResult = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() =>
429+
dialogService.ShowDialogAsync(credentialDialogViewModel));
430+
431+
if (dialogResult != DialogResult.Primary || credentialDialogViewModel.IsAnonymous)
432+
return new();
433+
434+
// Can't do more than that to mitigate immutability of strings. Perhaps convert DisposableArray to SecureString immediately?
435+
var credentials = new StorageCredential(credentialDialogViewModel.UserName, Encoding.UTF8.GetString(credentialDialogViewModel.Password));
436+
credentialDialogViewModel.Password?.Dispose();
437+
438+
if (isFtp)
439+
{
440+
var host = FtpHelpers.GetFtpHost(path);
441+
FtpManager.Credentials[host] = new NetworkCredential(credentials.UserName, credentials.SecurePassword);
442+
}
443+
444+
return credentials;
445+
}
417446
}
418447
}

0 commit comments

Comments
 (0)