@@ -120,9 +120,103 @@ public bool CanHandle(string path)
120120 if ( WebHandler . TryCanHandle ( path ) )
121121 return true ;
122122
123+ if ( Directory . Exists ( path ) )
124+ return false ;
125+
123126 // Disabled due mishandling text file types e.g., "*.config".
124127 // Only check extension for well known image and animated image types.
125- return ! Directory . Exists ( path ) && WellKnownExtensions . Any ( ext => path . EndsWith ( ext , StringComparison . OrdinalIgnoreCase ) ) ;
128+ if ( WellKnownExtensions . Any ( ext => path . EndsWith ( ext , StringComparison . OrdinalIgnoreCase ) ) )
129+ return true ;
130+
131+ // For files without extensions, check magic numbers for common image formats
132+ if ( ! Path . HasExtension ( path ) )
133+ {
134+ return IsImageByMagicNumber ( path ) ;
135+ }
136+
137+ return false ;
138+ }
139+
140+ private static bool IsImageByMagicNumber ( string path )
141+ {
142+ try
143+ {
144+ if ( ! File . Exists ( path ) )
145+ return false ;
146+
147+ ReadOnlySpan < byte > pngSignature = new byte [ ] { 0x89 , 0x50 , 0x4E , 0x47 , 0x0D , 0x0A , 0x1A , 0x0A } ;
148+ ReadOnlySpan < byte > jpegSignature = new byte [ ] { 0xFF , 0xD8 , 0xFF } ;
149+ ReadOnlySpan < byte > gif87Signature = new byte [ ] { 0x47 , 0x49 , 0x46 , 0x38 , 0x37 , 0x61 } ;
150+ ReadOnlySpan < byte > gif89Signature = new byte [ ] { 0x47 , 0x49 , 0x46 , 0x38 , 0x39 , 0x61 } ;
151+ ReadOnlySpan < byte > bmpSignature = new byte [ ] { 0x42 , 0x4D } ;
152+ ReadOnlySpan < byte > webpRiffSignature = new byte [ ] { 0x52 , 0x49 , 0x46 , 0x46 } ;
153+ ReadOnlySpan < byte > webpWebpSignature = new byte [ ] { 0x57 , 0x45 , 0x42 , 0x50 } ;
154+
155+ const int maxSignatureLength = 12 ;
156+
157+ using var fs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
158+ if ( fs . Length < bmpSignature . Length )
159+ return false ;
160+
161+ var buffer = new byte [ maxSignatureLength ] ;
162+ var bytesRead = fs . Read ( buffer , 0 , buffer . Length ) ;
163+ if ( bytesRead < bmpSignature . Length )
164+ return false ;
165+
166+ // PNG: 89 50 4E 47 0D 0A 1A 0A
167+ if ( bytesRead >= pngSignature . Length &&
168+ buffer . AsSpan ( 0 , pngSignature . Length ) . SequenceEqual ( pngSignature ) )
169+ {
170+ return true ;
171+ }
172+
173+ // JPEG: FF D8 FF
174+ if ( bytesRead >= jpegSignature . Length &&
175+ buffer . AsSpan ( 0 , jpegSignature . Length ) . SequenceEqual ( jpegSignature ) )
176+ {
177+ return true ;
178+ }
179+
180+ // GIF: GIF87a or GIF89a
181+ if ( bytesRead >= gif87Signature . Length &&
182+ ( buffer . AsSpan ( 0 , gif87Signature . Length ) . SequenceEqual ( gif87Signature ) ||
183+ buffer . AsSpan ( 0 , gif89Signature . Length ) . SequenceEqual ( gif89Signature ) ) )
184+ {
185+ return true ;
186+ }
187+
188+ // BMP: BM
189+ if ( bytesRead >= bmpSignature . Length &&
190+ buffer . AsSpan ( 0 , bmpSignature . Length ) . SequenceEqual ( bmpSignature ) )
191+ {
192+ return true ;
193+ }
194+
195+ // WebP: RIFF....WEBP
196+ if ( bytesRead >= 12 &&
197+ buffer . AsSpan ( 0 , webpRiffSignature . Length ) . SequenceEqual ( webpRiffSignature ) &&
198+ buffer . AsSpan ( 8 , webpWebpSignature . Length ) . SequenceEqual ( webpWebpSignature ) )
199+ {
200+ return true ;
201+ }
202+
203+ return false ;
204+ }
205+ catch ( IOException ex )
206+ {
207+ ProcessHelper . WriteLog ( $ "IO error while checking image magic number for { path } : { ex . Message } ") ;
208+ return false ;
209+ }
210+ catch ( UnauthorizedAccessException ex )
211+ {
212+ ProcessHelper . WriteLog ( $ "Access denied while checking image magic number for { path } : { ex . Message } ") ;
213+ return false ;
214+ }
215+ catch ( Exception ex )
216+ {
217+ ProcessHelper . WriteLog ( $ "Unexpected error while checking image magic number for { path } : { ex } ") ;
218+ return false ;
219+ }
126220 }
127221
128222 public void Prepare ( string path , ContextObject context )
0 commit comments