diff --git a/src/BlurHashSharp.SkiaSharp/BlurHashEncoder.cs b/src/BlurHashSharp.SkiaSharp/BlurHashEncoder.cs index e762fb6..acd79de 100644 --- a/src/BlurHashSharp.SkiaSharp/BlurHashEncoder.cs +++ b/src/BlurHashSharp.SkiaSharp/BlurHashEncoder.cs @@ -15,9 +15,10 @@ public static class BlurHashEncoder /// The number y components. /// The IO stream of an encoded image. /// BlurHash representation of the image. + /// The image is in an unsupported format or could not be decoded. public static string Encode(int xComponent, int yComponent, Stream stream) { - using (SKCodec codec = SKCodec.Create(stream)) + using (SKCodec codec = CreateCodec(stream)) { return Encode(xComponent, yComponent, codec); } @@ -30,9 +31,11 @@ public static string Encode(int xComponent, int yComponent, Stream stream) /// The number y components. /// The path to an encoded image on the file system. /// BlurHash representation of the image. + /// The file does not exist. + /// The image is in an unsupported format or could not be decoded. public static string Encode(int xComponent, int yComponent, string filename) { - using (SKCodec codec = SKCodec.Create(filename)) + using (SKCodec codec = CreateCodec(filename)) { return Encode(xComponent, yComponent, codec); } @@ -49,7 +52,7 @@ internal static string Encode(int xComponent, int yComponent, SKCodec codec) ColorSpace = SKColorSpace.CreateSrgb() }; - using (SKBitmap bitmap = SKBitmap.Decode(codec, newInfo)) + using (SKBitmap bitmap = DecodeBitmap(codec, newInfo)) { return EncodeInternal(xComponent, yComponent, bitmap); } @@ -64,9 +67,10 @@ internal static string Encode(int xComponent, int yComponent, SKCodec codec) /// The maximum width to resize the image to. /// The maximum height to resize the image to. /// BlurHash representation of the image. + /// The image is in an unsupported format or could not be decoded. public static string Encode(int xComponent, int yComponent, Stream stream, int maxWidth, int maxHeight) { - using (SKCodec codec = SKCodec.Create(stream)) + using (SKCodec codec = CreateCodec(stream)) { return Encode(xComponent, yComponent, codec, maxWidth, maxHeight); } @@ -81,9 +85,11 @@ public static string Encode(int xComponent, int yComponent, Stream stream, int m /// The maximum width to resize the image to. /// The maximum height to resize the image to. /// BlurHash representation of the image. + /// The file does not exist. + /// The image is in an unsupported format or could not be decoded. public static string Encode(int xComponent, int yComponent, string filename, int maxWidth, int maxHeight) { - using (SKCodec codec = SKCodec.Create(filename)) + using (SKCodec codec = CreateCodec(filename)) { return Encode(xComponent, yComponent, codec, maxWidth, maxHeight); } @@ -111,7 +117,7 @@ internal static string Encode(int xComponent, int yComponent, SKCodec codec, int ColorSpace = SKColorSpace.CreateSrgb() }; - using (SKBitmap bitmap = SKBitmap.Decode(codec, newInfo)) + using (SKBitmap bitmap = DecodeBitmap(codec, newInfo)) { if (scaleFactor == 0f) { @@ -129,6 +135,25 @@ internal static string Encode(int xComponent, int yComponent, SKCodec codec, int } } + private static SKCodec CreateCodec(Stream stream) + => SKCodec.Create(stream) + ?? throw new InvalidDataException("The stream does not contain an image in a format supported by SkiaSharp."); + + private static SKCodec CreateCodec(string filename) + { + if (!File.Exists(filename)) + { + throw new FileNotFoundException("The image file could not be found.", filename); + } + + return SKCodec.Create(filename) + ?? throw new InvalidDataException($"The file '{filename}' does not contain an image in a format supported by SkiaSharp."); + } + + private static SKBitmap DecodeBitmap(SKCodec codec, SKImageInfo info) + => SKBitmap.Decode(codec, info) + ?? throw new InvalidDataException("The image could not be decoded; the data is corrupt or truncated."); + internal static string EncodeInternal(int xComponent, int yComponent, SKBitmap bitmap) => CoreBlurHashEncoder.Encode(xComponent, yComponent, bitmap.Width, bitmap.Height, bitmap.GetPixelSpan(), bitmap.RowBytes, PixelFormat.RGB888x); } diff --git a/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashEncoderInvalidInputTests.cs b/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashEncoderInvalidInputTests.cs new file mode 100644 index 0000000..b4ecbaf --- /dev/null +++ b/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashEncoderInvalidInputTests.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using Xunit; + +namespace BlurHashSharp.SkiaSharp.Tests +{ + public class BlurHashEncoderInvalidInputTests + { + // A 64x96 progressive JPEG cut off half way through its first scan: Skia reads the header + // and creates a codec, but decoding the pixels fails with InvalidInput. + private static readonly string TruncatedImagePath = Path.Join(AppContext.BaseDirectory, "TestData", "truncated-progressive.jpg"); + + private static byte[] UnrecognizedData() + { + var data = new byte[2048]; + for (int i = 0; i < data.Length; i++) + { + data[i] = (byte)i; + } + + return data; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Encode_UnrecognizedStream_ThrowsInvalidDataException(bool resize) + { + using var stream = new MemoryStream(UnrecognizedData()); + + Assert.Throws(() => resize + ? BlurHashEncoder.Encode(4, 3, stream, 128, 128) + : BlurHashEncoder.Encode(4, 3, stream)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Encode_UnrecognizedFile_ThrowsInvalidDataException(bool resize) + { + var path = Path.Join(Path.GetTempPath(), Path.GetRandomFileName() + ".jpg"); + File.WriteAllBytes(path, UnrecognizedData()); + try + { + Assert.Throws(() => resize + ? BlurHashEncoder.Encode(4, 3, path, 128, 128) + : BlurHashEncoder.Encode(4, 3, path)); + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Encode_TruncatedStream_ThrowsInvalidDataException(bool resize) + { + using var stream = File.OpenRead(TruncatedImagePath); + + Assert.Throws(() => resize + ? BlurHashEncoder.Encode(4, 3, stream, 32, 32) + : BlurHashEncoder.Encode(4, 3, stream)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Encode_TruncatedFile_ThrowsInvalidDataException(bool resize) + { + Assert.Throws(() => resize + ? BlurHashEncoder.Encode(4, 3, TruncatedImagePath, 32, 32) + : BlurHashEncoder.Encode(4, 3, TruncatedImagePath)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Encode_MissingFile_ThrowsFileNotFoundException(bool resize) + { + var path = Path.Join(Path.GetTempPath(), Path.GetRandomFileName() + ".png"); + + Assert.Throws(() => resize + ? BlurHashEncoder.Encode(4, 3, path, 128, 128) + : BlurHashEncoder.Encode(4, 3, path)); + } + } +} diff --git a/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj b/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj index 169b878..3d18c02 100644 --- a/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj +++ b/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj @@ -18,6 +18,10 @@ + + + + diff --git a/tests/BlurHashSharp.SkiaSharp.Tests/TestData/truncated-progressive.jpg b/tests/BlurHashSharp.SkiaSharp.Tests/TestData/truncated-progressive.jpg new file mode 100644 index 0000000..0402dd3 Binary files /dev/null and b/tests/BlurHashSharp.SkiaSharp.Tests/TestData/truncated-progressive.jpg differ