11using System . Buffers ;
22using System . Diagnostics ;
3+ using System . Runtime . CompilerServices ;
34using System . Text . Encodings . Web ;
45using DotNext . Buffers ;
56
@@ -10,7 +11,13 @@ namespace DotNext.IO;
1011/// </summary>
1112public static class FileUri
1213{
13- private static string FileScheme => OperatingSystem . IsWindows ( ) ? "file:///" : "file://" ;
14+ // On Windows:
15+ // C:\folder => file:///C:/folder
16+ // \\hostname\folder => file://folder
17+ // \\?\folder => file://?/folder
18+ // \\.\folder => file://./folder
19+ private const string FileScheme = "file://" ;
20+ private const string UncPrefix = @"\\" ;
1421
1522 /// <summary>
1623 /// Encodes file name as URI.
@@ -23,7 +30,7 @@ public static Uri Encode(ReadOnlySpan<char> fileName, TextEncoderSettings? setti
2330 {
2431 ThrowIfNotFullyQualified ( fileName ) ;
2532 var encoder = settings is null ? UrlEncoder . Default : UrlEncoder . Create ( settings ) ;
26- var maxLength = FileScheme . Length + encoder . MaxOutputCharactersPerInputCharacter * fileName . Length ;
33+ var maxLength = GetMaxEncodedLengthCore ( fileName , encoder ) ;
2734 using var buffer = ( uint ) maxLength <= ( uint ) SpanOwner < char > . StackallocThreshold
2835 ? stackalloc char [ maxLength ]
2936 : new SpanOwner < char > ( maxLength ) ;
@@ -32,6 +39,20 @@ public static Uri Encode(ReadOnlySpan<char> fileName, TextEncoderSettings? setti
3239 return new ( buffer . Span . Slice ( 0 , writtenCount ) . ToString ( ) , UriKind . Absolute ) ;
3340 }
3441
42+ /// <summary>
43+ /// Gets the maximum number of characters that can be produced by <see cref="TryEncode"/> method.
44+ /// </summary>
45+ /// <param name="fileName">The file name to be encoded.</param>
46+ /// <param name="encoder">The encoder.</param>
47+ /// <returns>The maximum number of characters that can be produced by the encoder.</returns>
48+ public static int GetMaxEncodedLength ( ReadOnlySpan < char > fileName , UrlEncoder ? encoder = null )
49+ => GetMaxEncodedLengthCore ( fileName , encoder ?? UrlEncoder . Default ) ;
50+
51+ private static int GetMaxEncodedLengthCore ( ReadOnlySpan < char > fileName , UrlEncoder encoder )
52+ => FileScheme . Length
53+ + Unsafe . BitCast < bool , byte > ( OperatingSystem . IsWindows ( ) )
54+ + encoder . MaxOutputCharactersPerInputCharacter * fileName . Length ;
55+
3556 /// <summary>
3657 /// Tries to encode file name as URI.
3758 /// </summary>
@@ -57,9 +78,23 @@ private static void ThrowIfNotFullyQualified(ReadOnlySpan<char> fileName)
5778
5879 private static bool TryEncodeCore ( ReadOnlySpan < char > fileName , UrlEncoder encoder , Span < char > output , out int charsWritten )
5980 {
81+ const char slash = '/' ;
6082 var result = false ;
6183 var writer = new SpanWriter < char > ( output ) ;
6284 writer . Write ( FileScheme ) ;
85+ if ( ! OperatingSystem . IsWindows ( ) )
86+ {
87+ // nothing to do
88+ }
89+ else if ( fileName . StartsWith ( UncPrefix ) )
90+ {
91+ fileName = fileName . Slice ( UncPrefix . Length ) ;
92+ }
93+ else
94+ {
95+ writer . Add ( slash ) ;
96+ }
97+
6398 while ( ! fileName . IsEmpty )
6499 {
65100 var index = fileName . IndexOf ( Path . DirectorySeparatorChar ) ;
@@ -81,7 +116,7 @@ private static bool TryEncodeCore(ReadOnlySpan<char> fileName, UrlEncoder encode
81116
82117 writer . Advance ( charsWritten ) ;
83118 if ( index >= 0 )
84- writer . Add ( Path . DirectorySeparatorChar ) ;
119+ writer . Add ( slash ) ;
85120 }
86121
87122 charsWritten = writer . WrittenCount ;
0 commit comments