Skip to content

Commit

Permalink
Allow bypass BinaryFormatter in ImageListStreamer
Browse files Browse the repository at this point in the history
Contributes to dotnet#1251
  • Loading branch information
RussKie committed May 25, 2021
1 parent 10babcc commit e2edea4
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 66 deletions.
121 changes: 76 additions & 45 deletions src/System.Windows.Forms/src/System/Windows/Forms/ImageListStreamer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,16 @@ public sealed class ImageListStreamer : ISerializable, IDisposable
// compressed magic header. If we see this, the image stream is compressed.
// (unicode for MSFT).
private static readonly byte[] HEADER_MAGIC = new byte[] { 0x4D, 0x53, 0x46, 0X74 };
private static readonly object internalSyncObject = new object();
private static readonly object s_syncObject = new object();

private readonly ImageList imageList;
private ImageList.NativeImageList nativeImageList;
private readonly ImageList _imageList;
private ImageList.NativeImageList _nativeImageList;

internal ImageListStreamer(ImageList il)
{
imageList = il;
_imageList = il;
}

/**
* Constructor used in deserialization
*/
private ImageListStreamer(SerializationInfo info, StreamingContext context)
{
SerializationInfoEnumerator sie = info.GetEnumerator();
Expand All @@ -47,30 +44,10 @@ private ImageListStreamer(SerializationInfo info, StreamingContext context)
try
{
#endif
byte[] dat = (byte[])sie.Value;
if (dat != null)
byte[] data = (byte[])sie.Value;
if (data is not null)
{
// We enclose this imagelist handle create in a theming scope.
IntPtr userCookie = ThemingScope.Activate(Application.UseVisualStyles);

try
{
using MemoryStream ms = new MemoryStream(Decompress(dat));
lock (internalSyncObject)
{
ComCtl32.InitCommonControls();
nativeImageList = new ImageList.NativeImageList(new Ole32.GPStream(ms));
}
}
finally
{
ThemingScope.Deactivate(userCookie);
}

if (nativeImageList.Handle == IntPtr.Zero)
{
throw new InvalidOperationException(SR.ImageListStreamerLoadFailed);
}
Deserialize(data);
}
#if DEBUG
}
Expand All @@ -84,6 +61,23 @@ private ImageListStreamer(SerializationInfo info, StreamingContext context)
}
}

internal ImageListStreamer(Stream stream)
{
if (stream is MemoryStream ms
&& ms.TryGetBuffer(out ArraySegment<byte> buffer)
&& buffer.Offset == 0)
{
Deserialize(buffer.Array);
}
else
{
stream.Position = 0;
using MemoryStream copyStream = new(checked((int)stream.Length));
stream.CopyTo(copyStream);
Deserialize(copyStream.GetBuffer());
}
}

/// <summary>
/// Compresses the given input, returning a new array that represents
/// the compressed data.
Expand Down Expand Up @@ -206,50 +200,87 @@ private byte[] Decompress(byte[] input)
return output;
}

public void /*cpr: ISerializable*/GetObjectData(SerializationInfo si, StreamingContext context)
private void Deserialize(byte[] data)
{
MemoryStream stream = new MemoryStream();
// We enclose this imagelist handle create in a theming scope.
IntPtr userCookie = ThemingScope.Activate(Application.UseVisualStyles);

IntPtr handle = IntPtr.Zero;
if (imageList != null)
try
{
using MemoryStream ms = new MemoryStream(Decompress(data));
lock (s_syncObject)
{
ComCtl32.InitCommonControls();
_nativeImageList = new ImageList.NativeImageList(new Ole32.GPStream(ms));
}
}
finally
{
handle = imageList.Handle;
ThemingScope.Deactivate(userCookie);
}
else if (nativeImageList != null)

if (_nativeImageList.Handle == IntPtr.Zero)
{
handle = nativeImageList.Handle;
throw new InvalidOperationException(SR.ImageListStreamerLoadFailed);
}
}

if (handle == IntPtr.Zero || !WriteImageList(handle, stream))
public void GetObjectData(SerializationInfo si, StreamingContext context)
{
using MemoryStream stream = new MemoryStream();
if (!WriteImageList(stream))
{
throw new InvalidOperationException(SR.ImageListStreamerSaveFailed);
}

si.AddValue("Data", Compress(stream.ToArray()));
}

internal void GetObjectData(Stream stream)
{
if (!WriteImageList(stream))
{
throw new InvalidOperationException(SR.ImageListStreamerSaveFailed);
}
}

internal ImageList.NativeImageList GetNativeImageList()
{
return nativeImageList;
return _nativeImageList;
}

