diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiPlusStreamHelper.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiPlusStreamHelper.Unix.cs index 23494a11bef72..abe50711c9fd6 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiPlusStreamHelper.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiPlusStreamHelper.Unix.cs @@ -43,10 +43,10 @@ internal sealed partial class GdiPlusStreamHelper { private Stream _stream; - public unsafe GdiPlusStreamHelper(Stream stream, bool seekToOrigin) + public unsafe GdiPlusStreamHelper(Stream stream, bool seekToOrigin, bool makeSeekable = true) { // Seeking required - if (!stream.CanSeek) + if (makeSeekable && !stream.CanSeek) { var memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Unix.cs index ab88eaf44305f..7398b5e2c98bb 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Unix.cs @@ -74,7 +74,7 @@ private protected static IntPtr InitializeFromStream(Stream stream) // We use a custom API for this, because there's no easy way // to get the Stream down to libgdiplus. So, we wrap the stream // with a set of delegates. - GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, true); + GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, seekToOrigin: true); int st = Gdip.GdipLoadImageFromDelegate_linux(sh.GetHeaderDelegate, sh.GetBytesDelegate, sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, out IntPtr imagePtr); @@ -247,7 +247,7 @@ public void Save(Stream stream, ImageCodecInfo encoder, EncoderParameters? encod try { - GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, false); + GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, seekToOrigin: false, makeSeekable: false); st = Gdip.GdipSaveImageToDelegate_linux(nativeImage, sh.GetBytesDelegate, sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, ref guid, nativeEncoderParams); diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs index 4747b9c9ca436..da4dd3eac2fc6 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Image.Windows.cs @@ -243,7 +243,7 @@ public void Save(Stream stream, ImageCodecInfo encoder, EncoderParameters? encod { Gdip.CheckStatus(Gdip.GdipSaveImageToStream( new HandleRef(this, nativeImage), - new GPStream(stream), + new GPStream(stream, makeSeekable: false), ref g, new HandleRef(encoderParams, encoderParamsMemory))); } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.Unix.cs index d101b970b63f8..596a2e097fbf9 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.Unix.cs @@ -54,7 +54,7 @@ public Metafile(Stream stream) // With libgdiplus we use a custom API for this, because there's no easy way // to get the Stream down to libgdiplus. So, we wrap the stream with a set of delegates. - GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, false); + GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, seekToOrigin: false); int status = Gdip.GdipCreateMetafileFromDelegate_linux(sh.GetHeaderDelegate, sh.GetBytesDelegate, sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, out nativeImage); @@ -101,7 +101,7 @@ public Metafile(Stream stream, IntPtr referenceHdc, Rectangle frameRect, Metafil // With libgdiplus we use a custom API for this, because there's no easy way // to get the Stream down to libgdiplus. So, we wrap the stream with a set of delegates. - GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, false); + GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, seekToOrigin: false); int status = Gdip.GdipRecordMetafileFromDelegateI_linux(sh.GetHeaderDelegate, sh.GetBytesDelegate, sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, referenceHdc, type, ref frameRect, frameUnit, description, out nativeImage); @@ -120,7 +120,7 @@ public Metafile(Stream stream, IntPtr referenceHdc, RectangleF frameRect, Metafi // With libgdiplus we use a custom API for this, because there's no easy way // to get the Stream down to libgdiplus. So, we wrap the stream with a set of delegates. - GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, false); + GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, seekToOrigin: false); int status = Gdip.GdipRecordMetafileFromDelegate_linux(sh.GetHeaderDelegate, sh.GetBytesDelegate, sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, referenceHdc, type, ref frameRect, frameUnit, description, out nativeImage); @@ -189,7 +189,7 @@ public static MetafileHeader GetMetafileHeader(Stream stream) { // With libgdiplus we use a custom API for this, because there's no easy way // to get the Stream down to libgdiplus. So, we wrap the stream with a set of delegates. - GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, false); + GdiPlusStreamHelper sh = new GdiPlusStreamHelper(stream, seekToOrigin: false); int status = Gdip.GdipGetMetafileHeaderFromDelegate_linux(sh.GetHeaderDelegate, sh.GetBytesDelegate, sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, header); diff --git a/src/libraries/System.Drawing.Common/tests/BitmapTests.cs b/src/libraries/System.Drawing.Common/tests/BitmapTests.cs index 997b2069ad995..9c5ca385b9f99 100644 --- a/src/libraries/System.Drawing.Common/tests/BitmapTests.cs +++ b/src/libraries/System.Drawing.Common/tests/BitmapTests.cs @@ -1741,7 +1741,7 @@ public void FromNonSeekableStream() using (FileStream stream = new FileStream(path, FileMode.Open)) { - using (Bitmap bitmap = new Bitmap(new NonSeekableStream(stream))) + using (Bitmap bitmap = new Bitmap(new TestStream(stream, canSeek: false))) { Assert.Equal(100, bitmap.Height); Assert.Equal(100, bitmap.Width); @@ -1750,22 +1750,55 @@ public void FromNonSeekableStream() } } - private class NonSeekableStream : Stream + [ConditionalTheory(Helpers.IsDrawingSupported)] + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + public void SaveToRestrictiveStream(bool canRead, bool canSeek) + { + using (Stream backingStream = new MemoryStream()) + using (Stream restrictiveStream = new TestStream(backingStream, canRead, canSeek)) + { + using (Bitmap bitmap = new Bitmap(100, 100)) + { + bitmap.Save(restrictiveStream, ImageFormat.Png); + } + + backingStream.Position = 0; + + using (Bitmap bitmap = new Bitmap(backingStream)) + { + Assert.Equal(100, bitmap.Height); + Assert.Equal(100, bitmap.Width); + Assert.Equal(ImageFormat.Png, bitmap.RawFormat); + } + } + } + + private class TestStream : Stream { private Stream _stream; + private bool _canRead; + private bool _canSeek; - public NonSeekableStream(Stream stream) + public TestStream(Stream stream, bool canRead = true, bool canSeek = true) { _stream = stream; + _canRead = canRead; + _canSeek = canSeek; } - public override bool CanRead => _stream.CanRead; - public override bool CanSeek => false; + public override bool CanRead => _canRead && _stream.CanRead; + public override bool CanSeek => _canSeek && _stream.CanSeek; public override bool CanWrite => _stream.CanWrite; public override long Length => _stream.Length; - public override long Position { get => _stream.Position; set => throw new InvalidOperationException(); } + public override long Position + { + get => _stream.Position; + set => _stream.Position = _canSeek ? value : throw new NotSupportedException(); + } public override void Flush() => _stream.Flush(); - public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count); + public override int Read(byte[] buffer, int offset, int count) => _canRead ? _stream.Read(buffer, offset, count) : throw new NotSupportedException(); public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); public override void SetLength(long value) => _stream.SetLength(value); public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count); diff --git a/src/libraries/System.Drawing.Common/tests/IconTests.cs b/src/libraries/System.Drawing.Common/tests/IconTests.cs index 440fd59f2be77..25b8c5b9631e6 100644 --- a/src/libraries/System.Drawing.Common/tests/IconTests.cs +++ b/src/libraries/System.Drawing.Common/tests/IconTests.cs @@ -266,28 +266,6 @@ public void Ctor_NullIcon_ThrowsArgumentNullException() AssertExtensions.Throws("original", null, () => new Icon((Icon)null, new Size(32, 32))); } - // libgdiplus causes a segfault when given an invalid Icon handle. - [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalFact(Helpers.IsDrawingSupported)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34591", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] - public void Ctor_InvalidHandle_Success() - { - using (Icon icon = Icon.FromHandle((IntPtr)1)) - using (var stream = new MemoryStream()) - { - Exception ex = Assert.ThrowsAny(() => icon.Save(stream)); - Assert.True(ex is COMException || ex is ObjectDisposedException || ex is FileNotFoundException, $"{ex.GetType().ToString()} was thrown."); - - AssertExtensions.Throws(null, () => icon.ToBitmap()); - Assert.Equal(Size.Empty, icon.Size); - - using (var newIcon = new Icon(icon, 10, 10)) - { - Assert.Throws(() => newIcon.Handle); - } - } - } - [ConditionalFact(Helpers.IsDrawingSupported)] public void Ctor_Type_Resource() {