// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; namespace System.Globalization { public sealed partial class IdnMapping { private unsafe string IcuGetAsciiCore(string unicodeString, int index, int count) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); ReadOnlySpan<char> unicode = unicodeString.AsSpan(index, count); uint flags = IcuFlags; CheckInvalidIdnCharacters(unicode, flags, nameof(unicode)); const int StackallocThreshold = 512; // Each unicode character is represented by up to 3 ASCII chars // and the whole string is prefixed by "xn--" (length 4) int estimatedLength = (int)Math.Min(checked(count * 3L + 4), StackallocThreshold); int actualLength; if ((uint)estimatedLength < StackallocThreshold) { Span<char> outputStack = stackalloc char[estimatedLength]; actualLength = Interop.Globalization.ToAscii(flags, unicode, count, outputStack, estimatedLength); if (actualLength > 0 && actualLength <= estimatedLength) { return GetStringForOutput(unicodeString, unicode, outputStack.Slice(0, actualLength)); } } else { actualLength = Interop.Globalization.ToAscii(flags, unicode, count, Span<char>.Empty, 0); } if (actualLength == 0) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); } char[] outputHeap = new char[actualLength]; actualLength = Interop.Globalization.ToAscii(flags, unicode, count, outputHeap, actualLength); if (actualLength == 0 || actualLength > outputHeap.Length) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); } return GetStringForOutput(unicodeString, unicode, outputHeap.AsSpan(0, actualLength)); } private bool IcuTryGetAsciiCore(ReadOnlySpan<char> unicode, Span<char> destination, out int charsWritten) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); uint flags = IcuFlags; CheckInvalidIdnCharacters(unicode, flags, nameof(unicode)); int actualLength = Interop.Globalization.ToAscii(flags, unicode, unicode.Length, destination, destination.Length); if (actualLength <= destination.Length) { if (actualLength == 0) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); } charsWritten = actualLength; return true; } charsWritten = 0; return false; } private unsafe string IcuGetUnicodeCore(string asciiString, int index, int count) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); ReadOnlySpan<char> ascii = asciiString.AsSpan(index, count); uint flags = IcuFlags; CheckInvalidIdnCharacters(ascii, flags, nameof(ascii)); const int StackAllocThreshold = 512; if ((uint)count < StackAllocThreshold) { Span<char> output = stackalloc char[count]; return IcuGetUnicodeCore(asciiString, ascii, flags, output, reattempt: true); } else { char[] output = new char[count]; return IcuGetUnicodeCore(asciiString, ascii, flags, output, reattempt: true); } } private static string IcuGetUnicodeCore(string asciiString, ReadOnlySpan<char> ascii, uint flags, Span<char> output, bool reattempt) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); int realLen = Interop.Globalization.ToUnicode(flags, ascii, ascii.Length, output, output.Length); if (realLen == 0) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); } else if (realLen <= output.Length) { return GetStringForOutput(asciiString, ascii, output.Slice(0, realLen)); } else if (reattempt) { char[] newOutput = new char[realLen]; return IcuGetUnicodeCore(asciiString, ascii, flags, newOutput, reattempt: false); } throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); } private bool IcuTryGetUnicodeCore(ReadOnlySpan<char> ascii, Span<char> destination, out int charsWritten) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); uint flags = IcuFlags; CheckInvalidIdnCharacters(ascii, flags, nameof(ascii)); int actualLength = Interop.Globalization.ToUnicode(flags, ascii, ascii.Length, destination, destination.Length); if (actualLength <= destination.Length) { if (actualLength == 0) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); } charsWritten = actualLength; return true; } charsWritten = 0; return false; } private uint IcuFlags { get { int flags = (AllowUnassigned ? Interop.Globalization.AllowUnassigned : 0) | (UseStd3AsciiRules ? Interop.Globalization.UseStd3AsciiRules : 0); return (uint)flags; } } /// <summary> /// ICU doesn't check for invalid characters unless the STD3 rules option /// is enabled. /// /// To match Windows behavior, we walk the string ourselves looking for these /// bad characters so we can continue to throw ArgumentException in these cases. /// </summary> private static void CheckInvalidIdnCharacters(ReadOnlySpan<char> s, uint flags, string paramName) { if ((flags & Interop.Globalization.UseStd3AsciiRules) == 0) { for (int i = 0; i < s.Length; i++) { char c = s[i]; // These characters are prohibited regardless of the UseStd3AsciiRules property. // See https://msdn.microsoft.com/en-us/library/system.globalization.idnmapping.usestd3asciirules(v=vs.110).aspx if (c <= 0x1F || c == 0x7F) { throw new ArgumentException(SR.Argument_IdnIllegalName, paramName); } } } } } }