private bool WriteImageList(IntPtr imagelistHandle, Stream stream)
private bool WriteImageList(Stream stream)
{
IntPtr handle = IntPtr.Zero;
if (_imageList != null)
{
handle = _imageList.Handle;
}
else if (_nativeImageList != null)
{
handle = _nativeImageList.Handle;
}

if (handle == IntPtr.Zero)
{
return false;
}

// What we need to do here is use WriteEx if comctl 6 or above, and Write otherwise. However, till we can fix
// There isn't a reliable way to tell which version of comctl fusion is binding to.
// So for now, we try to bind to WriteEx, and if that entry point isn't found, we use Write.

try
{
HRESULT hr = ComCtl32.ImageList.WriteEx(new HandleRef(this, imagelistHandle), ComCtl32.ILP.DOWNLEVEL, new Ole32.GPStream(stream));
HRESULT hr = ComCtl32.ImageList.WriteEx(new HandleRef(this, handle), ComCtl32.ILP.DOWNLEVEL, new Ole32.GPStream(stream));
return hr == HRESULT.S_OK;
}
catch (EntryPointNotFoundException)
{
// WriteEx wasn't found - that's fine - we will use Write.
}

return ComCtl32.ImageList.Write(new HandleRef(this, imagelistHandle), new Ole32.GPStream(stream)).IsTrue();
return ComCtl32.ImageList.Write(new HandleRef(this, handle), new Ole32.GPStream(stream)).IsTrue();
}

