@@ -146,10 +146,10 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
146146 this . ReadGraphicalControlExtension ( stream ) ;
147147 break ;
148148 case GifConstants . CommentLabel :
149- this . ReadComments ( stream ) ;
149+ this . ExecuteAncillarySegmentAction ( ( ) => this . ReadComments ( stream ) ) ;
150150 break ;
151151 case GifConstants . ApplicationExtensionLabel :
152- this . ReadApplicationExtension ( stream ) ;
152+ this . ExecuteAncillarySegmentAction ( ( ) => this . ReadApplicationExtension ( stream ) ) ;
153153 break ;
154154 case GifConstants . PlainTextLabel :
155155 SkipBlock ( stream ) ; // Not supported by any known decoder.
@@ -226,10 +226,10 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok
226226 this . ReadGraphicalControlExtension ( stream ) ;
227227 break ;
228228 case GifConstants . CommentLabel :
229- this . ReadComments ( stream ) ;
229+ this . ExecuteAncillarySegmentAction ( ( ) => this . ReadComments ( stream ) ) ;
230230 break ;
231231 case GifConstants . ApplicationExtensionLabel :
232- this . ReadApplicationExtension ( stream ) ;
232+ this . ExecuteAncillarySegmentAction ( ( ) => this . ReadApplicationExtension ( stream ) ) ;
233233 break ;
234234 case GifConstants . PlainTextLabel :
235235 SkipBlock ( stream ) ; // Not supported by any known decoder.
@@ -266,6 +266,13 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok
266266 GifThrowHelper . ThrowNoHeader ( ) ;
267267 }
268268
269+ // Ignoring a malformed ancillary extension must not let identify succeed for a file
270+ // that never contained any readable image frame data.
271+ if ( previousFrame is null )
272+ {
273+ GifThrowHelper . ThrowNoData ( ) ;
274+ }
275+
269276 return new ImageInfo (
270277 new Size ( this . logicalScreenDescriptor . Width , this . logicalScreenDescriptor . Height ) ,
271278 this . metadata ,
@@ -331,51 +338,128 @@ private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
331338 private void ReadApplicationExtension ( BufferedReadStream stream )
332339 {
333340 int appLength = stream . ReadByte ( ) ;
341+ if ( appLength == - 1 )
342+ {
343+ GifThrowHelper . ThrowInvalidImageContentException ( "Unexpected end of stream while reading gif application extension" ) ;
344+ }
345+
346+ if ( appLength != GifConstants . ApplicationBlockSize )
347+ {
348+ this . ThrowOrIgnoreNonStrictSegmentError ( $ "Gif application extension length '{ appLength } ' is invalid") ;
349+ SkipBlock ( stream , appLength ) ;
350+ return ;
351+ }
334352
335353 // If the length is 11 then it's a valid extension and most likely
336354 // a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this.
337355 long position = stream . Position ;
338- if ( appLength == GifConstants . ApplicationBlockSize )
356+ int bytesRead = stream . Read ( this . buffer . Span , 0 , GifConstants . ApplicationBlockSize ) ;
357+ if ( bytesRead != GifConstants . ApplicationBlockSize )
339358 {
340- stream . Read ( this . buffer . Span , 0 , GifConstants . ApplicationBlockSize ) ;
341- bool isXmp = this . buffer . Span . StartsWith ( GifConstants . XmpApplicationIdentificationBytes ) ;
342- if ( isXmp && ! this . skipMetadata )
343- {
344- GifXmpApplicationExtension extension = GifXmpApplicationExtension . Read ( stream , this . memoryAllocator ) ;
345- if ( extension . Data . Length > 0 )
346- {
347- this . metadata ! . XmpProfile = new XmpProfile ( extension . Data ) ;
348- }
349- else
350- {
351- // Reset the stream position and continue.
352- stream . Position = position ;
353- SkipBlock ( stream , appLength ) ;
354- }
359+ GifThrowHelper . ThrowInvalidImageContentException ( "Unexpected end of stream while reading gif application extension" ) ;
360+ }
355361
356- return ;
357- }
362+ bool isXmp = this . buffer . Span . StartsWith ( GifConstants . XmpApplicationIdentificationBytes ) ;
363+ if ( isXmp )
364+ {
365+ this . ReadXmpApplicationExtension ( stream , position , appLength ) ;
366+ return ;
367+ }
358368
359- int subBlockSize = stream . ReadByte ( ) ;
369+ int subBlockSize = stream . ReadByte ( ) ;
370+ if ( subBlockSize == - 1 )
371+ {
372+ GifThrowHelper . ThrowInvalidImageContentException ( "Unexpected end of stream while reading gif application extension" ) ;
373+ }
360374
361- // TODO: There's also a NETSCAPE buffer extension.
362- // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
363- if ( subBlockSize == GifConstants . NetscapeLoopingSubBlockSize )
364- {
365- stream . Read ( this . buffer . Span , 0 , GifConstants . NetscapeLoopingSubBlockSize ) ;
366- this . gifMetadata ! . RepeatCount = GifNetscapeLoopingApplicationExtension . Parse ( this . buffer . Span [ 1 ..] ) . RepeatCount ;
367- stream . Skip ( 1 ) ; // Skip the terminator.
368- return ;
369- }
375+ // TODO: There's also a NETSCAPE buffer extension.
376+ // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
377+ if ( subBlockSize == GifConstants . NetscapeLoopingSubBlockSize )
378+ {
379+ this . ReadNetscapeApplicationExtension ( stream ) ;
380+ return ;
381+ }
382+
383+ // Could be something else not supported yet.
384+ // Skip the subblock and terminator.
385+ SkipBlock ( stream , subBlockSize ) ;
386+ }
387+
388+ /// <summary>
389+ /// Reads the GIF XMP application extension.
390+ /// </summary>
391+ /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
392+ /// <param name="applicationPosition">The stream position where the application identifier begins.</param>
393+ /// <param name="appLength">The application block length.</param>
394+ private void ReadXmpApplicationExtension ( BufferedReadStream stream , long applicationPosition , int appLength )
395+ {
396+ if ( this . skipMetadata )
397+ {
398+ stream . Position = applicationPosition ;
399+ SkipBlock ( stream , appLength ) ;
400+ return ;
401+ }
370402
371- // Could be something else not supported yet.
372- // Skip the subblock and terminator.
373- SkipBlock ( stream , subBlockSize ) ;
403+ bool completed = false ;
404+ this . ExecuteAncillarySegmentAction (
405+ ( ) =>
406+ {
407+ this . ReadXmpApplicationExtensionData ( stream , applicationPosition , appLength ) ;
408+ completed = true ;
409+ } ) ;
410+
411+ if ( ! completed )
412+ {
413+ stream . Position = applicationPosition ;
414+ SkipBlock ( stream , appLength ) ;
415+ }
416+ }
374417
418+ /// <summary>
419+ /// Reads the GIF XMP application extension data.
420+ /// </summary>
421+ /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
422+ /// <param name="applicationPosition">The stream position where the application identifier begins.</param>
423+ /// <param name="appLength">The application block length.</param>
424+ private void ReadXmpApplicationExtensionData ( BufferedReadStream stream , long applicationPosition , int appLength )
425+ {
426+ GifXmpApplicationExtension extension = GifXmpApplicationExtension . Read ( stream , this . memoryAllocator ) ;
427+ if ( extension . Data . Length > 0 )
428+ {
429+ this . metadata ! . XmpProfile = new XmpProfile ( extension . Data ) ;
375430 return ;
376431 }
377432
378- SkipBlock ( stream , appLength ) ; // Not supported by any known decoder.
433+ stream . Position = applicationPosition ;
434+ SkipBlock ( stream , appLength ) ;
435+ }
436+
437+ /// <summary>
438+ /// Reads the GIF NETSCAPE looping application extension.
439+ /// </summary>
440+ /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
441+ private void ReadNetscapeApplicationExtension ( BufferedReadStream stream ) =>
442+ this . ExecuteAncillarySegmentAction ( ( ) => this . ReadNetscapeApplicationExtensionData ( stream ) ) ;
443+
444+ /// <summary>
445+ /// Reads the GIF NETSCAPE looping application extension data.
446+ /// </summary>
447+ /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
448+ private void ReadNetscapeApplicationExtensionData ( BufferedReadStream stream )
449+ {
450+ int bytesRead = stream . Read ( this . buffer . Span , 0 , GifConstants . NetscapeLoopingSubBlockSize ) ;
451+ if ( bytesRead != GifConstants . NetscapeLoopingSubBlockSize )
452+ {
453+ throw new InvalidImageContentException ( "Unexpected end of stream while reading gif application extension" ) ;
454+ }
455+
456+ this . gifMetadata ! . RepeatCount = GifNetscapeLoopingApplicationExtension . Parse ( this . buffer . Span [ 1 ..] ) . RepeatCount ;
457+
458+ int terminator = stream . ReadByte ( ) ;
459+ if ( terminator == - 1 )
460+ {
461+ throw new InvalidImageContentException ( "Unexpected end of stream while reading gif application extension" ) ;
462+ }
379463 }
380464
381465 /// <summary>
@@ -428,7 +512,12 @@ private void ReadComments(BufferedReadStream stream)
428512 using IMemoryOwner < byte > commentsBuffer = this . memoryAllocator . Allocate < byte > ( length ) ;
429513 Span < byte > commentsSpan = commentsBuffer . GetSpan ( ) ;
430514
431- stream . Read ( commentsSpan ) ;
515+ int bytesRead = stream . Read ( commentsSpan ) ;
516+ if ( bytesRead != length )
517+ {
518+ GifThrowHelper . ThrowInvalidImageContentException ( "Unexpected end of stream while reading gif comment" ) ;
519+ }
520+
432521 string commentPart = GifConstants . Encoding . GetString ( commentsSpan ) ;
433522 stringBuilder . Append ( commentPart ) ;
434523 }
0 commit comments