Skip to content

Commit 47ac160

Browse files
Normalize WebP Chunk parsing. Fixes #3010
1 parent 84476b8 commit 47ac160

File tree

2 files changed

+46
-65
lines changed

2 files changed

+46
-65
lines changed

src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte>
258258
/// <param name="stream">The stream to read from.</param>
259259
/// <param name="buffer">The buffer to store the read data into.</param>
260260
/// <returns>A unsigned 24 bit integer.</returns>
261+
/// <exception cref="ImageFormatException">
262+
/// Thrown if the input stream is not valid.
263+
/// </exception>
261264
public static uint ReadUInt24LittleEndian(Stream stream, Span<byte> buffer)
262265
{
263266
if (stream.Read(buffer, 0, 3) == 3)
@@ -274,6 +277,9 @@ public static uint ReadUInt24LittleEndian(Stream stream, Span<byte> buffer)
274277
/// </summary>
275278
/// <param name="stream">The stream to read from.</param>
276279
/// <param name="data">The uint24 data to write.</param>
280+
/// <exception cref="InvalidDataException">
281+
/// Thrown if the data is not a valid unsigned 24 bit integer.
282+
/// </exception>
277283
public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data)
278284
{
279285
if (data >= 1 << 24)
@@ -296,18 +302,24 @@ public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data)
296302
/// </summary>
297303
/// <param name="stream">The stream to read the data from.</param>
298304
/// <param name="buffer">Buffer to store the data read from the stream.</param>
305+
/// <param name="required">If true, the chunk size is required to be read, otherwise it can be skipped.</param>
299306
/// <returns>The chunk size in bytes.</returns>
300-
public static uint ReadChunkSize(Stream stream, Span<byte> buffer)
307+
/// <exception cref="ImageFormatException">Thrown if the input stream is not valid.</exception>
308+
public static uint ReadChunkSize(Stream stream, Span<byte> buffer, bool required = true)
301309
{
302-
DebugGuard.IsTrue(buffer.Length is 4, "buffer has wrong length");
303-
304310
if (stream.Read(buffer) is 4)
305311
{
306312
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
307313
return chunkSize % 2 is 0 ? chunkSize : chunkSize + 1;
308314
}
309315

310-
throw new ImageFormatException("Invalid Webp data, could not read chunk size.");
316+
if (required)
317+
{
318+
throw new ImageFormatException("Invalid Webp data, could not read chunk size.");
319+
}
320+
321+
// Return the size of the remaining data in the stream.
322+
return (uint)(stream.Length - stream.Position);
311323
}
312324

313325
/// <summary>
@@ -320,14 +332,12 @@ public static uint ReadChunkSize(Stream stream, Span<byte> buffer)
320332
/// </exception>
321333
public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer)
322334
{
323-
DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length");
324-
325335
if (stream.Read(buffer) == 4)
326336
{
327-
WebpChunkType chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
328-
return chunkType;
337+
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
329338
}
330339

340+
// We should ignore unknown chunks but still be able to read the type.
331341
throw new ImageFormatException("Invalid Webp data, could not read chunk type.");
332342
}
333343

