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
5 changes: 2 additions & 3 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"recommendations": [
"ms-dotnettools.csharp",
"formulahendry.dotnet-test-explorer",
"github.vscode-pull-request-github"
"github.vscode-pull-request-github",
"ms-dotnettools.csdevkit"
]
}
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SanitizeFilename

Sanitizes file and directory names in a manner that is compatible with Windows, Linux and OsX.
Sanitizes file and directory names to ensure compatibility with Windows (NTFS), Linux (ext4), and macOS (APFS).

[![.NET build and test](https://github.com/Codeuctivity/SanitizeFilename/actions/workflows/dotnet.yml/badge.svg)](https://github.com/Codeuctivity/SanitizeFilename/actions/workflows/dotnet.yml) [![NuGet](https://img.shields.io/nuget/v/Codeuctivity.SanitizeFilename.svg)](https://www.nuget.org/packages/Codeuctivity.SanitizeFilename/) [![Donate](https://img.shields.io/static/v1?label=Paypal&message=Donate&color=informational)](https://www.paypal.com/donate?hosted_button_id=7M7UFMMRTS7UE)

Expand All @@ -27,22 +27,22 @@ Console.WriteLine($"SafeFileNameOptionalReplacementChar: {safeFileNameOptionalRe

Restrictions of Windows, Linux and OsX are alle combined to an replacement pattern, that will sanitize any filename to be compatible with any of the OS and common filesystem restrictions.

| Pattern | OS that don't support pattern | OS that support pattern | Example |
| -------------------------------- | ----------------------------- | ----------------------- | ------------------ |
| Reserved keywords | Windows | Linux, OsX | CON, PRN, AUX, ... |
| Reserved chars | Linux, Windows, OsX | | '/', '\0' |
| Reserved chars windows | Windows | Linux, OsX | '\\\', '""', ... |
| Invalid trailing chars | Windows | Linux, OsX | ' ', ',' |
| Max length Linux | Linux, | Windows, OsX | 255 bytes |
| Max length | Linux, Windows, OsX | | 255 chars |
| Unpaired Unicode surrogates | OsX, Linux | Windows | U+D800 - U+DFFF |
| NotAssigned to Unicode | OsX | Linux, Windows | U+67803, ... |
| "New" Unicode (today 16 + 17) | OsX | Linux, Windows | 🫩 (U+1FAE9), ... |
| Pattern | OS that don't support pattern | OS that support pattern | Example |
| ----------------------------- | ----------------------------- | ----------------------- | ------------------ |
| Reserved keywords | Windows | Linux, OsX | CON, PRN, AUX, ... |
| Reserved chars | Linux, Windows, OsX | | '/', '\0' |
| Reserved chars windows | Windows | Linux, OsX | '\\\', '""', ... |
| Invalid trailing chars | Windows | Linux, OsX | ' ', ',' |
| Max length Linux | Linux, | [Windows, OsX]() | 255 bytes |
| Max length | Linux, Windows, OsX | | 255 chars |
| Unpaired Unicode surrogates | OsX, Linux | Windows | U+D800 - U+DFFF |
| NotAssigned to Unicode | OsX | Linux, Windows | U+67803, ... |
| "New" Unicode (today 16 + 17) | OsX | Linux, Windows | 🫩 (U+1FAE9), ... |

## .NET framework support

- Support for legacy .NET versions will be maintained as long as it is [funded](https://github.com/sponsors/Codeuctivity).
- Support for .NET Framework 4.6.2 and higher was added in Version [2.0.145](https://www.nuget.org/packages/Codeuctivity.SanitizeFilename/2.0.145).
- Edge case Unicode sanitization: [.NET Framework](https://learn.microsoft.com/en-us/dotnet/framework/whats-new/#character-categories) uses Unicode 8.0, while .NET 8+ uses a newer version to detect unpaired surrogates and unassigned code points.
- This is relevant when dealing with emoticons.
- For example, ["💏🏻"](https://emojipedia.org/kiss-light-skin-tone) will be sanitized when running on .NET Framework 4.8, while it is supported as a valid filename on modern filesystems
- This is relevant when dealing with emoticons.
- For example, ["💏🏻"](https://emojipedia.org/kiss-light-skin-tone) will be sanitized when running on .NET Framework 4.8, while it is supported as a valid filename on modern filesystems
6 changes: 2 additions & 4 deletions SanitizeFilename/docs/nugetReadme.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# SanitizeFilename

Sanitizes file- and directory names in a manner that is compatible with Windows, Linux and OsX.

[![.NET build and test](https://github.com/Codeuctivity/SanitizeFilename/actions/workflows/dotnet.yml/badge.svg)](https://github.com/Codeuctivity/SanitizeFilename/actions/workflows/dotnet.yml) [![NuGet](https://img.shields.io/nuget/v/Codeuctivity.SanitizeFilename.svg)](https://www.nuget.org/packages/Codeuctivity.SanitizeFilename/) [![Donate](https://img.shields.io/static/v1?label=Paypal&message=Donate&color=informational)](https://www.paypal.com/donate?hosted_button_id=7M7UFMMRTS7UE)
Sanitizes file and directory names to ensure compatibility with Windows (NTFS), Linux (ext4), and macOS (APFS).

Implements rules documented by [Microsoft](https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions) + file name length truncation to 255 bytes - common on [many modern](https://en.wikipedia.org/wiki/Comparison_of_file_systems) file systems. Runs on any .net8 target platform.

Expand All @@ -21,4 +19,4 @@ Console.WriteLine($"Sanitized: {safeFileName}");
string safeFileNameOptionalReplacementChar = unsafeString.SanitizeFilename(' ');
Console.WriteLine($"SafeFileNameOptionalReplacementChar: {safeFileNameOptionalReplacementChar}");
//SafeFileNameOptionalReplacementChar: file Name
```
```
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void ShouldBehaviorOsDependentOnCreatingDirectoryWithMoreThan255Bytes()
{
if (IsRunningOnNet4x())
{
Assert.Pass("Test is not thought to be run with .net framwework / unicode 8");
Assert.Pass("Test is not thought to be run with .net framework / unicode 8");
Copy link

Copilot AI May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling: '.net framwework' is now '.net framework'.

Copilot uses AI. Check for mistakes.
}

var expected = !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void ShouldThrow(string invalidDirectoryName, char replacement)

if (IsRunningOnNet4x())
{
Assert.Pass("Test is not thought to be run with .net framwework / unicode 8");
Assert.Pass("Test is not thought to be run with .net framework / unicode 8");
Copy link

Copilot AI May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling: '.net framwework' is now '.net framework'.

Copilot uses AI. Check for mistakes.
}

Assert.That(ex.Message, Is.EqualTo("Replacement '*' is invalid for Windows (Parameter 'replacement')"));
Expand Down Expand Up @@ -171,7 +171,7 @@ public void ShouldTruncateLongFileNamesPreserveUnicodeTextElements(string testSu
{
if (IsRunningOnNet4x())
{
Assert.Pass("Test is not thought to be run with .net framwework / unicode 8");
Assert.Pass("Test is not thought to be run with .net framework / unicode 8");
Copy link

Copilot AI May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling: '.net framwework' is now '.net framework'.

Copilot uses AI. Check for mistakes.
}

var invalidDirectoryName = new string('a', countOfFillingAChars) + testSuffix;
Expand Down
2 changes: 1 addition & 1 deletion SanitizeFilenameTests/FilenameTests/LinuxSpecificTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void ShouldBehaviorOsDependentOnWritingFilenameWithMoreThan255Bytes()
{
if (IsRunningOnNet4x())
{
Assert.Pass("Test is not thought to be run with .net framwework / unicode 8");
Assert.Pass("Test is not thought to be run with .net framework / unicode 8");
Copy link

Copilot AI May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typo in '.net framwework' has been corrected to '.net framework'.

Copilot uses AI. Check for mistakes.
}

var expected = !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
Expand Down
4 changes: 2 additions & 2 deletions SanitizeFilenameTests/FilenameTests/SanitizeFilenamesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public void ShouldTruncateLongFileNamesPreserveUnicodeTextElements(string testSu
{
if (IsRunningOnNet4x())
{
Assert.Pass("Test is not thought to be run with .net framwework / unicode 8");
Assert.Pass("Test is not thought to be run with .net framework / unicode 8");
Copy link

Copilot AI May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typo in '.net framwework' has been corrected to '.net framework'.

Copilot uses AI. Check for mistakes.
}

var invalidFilename = new string('a', countOfFillingAChars) + testSuffix;
Expand Down Expand Up @@ -202,7 +202,7 @@ public void ShouldNotBeTouchedBySanitizer(string unicodeSpecificEmoticon, int? u
Assert.That(FileWriteAsserter.TryWriteFileToTempDirectory(sanitizedFilename), Is.True);
}

// https://learn.microsoft.com/en-us/dotnet/api/system.globalization.charunicodeinfo?view=net-8.0#notes-to-callers
// https://learn.microsoft.com/en-us/dotnet/api/system.globalization.charunicodeinfo?view=net-8.0#notes-to-callers
// Unicode examples https://emojipedia.org/unicode-17.0
[TestCase("😀", "Unicode 6.1 example https://emojipedia.org/grinning-face")]
[TestCase("🚴", "Unicode 6 example https://emojipedia.org/person-biking")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@

namespace SanitizeFilenameTests
namespace SanitizeFilenameTests
{
[Parallelizable(ParallelScope.Fixtures)]
public class SanitizeFilenamesTestsBase
{

public static bool IsRunningOnNet4x()
{
var version = Environment.Version;
Expand Down
4 changes: 2 additions & 2 deletions SanitizeFilenameTests/FilenameTests/WindowsSpecificTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public void ShouldBehaviorOsDependentOnWritingFilenameWithKnownWindowsSpecificEx
{
foreach (var invalidOnWindows in SanitizeFilename.InvalidCharsInWindowsFileNames)
{
var filenameInvalidOnMacOs = "valid" + invalidOnWindows + "filename";
var actual = FileWriteAsserter.TryWriteFileToTempDirectory(filenameInvalidOnMacOs);
var filenameInvalidOnWindows = "valid" + invalidOnWindows + "filename";
var actual = FileWriteAsserter.TryWriteFileToTempDirectory(filenameInvalidOnWindows);
Assert.That(actual, Is.False);
}
}
Expand Down
4 changes: 2 additions & 2 deletions SanitizeFilenameTests/SanitizeFilenameTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.7.0">
<PackageReference Include="NUnit.Analyzers" Version="4.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down