Skip to content

Commit ba124de

Browse files
Fix: Fixed issue with resolving paths containing dots (#11758)
1 parent c9d8471 commit ba124de

File tree

5 files changed

+161
-93
lines changed

5 files changed

+161
-93
lines changed

src/Files.App/Filesystem/StorageFileHelpers/StorageFileExtensions.cs

Lines changed: 123 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
using CommunityToolkit.Mvvm.DependencyInjection;
2+
using Files.App.Contexts;
13
using Files.App.Extensions;
24
using Files.App.Filesystem.StorageItems;
35
using Files.App.Helpers;
46
using Files.App.Views;
57
using Files.Shared.Extensions;
68
using System;
79
using System.Collections.Generic;
10+
using System.Collections.Immutable;
811
using System.IO;
912
using System.Linq;
13+
using System.Text;
1014
using System.Threading.Tasks;
1115
using Windows.Storage;
1216
using Windows.Storage.Search;
@@ -15,12 +19,17 @@ namespace Files.App.Filesystem
1519
{
1620
public static class StorageFileExtensions
1721
{
18-
public static BaseStorageFile AsBaseStorageFile(this IStorageItem item)
22+
private const int SINGLE_DOT_DIRECTORY_LENGTH = 2;
23+
private const int DOUBLE_DOT_DIRECTORY_LENGTH = 3;
24+
25+
public static readonly ImmutableHashSet<string> _ftpPaths =
26+
new HashSet<string>() { "ftp:/", "ftps:/", "ftpes:/" }.ToImmutableHashSet();
27+
28+
public static BaseStorageFile? AsBaseStorageFile(this IStorageItem item)
1929
{
2030
if (item is null || !item.IsOfType(StorageItemTypes.File))
21-
{
2231
return null;
23-
}
32+
2433
return item is StorageFile file ? (BaseStorageFile)file : item as BaseStorageFile;
2534
}
2635

@@ -55,7 +64,8 @@ public static bool AreItemsInSameDrive(this IEnumerable<string> itemsPath, strin
5564
{
5665
try
5766
{
58-
return itemsPath.Any(itemPath => Path.GetPathRoot(itemPath).Equals(Path.GetPathRoot(destinationPath), StringComparison.OrdinalIgnoreCase));
67+
var destinationRoot = Path.GetPathRoot(destinationPath);
68+
return itemsPath.Any(itemPath => Path.GetPathRoot(itemPath).Equals(destinationRoot, StringComparison.OrdinalIgnoreCase));
5969
}
6070
catch
6171
{
@@ -71,7 +81,8 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable<string> itemsPath, s
7181
{
7282
try
7383
{
74-
return itemsPath.All(itemPath => Path.GetDirectoryName(itemPath).Equals(destinationPath.TrimPath(), StringComparison.OrdinalIgnoreCase));
84+
var trimmedPath = destinationPath.TrimPath();
85+
return itemsPath.All(itemPath => Path.GetDirectoryName(itemPath).Equals(trimmedPath, StringComparison.OrdinalIgnoreCase));
7586
}
7687
catch
7788
{
@@ -83,23 +94,11 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable<IStorageItem> storag
8394
public static bool AreItemsAlreadyInFolder(this IEnumerable<IStorageItemWithPath> storageItems, string destinationPath)
8495
=> storageItems.Select(x => x.Path).AreItemsAlreadyInFolder(destinationPath);
8596

86-
public static BaseStorageFolder AsBaseStorageFolder(this IStorageItem item)
97+
public static BaseStorageFolder? AsBaseStorageFolder(this IStorageItem item)
8798
{
88-
if (item is null)
89-
{
90-
return null;
91-
}
92-
else if (item.IsOfType(StorageItemTypes.Folder))
93-
{
94-
if (item is StorageFolder folder)
95-
{
96-
return (BaseStorageFolder)folder;
97-
}
98-
else
99-
{
100-
return item as BaseStorageFolder;
101-
}
102-
}
99+
if (item is not null && item.IsOfType(StorageItemTypes.Folder))
100+
return item is StorageFolder folder ? (BaseStorageFolder)folder : item as BaseStorageFolder;
101+
103102
return null;
104103
}
105104

@@ -110,9 +109,7 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
110109
if (value.Contains('/', StringComparison.Ordinal))
111110
{
112111
if (!value.EndsWith('/'))
113-
{
114112
value += "/";
115-
}
116113
}
117114
else if (!value.EndsWith('\\'))
118115
{
@@ -133,10 +130,8 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
133130

134131
var component = value.Substring(lastIndex, i - lastIndex);
135132
var path = value.Substring(0, i + 1);
136-
if (!new[] { "ftp:/", "ftps:/", "ftpes:/" }.Contains(path, StringComparer.OrdinalIgnoreCase))
137-
{
133+
if (!_ftpPaths.Contains(path, StringComparer.OrdinalIgnoreCase))
138134
pathBoxItems.Add(GetPathItem(component, path));
139-
}
140135

141136
lastIndex = i + 1;
142137
}
@@ -145,29 +140,10 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
145140
return pathBoxItems;
146141
}
147142

148-
public static string GetPathWithoutEnvironmentVariable(string path)
143+
public static string GetResolvedPath(string path, bool isFtp)
149144
{
150-
if (path.StartsWith("~\\", StringComparison.Ordinal))
151-
{
152-
path = $"{CommonPaths.HomePath}{path.Remove(0, 1)}";
153-
}
154-
if (path.Contains("%temp%", StringComparison.OrdinalIgnoreCase))
155-
{
156-
path = path.Replace("%temp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);
157-
}
158-
if (path.Contains("%tmp%", StringComparison.OrdinalIgnoreCase))
159-
{
160-
path = path.Replace("%tmp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);
161-
}
162-
if (path.Contains("%localappdata%", StringComparison.OrdinalIgnoreCase))
163-
{
164-
path = path.Replace("%localappdata%", CommonPaths.LocalAppDataPath, StringComparison.OrdinalIgnoreCase);
165-
}
166-
if (path.Contains("%homepath%", StringComparison.OrdinalIgnoreCase))
167-
{
168-
path = path.Replace("%homepath%", CommonPaths.HomePath, StringComparison.OrdinalIgnoreCase);
169-
}
170-
return Environment.ExpandEnvironmentVariables(path);
145+
var withoutEnvirnment = GetPathWithoutEnvironmentVariable(path);
146+
return ResolvePath(withoutEnvirnment, isFtp);
171147
}
172148

173149
public async static Task<BaseStorageFile> DangerousGetFileFromPathAsync
@@ -280,9 +256,7 @@ public async static Task<IList<StorageFolderWithPath>> GetFoldersWithPathAsync
280256
(this StorageFolderWithPath parentFolder, string nameFilter, uint maxNumberOfItems = uint.MaxValue)
281257
{
282258
if (parentFolder is null)
283-
{
284259
return null;
285-
}
286260

287261
var queryOptions = new QueryOptions
288262
{
@@ -327,5 +301,104 @@ private static PathBoxItem GetPathItem(string component, string path)
327301
};
328302
}
329303
}
304+
305+
private static string GetPathWithoutEnvironmentVariable(string path)
306+
{
307+
if (path.StartsWith("~\\", StringComparison.Ordinal))
308+
path = $"{CommonPaths.HomePath}{path.Remove(0, 1)}";
309+
310+
path = path.Replace("%temp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);
311+
312+
path = path.Replace("%tmp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);
313+
314+
path = path.Replace("%localappdata%", CommonPaths.LocalAppDataPath, StringComparison.OrdinalIgnoreCase);
315+
316+
path = path.Replace("%homepath%", CommonPaths.HomePath, StringComparison.OrdinalIgnoreCase);
317+
318+
return Environment.ExpandEnvironmentVariables(path);
319+
}
320+
321+
private static string ResolvePath(string path, bool isFtp)
322+
{
323+
if (path.StartsWith("Home"))
324+
return "Home";
325+
326+
var pathBuilder = new StringBuilder(path);
327+
var lastPathIndex = path.Length - 1;
328+
var separatorChar = isFtp || path.Contains('/', StringComparison.Ordinal) ? '/' : '\\';
329+
var rootIndex = isFtp ? FtpHelpers.GetRootIndex(path) + 1 : path.IndexOf($":{separatorChar}", StringComparison.Ordinal) + 2;
330+
331+
for (int i = 0, lastIndex = 0; i < pathBuilder.Length; i++)
332+
{
333+
if (pathBuilder[i] is not '?' &&
334+
pathBuilder[i] != Path.DirectorySeparatorChar &&
335+
pathBuilder[i] != Path.AltDirectorySeparatorChar &&
336+
i != lastIndex)
337+
continue;
338+
339+
if (lastIndex == i)
340+
{
341+
++lastIndex;
342+
continue;
343+
}
344+
345+
var component = pathBuilder.ToString().Substring(lastIndex, i - lastIndex);
346+
if (component is "..")
347+
{
348+
if (lastIndex is 0)
349+
{
350+
SetCurrentWorkingDirectory(pathBuilder, separatorChar, lastIndex, ref i);
351+
}
352+
else if (lastIndex == rootIndex)
353+
{
354+
pathBuilder.Remove(lastIndex, DOUBLE_DOT_DIRECTORY_LENGTH);
355+
i = lastIndex - 1;
356+
}
357+
else
358+
{
359+
var directoryIndex = pathBuilder.ToString().LastIndexOf(
360+
separatorChar,
361+
lastIndex - DOUBLE_DOT_DIRECTORY_LENGTH);
362+
363+
if (directoryIndex is not -1)
364+
{
365+
pathBuilder.Remove(directoryIndex, i - directoryIndex);
366+
i = directoryIndex;
367+
}
368+
}
369+
370+
lastPathIndex = pathBuilder.Length - 1;
371+
}
372+
else if (component is ".")
373+
{
374+
if (lastIndex is 0)
375+
{
376+
SetCurrentWorkingDirectory(pathBuilder, separatorChar, lastIndex, ref i);
377+
}
378+
else
379+
{
380+
pathBuilder.Remove(lastIndex, SINGLE_DOT_DIRECTORY_LENGTH);
381+
i -= 3;
382+
}
383+
lastPathIndex = pathBuilder.Length - 1;
384+
}
385+
386+
lastIndex = i + 1;
387+
}
388+
389+
return pathBuilder.ToString();
390+
}
391+
392+
private static void SetCurrentWorkingDirectory(StringBuilder path, char separator, int substringIndex, ref int i)
393+
{
394+
var context = Ioc.Default.GetRequiredService<IContentPageContext>();
395+
var subPath = path.ToString().Substring(substringIndex);
396+
397+
path.Clear();
398+
path.Append(context.ShellPage?.FilesystemViewModel.WorkingDirectory);
399+
path.Append(separator);
400+
path.Append(subPath);
401+
i = -1;
402+
}
330403
}
331404
}

src/Files.App/Helpers/FtpHelpers.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ public static ushort GetFtpPort(string path)
5656
var index = authority.IndexOf(':', StringComparison.Ordinal);
5757

5858
if (index == -1)
59-
{
6059
return path.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase) ? (ushort)990 : (ushort)21;
61-
}
6260

6361
return ushort.Parse(authority.Substring(index + 1));
6462
}
@@ -70,9 +68,8 @@ public static string GetFtpAuthority(string path)
7068
var hostIndex = path.IndexOf("/", schemaIndex, StringComparison.Ordinal);
7169

7270
if (hostIndex == -1)
73-
{
7471
hostIndex = path.Length;
75-
}
72+
7673

7774
return path.Substring(schemaIndex, hostIndex - schemaIndex);
7875
}
@@ -84,5 +81,12 @@ public static string GetFtpPath(string path)
8481
var hostIndex = path.IndexOf("/", schemaIndex, StringComparison.Ordinal);
8582
return hostIndex == -1 ? "/" : path.Substring(hostIndex);
8683
}
84+
85+
public static int GetRootIndex(string path)
86+
{
87+
path = path.Replace("\\", "/", StringComparison.Ordinal);
88+
var schemaIndex = path.IndexOf("://", StringComparison.Ordinal) + 3;
89+
return path.IndexOf("/", schemaIndex, StringComparison.Ordinal);
90+
}
8791
}
8892
}

src/Files.App/Helpers/PathNormalization.cs

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,81 +8,68 @@ public static class PathNormalization
88
public static string GetPathRoot(string path)
99
{
1010
if (string.IsNullOrEmpty(path))
11-
{
12-
return "";
13-
}
14-
string rootPath = "";
11+
return string.Empty;
12+
13+
string rootPath = string.Empty;
1514
try
1615
{
1716
var pathAsUri = new Uri(path.Replace("\\", "/", StringComparison.Ordinal));
1817
rootPath = pathAsUri.GetLeftPart(UriPartial.Authority);
1918
if (pathAsUri.IsFile && !string.IsNullOrEmpty(rootPath))
20-
{
2119
rootPath = new Uri(rootPath).LocalPath;
22-
}
2320
}
2421
catch (UriFormatException)
2522
{
2623
}
2724
if (string.IsNullOrEmpty(rootPath))
28-
{
29-
rootPath = Path.GetPathRoot(path);
30-
}
25+
rootPath = Path.GetPathRoot(path) ?? string.Empty;
26+
3127
return rootPath;
3228
}
3329

3430
public static string NormalizePath(string path)
3531
{
3632
if (string.IsNullOrEmpty(path))
37-
{
3833
return path;
39-
}
34+
4035
if (path.StartsWith("\\\\", StringComparison.Ordinal) || path.StartsWith("//", StringComparison.Ordinal) || FtpHelpers.IsFtpPath(path))
41-
{
4236
return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToUpperInvariant();
37+
38+
if (!path.EndsWith(Path.DirectorySeparatorChar))
39+
path += Path.DirectorySeparatorChar;
40+
41+
try
42+
{
43+
return Path.GetFullPath(new Uri(path).LocalPath)
44+
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
45+
.ToUpperInvariant();
4346
}
44-
else
47+
catch (UriFormatException ex)
4548
{
46-
if (!path.EndsWith(Path.DirectorySeparatorChar))
47-
{
48-
path += Path.DirectorySeparatorChar;
49-
}
50-
51-
try
52-
{
53-
return Path.GetFullPath(new Uri(path).LocalPath)
54-
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
55-
.ToUpperInvariant();
56-
}
57-
catch (UriFormatException ex)
58-
{
59-
App.Logger.Warn(ex, path);
60-
return path;
61-
}
49+
App.Logger.Warn(ex, path);
50+
return path;
6251
}
6352
}
6453

65-
public static string TrimPath(this string path)
54+
public static string? TrimPath(this string path)
6655
{
6756
return path?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
6857
}
6958

7059
public static string GetParentDir(string path)
7160
{
7261
if (string.IsNullOrEmpty(path))
73-
{
7462
return string.Empty;
75-
}
63+
7664
var index = path.Contains('/', StringComparison.Ordinal) ? path.LastIndexOf("/", StringComparison.Ordinal) : path.LastIndexOf("\\", StringComparison.Ordinal);
7765
return path.Substring(0, index != -1 ? index : path.Length);
7866
}
7967

8068
public static string Combine(string folder, string name)
8169
{
8270
if (string.IsNullOrEmpty(folder))
83-
{
8471
return name;
85-
}
72+
8673
return folder.Contains('/', StringComparison.Ordinal) ? Path.Combine(folder, name).Replace("\\", "/", StringComparison.Ordinal) : Path.Combine(folder, name);
8774
}
8875
}

0 commit comments

Comments
 (0)