Skip to content

Fix: Fixed issue with resolving paths containing dots #11758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 19, 2023
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
173 changes: 123 additions & 50 deletions src/Files.App/Filesystem/StorageFileHelpers/StorageFileExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using Files.App.Contexts;
using Files.App.Extensions;
using Files.App.Filesystem.StorageItems;
using Files.App.Helpers;
using Files.App.Views;
using Files.Shared.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Search;
Expand All @@ -15,12 +19,17 @@ namespace Files.App.Filesystem
{
public static class StorageFileExtensions
{
public static BaseStorageFile AsBaseStorageFile(this IStorageItem item)
private const int SINGLE_DOT_DIRECTORY_LENGTH = 2;
private const int DOUBLE_DOT_DIRECTORY_LENGTH = 3;

public static readonly ImmutableHashSet<string> _ftpPaths =
new HashSet<string>() { "ftp:/", "ftps:/", "ftpes:/" }.ToImmutableHashSet();

public static BaseStorageFile? AsBaseStorageFile(this IStorageItem item)
{
if (item is null || !item.IsOfType(StorageItemTypes.File))
{
return null;
}

return item is StorageFile file ? (BaseStorageFile)file : item as BaseStorageFile;
}

Expand Down Expand Up @@ -55,7 +64,8 @@ public static bool AreItemsInSameDrive(this IEnumerable<string> itemsPath, strin
{
try
{
return itemsPath.Any(itemPath => Path.GetPathRoot(itemPath).Equals(Path.GetPathRoot(destinationPath), StringComparison.OrdinalIgnoreCase));
var destinationRoot = Path.GetPathRoot(destinationPath);
return itemsPath.Any(itemPath => Path.GetPathRoot(itemPath).Equals(destinationRoot, StringComparison.OrdinalIgnoreCase));
}
catch
{
Expand All @@ -71,7 +81,8 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable<string> itemsPath, s
{
try
{
return itemsPath.All(itemPath => Path.GetDirectoryName(itemPath).Equals(destinationPath.TrimPath(), StringComparison.OrdinalIgnoreCase));
var trimmedPath = destinationPath.TrimPath();
return itemsPath.All(itemPath => Path.GetDirectoryName(itemPath).Equals(trimmedPath, StringComparison.OrdinalIgnoreCase));
}
catch
{
Expand All @@ -83,23 +94,11 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable<IStorageItem> storag
public static bool AreItemsAlreadyInFolder(this IEnumerable<IStorageItemWithPath> storageItems, string destinationPath)
=> storageItems.Select(x => x.Path).AreItemsAlreadyInFolder(destinationPath);

public static BaseStorageFolder AsBaseStorageFolder(this IStorageItem item)
public static BaseStorageFolder? AsBaseStorageFolder(this IStorageItem item)
{
if (item is null)
{
return null;
}
else if (item.IsOfType(StorageItemTypes.Folder))
{
if (item is StorageFolder folder)
{
return (BaseStorageFolder)folder;
}
else
{
return item as BaseStorageFolder;
}
}
if (item is not null && item.IsOfType(StorageItemTypes.Folder))
return item is StorageFolder folder ? (BaseStorageFolder)folder : item as BaseStorageFolder;

return null;
}

Expand All @@ -110,9 +109,7 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
if (value.Contains('/', StringComparison.Ordinal))
{
if (!value.EndsWith('/'))
{
value += "/";
}
}
else if (!value.EndsWith('\\'))
{
Expand All @@ -133,10 +130,8 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)

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

lastIndex = i + 1;
}
Expand All @@ -145,29 +140,10 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
return pathBoxItems;
}

public static string GetPathWithoutEnvironmentVariable(string path)
public static string GetResolvedPath(string path, bool isFtp)
{
if (path.StartsWith("~\\", StringComparison.Ordinal))
{
path = $"{CommonPaths.HomePath}{path.Remove(0, 1)}";
}
if (path.Contains("%temp%", StringComparison.OrdinalIgnoreCase))
{
path = path.Replace("%temp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);
}
if (path.Contains("%tmp%", StringComparison.OrdinalIgnoreCase))
{
path = path.Replace("%tmp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);
}
if (path.Contains("%localappdata%", StringComparison.OrdinalIgnoreCase))
{
path = path.Replace("%localappdata%", CommonPaths.LocalAppDataPath, StringComparison.OrdinalIgnoreCase);
}
if (path.Contains("%homepath%", StringComparison.OrdinalIgnoreCase))
{
path = path.Replace("%homepath%", CommonPaths.HomePath, StringComparison.OrdinalIgnoreCase);
}
return Environment.ExpandEnvironmentVariables(path);
var withoutEnvirnment = GetPathWithoutEnvironmentVariable(path);
return ResolvePath(withoutEnvirnment, isFtp);
}

public async static Task<BaseStorageFile> DangerousGetFileFromPathAsync
Expand Down Expand Up @@ -280,9 +256,7 @@ public async static Task<IList<StorageFolderWithPath>> GetFoldersWithPathAsync
(this StorageFolderWithPath parentFolder, string nameFilter, uint maxNumberOfItems = uint.MaxValue)
{
if (parentFolder is null)
{
return null;
}

var queryOptions = new QueryOptions
{
Expand Down Expand Up @@ -327,5 +301,104 @@ private static PathBoxItem GetPathItem(string component, string path)
};
}
}

private static string GetPathWithoutEnvironmentVariable(string path)
{
if (path.StartsWith("~\\", StringComparison.Ordinal))
path = $"{CommonPaths.HomePath}{path.Remove(0, 1)}";

path = path.Replace("%temp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);

path = path.Replace("%tmp%", CommonPaths.TempPath, StringComparison.OrdinalIgnoreCase);

path = path.Replace("%localappdata%", CommonPaths.LocalAppDataPath, StringComparison.OrdinalIgnoreCase);

path = path.Replace("%homepath%", CommonPaths.HomePath, StringComparison.OrdinalIgnoreCase);

return Environment.ExpandEnvironmentVariables(path);
}

private static string ResolvePath(string path, bool isFtp)
{
if (path.StartsWith("Home"))
return "Home";

var pathBuilder = new StringBuilder(path);
var lastPathIndex = path.Length - 1;
var separatorChar = isFtp || path.Contains('/', StringComparison.Ordinal) ? '/' : '\\';
var rootIndex = isFtp ? FtpHelpers.GetRootIndex(path) + 1 : path.IndexOf($":{separatorChar}", StringComparison.Ordinal) + 2;

for (int i = 0, lastIndex = 0; i < pathBuilder.Length; i++)
{
if (pathBuilder[i] is not '?' &&
pathBuilder[i] != Path.DirectorySeparatorChar &&
pathBuilder[i] != Path.AltDirectorySeparatorChar &&
i != lastIndex)
continue;

if (lastIndex == i)
{
++lastIndex;
continue;
}

var component = pathBuilder.ToString().Substring(lastIndex, i - lastIndex);
if (component is "..")
{
if (lastIndex is 0)
{
SetCurrentWorkingDirectory(pathBuilder, separatorChar, lastIndex, ref i);
}
else if (lastIndex == rootIndex)
{
pathBuilder.Remove(lastIndex, DOUBLE_DOT_DIRECTORY_LENGTH);
i = lastIndex - 1;
}
else
{
var directoryIndex = pathBuilder.ToString().LastIndexOf(
separatorChar,
lastIndex - DOUBLE_DOT_DIRECTORY_LENGTH);

if (directoryIndex is not -1)
{
pathBuilder.Remove(directoryIndex, i - directoryIndex);
i = directoryIndex;
}
}

lastPathIndex = pathBuilder.Length - 1;
}
else if (component is ".")
{
if (lastIndex is 0)
{
SetCurrentWorkingDirectory(pathBuilder, separatorChar, lastIndex, ref i);
}
else
{
pathBuilder.Remove(lastIndex, SINGLE_DOT_DIRECTORY_LENGTH);
i -= 3;
}
lastPathIndex = pathBuilder.Length - 1;
}

lastIndex = i + 1;
}

return pathBuilder.ToString();
}

private static void SetCurrentWorkingDirectory(StringBuilder path, char separator, int substringIndex, ref int i)
{
var context = Ioc.Default.GetRequiredService<IContentPageContext>();
var subPath = path.ToString().Substring(substringIndex);

path.Clear();
path.Append(context.ShellPage?.FilesystemViewModel.WorkingDirectory);
path.Append(separator);
path.Append(subPath);
i = -1;
}
}
}
12 changes: 8 additions & 4 deletions src/Files.App/Helpers/FtpHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ public static ushort GetFtpPort(string path)
var index = authority.IndexOf(':', StringComparison.Ordinal);

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

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

if (hostIndex == -1)
{
hostIndex = path.Length;
}


return path.Substring(schemaIndex, hostIndex - schemaIndex);
}
Expand All @@ -84,5 +81,12 @@ public static string GetFtpPath(string path)
var hostIndex = path.IndexOf("/", schemaIndex, StringComparison.Ordinal);
return hostIndex == -1 ? "/" : path.Substring(hostIndex);
}

public static int GetRootIndex(string path)
{
path = path.Replace("\\", "/", StringComparison.Ordinal);
var schemaIndex = path.IndexOf("://", StringComparison.Ordinal) + 3;
return path.IndexOf("/", schemaIndex, StringComparison.Ordinal);
}
}
}
55 changes: 21 additions & 34 deletions src/Files.App/Helpers/PathNormalization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,81 +8,68 @@ public static class PathNormalization
public static string GetPathRoot(string path)
{
if (string.IsNullOrEmpty(path))
{
return "";
}
string rootPath = "";
return string.Empty;

string rootPath = string.Empty;
try
{
var pathAsUri = new Uri(path.Replace("\\", "/", StringComparison.Ordinal));
rootPath = pathAsUri.GetLeftPart(UriPartial.Authority);
if (pathAsUri.IsFile && !string.IsNullOrEmpty(rootPath))
{
rootPath = new Uri(rootPath).LocalPath;
}
}
catch (UriFormatException)
{
}
if (string.IsNullOrEmpty(rootPath))
{
rootPath = Path.GetPathRoot(path);
}
rootPath = Path.GetPathRoot(path) ?? string.Empty;

return rootPath;
}

public static string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path))
{
return path;
}

