Skip to content

Commit 39c6b28

Browse files
Minor refactoring in DataObject-related code (dotnet#12642)
Factored out code that reads formats that allow only restricted deserialization to run, such as OLE formats.
1 parent 6d79416 commit 39c6b28

File tree

4 files changed

+94
-42
lines changed

4 files changed

+94
-42
lines changed

src/System.Windows.Forms/src/System/Windows/Forms/Internal/TypeExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,10 @@ static bool AssemblyNamesMatch(AssemblyNameInfo? name1, AssemblyNameInfo? name2)
235235
}
236236

237237
/// <summary>
238-
/// Convert <paramref name="type"/> to <see cref="TypeName"/>. This method removes nullability wrapper
239-
/// from the top level type only because <see cref="TypeName"/> in the serialization root record is not nullable.
238+
/// Convert <paramref name="type"/> to <see cref="TypeName"/>. Take into account type forwarding in order
239+
/// to create <see cref="TypeName"/> compatible with the type names serialized to the binary format.This
240+
/// method removes nullability wrapper from the top level type only because <see cref="TypeName"/> in the
241+
/// serialization root record is not nullable, but the generic types could be nullable.
240242
/// </summary>
241243
public static TypeName ToTypeName(this Type type)
242244
{

src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.BinaryFormatUtilities.cs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,31 +66,24 @@ internal static void WriteObjectToStream(MemoryStream stream, object data, bool
6666

6767
internal static object? ReadObjectFromStream<T>(
6868
MemoryStream stream,
69-
bool restrictDeserialization,
7069
Func<TypeName, Type>? resolver,
7170
bool legacyMode)
7271
{
7372
long startPosition = stream.Position;
7473
SerializationRecord? record;
75-
7674
SerializationBinder binder = new Binder(typeof(T), resolver, legacyMode);
77-
7875
IReadOnlyDictionary<SerializationRecordId, SerializationRecord> recordMap;
76+
7977
try
8078
{
8179
record = stream.Decode(out recordMap);
8280
}
8381
catch (Exception ex) when (!ex.IsCriticalException())
8482
{
8583
// Couldn't parse for some reason, let BinaryFormatter handle the legacy invocation.
86-
// The types APIs can't compare the specified type when the root record is not available.
84+
// The typed APIs can't compare the specified type when the root record is not available.
8785
if (legacyMode && LocalAppContextSwitches.ClipboardDragDropEnableUnsafeBinaryFormatterSerialization)
8886
{
89-
if (restrictDeserialization)
90-
{
91-
throw new RestrictedTypeDeserializationException(SR.UnexpectedClipboardType);
92-
}
93-
9487
stream.Position = startPosition;
9588
return ReadObjectWithBinaryFormatter<T>(stream, binder);
9689
}
@@ -108,7 +101,8 @@ record = stream.Decode(out recordMap);
108101

109102
// For the new TryGet APIs, ensure that the stream contains the requested type,
110103
// or type that can be assigned to the requested type.
111-
if (!legacyMode && !typeof(T).MatchExceptAssemblyVersion(record.TypeName))
104+
Type type = typeof(T);
105+
if (!legacyMode && !type.MatchExceptAssemblyVersion(record.TypeName))
112106
{
113107
#if false // TODO (TanyaSo): - modify TryGetObjectFromJson to take a resolver and rename to HasJsonData???
114108
// Return true if the payload contains valid JsonData<T> struct, type matches or not
@@ -119,7 +113,7 @@ record = stream.Decode(out recordMap);
119113
}
120114
#endif
121115

122-
if (!TypeNameIsAssignableToType(record.TypeName, typeof(T), (ITypeResolver)binder))
116+
if (!TypeNameIsAssignableToType(record.TypeName, type, (ITypeResolver)binder))
123117
{
124118
// If clipboard contains an exception from SetData, we will get its message and throw.
125119
if (record.TypeName.FullName == typeof(NotSupportedException).FullName
@@ -138,11 +132,6 @@ record = stream.Decode(out recordMap);
138132
return value;
139133
}
140134

141-
if (restrictDeserialization)
142-
{
143-
throw new RestrictedTypeDeserializationException(SR.UnexpectedClipboardType);
144-
}
145-
146135
if (!LocalAppContextSwitches.ClipboardDragDropEnableUnsafeBinaryFormatterSerialization)
147136
{
148137
throw new NotSupportedException(string.Format(
@@ -170,6 +159,47 @@ record = stream.Decode(out recordMap);
170159
return ReadObjectWithBinaryFormatter<T>(stream, binder);
171160
}
172161

162+
internal static object? ReadRestrictedObjectFromStream<T>(
163+
MemoryStream stream,
164+
Func<TypeName, Type>? resolver,
165+
bool legacyMode)
166+
{
167+
long startPosition = stream.Position;
168+
SerializationRecord? record;
169+
170+
try
171+
{
172+
record = stream.Decode();
173+
}
174+
catch (Exception ex) when (!ex.IsCriticalException())
175+
{
176+
throw new RestrictedTypeDeserializationException(SR.UnexpectedClipboardType);
177+
}
178+
179+
// For the new TryGet APIs, ensure that the stream contains the requested type,
180+
// or type that can be assigned to the requested type.
181+
Type type = typeof(T);
182+
if (!legacyMode && !type.MatchExceptAssemblyVersion(record.TypeName))
183+
{
184+
if (!TypeNameIsAssignableToType(record.TypeName, type, new Binder(type, resolver, legacyMode)))
185+
{
186+
// If clipboard contains an exception from SetData, we will get its message and throw.
187+
if (record.TypeName.FullName == typeof(NotSupportedException).FullName
188+
&& record.TryGetNotSupportedException(out object? @object)
189+
&& @object is NotSupportedException exception)
190+
{
191+
throw new NotSupportedException(exception.Message);
192+
}
193+
194+
return null;
195+
}
196+
}
197+
198+
return record.TryGetCommonObject(out object? value)
199+
? value
200+
: throw new RestrictedTypeDeserializationException(SR.UnexpectedClipboardType);
201+
}
202+
173203
private static bool TypeNameIsAssignableToType(TypeName typeName, Type type, ITypeResolver resolver)
174204
{
175205
try

src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToWinFormsAdapter.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ private static bool TryGetDataFromHGLOBAL<T>(
133133
MemoryStream stream = ReadByteStreamFromHGLOBAL(hglobal, out bool isSerializedObject);
134134
return !isSerializedObject
135135
? stream
136-
: BinaryFormatUtilities.ReadObjectFromStream<T>(stream, restrictDeserialization, resolver, legacyMode);
136+
: restrictDeserialization
137+
? BinaryFormatUtilities.ReadRestrictedObjectFromStream<T>(stream, resolver, legacyMode)
138+
: BinaryFormatUtilities.ReadObjectFromStream<T>(stream, resolver, legacyMode);
137139
}
138140
}
139141

src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormatUtilitiesTests.cs

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,36 @@ public partial class BinaryFormatUtilitiesTests : IDisposable
2222
private void WriteObjectToStream(object value, bool restrictSerialization = false) =>
2323
Utilities.WriteObjectToStream(_stream, value, restrictSerialization);
2424

25-
private object? ReadObjectFromStream(bool restrictDeserialization = false)
25+
private object? ReadObjectFromStream()
2626
{
2727
_stream.Position = 0;
28-
return Utilities.ReadObjectFromStream<object>(_stream, restrictDeserialization, resolver: null, legacyMode: true);
28+
return Utilities.ReadObjectFromStream<object>(_stream, resolver: null, legacyMode: true);
29+
}
30+
31+
private object? ReadRestrictedObjectFromStream()
32+
{
33+
_stream.Position = 0;
34+
return Utilities.ReadRestrictedObjectFromStream<object>(_stream, resolver: null, legacyMode: true);
2935
}
3036

3137
private object? ReadObjectFromStream<T>(bool restrictDeserialization, Func<TypeName, Type>? resolver)
3238
{
3339
_stream.Position = 0;
34-
return Utilities.ReadObjectFromStream<T>(_stream, restrictDeserialization, resolver, legacyMode: false);
40+
return restrictDeserialization
41+
? Utilities.ReadRestrictedObjectFromStream<T>(_stream, resolver, legacyMode: false)
42+
: Utilities.ReadObjectFromStream<T>(_stream, resolver, legacyMode: false);
43+
}
44+
45+
private object? ReadObjectFromStream<T>(Func<TypeName, Type>? resolver)
46+
{
47+
_stream.Position = 0;
48+
return Utilities.ReadObjectFromStream<T>(_stream, resolver, legacyMode: false);
49+
}
50+
51+
private object? ReadRestrictedObjectFromStream<T>(Func<TypeName, Type>? resolver)
52+
{
53+
_stream.Position = 0;
54+
return Utilities.ReadRestrictedObjectFromStream<T>(_stream, resolver, legacyMode: false);
3555
}
3656

3757
private object? RoundTripObject(object value)
@@ -46,31 +66,31 @@ private void WriteObjectToStream(object value, bool restrictSerialization = fals
4666
{
4767
// This is equivalent to SetData/GetData methods using registered OLE formats, resolves only the known types
4868
WriteObjectToStream(value, restrictSerialization: true);
49-
return ReadObjectFromStream(restrictDeserialization: true);
69+
return ReadRestrictedObjectFromStream();
5070
}
5171

5272
private object? RoundTripOfType<T>(object value)
5373
{
5474
// This is equivalent to SetData/TryGetData<T> methods using unbounded OLE formats,
5575
// and works with the BinaryFormat AppContext switches.
5676
WriteObjectToStream(value);
57-
return ReadObjectFromStream<T>(restrictDeserialization: false, NotSupportedResolver);
77+
return ReadObjectFromStream<T>(NotSupportedResolver);
5878
}
5979

6080
private object? RoundTripOfType_RestrictedFormat<T>(object value)
6181
{
6282
// This is equivalent to SetData/TryGetData<T> methods using OLE formats. Deserialization is restricted
6383
// to known types.
6484
WriteObjectToStream(value, restrictSerialization: true);
65-
return ReadObjectFromStream<T>(restrictDeserialization: true, NotSupportedResolver);
85+
return ReadRestrictedObjectFromStream<T>(NotSupportedResolver);
6686
}
6787

6888
private object? RoundTripOfType<T>(object value, Func<TypeName, Type>? resolver)
6989
{
7090
// This is equivalent to SetData/TryGetData<T> methods using unbounded formats,
7191
// serialization is restricted by the resolver and BinaryFormat AppContext switches.
7292
WriteObjectToStream(value);
73-
return ReadObjectFromStream<T>(restrictDeserialization: false, resolver);
93+
return ReadObjectFromStream<T>(resolver);
7494
}
7595

7696
// Primitive types as defined by the NRBF spec.
@@ -311,14 +331,14 @@ public void RoundTrip_RestrictedFormat_ImageList()
311331
public void RoundTrip_Bitmap()
312332
{
313333
using Bitmap value = new(10, 10);
314-
RoundTripObject(value).Should().BeOfType<Bitmap>().Subject.Size.Should().Be(value.Size);
334+
RoundTripObject(value).Should().BeOfType<Bitmap>().Which.Size.Should().Be(value.Size);
315335
}
316336

317337
[Fact]
318338
public void RoundTrip_RestrictedFormat_Bitmap()
319339
{
320340
using Bitmap value = new(10, 10);
321-
RoundTripObject_RestrictedFormat(value).Should().BeOfType<Bitmap>().Subject.Size.Should().Be(value.Size);
341+
RoundTripObject_RestrictedFormat(value).Should().BeOfType<Bitmap>().Which.Size.Should().Be(value.Size);
322342
}
323343

324344
[Theory]
@@ -402,12 +422,12 @@ public void RoundTripOfType_Unsupported()
402422
ReadAndValidate();
403423
}
404424

405-
Action read = () => ReadObjectFromStream<List<object>>(restrictDeserialization: false, ObjectListResolver);
425+
Action read = () => ReadObjectFromStream<List<object>>(ObjectListResolver);
406426
read.Should().Throw<NotSupportedException>();
407427

408428
void ReadAndValidate()
409429
{
410-
var result = ReadObjectFromStream<List<object>>(restrictDeserialization: false, ObjectListResolver)
430+
var result = ReadObjectFromStream<List<object>>(ObjectListResolver)
411431
.Should().BeOfType<List<object>>().Subject;
412432
result.Count.Should().Be(1);
413433
result[0].Should().Be("text");
@@ -450,10 +470,10 @@ public void RoundTripOfType_RestrictedFormat_AsUnmatchingType_Simple()
450470
// but in this case requested type will not match the payload type.
451471
WriteObjectToStream(value);
452472

453-
ReadObjectFromStream<Control>(restrictDeserialization: true, NotSupportedResolver).Should().BeNull();
473+
ReadRestrictedObjectFromStream<Control>(NotSupportedResolver).Should().BeNull();
454474

455475
using BinaryFormatterFullCompatScope scope = new();
456-
ReadObjectFromStream<Control>(restrictDeserialization: true, NotSupportedResolver).Should().BeNull();
476+
ReadRestrictedObjectFromStream<Control>(NotSupportedResolver).Should().BeNull();
457477
}
458478

459479
[Fact]
@@ -471,7 +491,7 @@ public void RoundTripOfType_RestrictedFormat_intNullableArray_NotSupportedResolv
471491

472492
using BinaryFormatterFullCompatScope scope = new();
473493
WriteObjectToStream(value);
474-
Action read = () => ReadObjectFromStream<int?[]>(restrictDeserialization: true, NotSupportedResolver);
494+
Action read = () => ReadRestrictedObjectFromStream<int?[]>(NotSupportedResolver);
475495

476496
// nullable struct requires a custom resolver.
477497
// RestrictedTypeDeserializationException
@@ -485,10 +505,11 @@ public void RoundTripOfType_intNullableArray_NotSupportedResolver()
485505

486506
using BinaryFormatterFullCompatScope scope = new();
487507
WriteObjectToStream(value);
488-
Action read = () => ReadObjectFromStream<int?[]>(restrictDeserialization: false, NotSupportedResolver);
508+
Action read = () => ReadObjectFromStream<int?[]>(NotSupportedResolver);
489509

490510
// nullable struct requires a custom resolver.
491-
read.Should().Throw<SerializationException>();
511+
// This is either NotSupportedException of RestrictedTypeDeserializationException, depending on format.
512+
read.Should().Throw<Exception>();
492513
}
493514

494515
[Theory]
@@ -507,7 +528,7 @@ public void RoundTripOfType_OffsetArray_NotSupportedResolver(bool restrictDeseri
507528
WriteObjectToStream(value);
508529
Action read = () => ReadObjectFromStream<uint[,]>(restrictDeserialization, NotSupportedResolver);
509530

510-
read.Should().Throw<NotSupportedException>();
531+
read.Should().Throw<Exception>();
511532
}
512533

513534
[Fact]
@@ -583,7 +604,7 @@ public void RoundTripOfType_TestData_InvalidResolver()
583604
WriteObjectToStream(value);
584605

585606
// Resolver that returns a null is blocked in our SerializationBinder wrapper.
586-
Action read = () => ReadObjectFromStream<TestData>(restrictDeserialization: false, InvalidResolver);
607+
Action read = () => ReadObjectFromStream<TestData>(InvalidResolver);
587608

588609
read.Should().Throw<SerializationException>();
589610

@@ -649,7 +670,6 @@ public void ReadFontSerializedOnNet481()
649670
stream.Position = 0;
650671
Action getData = () => Utilities.ReadObjectFromStream<object>(
651672
stream,
652-
restrictDeserialization: false,
653673
resolver: null,
654674
legacyMode: true);
655675

@@ -664,7 +684,6 @@ public void ReadFontSerializedOnNet481()
664684
stream.Position = 0;
665685
var result = Utilities.ReadObjectFromStream<object>(
666686
stream,
667-
restrictDeserialization: false,
668687
resolver: null,
669688
legacyMode: true).Should().BeOfType<Font>().Subject;
670689

@@ -679,7 +698,6 @@ static void TryGetData(MemoryStream stream)
679698
stream.Position = 0;
680699
var result = Utilities.ReadObjectFromStream<Font>(
681700
stream,
682-
restrictDeserialization: false,
683701
resolver: FontResolver,
684702
legacyMode: false).Should().BeOfType<Font>().Subject;
685703

@@ -721,7 +739,7 @@ public void Sample_GetData_UseBinaryFormatter()
721739

722740
// legacyMode == true follows the GetData path.
723741
_stream.Position = 0;
724-
Utilities.ReadObjectFromStream<MyClass1>(_stream, restrictDeserialization: false, resolver: null, legacyMode: true)
742+
Utilities.ReadObjectFromStream<MyClass1>(_stream, resolver: null, legacyMode: true)
725743
.Should().BeEquivalentTo(value);
726744
}
727745

@@ -736,7 +754,7 @@ public void Sample_GetData_UseNrbfDeserialize()
736754

737755
// This works because GetData falls back to the BinaryFormatter deserializer, NRBF deserializer fails because it requires a resolver.
738756
_stream.Position = 0;
739-
Utilities.ReadObjectFromStream<MyClass1>(_stream, restrictDeserialization: false, resolver: null, legacyMode: true)
757+
Utilities.ReadObjectFromStream<MyClass1>(_stream, resolver: null, legacyMode: true)
740758
.Should().BeEquivalentTo(value);
741759
}
742760

0 commit comments

Comments
 (0)