99using SixLabors . ImageSharp . Memory ;
1010using SixLabors . ImageSharp . Metadata ;
1111using SixLabors . ImageSharp . Metadata . Profiles . Exif ;
12+ using SixLabors . ImageSharp . Metadata . Profiles . Icc ;
1213using SixLabors . ImageSharp . Metadata . Profiles . Xmp ;
1314
1415namespace SixLabors . ImageSharp . Formats . Webp ;
@@ -258,6 +259,9 @@ public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte>
258259 /// <param name="stream">The stream to read from.</param>
259260 /// <param name="buffer">The buffer to store the read data into.</param>
260261 /// <returns>A unsigned 24 bit integer.</returns>
262+ /// <exception cref="ImageFormatException">
263+ /// Thrown if the input stream is not valid.
264+ /// </exception>
261265 public static uint ReadUInt24LittleEndian ( Stream stream , Span < byte > buffer )
262266 {
263267 if ( stream . Read ( buffer , 0 , 3 ) == 3 )
@@ -272,8 +276,11 @@ public static uint ReadUInt24LittleEndian(Stream stream, Span<byte> buffer)
272276 /// <summary>
273277 /// Writes a unsigned 24 bit integer.
274278 /// </summary>
275- /// <param name="stream">The stream to read from .</param>
279+ /// <param name="stream">The stream to write to .</param>
276280 /// <param name="data">The uint24 data to write.</param>
281+ /// <exception cref="InvalidDataException">
282+ /// Thrown if the data is not a valid unsigned 24 bit integer.
283+ /// </exception>
277284 public static unsafe void WriteUInt24LittleEndian ( Stream stream , uint data )
278285 {
279286 if ( data >= 1 << 24 )
@@ -296,18 +303,24 @@ public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data)
296303 /// </summary>
297304 /// <param name="stream">The stream to read the data from.</param>
298305 /// <param name="buffer">Buffer to store the data read from the stream.</param>
306+ /// <param name="required">If true, the chunk size is required to be read, otherwise it can be skipped.</param>
299307 /// <returns>The chunk size in bytes.</returns>
300- public static uint ReadChunkSize ( Stream stream , Span < byte > buffer )
308+ /// <exception cref="ImageFormatException">Thrown if the input stream is not valid.</exception>
309+ public static uint ReadChunkSize ( Stream stream , Span < byte > buffer , bool required = true )
301310 {
302- DebugGuard . IsTrue ( buffer . Length is 4 , "buffer has wrong length" ) ;
303-
304311 if ( stream . Read ( buffer ) is 4 )
305312 {
306313 uint chunkSize = BinaryPrimitives . ReadUInt32LittleEndian ( buffer ) ;
307314 return chunkSize % 2 is 0 ? chunkSize : chunkSize + 1 ;
308315 }
309316
310- throw new ImageFormatException ( "Invalid Webp data, could not read chunk size." ) ;
317+ if ( required )
318+ {
319+ throw new ImageFormatException ( "Invalid Webp data, could not read chunk size." ) ;
320+ }
321+
322+ // Return the size of the remaining data in the stream.
323+ return ( uint ) ( stream . Length - stream . Position ) ;
311324 }
312325
313326 /// <summary>
@@ -320,14 +333,13 @@ public static uint ReadChunkSize(Stream stream, Span<byte> buffer)
320333 /// </exception>
321334 public static WebpChunkType ReadChunkType ( BufferedReadStream stream , Span < byte > buffer )
322335 {
323- DebugGuard . IsTrue ( buffer . Length == 4 , "buffer has wrong length" ) ;
324-
325336 if ( stream . Read ( buffer ) == 4 )
326337 {
327- WebpChunkType chunkType = ( WebpChunkType ) BinaryPrimitives . ReadUInt32BigEndian ( buffer ) ;
328- return chunkType ;
338+ return ( WebpChunkType ) BinaryPrimitives . ReadUInt32BigEndian ( buffer ) ;
329339 }
330340
341+ // While we ignore unknown chunks we still need a to be a ble to read a chunk type
342+ // known or otherwise from the stream.
331343 throw new ImageFormatException ( "Invalid Webp data, could not read chunk type." ) ;
332344 }
333345
@@ -336,82 +348,182 @@ public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte>
336348 /// If there are more such chunks, readers MAY ignore all except the first one.
337349 /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
338350 /// </summary>
351+ /// <param name="stream">The stream to read the data from.</param>
352+ /// <param name="chunkType">The chunk type to parse.</param>
353+ /// <param name="metadata">The image metadata to write to.</param>
354+ /// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
355+ /// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
356+ /// <param name="buffer">Buffer to store the data read from the stream.</param>
339357 public static void ParseOptionalChunks (
340358 BufferedReadStream stream ,
341359 WebpChunkType chunkType ,
342360 ImageMetadata metadata ,
343- bool ignoreMetaData ,
361+ bool ignoreMetadata ,
344362 SegmentIntegrityHandling segmentIntegrityHandling ,
345363 Span < byte > buffer )
346364 {
347365 long streamLength = stream . Length ;
348366 while ( stream . Position < streamLength )
349367 {
350- uint chunkLength = ReadChunkSize ( stream , buffer ) ;
351-
352- if ( ignoreMetaData )
353- {
354- stream . Skip ( ( int ) chunkLength ) ;
355- }
356-
357- int bytesRead ;
358368 switch ( chunkType )
359369 {
360- case WebpChunkType . Exif :
361- byte [ ] exifData = new byte [ chunkLength ] ;
362- bytesRead = stream . Read ( exifData , 0 , ( int ) chunkLength ) ;
363- if ( bytesRead != chunkLength )
364- {
365- if ( segmentIntegrityHandling == SegmentIntegrityHandling . IgnoreNone )
366- {
367- WebpThrowHelper . ThrowImageFormatException ( "Could not read enough data for the EXIF profile" ) ;
368- }
369-
370- return ;
371- }
372-
373- if ( metadata . ExifProfile == null )
374- {
375- ExifProfile exifProfile = new ( exifData ) ;
376-
377- // Set the resolution from the metadata.
378- double horizontalValue = GetExifResolutionValue ( exifProfile , ExifTag . XResolution ) ;
379- double verticalValue = GetExifResolutionValue ( exifProfile , ExifTag . YResolution ) ;
380-
381- if ( horizontalValue > 0 && verticalValue > 0 )
382- {
383- metadata . HorizontalResolution = horizontalValue ;
384- metadata . VerticalResolution = verticalValue ;
385- metadata . ResolutionUnits = UnitConverter . ExifProfileToResolutionUnit ( exifProfile ) ;
386- }
387-
388- metadata . ExifProfile = exifProfile ;
389- }
370+ case WebpChunkType . Iccp :
371+ ReadIccProfile ( stream , metadata , ignoreMetadata , segmentIntegrityHandling , buffer ) ;
372+ break ;
390373
374+ case WebpChunkType . Exif :
375+ ReadExifProfile ( stream , metadata , ignoreMetadata , segmentIntegrityHandling , buffer ) ;
391376 break ;
392377 case WebpChunkType . Xmp :
393- byte [ ] xmpData = new byte [ chunkLength ] ;
394- bytesRead = stream . Read ( xmpData , 0 , ( int ) chunkLength ) ;
395- if ( bytesRead != chunkLength )
396- {
397- if ( segmentIntegrityHandling == SegmentIntegrityHandling . IgnoreNone )
398- {
399- WebpThrowHelper . ThrowImageFormatException ( "Could not read enough data for the XMP profile" ) ;
400- }
401-
402- return ;
403- }
404-
405- metadata . XmpProfile ??= new XmpProfile ( xmpData ) ;
406-
378+ ReadXmpProfile ( stream , metadata , ignoreMetadata , segmentIntegrityHandling , buffer ) ;
407379 break ;
408380 default :
381+
382+ // Ignore unknown chunks.
383+ // These must always fall after the image data so we are safe to always skip them.
384+ uint chunkLength = ReadChunkSize ( stream , buffer , false ) ;
409385 stream . Skip ( ( int ) chunkLength ) ;
410386 break ;
411387 }
412388 }
413389 }
414390
391+ /// <summary>
392+ /// Reads the ICCP chunk from the stream.
393+ /// </summary>
394+ /// <param name="stream">The stream to decode from.</param>
395+ /// <param name="metadata">The image metadata.</param>
396+ /// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
397+ /// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
398+ /// <param name="buffer">Temporary buffer.</param>
399+ public static void ReadIccProfile (
400+ BufferedReadStream stream ,
401+ ImageMetadata metadata ,
402+ bool ignoreMetadata ,
403+ SegmentIntegrityHandling segmentIntegrityHandling ,
404+ Span < byte > buffer )
405+ {
406+ // While ICC profiles are optional, an invalid ICC profile cannot be ignored as it must precede the image data
407+ // and since we canot determine its size to allow skipping without reading the chunk size, we have to throw if it's invalid.
408+ // Hence we do not consider segment integrity handling here.
409+ uint iccpChunkSize = ReadChunkSize ( stream , buffer ) ;
410+ if ( ignoreMetadata || metadata . IccProfile != null )
411+ {
412+ stream . Skip ( ( int ) iccpChunkSize ) ;
413+ }
414+ else
415+ {
416+ bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling . IgnoreNone ;
417+ byte [ ] iccpData = new byte [ iccpChunkSize ] ;
418+ int bytesRead = stream . Read ( iccpData , 0 , ( int ) iccpChunkSize ) ;
419+
420+ // We have the size but the profile is invalid if we cannot read enough data.
421+ // Use the segment integrity handling to determine if we throw.
422+ if ( bytesRead != iccpChunkSize && ignoreNone )
423+ {
424+ WebpThrowHelper . ThrowInvalidImageContentException ( "Not enough data to read the iccp chunk" ) ;
425+ }
426+
427+ IccProfile profile = new ( iccpData ) ;
428+ if ( profile . CheckIsValid ( ) )
429+ {
430+ metadata . IccProfile = profile ;
431+ }
432+ }
433+ }
434+
435+ /// <summary>
436+ /// Reads the EXIF profile from the stream.
437+ /// </summary>
438+ /// <param name="stream">The stream to decode from.</param>
439+ /// <param name="metadata">The image metadata.</param>
440+ /// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
441+ /// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
442+ /// <param name="buffer">Temporary buffer.</param>
443+ public static void ReadExifProfile (
444+ BufferedReadStream stream ,
445+ ImageMetadata metadata ,
446+ bool ignoreMetadata ,
447+ SegmentIntegrityHandling segmentIntegrityHandling ,
448+ Span < byte > buffer )
449+ {
450+ bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling . IgnoreNone ;
451+ uint exifChunkSize = ReadChunkSize ( stream , buffer , ignoreNone ) ;
452+ if ( ignoreMetadata || metadata . ExifProfile != null )
453+ {
454+ stream . Skip ( ( int ) exifChunkSize ) ;
455+ }
456+ else
457+ {
458+ byte [ ] exifData = new byte [ exifChunkSize ] ;
459+ int bytesRead = stream . Read ( exifData , 0 , ( int ) exifChunkSize ) ;
460+ if ( bytesRead != exifChunkSize )
461+ {
462+ if ( ignoreNone )
463+ {
464+ WebpThrowHelper . ThrowImageFormatException ( "Could not read enough data for the EXIF profile" ) ;
465+ }
466+
467+ return ;
468+ }
469+
470+ ExifProfile exifProfile = new ( exifData ) ;
471+
472+ // Set the resolution from the metadata.
473+ double horizontalValue = GetExifResolutionValue ( exifProfile , ExifTag . XResolution ) ;
474+ double verticalValue = GetExifResolutionValue ( exifProfile , ExifTag . YResolution ) ;
475+
476+ if ( horizontalValue > 0 && verticalValue > 0 )
477+ {
478+ metadata . HorizontalResolution = horizontalValue ;
479+ metadata . VerticalResolution = verticalValue ;
480+ metadata . ResolutionUnits = UnitConverter . ExifProfileToResolutionUnit ( exifProfile ) ;
481+ }
482+
483+ metadata . ExifProfile = exifProfile ;
484+ }
485+ }
486+
487+ /// <summary>
488+ /// Reads the XMP profile the stream.
489+ /// </summary>
490+ /// <param name="stream">The stream to decode from.</param>
491+ /// <param name="metadata">The image metadata.</param>
492+ /// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
493+ /// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
494+ /// <param name="buffer">Temporary buffer.</param>
495+ public static void ReadXmpProfile (
496+ BufferedReadStream stream ,
497+ ImageMetadata metadata ,
498+ bool ignoreMetadata ,
499+ SegmentIntegrityHandling segmentIntegrityHandling ,
500+ Span < byte > buffer )
501+ {
502+ bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling . IgnoreNone ;
503+
504+ uint xmpChunkSize = ReadChunkSize ( stream , buffer , ignoreNone ) ;
505+ if ( ignoreMetadata || metadata . XmpProfile != null )
506+ {
507+ stream . Skip ( ( int ) xmpChunkSize ) ;
508+ }
509+ else
510+ {
511+ byte [ ] xmpData = new byte [ xmpChunkSize ] ;
512+ int bytesRead = stream . Read ( xmpData , 0 , ( int ) xmpChunkSize ) ;
513+ if ( bytesRead != xmpChunkSize )
514+ {
515+ if ( ignoreNone )
516+ {
517+ WebpThrowHelper . ThrowImageFormatException ( "Could not read enough data for the XMP profile" ) ;
518+ }
519+
520+ return ;
521+ }
522+
523+ metadata . XmpProfile = new XmpProfile ( xmpData ) ;
524+ }
525+ }
526+
415527 private static double GetExifResolutionValue ( ExifProfile exifProfile , ExifTag < Rational > tag )
416528 {
417529 if ( exifProfile . TryGetValue ( tag , out IExifValue < Rational > ? resolution ) )
0 commit comments