/// <summary>
Expand All @@ -265,10 +296,10 @@ private void Dispose(bool disposing)
{
if (disposing)
{
if (nativeImageList != null)
if (_nativeImageList != null)
{
nativeImageList.Dispose();
nativeImageList = null;
_nativeImageList.Dispose();
_nativeImageList = null;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Windows.Forms.IntegrationTests.Common;
using ReflectTools;
using WFCTestLib.Log;
using static System.Windows.Forms.ImageList;
using static Interop;

namespace System.Windows.Forms.IntegrationTests.MauiTests
Expand Down Expand Up @@ -85,34 +86,21 @@ private Form CreateForm()
form.Text = "ListView Test";

ImageList imageList1 = new();
imageList1.ImageStream = (ImageListStreamer)FromBase64String(ClassicImageListStreamer);
imageList1.ImageStream = DeserializeStreamer(DevMsImageListStreamer);
listView1.SmallImageList = imageList1;

return form;
}

private static object FromBase64String(string base64String)
private static ImageListStreamer DeserializeStreamer(string base64String)
{
byte[] raw = Convert.FromBase64String(base64String);
return FromByteArray(raw);
byte[] bytes = Convert.FromBase64String(base64String);
using MemoryStream ms = new(bytes);
return new ImageListStreamer(ms);
}

private static object FromByteArray(byte[] raw)
{
var binaryFormatter = new BinaryFormatter
{
AssemblyFormat = /* FormatterAssemblyStyle.Simple */0
};

using (var serializedStream = new MemoryStream(raw))
{
#pragma warning disable SYSLIB0011 // Type or member is obsolete
return binaryFormatter.Deserialize(serializedStream);
#pragma warning restore SYSLIB0011 // Type or member is obsolete
}
}

private const string ClassicImageListStreamer =
"AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAAqBwAAAk1TRnQBSQFMAwEBAAEIAQABCAEAARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAEQAwABAQEAAQgGAAEEGAABgAIAAYADAAKAAQABgAMAAYABAAGAAQACgAIAA8ABAAHAAdwBwAEAAfABygGmAQABMwUAATMBAAEzAQABMwEAAjMCAAMWAQADHAEAAyIBAAMpAQADVQEAA00BAANCAQADOQEAAYABfAH/AQACUAH/AQABkwEAAdYBAAH/AewBzAEAAcYB1gHvAQAB1gLnAQABkAGpAa0CAAH/ATMDAAFmAwABmQMAAcwCAAEzAwACMwIAATMBZgIAATMBmQIAATMBzAIAATMB/wIAAWYDAAFmATMCAAJmAgABZgGZAgABZgHMAgABZgH/AgABmQMAAZkBMwIAAZkBZgIAApkCAAGZAcwCAAGZAf8CAAHMAwABzAEzAgABzAFmAgABzAGZAgACzAIAAcwB/wIAAf8BZgIAAf8BmQIAAf8BzAEAATMB/wIAAf8BAAEzAQABMwEAAWYBAAEzAQABmQEAATMBAAHMAQABMwEAAf8BAAH/ATMCAAMzAQACMwFmAQACMwGZAQACMwHMAQACMwH/AQABMwFmAgABMwFmATMBAAEzAmYBAAEzAWYBmQEAATMBZgHMAQABMwFmAf8BAAEzAZkCAAEzAZkBMwEAATMBmQFmAQABMwKZAQABMwGZAcwBAAEzAZkB/wEAATMBzAIAATMBzAEzAQABMwHMAWYBAAEzAcwBmQEAATMCzAEAATMBzAH/AQABMwH/ATMBAAEzAf8BZgEAATMB/wGZAQABMwH/AcwBAAEzAv8BAAFmAwABZgEAATMBAAFmAQABZgEAAWYBAAGZAQABZgEAAcwBAAFmAQAB/wEAAWYBMwIAAWYCMwEAAWYBMwFmAQABZgEzAZkBAAFmATMBzAEAAWYBMwH/AQACZgIAAmYBMwEAA2YBAAJmAZkBAAJmAcwBAAFmAZkCAAFmAZkBMwEAAWYBmQFmAQABZgKZAQABZgGZAcwBAAFmAZkB/wEAAWYBzAIAAWYBzAEzAQABZgHMAZkBAAFmAswBAAFmAcwB/wEAAWYB/wIAAWYB/wEzAQABZgH/AZkBAAFmAf8BzAEAAcwBAAH/AQAB/wEAAcwBAAKZAgABmQEzAZkBAAGZAQABmQEAAZkBAAHMAQABmQMAAZkCMwEAAZkBAAFmAQABmQEzAcwBAAGZAQAB/wEAAZkBZgIAAZkBZgEzAQABmQEzAWYBAAGZAWYBmQEAAZkBZgHMAQABmQEzAf8BAAKZATMBAAKZAWYBAAOZAQACmQHMAQACmQH/AQABmQHMAgABmQHMATMBAAFmAcwBZgEAAZkBzAGZAQABmQLMAQABmQHMAf8BAAGZAf8CAAGZAf8BMwEAAZkBzAFmAQABmQH/AZkBAAGZAf8BzAEAAZkC/wEAAcwDAAGZAQABMwEAAcwBAAFmAQABzAEAAZkBAAHMAQABzAEAAZkBMwIAAcwCMwEAAcwBMwFmAQABzAEzAZkBAAHMATMBzAEAAcwBMwH/AQABzAFmAgABzAFmATMBAAGZAmYBAAHMAWYBmQEAAcwBZgHMAQABmQFmAf8BAAHMAZkCAAHMAZkBMwEAAcwBmQFmAQABzAKZAQABzAGZAcwBAAHMAZkB/wEAAswCAALMATMBAALMAWYBAALMAZkBAAPMAQACzAH/AQABzAH/AgABzAH/ATMBAAGZAf8BZgEAAcwB/wGZAQABzAH/AcwBAAHMAv8BAAHMAQABMwEAAf8BAAFmAQAB/wEAAZkBAAHMATMCAAH/AjMBAAH/ATMBZgEAAf8BMwGZAQAB/wEzAcwBAAH/ATMB/wEAAf8BZgIAAf8BZgEzAQABzAJmAQAB/wFmAZkBAAH/AWYBzAEAAcwBZgH/AQAB/wGZAgAB/wGZATMBAAH/AZkBZgEAAf8CmQEAAf8BmQHMAQAB/wGZAf8BAAH/AcwCAAH/AcwBMwEAAf8BzAFmAQAB/wHMAZkBAAH/AswBAAH/AcwB/wEAAv8BMwEAAcwB/wFmAQAC/wGZAQAC/wHMAQACZgH/AQABZgH/AWYBAAFmAv8BAAH/AmYBAAH/AWYB/wEAAv8BZgEAASEBAAGlAQADXwEAA3cBAAOGAQADlgEAA8sBAAOyAQAD1wEAA90BAAPjAQAD6gEAA/EBAAP4AQAB8AH7Af8BAAGkAqABAAOAAwAB/wIAAf8DAAL/AQAB/wMAAf8BAAH/AQAC/wIAA///AP8A/wD/AAUAAUIBTQE+BwABPgMAASgDAAFAAwABEAMAAQEBAAEBBQABgBcAA/8BAAL/BgAC/wYAAv8GAAL/BgAC/wYAAv8GAAL/BgAC/wYAAv8GAAL/BgAC/wYAAv8GAAL/BgAC/wYAAv8GAAL/BgAL";
// Base64-reencoded streamer using GetObjectData(Stream) from a decoded ClassicImageListStreamer
private const string DevMsImageListStreamer =
"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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQk0+AAAAAAAAAD4AAAAoAAAAQAAAABAAAAABAAEAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
}
}
Loading

0 comments on commit e2edea4

Please sign in to comment.