Skip to content
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

Sanitise file paths from AFC service better #85

Merged
merged 1 commit into from
Feb 13, 2025
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
5 changes: 3 additions & 2 deletions Netimobiledevice/Afc/AfcService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Netimobiledevice.Extentions;
using Netimobiledevice.Lockdown;
using Netimobiledevice.Plist;
using Netimobiledevice.Utils;
using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down Expand Up @@ -421,8 +422,8 @@ public async Task Pull(string relativeSrc, string dst, CancellationToken cancell
string[] splitSrc = relativeSrc.Split('/');
string dstPath = splitSrc.Length > 1 ? Path.Combine(dst, splitSrc[^1]) : Path.Combine(dst, relativeSrc);
if (OperatingSystem.IsWindows()) {
// Windows filesystems can't cope with ':' so we replace these with '-'
dstPath = dstPath.Replace(':', '-');
// Windows filesystems (NTFS) are more restrictive than unix files systems so we gotta sanitise
dstPath = PathSanitiser.SantiseWindowsPath(dstPath);
}
Logger?.LogInformation("{src} --> {dst}", src, dst);

Expand Down
2 changes: 1 addition & 1 deletion Netimobiledevice/Netimobiledevice.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<RepositoryUrl>https://github.com/artehe/Netimobiledevice</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Title>$(AssemblyName)</Title>
<Version>2.2.1</Version>
<Version>2.2.2</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
59 changes: 59 additions & 0 deletions Netimobiledevice/Utils/PathSanitiser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.IO;
using System.Linq;
using System.Text;

namespace Netimobiledevice.Utils
{
public static class PathSanitiser
{
private enum WindowsPathParserState
{
PossibleDriveLetter,
PossibleDriveLetterSeparator,
Path
}

public static string SantiseWindowsPath(string sourcePath)
{
if (string.IsNullOrEmpty(sourcePath)) {
throw new ArgumentException("sourcePath cannot be null or empty", nameof(sourcePath));
}

// Remove the leading and trailing white spaces so we know we are starting from a sane position
sourcePath = sourcePath.Trim();

StringBuilder output = new StringBuilder(sourcePath.Length);
WindowsPathParserState state = WindowsPathParserState.PossibleDriveLetter;
foreach (char current in sourcePath) {
if (
(current >= 'a' && current <= 'z') ||
(current >= 'A' && current <= 'Z')
) {
output.Append(current);
if (state == WindowsPathParserState.PossibleDriveLetter) {
state = WindowsPathParserState.PossibleDriveLetterSeparator;
}
else {
state = WindowsPathParserState.Path;
}
}
else if (
current == Path.DirectorySeparatorChar ||
current == Path.AltDirectorySeparatorChar ||
(current == ':' && state == WindowsPathParserState.PossibleDriveLetterSeparator) ||
!Path.GetInvalidFileNameChars().Contains(current)
) {

output.Append(current);
state = WindowsPathParserState.Path;
}
else {
output.Append('_');
state = WindowsPathParserState.Path;
}
}
return output.ToString();
}
}
}
68 changes: 68 additions & 0 deletions NetimobiledeviceTest/Utils/PathSanitiserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Netimobiledevice.Utils;

namespace NetimobiledeviceTest.Utils;

[TestClass]
public class PathSanitiserTests
{
[TestMethod]
public void HandleIncorrectWindowsPath()
{
string sourcePath = @"C:\\Users\\User\\My:Folder*<File>?\\";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
string expectedPath = @"C:\\Users\\User\\My_Folder__File__\\";
Assert.AreEqual(expectedPath, resultPath);
}

[TestMethod]
public void HandleIncorrectWindowsPath2()
{
string sourcePath = @"C:\path\something\output_at_13:26:43.txt";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
string expectedPath = @"C:\path\something\output_at_13_26_43.txt";
Assert.AreEqual(expectedPath, resultPath);
}

[TestMethod]
public void HandleNormalWindowsPath()
{
string sourcePath = @"C:\\Users\\User\\MyFolder\\File.txt";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
Assert.AreEqual(sourcePath, resultPath);
}

[TestMethod]
public void HandleStartingWhiteSpaceWindowsPath()
{
string sourcePath = @" C:\\Users\\User\\MyFolder\\File.txt";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
string expectedPath = @"C:\\Users\\User\\MyFolder\\File.txt";
Assert.AreEqual(expectedPath, resultPath);
}

[TestMethod]
public void HandleTrailingWhiteSpaceWindowsPath()
{
string sourcePath = @"C:\\Users\\User\\MyFolder\\File.txt ";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
string expectedPath = @"C:\\Users\\User\\MyFolder\\File.txt";
Assert.AreEqual(expectedPath, resultPath);
}

[TestMethod]
public void HandleWhiteSpaceWindowsPath()
{
string sourcePath = @" C:\\Users\\User\\MyFolder\\File.txt ";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
string expectedPath = @"C:\\Users\\User\\MyFolder\\File.txt";
Assert.AreEqual(expectedPath, resultPath);
}

[TestMethod]
public void HandleNoDriveLetterWindowsPath()
{
string sourcePath = @"Users\\User\\MyFolder\\File.txt";
string resultPath = PathSanitiser.SantiseWindowsPath(sourcePath);
Assert.AreEqual(sourcePath, resultPath);
}
}
Loading