WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 750daad

Browse files
Merge pull request #3021 from SixLabors/js/fix-3010
Normalize WebP Chunk parsing.
2 parents 84476b8 + 4979e99 commit 750daad

File tree

3 files changed

+205
-239
lines changed

3 files changed

+205
-239
lines changed

src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,12 @@ public ImageInfo Identify(
112112
this.webpMetadata = this.metadata.GetWebpMetadata();
113113
this.webpMetadata.RepeatCount = features.AnimationLoopCount;
114114

115-
Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
115+
this.webpMetadata.BackgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
116116
? Color.Transparent
117117
: features.AnimationBackgroundColor!.Value;
118118

119-
this.webpMetadata.BackgroundColor = backgroundColor;
120-
119+
bool ignoreMetadata = this.skipMetadata;
120+
SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling;
121121
Span<byte> buffer = stackalloc byte[4];
122122
uint frameCount = 0;
123123
int remainingBytes = (int)completeDataSize;
@@ -135,9 +135,16 @@ public ImageInfo Identify(
135135

136136
remainingBytes -= (int)dataSize;
137137
break;
138+
case WebpChunkType.Iccp:
138139
case WebpChunkType.Xmp:
139140
case WebpChunkType.Exif:
140-
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, this.metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer);
141+
WebpChunkParsingUtils.ParseOptionalChunks(
142+
stream,
143+
chunkType,
144+
this.metadata,
145+
ignoreMetadata,
146+
segmentIntegrityHandling,
147+
buffer);
141148
break;
142149
default:
143150

@@ -187,9 +194,12 @@ public Image<TPixel> Decode<TPixel>(
187194
this.webpMetadata.BackgroundColor = backgroundColor;
188195
TPixel backgroundPixel = backgroundColor.ToPixel<TPixel>();
189196

197+
bool ignoreMetadata = this.skipMetadata;
198+
SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling;
190199
Span<byte> buffer = stackalloc byte[4];
191200
uint frameCount = 0;
192201
int remainingBytes = (int)completeDataSize;
202+
193203
while (remainingBytes > 0)
194204
{
195205
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
@@ -209,9 +219,10 @@ public Image<TPixel> Decode<TPixel>(
209219

210220
remainingBytes -= (int)dataSize;
211221
break;
222+
case WebpChunkType.Iccp:
212223
case WebpChunkType.Xmp:
213224
case WebpChunkType.Exif:
214-
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer);
225+
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, ignoreMetadata, segmentIntegrityHandling, buffer);
215226
break;
216227
default:
217228

src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

Lines changed: 174 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using SixLabors.ImageSharp.Memory;
1010
using SixLabors.ImageSharp.Metadata;
1111
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
12+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
1213
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
1314

1415
namespace 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

Comments
 (0)