Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ internal static void AddMappedFormats<T>(string format, T formats)
break;
case BinaryFormatMetafile:
formats.Add(Emf);
break;
default:
// The formats aren't case sensitive, notably when they are looked up via the OLE IDataObject
// proxy. We're most likely to see "TEXT", so we'll handle text cases.
if (string.Equals(format, Text, StringComparison.OrdinalIgnoreCase))
{
formats.Add(String);
formats.Add(UnicodeText);
}
else if (string.Equals(format, UnicodeText, StringComparison.OrdinalIgnoreCase))
{
formats.Add(String);
formats.Add(Text);
}
else if (string.Equals(format, String, StringComparison.OrdinalIgnoreCase))
{
formats.Add(Text);
formats.Add(UnicodeText);
}

break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public bool GetDataPresent(string format, bool autoConvert)

for (int i = 0; i < formats.Length; i++)
{
if (format.Equals(formats[i]))
if (format.Equals(formats[i], StringComparison.OrdinalIgnoreCase))
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public void RoundTrip_Text()
}

[Fact]
public void Clipboard_DerivedDataObject_DataPresent()
public void DerivedDataObject_DataPresent()
{
// https://github.com/dotnet/winforms/issues/12789
SomeDataObject data = new();
Expand All @@ -152,4 +152,31 @@ internal class SomeDataObject : DataObject
public override bool GetDataPresent(string format, bool autoConvert)
=> format == Format || base.GetDataPresent(format, autoConvert);
}

[Fact]
public void SerializableObject_InProcess_DoesNotUseBinaryFormatter()
{
// This test ensures that the SerializableObject does not use BinaryFormatter when running in process.
using ClipboardScope scope = new();

DataObject dataObject = new();
SerializablePerson person = new() { Name = "John Doe", Age = 30 };
dataObject.SetData(person);
HRESULT result = ClipboardCore.SetData(dataObject, copy: false, retryTimes: 1, retryDelay: 0);
result.Should().Be(HRESULT.S_OK);

result = ClipboardCore.GetDataObject<DataObject, ITestDataObject>(out var data, retryTimes: 1, retryDelay: 0);
result.Should().Be(HRESULT.S_OK);
data.Should().NotBeNull();
data!.GetDataPresent(typeof(SerializablePerson).FullName!).Should().BeTrue();

data.GetData(typeof(SerializablePerson).FullName!).Should().BeSameAs(person);
}

[Serializable]
internal class SerializablePerson
{
public string Name { get; set; } = "DefaultName";
public int Age { get; set; }
}
}
1 change: 1 addition & 0 deletions src/System.Windows.Forms/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static System.Windows.Forms.Clipboard.TryGetData<T>(out T data) -> bool
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ public static bool ContainsText(TextDataFormat format)
return dataObject.TryGetData(format, out data);
}

/// <inheritdoc cref="TryGetData{T}(string, out T)"/>
public static bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
[NotNullWhen(true), MaybeNullWhen(false)] out T data) => TryGetData(typeof(T).FullName!, out data);

/// <summary>
/// Retrieves a collection of file names from the <see cref="Clipboard"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public WrappingDataObject(object data) : base(data)
{
// Don't wrap existing DataObject instances.
Debug.Assert(data is not DataObject);

_originalIsIDataObject = data is IDataObject;
}

Expand All @@ -26,7 +25,25 @@ internal override bool TryUnwrapUserDataObject([NotNullWhen(true)] out IDataObje
return base.TryUnwrapUserDataObject(out dataObject);
}

dataObject = null;
return false;
// When we hand back our wrapper we're emulating what would happen via the native IDataObject proxy.
// As the native interface has no concept of "autoConvert", we need to always consider it "true"
// as our native IDataObject implementation would.
dataObject = this;
return true;
}

public override string[] GetFormats(bool autoConvert)
// Always auto convert to emulate native IDataObject behavior.
=> base.GetFormats(autoConvert: true);

public override bool GetDataPresent(string format, bool autoConvert)
// Always auto convert to emulate native IDataObject behavior.
=> base.GetDataPresent(format, autoConvert: true);

