11using System . Buffers ;
22using System . Diagnostics ;
33using System . Runtime . CompilerServices ;
4- using System . Runtime . InteropServices ;
54using System . Text . Encodings . Web ;
65
76namespace DotNext . IO ;
@@ -20,10 +19,6 @@ public static class FileUri
2019 // \\.\folder => file://./folder
2120 private const string FileScheme = "file://" ;
2221 private const string UncPrefix = @"\\" ;
23- private const char DriveSeparatorChar = ':' ;
24-
25- private static readonly SearchValues < char > Delimiters =
26- SearchValues . Create ( OperatingSystem . IsWindows ( ) ? [ Path . DirectorySeparatorChar , DriveSeparatorChar ] : [ Path . DirectorySeparatorChar ] ) ;
2722
2823 /// <summary>
2924 /// Encodes file name as URI.
@@ -86,9 +81,12 @@ private static void ThrowIfPartiallyQualified(ReadOnlySpan<char> fileName)
8681 private static bool TryEncodeCore ( ReadOnlySpan < char > fileName , UrlEncoder encoder , Span < char > output , out int charsWritten )
8782 {
8883 const char slash = '/' ;
84+ const char driveSeparator = ':' ;
8985 const char escapedDriveSeparatorChar = '|' ;
9086 var writer = new SpanWriter < char > ( output ) ;
9187 writer . Write ( FileScheme ) ;
88+
89+ bool endsWithTrailingSeparator ;
9290 if ( ! OperatingSystem . IsWindows ( ) )
9391 {
9492 // nothing to do
@@ -97,42 +95,42 @@ private static bool TryEncodeCore(ReadOnlySpan<char> fileName, UrlEncoder encode
9795 {
9896 fileName = fileName . Slice ( UncPrefix . Length ) ;
9997 }
100- else
98+ else if ( GetPathComponent ( ref fileName , out endsWithTrailingSeparator ) is [ .. var drive , driveSeparator ] )
10199 {
102- writer . Add ( slash ) ;
100+ writer . Write ( drive ) ;
101+ writer . Write ( endsWithTrailingSeparator ? [ escapedDriveSeparatorChar , slash ] : [ escapedDriveSeparatorChar ] ) ;
103102 }
104103
105- while ( ! fileName . IsEmpty )
104+ for ( ; ; writer . Add ( slash ) )
106105 {
107- var index = fileName . IndexOfAny ( Delimiters ) ;
108- char replacement ;
109- ReadOnlySpan < char > component ;
110- if ( index >= 0 )
111- {
112- component = fileName . Slice ( 0 , index ) ;
113-
114- // skip bounds check
115- replacement = OperatingSystem . IsWindows ( ) && Unsafe . Add ( ref MemoryMarshal . GetReference ( fileName ) , index ) is DriveSeparatorChar
116- ? escapedDriveSeparatorChar
117- : slash ;
118- fileName = fileName . Slice ( index + 1 ) ;
119- }
120- else
121- {
122- component = fileName ;
123- fileName = default ;
124- replacement = '\0 ' ;
125- }
126-
106+ var component = GetPathComponent ( ref fileName , out endsWithTrailingSeparator ) ;
127107 if ( encoder . Encode ( component , writer . RemainingSpan , out _ , out charsWritten ) is not OperationStatus . Done )
128108 return false ;
129109
130110 writer . Advance ( charsWritten ) ;
131- if ( replacement is not ' \0 ' )
132- writer . Add ( replacement ) ;
111+ if ( ! endsWithTrailingSeparator )
112+ break ;
133113 }
134114
135115 charsWritten = writer . WrittenCount ;
136116 return true ;
137117 }
118+
119+ private static ReadOnlySpan < char > GetPathComponent ( ref ReadOnlySpan < char > fileName , out bool endsWithTrailingSeparator )
120+ {
121+ ReadOnlySpan < char > component ;
122+ var index = fileName . IndexOf ( Path . DirectorySeparatorChar ) ;
123+ if ( endsWithTrailingSeparator = index >= 0 )
124+ {
125+ component = fileName . Slice ( 0 , index ) ;
126+ fileName = fileName . Slice ( index + 1 ) ;
127+ }
128+ else
129+ {
130+ component = fileName ;
131+ fileName = default ;
132+ }
133+
134+ return component ;
135+ }
138136}
0 commit comments