@@ -19,71 +19,78 @@ namespace SixLabors.ImageSharp.Web
1919 /// </summary>
2020 public sealed class FormatUtilities
2121 {
22- private readonly IImageFormat [ ] imageFormats ;
23- private readonly Dictionary < IImageFormat , string [ ] > fileExtensions = new Dictionary < IImageFormat , string [ ] > ( ) ;
24- private readonly Dictionary < string , string > fileExtension = new Dictionary < string , string > ( ) ;
22+ private readonly List < string > extensions = new ( ) ;
23+ private readonly Dictionary < string , string > extensionsByMimeType = new ( ) ;
2524
2625 /// <summary>
27- /// Initializes a new instance of the <see cref="FormatUtilities"/> class.
26+ /// Initializes a new instance of the <see cref="FormatUtilities" /> class.
2827 /// </summary>
2928 /// <param name="options">The middleware options.</param>
3029 public FormatUtilities ( IOptions < ImageSharpMiddlewareOptions > options )
3130 {
3231 Guard . NotNull ( options , nameof ( options ) ) ;
3332
34- // The formats contained in the configuration are used a lot in hash generation
35- // so we need them to be enumerated to remove allocations and allow indexing.
36- this . imageFormats = options . Value . Configuration . ImageFormats . ToArray ( ) ;
37- for ( int i = 0 ; i < this . imageFormats . Length ; i ++ )
33+ foreach ( IImageFormat imageFormat in options . Value . Configuration . ImageFormats )
3834 {
39- string [ ] extensions = this . imageFormats [ i ] . FileExtensions . ToArray ( ) ;
40- this . fileExtensions [ this . imageFormats [ i ] ] = extensions ;
41- this . fileExtension [ this . imageFormats [ i ] . DefaultMimeType ] = extensions [ 0 ] ;
35+ string [ ] extensions = imageFormat . FileExtensions . ToArray ( ) ;
36+
37+ foreach ( string extension in extensions )
38+ {
39+ this . extensions . Add ( extension ) ;
40+ }
41+
42+ this . extensionsByMimeType [ imageFormat . DefaultMimeType ] = extensions [ 0 ] ;
4243 }
4344 }
4445
4546 /// <summary>
4647 /// Gets the file extension for the given image uri.
4748 /// </summary>
4849 /// <param name="uri">The full request uri.</param>
49- /// <returns>The <see cref="string"/>.</returns>
50+ /// <returns>The <see cref="string" />.</returns>
5051 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
5152 public string GetExtensionFromUri ( string uri )
5253 {
53- // TODO: Investigate using span to reduce allocations here.
54- string [ ] parts = uri . Split ( '?' ) ;
55- if ( parts . Length > 1 && QueryHelpers . ParseQuery ( parts [ 1 ] ) . TryGetValue ( FormatWebProcessor . Format , out StringValues ext ) )
54+ int query = uri . IndexOf ( '?' ) ;
55+ ReadOnlySpan < char > path ;
56+
57+ if ( query > - 1 )
58+ {
59+ if ( uri . Contains ( FormatWebProcessor . Format , StringComparison . OrdinalIgnoreCase ) && QueryHelpers . ParseQuery ( uri . Substring ( query ) ) . TryGetValue ( FormatWebProcessor . Format , out StringValues ext ) )
60+ {
61+ return ext ;
62+ }
63+
64+ path = uri . AsSpan ( 0 , query ) ;
65+ }
66+ else
5667 {
57- return ext ;
68+ path = uri ;
5869 }
5970
60- string path = parts [ 0 ] ;
61- string extension = null ;
62- int index = 0 ;
63- for ( int i = 0 ; i < this . imageFormats . Length ; i ++ )
71+ int extensionIndex ;
72+ if ( ( extensionIndex = path . LastIndexOf ( '.' ) ) != - 1 )
6473 {
65- for ( int j = 0 ; j < this . fileExtensions [ this . imageFormats [ i ] ] . Length ; j ++ )
74+ ReadOnlySpan < char > pathExtension = path . Slice ( extensionIndex + 1 ) ;
75+
76+ foreach ( string extension in this . extensions )
6677 {
67- int li = path . LastIndexOf ( $ ".{ this . fileExtensions [ this . imageFormats [ i ] ] [ j ] } ", StringComparison . OrdinalIgnoreCase ) ;
68- if ( li < index )
78+ if ( pathExtension . Equals ( extension , StringComparison . OrdinalIgnoreCase ) )
6979 {
70- continue ;
80+ return extension ;
7181 }
72-
73- index = li ;
74- extension = this . fileExtensions [ this . imageFormats [ i ] ] [ j ] ;
7582 }
7683 }
7784
78- return extension ;
85+ return null ;
7986 }
8087
8188 /// <summary>
8289 /// Gets the correct extension for the given content type (mime-type).
8390 /// </summary>
8491 /// <param name="contentType">The content type (mime-type).</param>
85- /// <returns>The <see cref="string"/>.</returns>
92+ /// <returns>The <see cref="string" />.</returns>
8693 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
87- public string GetExtensionFromContentType ( string contentType ) => this . fileExtension [ contentType ] ;
94+ public string GetExtensionFromContentType ( string contentType ) => this . extensionsByMimeType [ contentType ] ;
8895 }
8996}
0 commit comments