@@ -336,6 +346,12 @@ public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte>
336346
/// If there are more such chunks, readers MAY ignore all except the first one.
337347
/// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
338348
/// </summary>
349+
/// <param name="stream">The stream to read the data from.</param>
350+
/// <param name="chunkType">The chunk type to parse.</param>
351+
/// <param name="metadata">The image metadata to write to.</param>
352+
/// <param name="ignoreMetaData">If true, metadata will be ignored.</param>
353+
/// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
354+
/// <param name="buffer">Buffer to store the data read from the stream.</param>
339355
public static void ParseOptionalChunks(
340356
BufferedReadStream stream,
341357
WebpChunkType chunkType,
@@ -344,10 +360,13 @@ public static void ParseOptionalChunks(
344360
SegmentIntegrityHandling segmentIntegrityHandling,
345361
Span<byte> buffer)
346362
{
363+
bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone;
347364
long streamLength = stream.Length;
348365
while (stream.Position < streamLength)
349366
{
350-
uint chunkLength = ReadChunkSize(stream, buffer);
367+
// Ignore unknown chunk types or when metadata is to be ignored.
368+
// If handling should ignore none, we still need to validate the chunk length.
369+
uint chunkLength = ReadChunkSize(stream, buffer, ignoreNone && (chunkType is WebpChunkType.Exif or WebpChunkType.Xmp) && !ignoreMetaData);
351370

352371
if (ignoreMetaData)
353372
{
@@ -362,7 +381,7 @@ public static void ParseOptionalChunks(
362381
bytesRead = stream.Read(exifData, 0, (int)chunkLength);
363382
if (bytesRead != chunkLength)
364383
{
365-
if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
384+
if (ignoreNone)
366385
{
367386
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
368387
}
@@ -394,7 +413,7 @@ public static void ParseOptionalChunks(
394413
bytesRead = stream.Read(xmpData, 0, (int)chunkLength);
395414
if (bytesRead != chunkLength)
396415
{
397-
if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
416+
if (ignoreNone)
398417
{
399418
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
400419
}

src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metad
248248
else
249249
{
250250
// Ignore unknown chunks.
251-
uint chunkSize = ReadChunkSize(stream, buffer, false);
251+
uint chunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer, false);
252252
stream.Skip((int)chunkSize);
253253
}
254254
}
@@ -328,7 +328,7 @@ private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metada
328328
while (stream.Position < streamLength)
329329
{
330330
// Read chunk header.
331-
WebpChunkType chunkType = ReadChunkType(stream, buffer);
331+
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
332332
if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null)
333333
{
334334
this.ReadExifProfile(stream, metadata, buffer);
@@ -340,7 +340,7 @@ private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metada
340340
else
341341
{
342342
// Skip duplicate XMP or EXIF chunk.
343-
uint chunkLength = ReadChunkSize(stream, buffer);
343+
uint chunkLength = WebpChunkParsingUtils.ReadChunkSize(stream, buffer, false);
344344
stream.Skip((int)chunkLength);
345345
}
346346
}
@@ -354,8 +354,11 @@ private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metada
354354
/// <param name="buffer">Temporary buffer.</param>
355355
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
356356
{
357-
uint exifChunkSize = ReadChunkSize(stream, buffer);
358-
if (this.skipMetadata)
357+
bool ignoreMetadata = this.skipMetadata;
358+
bool ignoreNone = this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone && !ignoreMetadata;
359+
360+
uint exifChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer, ignoreNone);
361+
if (ignoreMetadata)
359362
{
360363
stream.Skip((int)exifChunkSize);
361364
}
@@ -365,7 +368,7 @@ private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata,
365368
int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
366369
if (bytesRead != exifChunkSize)
367370
{
368-
if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
371+
if (ignoreNone)
369372
{
370373
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
371374
}
@@ -408,8 +411,11 @@ private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag<Ra
408411
/// <param name="buffer">Temporary buffer.</param>
409412
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
410413
{
411-
uint xmpChunkSize = ReadChunkSize(stream, buffer);
412-
if (this.skipMetadata)
414+
bool ignoreMetadata = this.skipMetadata;
415+
bool ignoreNone = this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone && !ignoreMetadata;
416+
417+
uint xmpChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer, ignoreNone);
418+
if (ignoreMetadata)
413419
{
414420
stream.Skip((int)xmpChunkSize);
415421
}
@@ -419,7 +425,7 @@ private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, S
419425
int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
420426
if (bytesRead != xmpChunkSize)
421427
{
422-
if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
428+
if (ignoreNone)
423429
{
424430
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
425431
}
@@ -439,7 +445,7 @@ private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, S
439445
/// <param name="buffer">Temporary buffer.</param>
440446
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
441447
{
442-
uint iccpChunkSize = ReadChunkSize(stream, buffer);
448+
uint iccpChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer);
443449
if (this.skipMetadata)
444450
{
445451
stream.Skip((int)iccpChunkSize);
@@ -512,50 +518,6 @@ private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, boo
512518
}
513519
}
514520

515-
/// <summary>
516-
/// Identifies the chunk type from the chunk.
517-
/// </summary>
518-
/// <param name="stream">The stream to decode from.</param>
519-
/// <param name="buffer">Temporary buffer.</param>
520-
/// <exception cref="ImageFormatException">
521-
/// Thrown if the input stream is not valid.
522-
/// </exception>
523-
private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer)
524-
{
525-
if (stream.Read(buffer, 0, 4) == 4)
526-
{
527-
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
528-
}
529-
530-
throw new ImageFormatException("Invalid Webp data.");
531-
}
532-
533-
/// <summary>
534-
/// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
535-
/// so the chunk size will be increased by 1 in those cases.
536-
/// </summary>
537-
/// <param name="stream">The stream to decode from.</param>
538-
/// <param name="buffer">Temporary buffer.</param>
539-
/// <param name="required">If true, the chunk size is required to be read, otherwise it can be skipped.</param>
540-
/// <returns>The chunk size in bytes.</returns>
541-
/// <exception cref="ImageFormatException">Invalid data.</exception>
542-
private static uint ReadChunkSize(BufferedReadStream stream, Span<byte> buffer, bool required = true)
543-
{
544-
if (stream.Read(buffer, 0, 4) == 4)
545-
{
546-
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
547-
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
548-
}
549-
550-
if (required)
551-
{
552-
throw new ImageFormatException("Invalid Webp data.");
553-
}
554-
555-
// Return the size of the remaining data in the stream.
556-
return (uint)(stream.Length - stream.Position);
557-
}
558-
559521
/// <inheritdoc/>
560522
public void Dispose() => this.alphaData?.Dispose();
561523
}

0 commit comments

Comments
 (0)