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
6 changes: 3 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ on:
push:
paths-ignore:
- '*.md'
pull_request:
paths-ignore:
- '*.md'

jobs:
build:
Expand All @@ -25,6 +22,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Print OS name
shell: bash
run: |
Expand Down Expand Up @@ -59,6 +57,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down Expand Up @@ -89,6 +88,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# SanitizeFilename

Sanitizes file and directory names to ensure compatibility with Windows (NTFS), Linux (ext4), and macOS (APFS).
Sanitizes file and directory names to ensure compatibility with Windows (NTFS & exFat), 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)

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 .NET platform.
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, which is common on [many modern](https://en.wikipedia.org/wiki/Comparison_of_file_systems) file systems. Runs on any .NET platform.

## Example

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

## Rules

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.
Restrictions of Windows, Linux and macOS are all 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](https://github.com/Codeuctivity/SanitizeFilename/blob/387103492098cd9cef0f8596a96dc6c2dfe2eba3/SanitizeFilenameTests/FilenameTests/LinuxSpecificTests.cs#L20) | 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), ... |
| Reserved keywords | Windows | Linux, macOS | CON, PRN, AUX, ... |
| Reserved chars | Linux, Windows, macOS | | '/', '\0' |
| Reserved chars windows | Windows | Linux, macOS | '\\\', '""', ... |
| Invalid trailing chars | Windows | Linux, macOS | ' ', ',' |
| Max length Linux | Linux, | [Windows, macOS](https://github.com/Codeuctivity/SanitizeFilename/blob/387103492098cd9cef0f8596a96dc6c2dfe2eba3/SanitizeFilenameTests/FilenameTests/LinuxSpecificTests.cs#L20) | 255 bytes |
| Max length | Linux, Windows, macOS | | 255 chars |
| Unpaired Unicode surrogates | macOS, Linux | Windows | U+D800 - U+DFFF |
| NotAssigned to Unicode | macOS | Linux, Windows | U+67803, ... |
| "New" Unicode (today 17+) | macOS | Linux, Windows | 🫩 (U+1FAE9), ... |

## .NET framework support

Expand All @@ -49,12 +49,15 @@ Restrictions of Windows, Linux and OsX are alle combined to an replacement patte

## Test setup

The ExFat specific tests are skipped as long as no ExFat filesystem is available. Use this snippet to enable them:
The exFat specific tests are skipped as long as no exFat filesystem is available. Use this snippet to enable them:

### Windows

```powershell
$vhdpath = 'C:\temp\ExFatTestContainer.vhd'
$vhdpath = [System.IO.Path]::Combine($env:TEMP, 'ExFatTestContainer.vhd')
Remove-Item $vhdpath -ErrorAction SilentlyContinue
$vhdsize = 100MB
New-VHD -Path $vhdpath -Dynamic -SizeBytes $vhdsize | Mount-VHD -Passthru |Initialize-Disk -Passthru |New-Partition -AssignDriveLetter -UseMaximumSize |Format-Volume -FileSystem 'exFAT' -Confirm:$false -NewFileSystemLabel '{exfatLabel}' -Force|Out-Null
```

Running as admin will automaticly create and mount a Exfat drive while tests are running.
Running as admin will automatically create and mount a exFat drive while tests are running.
1 change: 1 addition & 0 deletions SanitizeFilename.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\dependabot.yml = .github\dependabot.yml
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
README.md = README.md
testenvironments.json = testenvironments.json
EndProjectSection
EndProject
Global
Expand Down
2 changes: 1 addition & 1 deletion SanitizeFilename/SanitizeFilename.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down
2 changes: 1 addition & 1 deletion SanitizeFilename/docs/nugetReadme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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.
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 target platform.

## Example

Expand Down
9 changes: 4 additions & 5 deletions SanitizeFilenameTests/FilenameTests/SanitizeFilenamesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,16 @@ public void ShouldNotBeTouchedBySanitizer(string unicodeSpecificEmoticon, int? u
// 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")]
[TestCase("🙂", "Unicode 8 example https://emojipedia.org/person-biking")]
[TestCase("🙃", "Unicode 8 example https://emojipedia.org/upside-down-face")]
[TestCase("🤩", "Unicode 10 example https://emojipedia.org/star-struck#emoji")]
[TestCase("🥰", "Unicode 11 example https://emojipedia.org/smiling-face-with-hearts")]
[TestCase("🦿", "Unicode 12 example https://emojipedia.org/mechanical-leg")]
[TestCase("🫀", "Unicode 13.1 example https://emojipedia.org/anatomical-heart")]
[TestCase("🫠", "Unicode 14 example https://emojipedia.org/melting-face")]
[TestCase("🫥", "Unicode 14 example https://emojipedia.org/dotted-line-face")]
[TestCase("🪿", "Unicode 15 example https://emojipedia.org/goose")]
#pragma warning disable IDE0060 // unicodeVersionNote is used for documentation purposes only
public void ShouldSanitizeUnicodeVersion9Plus(string unicodeSpecificEmoticon, string unicodeVersion)
[TestCase("🫩", "Unicode 16 example https://emojipedia.org/face-with-bags-under-eyes")]
public void SanitizesUnicodeCodePointsThatAreSupportedByEveryOs(string unicodeSpecificEmoticon, string unicodeVersion)
{
var sanitizedFilename = unicodeSpecificEmoticon.SanitizeFilename();
Assert.That(sanitizedFilename, Is.Not.EqualTo(unicodeSpecificEmoticon));
Expand All @@ -226,9 +226,8 @@ public void ShouldSanitizeUnicodeVersion9Plus(string unicodeSpecificEmoticon, st

// This emoticons are supported by every OS/FS tested, except macOS, because unicode 16 and 17 specific code points are not supported by macOS today
// Behavior on macos is expected to change over time
[TestCase("🫩", "Unicode 16 example https://emojipedia.org/face-with-bags-under-eyes")]
[TestCase("🫝", "Unicdoe 17 example https://emojipedia.org/apple-core")]
public void Unicode17SpecificMacoOsBehavior(string unicodeSpecificEmoticon, string unicodeVersion)
public void UnicodeSpecificMacoOsBehavior(string unicodeSpecificEmoticon, string unicodeVersion)
{
var sanitizedFilename = unicodeSpecificEmoticon.SanitizeFilename();
Assert.That(sanitizedFilename, Is.Not.EqualTo(unicodeSpecificEmoticon));
Expand Down
4 changes: 2 additions & 2 deletions SanitizeFilenameTests/SanitizeFilenameTests.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT' ">net8.0;net9.0;net48</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT' ">net8.0;net9.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT' ">net8.0;net10.0;net48</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT' ">net10.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down