#pragma warning disable WFDEV005 // Type or member is obsolete
[Obsolete]
public override object? GetData(string format, bool autoConvert)
// Always auto convert to emulate native IDataObject behavior.
=> base.GetData(format, autoConvert: true);
#pragma warning restore WFDEV005 // Type or member is obsolete
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ public void SetDataObject_InvokeObjectNotIComDataObject_GetReturnsExpected(objec
{
Clipboard.SetDataObject(data);

DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType<DataObject>().Subject;
DataObject dataObject = Clipboard.GetDataObject().Should().BeAssignableTo<DataObject>().Subject;
dataObject.GetData(data.GetType()).Should().Be(data);
Clipboard.ContainsData(data.GetType().FullName).Should().BeTrue();
}
Expand All @@ -303,7 +303,7 @@ public void SetDataObject_InvokeObjectBoolNotIComDataObject_GetReturnsExpected(o
{
Clipboard.SetDataObject(data, copy);

DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType<DataObject>().Subject;
DataObject dataObject = Clipboard.GetDataObject().Should().BeAssignableTo<DataObject>().Subject;
dataObject.GetData(data.GetType()).Should().Be(data);
Clipboard.ContainsData(data.GetType().FullName).Should().BeTrue();
}
Expand Down Expand Up @@ -332,7 +332,7 @@ public void SetDataObject_InvokeObjectBoolIntIntNotIComDataObject_GetReturnsExpe
{
Clipboard.SetDataObject(data, copy, retryTimes, retryDelay);

DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType<DataObject>().Subject;
DataObject dataObject = Clipboard.GetDataObject().Should().BeAssignableTo<DataObject>().Subject;
dataObject.GetData(data.GetType()).Should().Be(data);
Clipboard.ContainsData(data.GetType().FullName).Should().BeTrue();
}
Expand Down Expand Up @@ -1193,7 +1193,7 @@ public void SetData_Text_Format_AllUpper()
formats.Should().BeEquivalentTo(["System.String", "UnicodeText", "Text"]);

formats = dataObject.GetFormats(autoConvert: false);
formats.Should().BeEquivalentTo(["Text"]);
formats.Should().BeEquivalentTo(["System.String", "UnicodeText", "Text"]);

// CLIPBRD_E_BAD_DATA returned when trying to get clipboard data.
Clipboard.GetText().Should().BeEmpty();
Expand Down Expand Up @@ -1256,10 +1256,8 @@ public void SetDataObject_Text()

Clipboard.GetData("System.String").Should().Be(expected);

// Case sensitivity matters so we end up reading stream/object from HGLOBAL instead of string.
MemoryStream stream = Clipboard.GetData("TEXT").Should().BeOfType<MemoryStream>().Subject;
byte[] array = stream.ToArray();
array.Should().BeEquivalentTo("Hello, World!\0"u8.ToArray());
string result = Clipboard.GetData("TEXT").Should().BeOfType<string>().Subject;
result.Should().Be(expected);
}

[WinFormsFact]
Expand Down Expand Up @@ -1324,18 +1322,19 @@ public void RoundTrip_Object_SupportsTypedInterface(bool copy)
string format = typeof(SerializableTestData).FullName!;

// Opt-in into access to the binary formatted stream.
using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: copy);

// We need the BinaryFormatter to flush the data from the managed object to the HGLOBAL
// and to write data to HGLOBAL as a binary formatted stream now if it hadn't been flushed.
using BinaryFormatterScope scope = new(enable: true);
using BinaryFormatterScope scope = new(enable: copy);

Clipboard.SetDataObject(data, copy);

DataObject received = Clipboard.GetDataObject().Should().BeOfType<DataObject>().Subject;
DataObject received = Clipboard.GetDataObject().Should().BeAssignableTo<DataObject>().Subject;

received.TryGetData(
format,
(TypeName name) => name.FullName == typeof(SerializableTestData).FullName ? typeof(SerializableTestData) : null,
name => name.FullName == typeof(SerializableTestData).FullName ? typeof(SerializableTestData) : null,
autoConvert: false,
out SerializableTestData? result).Should().BeTrue();

Expand Down Expand Up @@ -1450,4 +1449,27 @@ public unsafe void OleGetClipboard_ProxyBehavior()
using ComScope<IUnknown> wrapperUnknown = wrapper.Query<IUnknown>();
((nint)wrapperUnknown.Value).Should().Be((nint)originalUnknown.Value);
}

[WinFormsFact]
public void SetDataObject_SerializableObject_CopyFalse_DoesNotUseBinaryFormatter()
{
// This test ensures that the SerializableObject does not use BinaryFormatter when running in process.
// This only works when copy is not set to true as that is the only case where we are able to extract
// the original DataObject we created for the user data.

SerializablePerson person = new() { Name = "John Doe", Age = 30 };
Clipboard.SetDataObject(person);

bool result = Clipboard.TryGetData(out SerializablePerson? data);
result.Should().BeTrue();
data.Should().NotBeNull();
data.Should().BeSameAs(person);
}

[Serializable]
internal class SerializablePerson
{
public string Name { get; set; } = "DefaultName";
public int Age { get; set; }
}
}