diff --git a/docs/project/list-of-obsoletions.md b/docs/project/list-of-obsoletions.md index 5083069cb799de..40e833ae20f80b 100644 --- a/docs/project/list-of-obsoletions.md +++ b/docs/project/list-of-obsoletions.md @@ -13,5 +13,5 @@ Currently the identifiers `MSLIB0001` through `MSLIB0999` are carved out for obs | Diagnostic ID | Description | | :--------------- | :---------- | -| __`MSLIB0001`__ | (Reserved for `Encoding.UTF7`.) | +| __`MSLIB0001`__ | The UTF-7 encoding is insecure and should not be used. Consider using UTF-8 instead. | | __`MSLIB0002`__ | `PrincipalPermissionAttribute` is not honored by the runtime and must not be used. | diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs index c6a91b550cf475..ed170eb696d4f9 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs @@ -369,14 +369,7 @@ private set if (value != null && (value.Equals(Encoding.BigEndianUnicode) - || value.Equals(Encoding.Unicode) -#if FEATURE_UTF32 - || value.Equals(Encoding.UTF32) -#endif // FEATURE_UTF32 -#if FEATURE_UTF7 - || value.Equals(Encoding.UTF7) -#endif // FEATURE_UTF7 - )) + || value.Equals(Encoding.Unicode))) { throw new ArgumentException(SR.EntryNameEncodingNotSupported, nameof(EntryNameEncoding)); } diff --git a/src/libraries/System.IO.Ports/tests/SerialPort/Encoding.cs b/src/libraries/System.IO.Ports/tests/SerialPort/Encoding.cs index 60b309a89b11ad..5f1629c9687d11 100644 --- a/src/libraries/System.IO.Ports/tests/SerialPort/Encoding.cs +++ b/src/libraries/System.IO.Ports/tests/SerialPort/Encoding.cs @@ -111,7 +111,7 @@ public void Encoding_ISCIIAssemese() public void Encoding_UTF7() { Debug.WriteLine("Verifying UTF7Encoding Encoding"); - VerifyException(Encoding.UTF7, ThrowAt.Set, typeof(ArgumentException)); + VerifyException(LegacyUTF7Encoding, ThrowAt.Set, typeof(ArgumentException)); } [ConditionalFact(nameof(HasOneSerialPort))] diff --git a/src/libraries/System.IO.Ports/tests/SerialPort/ReadTo.cs b/src/libraries/System.IO.Ports/tests/SerialPort/ReadTo.cs index 4a63d75a1ebf49..6f05b6d7154247 100644 --- a/src/libraries/System.IO.Ports/tests/SerialPort/ReadTo.cs +++ b/src/libraries/System.IO.Ports/tests/SerialPort/ReadTo.cs @@ -691,7 +691,7 @@ private void PerformReadOnCom1FromCom2(SerialPort com1, SerialPort com2, string int totalCharsRead; int lastIndexOfNewLine = -newLineStringLength; string expectedString; - bool isUTF7Encoding = com1.Encoding.EncodingName == Encoding.UTF7.EncodingName; + bool isUTF7Encoding = IsUTF7Encoding(com1.Encoding); char[] charsToWrite = strToWrite.ToCharArray(); byte[] bytesToWrite = com1.Encoding.GetBytes(charsToWrite); @@ -805,7 +805,7 @@ private void VerifyReadToWithWriteLine(Encoding encoding, string newLine) Random rndGen = new Random(-55); StringBuilder strBldrToWrite; string strExpected; - bool isUTF7Encoding = encoding.EncodingName == Encoding.UTF7.EncodingName; + bool isUTF7Encoding = IsUTF7Encoding(encoding); Debug.WriteLine("Verifying ReadTo with WriteLine encoding={0}, newLine={1}", encoding, newLine); @@ -861,10 +861,10 @@ private string GenRandomNewLine(bool validAscii) private int GetUTF7EncodingBytes(char[] chars, int index, int count) { - byte[] bytes = Encoding.UTF7.GetBytes(chars, index, count); + byte[] bytes = LegacyUTF7Encoding.GetBytes(chars, index, count); int byteCount = bytes.Length; - while (Encoding.UTF7.GetCharCount(bytes, 0, byteCount) == count) + while (LegacyUTF7Encoding.GetCharCount(bytes, 0, byteCount) == count) { --byteCount; } diff --git a/src/libraries/System.IO.Ports/tests/SerialPort/Read_char_int_int.cs b/src/libraries/System.IO.Ports/tests/SerialPort/Read_char_int_int.cs index 30561e194a90ba..4c1ec55496660a 100644 --- a/src/libraries/System.IO.Ports/tests/SerialPort/Read_char_int_int.cs +++ b/src/libraries/System.IO.Ports/tests/SerialPort/Read_char_int_int.cs @@ -950,18 +950,9 @@ private void VerifyBytesFollowedByChars(Encoding encoding) Fail("ERROR!!!: Expected to read {0} chars actually read {1}", xmitCharBuffer.Length, numRead); } - if (encoding.EncodingName == Encoding.UTF7.EncodingName) + if (IsUTF7Encoding(encoding)) { - //If UTF7Encoding is being used we might leave a - in the stream - if (com1.BytesToRead == xmitByteBuffer.Length + 1) - { - int byteRead; - - if ('-' != (char)(byteRead = com1.ReadByte())) - { - Fail("Err_29282naie Expected '-' to be left in the stream with UTF7Encoding and read {0}", byteRead); - } - } + Fail("UTF-7 encoding not expected to be passed to this test."); } if (xmitByteBuffer.Length != (numRead = com1.Read(rcvByteBuffer, 0, rcvByteBuffer.Length))) diff --git a/src/libraries/System.IO.Ports/tests/Support/PortsTest.cs b/src/libraries/System.IO.Ports/tests/Support/PortsTest.cs index 74148b20a3250b..ef10226595bca4 100644 --- a/src/libraries/System.IO.Ports/tests/Support/PortsTest.cs +++ b/src/libraries/System.IO.Ports/tests/Support/PortsTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Text; using System.IO; using Legacy.Support; using Xunit; @@ -36,5 +37,20 @@ public static void Fail(string format, params object[] args) { Assert.True(false, string.Format(format, args)); } + +#pragma warning disable MSLIB0001 // Encoding.UTF7 property is obsolete + protected static Encoding LegacyUTF7Encoding => Encoding.UTF7; +#pragma warning restore MSLIB0001 + + /// + /// Returns a value stating whether is UTF-7. + /// + /// + /// This method checks only for the code page 65000. + /// + internal static bool IsUTF7Encoding(Encoding encoding) + { + return (encoding.CodePage == LegacyUTF7Encoding.CodePage); + } } } diff --git a/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingReadStreamTests.cs b/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingReadStreamTests.cs index 3a8ee5840f7dfc..ef8904bf72fd22 100644 --- a/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingReadStreamTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingReadStreamTests.cs @@ -232,15 +232,6 @@ public Task ReadAsync_Works_WhenInputIs_Unicode(string message) return ReadAsyncTest(sourceEncoding, message); } - [Theory] - [MemberData(nameof(ReadAsyncInputLatin), "utf-7")] - [MemberData(nameof(ReadAsyncInputUnicode), "utf-7")] - public Task ReadAsync_Works_WhenInputIs_UTF7(string message) - { - Encoding sourceEncoding = Encoding.UTF7; - return ReadAsyncTest(sourceEncoding, message); - } - [Theory] [MemberData(nameof(ReadAsyncInputLatin), "iso-8859-1")] public Task ReadAsync_Works_WhenInputIs_WesternEuropeanEncoding(string message) diff --git a/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingWriteStreamTests.cs b/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingWriteStreamTests.cs index 546f666c7c1133..909f174e125bb8 100644 --- a/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingWriteStreamTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/UnitTests/TranscodingWriteStreamTests.cs @@ -42,14 +42,6 @@ public Task WriteAsync_Works_WhenOutputIs_Unicode(string message) return WriteAsyncTest(targetEncoding, message); } - [Theory] - [MemberData(nameof(WriteAsyncInputLatin))] - public Task WriteAsync_Works_WhenOutputIs_UTF7(string message) - { - Encoding targetEncoding = Encoding.UTF7; - return WriteAsyncTest(targetEncoding, message); - } - [Theory] [MemberData(nameof(WriteAsyncInputLatin))] public Task WriteAsync_Works_WhenOutputIs_WesternEuropeanEncoding(string message) diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml index 44149875d2b0a4..1ebf944c46ad14 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml @@ -9,5 +9,8 @@ + + + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 52cce0d6db9f02..8dbea2ed27172f 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3742,6 +3742,9 @@ Unmatched value was {0}. + + Support for UTF-7 is disabled. See {0} for more information. + Type '{0}' returned by IDynamicInterfaceCastable does not implement the requested interface '{1}'. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1a55884cc73351..78f6a5ddbdd989 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -455,6 +455,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs b/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs index a8c2b187d15ff7..924504aa0f1a11 100644 --- a/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs +++ b/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs @@ -8,6 +8,13 @@ namespace System { internal static partial class LocalAppContextSwitches { + private static int s_enableUnsafeUTF7Encoding; + public static bool EnableUnsafeUTF7Encoding + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue("System.Text.Encoding.EnableUnsafeUTF7Encoding", ref s_enableUnsafeUTF7Encoding); + } + private static int s_enforceJapaneseEraYearRanges; public static bool EnforceJapaneseEraYearRanges { diff --git a/src/libraries/System.Private.CoreLib/src/System/Obsoletions.cs b/src/libraries/System.Private.CoreLib/src/System/Obsoletions.cs new file mode 100644 index 00000000000000..549aa39bbad4df --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Obsoletions.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System +{ + internal static class Obsoletions + { + internal const string SharedUrlFormat = "https://aka.ms/dotnet-warnings/{0}"; + + internal const string SystemTextEncodingUTF7Message = "The UTF-7 encoding is insecure and should not be used. Consider using UTF-8 instead."; + internal const string SystemTextEncodingUTF7DiagId = "MSLIB0001"; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs index c87e5ccda21d7a..6d76a152fd95b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -105,7 +106,7 @@ public abstract partial class Encoding : ICloneable internal const int ISO_8859_1 = 28591; // Latin1 // Special code pages - private const int CodePageUTF7 = 65000; + internal const int CodePageUTF7 = 65000; private const int CodePageUTF8 = 65001; private const int CodePageUTF32 = 12000; private const int CodePageUTF32BE = 12001; @@ -214,7 +215,7 @@ public static void RegisterProvider(EncodingProvider provider) public static Encoding GetEncoding(int codepage) { - Encoding? result = EncodingProvider.GetEncodingFromProvider(codepage); + Encoding? result = FilterDisallowedEncodings(EncodingProvider.GetEncodingFromProvider(codepage)); if (result != null) return result; @@ -225,7 +226,6 @@ public static Encoding GetEncoding(int codepage) case CodePageBigEndian: return BigEndianUnicode; // 1201 case CodePageUTF32: return UTF32; // 12000 case CodePageUTF32BE: return BigEndianUTF32; // 12001 - case CodePageUTF7: return UTF7; // 65000 case CodePageUTF8: return UTF8; // 65001 case CodePageASCII: return ASCII; // 20127 case ISO_8859_1: return Latin1; // 28591 @@ -236,6 +236,27 @@ public static Encoding GetEncoding(int codepage) case CodePageNoThread: // 3 CP_THREAD_ACP case CodePageNoSymbol: // 42 CP_SYMBOL throw new ArgumentException(SR.Format(SR.Argument_CodepageNotSupported, codepage), nameof(codepage)); + + case CodePageUTF7: // 65000 + { + // Support for UTF-7 is disabled by default. It can be re-enabled by registering a custom + // provider (which early-exits this method before the 'switch' statement) or by using + // AppContext. If support is not enabled, we'll provide a friendly error message stating + // how the developer can re-enable it in their application. + + if (LocalAppContextSwitches.EnableUnsafeUTF7Encoding) + { +#pragma warning disable MSLIB0001 // Encoding.UTF7 property getter is obsolete + return UTF7; +#pragma warning restore MSLIB0001 + } + else + { + string moreInfoUrl = string.Format(CultureInfo.InvariantCulture, Obsoletions.SharedUrlFormat, Obsoletions.SystemTextEncodingUTF7DiagId); + string exceptionMessage = SR.Format(SR.Encoding_UTF7_Disabled, moreInfoUrl); + throw new NotSupportedException(exceptionMessage); // matches generic "unknown code page" exception type + } + } } if (codepage < 0 || codepage > 65535) @@ -250,7 +271,7 @@ public static Encoding GetEncoding(int codepage) public static Encoding GetEncoding(int codepage, EncoderFallback encoderFallback, DecoderFallback decoderFallback) { - Encoding? baseEncoding = EncodingProvider.GetEncodingFromProvider(codepage, encoderFallback, decoderFallback); + Encoding? baseEncoding = FilterDisallowedEncodings(EncodingProvider.GetEncodingFromProvider(codepage, encoderFallback, decoderFallback)); if (baseEncoding != null) return baseEncoding; @@ -274,7 +295,7 @@ public static Encoding GetEncoding(string name) // add the corresponding item in EncodingTable. // Otherwise, the code below will throw exception when trying to call // EncodingTable.GetCodePageFromName(). - return EncodingProvider.GetEncodingFromProvider(name) ?? + return FilterDisallowedEncodings(EncodingProvider.GetEncodingFromProvider(name)) ?? GetEncoding(EncodingTable.GetCodePageFromName(name)); } @@ -287,10 +308,24 @@ public static Encoding GetEncoding(string name, // add the corresponding item in EncodingTable. // Otherwise, the code below will throw exception when trying to call // EncodingTable.GetCodePageFromName(). - return EncodingProvider.GetEncodingFromProvider(name, encoderFallback, decoderFallback) ?? + return FilterDisallowedEncodings(EncodingProvider.GetEncodingFromProvider(name, encoderFallback, decoderFallback)) ?? GetEncoding(EncodingTable.GetCodePageFromName(name), encoderFallback, decoderFallback); } + // If the input encoding is forbidden (currently, only UTF-7), returns null. + // Otherwise returns the input encoding unchanged. + private static Encoding? FilterDisallowedEncodings(Encoding? encoding) + { + if (LocalAppContextSwitches.EnableUnsafeUTF7Encoding) + { + return encoding; + } + else + { + return (encoding?.CodePage == CodePageUTF7) ? null : encoding; + } + } + /// /// Get the list from the runtime and all registered encoding providers /// @@ -1020,6 +1055,7 @@ public virtual string GetString(byte[] bytes, int index, int count) => // Returns an encoding for the UTF-7 format. The returned encoding will be // an instance of the UTF7Encoding class. + [Obsolete(Obsoletions.SystemTextEncodingUTF7Message, DiagnosticId = Obsoletions.SystemTextEncodingUTF7DiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static Encoding UTF7 => UTF7Encoding.s_default; // Returns an encoding for the UTF-8 format. The returned encoding will be diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/EncodingTable.cs b/src/libraries/System.Private.CoreLib/src/System/Text/EncodingTable.cs index 40904f1cbc2c38..5931474d9d6f69 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/EncodingTable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/EncodingTable.cs @@ -106,20 +106,31 @@ private static int InternalGetCodePageFromName(string name) // Return a list of all EncodingInfo objects describing all of our encodings internal static EncodingInfo[] GetEncodings() { + // If UTF-7 encoding is not enabled, we adjust the return array length by -1 + // to account for the skipped EncodingInfo element. + ushort[] mappedCodePages = s_mappedCodePages; - EncodingInfo[] arrayEncodingInfo = new EncodingInfo[mappedCodePages.Length]; + EncodingInfo[] arrayEncodingInfo = new EncodingInfo[(LocalAppContextSwitches.EnableUnsafeUTF7Encoding) ? mappedCodePages.Length : (mappedCodePages.Length - 1)]; string webNames = s_webNames; int[] webNameIndices = s_webNameIndices; + int arrayEncodingInfoIdx = 0; for (int i = 0; i < mappedCodePages.Length; i++) { - arrayEncodingInfo[i] = new EncodingInfo( - mappedCodePages[i], + int codePage = mappedCodePages[i]; + if (codePage == Encoding.CodePageUTF7 && !LocalAppContextSwitches.EnableUnsafeUTF7Encoding) + { + continue; // skip this entry; UTF-7 is disabled + } + + arrayEncodingInfo[arrayEncodingInfoIdx++] = new EncodingInfo( + codePage, webNames[webNameIndices[i]..webNameIndices[i + 1]], - GetDisplayName(mappedCodePages[i], i) + GetDisplayName(codePage, i) ); } + Debug.Assert(arrayEncodingInfoIdx == arrayEncodingInfo.Length); return arrayEncodingInfo; } @@ -132,13 +143,28 @@ internal static EncodingInfo[] GetEncodings(Dictionary encodi for (int i = 0; i < mappedCodePages.Length; i++) { - if (!encodingInfoList.ContainsKey(mappedCodePages[i])) + int codePage = mappedCodePages[i]; + if (!encodingInfoList.ContainsKey(codePage)) { - encodingInfoList[mappedCodePages[i]] = new EncodingInfo(mappedCodePages[i], webNames[webNameIndices[i]..webNameIndices[i + 1]], - GetDisplayName(mappedCodePages[i], i)); + // If UTF-7 encoding is not enabled, don't add it to the provided dictionary instance. + // Exception: If somebody already registered a custom UTF-7 provider, the dictionary + // will already contain an entry for the UTF-7 code page key, and we'll let it go through. + + if (codePage != Encoding.CodePageUTF7 || LocalAppContextSwitches.EnableUnsafeUTF7Encoding) + { + encodingInfoList[codePage] = new EncodingInfo(codePage, webNames[webNameIndices[i]..webNameIndices[i + 1]], + GetDisplayName(codePage, i)); + } } } + // Just in case a provider registered UTF-7 without the application's consent + + if (!LocalAppContextSwitches.EnableUnsafeUTF7Encoding) + { + encodingInfoList.Remove(Encoding.CodePageUTF7); // won't throw if doesn't exist + } + var result = new EncodingInfo[encodingInfoList.Count]; int j = 0; foreach (KeyValuePair pair in encodingInfoList) diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/UTF7Encoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/UTF7Encoding.cs index 50fd0ed4074a3b..3466a57f48cbf5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/UTF7Encoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/UTF7Encoding.cs @@ -27,9 +27,11 @@ public class UTF7Encoding : Encoding private const string optionalChars = "!\"#$%&*;<=>@[]^_`{|}"; +#pragma warning disable MSLIB0001 // Used by Encoding.UTF7 for lazy initialization // The initialization code will not be run until a static member of the class is referenced internal static readonly UTF7Encoding s_default = new UTF7Encoding(); +#pragma warning restore MSLIB0001 // The set of base 64 characters. private byte[] _base64Bytes; @@ -46,11 +48,13 @@ public class UTF7Encoding : Encoding private const int UTF7_CODEPAGE = 65000; + [Obsolete(Obsoletions.SystemTextEncodingUTF7Message, DiagnosticId = Obsoletions.SystemTextEncodingUTF7DiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public UTF7Encoding() : this(false) { } + [Obsolete(Obsoletions.SystemTextEncodingUTF7Message, DiagnosticId = Obsoletions.SystemTextEncodingUTF7DiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public UTF7Encoding(bool allowOptionals) : base(UTF7_CODEPAGE) // Set the data item. { diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 667e8fcc36cbe0..6caea918b33e01 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10443,6 +10443,7 @@ protected Encoding(int codePage, System.Text.EncoderFallback? encoderFallback, S public virtual System.ReadOnlySpan Preamble { get { throw null; } } public static System.Text.Encoding Unicode { get { throw null; } } public static System.Text.Encoding UTF32 { get { throw null; } } + [System.ObsoleteAttribute("The UTF-7 encoding is insecure and should not be used. Consider using UTF-8 instead.", DiagnosticId = "MSLIB0001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public static System.Text.Encoding UTF7 { get { throw null; } } public static System.Text.Encoding UTF8 { get { throw null; } } public virtual string WebName { get { throw null; } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index cd5ecb781c6e45..a565661a5f4016 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -242,6 +242,7 @@ + @@ -274,4 +275,7 @@ + + + diff --git a/src/libraries/System.Runtime/tests/System/Text/EncodingTests.cs b/src/libraries/System.Runtime/tests/System/Text/EncodingTests.cs new file mode 100644 index 00000000000000..1e6dcbea93f457 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Text/EncodingTests.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using Moq; +using Xunit; + +namespace System.Text.Tests +{ + public class EncodingTests + { +#pragma warning disable MSLIB0001 // UTF7Encoding is obsolete + private static UTF7Encoding _utf7Encoding = new UTF7Encoding(); +#pragma warning restore MSLIB0001 + + public static IEnumerable DisallowedEncodings() + { + yield return new object[] { "utf-7", 65000 }; + } + + [Theory] + [MemberData(nameof(DisallowedEncodings))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public void GetEncoding_BuiltIn_ByCodePage_WithDisallowedEncoding_Throws(string encodingName, int codePage) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters + { + Assert.Throws(() => Encoding.GetEncoding(codePage)); + Assert.Throws(() => Encoding.GetEncoding(codePage, EncoderFallback.ReplacementFallback, DecoderFallback.ReplacementFallback)); + } + + [Theory] + [MemberData(nameof(DisallowedEncodings))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public void GetEncoding_FromProvider_ByCodePage_WithDisallowedEncoding_Throws(string encodingName, int codePage) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters + { + Mock mockEncoding = new Mock(); + mockEncoding.Setup(o => o.CodePage).Returns(codePage); + + Mock mockProvider = new Mock(); + mockProvider.Setup(o => o.GetEncoding(codePage)).Returns(mockEncoding.Object); + mockProvider.Setup(o => o.GetEncoding(codePage, It.IsAny(), It.IsAny())).Returns(mockEncoding.Object); + + ThreadStaticEncodingProvider.WithEncodingProvider(mockProvider.Object, () => + { + Assert.Throws(() => Encoding.GetEncoding(codePage)); + Assert.Throws(() => Encoding.GetEncoding(codePage, EncoderFallback.ReplacementFallback, DecoderFallback.ReplacementFallback)); + }); + } + + [Theory] + [MemberData(nameof(DisallowedEncodings))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public void GetEncoding_BuiltIn_ByEncodingName_WithDisallowedEncoding_Throws(string encodingName, int codePage) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters + { + Assert.Throws(() => Encoding.GetEncoding(encodingName)); + Assert.Throws(() => Encoding.GetEncoding(encodingName, EncoderFallback.ReplacementFallback, DecoderFallback.ReplacementFallback)); + } + + [Theory] + [MemberData(nameof(DisallowedEncodings))] + public void GetEncoding_FromProvider_ByEncodingName_WithDisallowedEncoding_Throws(string encodingName, int codePage) + { + Mock mockEncoding = new Mock(); + mockEncoding.Setup(o => o.CodePage).Returns(codePage); + + Mock mockProvider = new Mock(); + mockProvider.Setup(o => o.GetEncoding(encodingName)).Returns(mockEncoding.Object); + mockProvider.Setup(o => o.GetEncoding(encodingName, It.IsAny(), It.IsAny())).Returns(mockEncoding.Object); + + ThreadStaticEncodingProvider.WithEncodingProvider(mockProvider.Object, () => + { + Assert.Throws(() => Encoding.GetEncoding(encodingName)); + Assert.Throws(() => Encoding.GetEncoding(encodingName, EncoderFallback.ReplacementFallback, DecoderFallback.ReplacementFallback)); + }); + } + + [Theory] + [MemberData(nameof(DisallowedEncodings))] + public void GetEncodings_BuiltIn_DoesNotContainDisallowedEncodings(string encodingName, int codePage) + { + foreach (EncodingInfo encodingInfo in Encoding.GetEncodings()) + { + Assert.NotEqual(encodingName, encodingInfo.Name, StringComparer.OrdinalIgnoreCase); + Assert.NotEqual(codePage, encodingInfo.CodePage); + } + } + + [Theory] + [MemberData(nameof(DisallowedEncodings))] + public void GetEncodings_FromProvider_DoesNotContainDisallowedEncodings(string encodingName, int codePage) + { + Mock mockProvider = new Mock(MockBehavior.Strict); + mockProvider.Setup(o => o.GetEncodings()).Returns( + new[] { new EncodingInfo(mockProvider.Object, codePage, encodingName, "UTF-7") }); + + ThreadStaticEncodingProvider.WithEncodingProvider(mockProvider.Object, () => + { + foreach (EncodingInfo encodingInfo in Encoding.GetEncodings()) + { + Assert.NotEqual(encodingName, encodingInfo.Name, StringComparer.OrdinalIgnoreCase); + Assert.NotEqual(codePage, encodingInfo.CodePage); + } + }); + } + + private sealed class ThreadStaticEncodingProvider : EncodingProvider + { + private static readonly object _globalRegistrationLockObj = new object(); + private static bool _globalRegistrationCompleted; + + [ThreadStatic] + private static EncodingProvider _staticInstance; + + private ThreadStaticEncodingProvider() { } + + public static void WithEncodingProvider(EncodingProvider instance, Action action) + { + EnsureProviderRegistered(); + + EncodingProvider oldInstance = _staticInstance; + try + { + _staticInstance = instance; + action(); + } + finally + { + _staticInstance = oldInstance; + } + } + + private static void EnsureProviderRegistered() + { + if (!_globalRegistrationCompleted) + { + lock (_globalRegistrationLockObj) + { + if (!_globalRegistrationCompleted) + { + Encoding.RegisterProvider(new ThreadStaticEncodingProvider()); + _globalRegistrationCompleted = true; + } + } + } + } + + public override Encoding GetEncoding(int codepage) + => _staticInstance?.GetEncoding(codepage); + + public override Encoding GetEncoding(int codepage, EncoderFallback encoderFallback, DecoderFallback decoderFallback) + => _staticInstance?.GetEncoding(codepage, encoderFallback, decoderFallback); + + public override Encoding GetEncoding(string name) + => _staticInstance?.GetEncoding(name); + + public override Encoding GetEncoding(string name, EncoderFallback encoderFallback, DecoderFallback decoderFallback) + => _staticInstance?.GetEncoding(name, encoderFallback, decoderFallback); + + public override IEnumerable GetEncodings() + => _staticInstance?.GetEncodings() ?? Enumerable.Empty(); + } + } +} diff --git a/src/libraries/System.Text.Encoding.CodePages/tests/EncodingCodePages.cs b/src/libraries/System.Text.Encoding.CodePages/tests/EncodingCodePages.cs index edd6e158fdfc7d..faca9b5a5f23cd 100644 --- a/src/libraries/System.Text.Encoding.CodePages/tests/EncodingCodePages.cs +++ b/src/libraries/System.Text.Encoding.CodePages/tests/EncodingCodePages.cs @@ -447,8 +447,13 @@ private static IEnumerable> CrossplatformDefaultEncodi yield return Map(1200, "utf-16"); yield return Map(12000, "utf-32"); yield return Map(20127, "us-ascii"); - yield return Map(65000, "utf-7"); yield return Map(65001, "utf-8"); + + if (!PlatformDetection.IsNetCore) + { + // Encoding.GetEncodings() doesn't include UTF-7 in the result set as of .NET 5.0 + yield return Map(65000, "utf-7"); + } } private static KeyValuePair Map(int codePage, string webName) diff --git a/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs b/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs index 3f32a97fc837ac..67e741cc1dfae5 100644 --- a/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs +++ b/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs @@ -96,7 +96,9 @@ public UTF32Encoding(bool bigEndian, bool byteOrderMark, bool throwOnInvalidChar } public partial class UTF7Encoding : System.Text.Encoding { + [System.ObsoleteAttribute("The UTF-7 encoding is insecure and should not be used. Consider using UTF-8 instead.", DiagnosticId = "MSLIB0001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public UTF7Encoding() { } + [System.ObsoleteAttribute("The UTF-7 encoding is insecure and should not be used. Consider using UTF-8 instead.", DiagnosticId = "MSLIB0001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public UTF7Encoding(bool allowOptionals) { } public override bool Equals(object? value) { throw null; } [System.CLSCompliantAttribute(false)] diff --git a/src/libraries/System.Text.Encoding/tests/Encoding/Encoding.cs b/src/libraries/System.Text.Encoding/tests/Encoding/Encoding.cs index 0cacbaa18c7b8d..703d37788f783f 100644 --- a/src/libraries/System.Text.Encoding/tests/Encoding/Encoding.cs +++ b/src/libraries/System.Text.Encoding/tests/Encoding/Encoding.cs @@ -65,6 +65,7 @@ public static void GetEncodingsTest() } } + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json [Theory] [MemberData(nameof(Encoding_TestData))] public static void VerifyCodePageAttributes(int codepage, string name, string bodyName, string headerName, bool isBrowserDisplay, @@ -81,6 +82,7 @@ public static void VerifyCodePageAttributes(int codepage, string name, string bo Assert.Equal(windowsCodePage, encoding.WindowsCodePage); } + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json [Theory] [MemberData(nameof(Normalization_TestData))] public static void NormalizationTest(int codepage, bool normalized, bool normalizedC, bool normalizedD, bool normalizedKC, bool normalizedKD) diff --git a/src/libraries/System.Text.Encoding/tests/Encoding/EncodingGetEncodingTests.cs b/src/libraries/System.Text.Encoding/tests/Encoding/EncodingGetEncodingTests.cs index 69b2b3894ffd75..33b006c83ed648 100644 --- a/src/libraries/System.Text.Encoding/tests/Encoding/EncodingGetEncodingTests.cs +++ b/src/libraries/System.Text.Encoding/tests/Encoding/EncodingGetEncodingTests.cs @@ -89,6 +89,7 @@ public CodePageMapping(string name, int codepage) new CodePageMapping("x-unicode-2-0-utf-8", 65001) }; + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json [Fact] public void TestEncodingNameAndCopdepageNumber() { @@ -99,6 +100,7 @@ public void TestEncodingNameAndCopdepageNumber() } } + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json [Fact] public void GetEncoding_EncodingName() { @@ -118,6 +120,7 @@ public void GetEncoding_EncodingName() } } + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json [Fact] public void GetEncoding_WebName() { diff --git a/src/libraries/System.Text.Encoding/tests/System.Text.Encoding.Tests.csproj b/src/libraries/System.Text.Encoding/tests/System.Text.Encoding.Tests.csproj index 7b990f30a054cb..f4077aa49f2736 100644 --- a/src/libraries/System.Text.Encoding/tests/System.Text.Encoding.Tests.csproj +++ b/src/libraries/System.Text.Encoding/tests/System.Text.Encoding.Tests.csproj @@ -4,6 +4,8 @@ true true $(NetCoreAppCurrent) + + $(NoWarn),MSLIB0001 @@ -78,6 +80,9 @@ + + + diff --git a/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingEncode.cs b/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingEncode.cs index 2a9503cf5ba4c9..4d7c79317d4ba7 100644 --- a/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingEncode.cs +++ b/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingEncode.cs @@ -7,6 +7,7 @@ namespace System.Text.Tests { + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json public class UTF7EncodingEncode { public static IEnumerable Encode_Basic_TestData() diff --git a/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingTests.cs b/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingTests.cs index 0935ab36a9e5f6..e46722fbed546c 100644 --- a/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingTests.cs +++ b/src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingTests.cs @@ -7,6 +7,7 @@ namespace System.Text.Tests { + [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json public class UTF7EncodingTests { [Fact] diff --git a/src/libraries/System.Text.Encoding/tests/runtimeconfig.template.json b/src/libraries/System.Text.Encoding/tests/runtimeconfig.template.json new file mode 100644 index 00000000000000..f24ff824948f67 --- /dev/null +++ b/src/libraries/System.Text.Encoding/tests/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Text.Encoding.EnableUnsafeUTF7Encoding": true + } +}