if (path.StartsWith("\\\\", StringComparison.Ordinal) || path.StartsWith("//", StringComparison.Ordinal) || FtpHelpers.IsFtpPath(path))
{
return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToUpperInvariant();

if (!path.EndsWith(Path.DirectorySeparatorChar))
path += Path.DirectorySeparatorChar;

try
{
return Path.GetFullPath(new Uri(path).LocalPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToUpperInvariant();
}
else
catch (UriFormatException ex)
{
if (!path.EndsWith(Path.DirectorySeparatorChar))
{
path += Path.DirectorySeparatorChar;
}

try
{
return Path.GetFullPath(new Uri(path).LocalPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToUpperInvariant();
}
catch (UriFormatException ex)
{
App.Logger.Warn(ex, path);
return path;
}
App.Logger.Warn(ex, path);
return path;
}
}

public static string TrimPath(this string path)
public static string? TrimPath(this string path)
{
return path?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}

public static string GetParentDir(string path)
{
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}

var index = path.Contains('/', StringComparison.Ordinal) ? path.LastIndexOf("/", StringComparison.Ordinal) : path.LastIndexOf("\\", StringComparison.Ordinal);
return path.Substring(0, index != -1 ? index : path.Length);
}

public static string Combine(string folder, string name)
{
if (string.IsNullOrEmpty(folder))
{
return name;
}

return folder.Contains('/', StringComparison.Ordinal) ? Path.Combine(folder, name).Replace("\\", "/", StringComparison.Ordinal) : Path.Combine(folder, name);
}
}
Expand Down
Loading