// 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.Diagnostics; namespace System.Globalization { public sealed partial class IdnMapping { private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(unicodeString != null && unicodeString.Length >= count); uint flags = Flags; CheckInvalidIdnCharacters(unicode, count, 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(count * 3L + 4, StackallocThreshold); int actualLength; if (estimatedLength < StackallocThreshold) { char* outputStack = stackalloc char[estimatedLength]; actualLength = Interop.Globalization.ToAscii(flags, unicode, count, outputStack, estimatedLength); if (actualLength > 0 && actualLength <= estimatedLength) { return GetStringForOutput(unicodeString, unicode, count, outputStack, actualLength); } } else { actualLength = Interop.Globalization.ToAscii(flags, unicode, count, null, 0); } if (actualLength == 0) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); } char[] outputHeap = new char[actualLength]; fixed (char* pOutputHeap = &outputHeap[0]) { actualLength = Interop.Globalization.ToAscii(flags, unicode, count, pOutputHeap, actualLength); if (actualLength == 0 || actualLength > outputHeap.Length) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); } return GetStringForOutput(unicodeString, unicode, count, pOutputHeap, actualLength); } } private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(asciiString != null && asciiString.Length >= count); uint flags = Flags; CheckInvalidIdnCharacters(ascii, count, flags, nameof(ascii)); const int StackAllocThreshold = 512; if (count < StackAllocThreshold) { char* output = stackalloc char[count]; return GetUnicodeCore(asciiString, ascii, count, flags, output, count, reattempt: true); } else { char[] output = new char[count]; fixed (char* pOutput = &output[0]) { return GetUnicodeCore(asciiString, ascii, count, flags, pOutput, count, reattempt: true); } } } private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count, uint flags, char* output, int outputLength, bool reattempt) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(asciiString != null && asciiString.Length >= count); int realLen = Interop.Globalization.ToUnicode(flags, ascii, count, output, outputLength); if (realLen == 0) { throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); } else if (realLen <= outputLength) { return GetStringForOutput(asciiString, ascii, count, output, realLen); } else if (reattempt) { char[] newOutput = new char[realLen]; fixed (char* pNewOutput = newOutput) { return GetUnicodeCore(asciiString, ascii, count, flags, pNewOutput, realLen, reattempt: false); } } throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); } // ----------------------------- // ---- PAL layer ends here ---- // ----------------------------- private uint Flags { get { int flags = (AllowUnassigned ? Interop.Globalization.AllowUnassigned : 0) | (UseStd3AsciiRules ? Interop.Globalization.UseStd3AsciiRules : 0); return (uint)flags; } } /// /// 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. /// private static unsafe void CheckInvalidIdnCharacters(char* s, int count, uint flags, string paramName) { if ((flags & Interop.Globalization.UseStd3AsciiRules) == 0) { for (int i = 0; i < count; 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); } } } } } }