Skip to content

[Test Only] Fix Idn tests #115030

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 26, 2025
Merged
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
@@ -27,22 +27,30 @@ public class ConformanceIdnaTestResult
/// </summary>
public string Value { get; private set; }

public string? Source { get; private set; }

public IdnaTestResultType ResultType { get; private set; }

public string StatusValue { get; private set; }

public ConformanceIdnaTestResult(string entry, string fallbackValue, IdnaTestResultType resultType = IdnaTestResultType.ToAscii)
: this(entry, fallbackValue, null, null, useValueForStatus: true, resultType)
: this(entry, fallbackValue, null, null, useValueForStatus: true, resultType, null)
{
}

public ConformanceIdnaTestResult(string entry, string fallbackValue, string statusValue, string statusFallbackValue, IdnaTestResultType resultType = IdnaTestResultType.ToAscii)
: this(entry, fallbackValue, statusValue, statusFallbackValue, useValueForStatus: false, resultType)
: this(entry, fallbackValue, statusValue, statusFallbackValue, useValueForStatus: false, resultType, null)
{
}

public ConformanceIdnaTestResult(string entry, string fallbackValue, string statusValue, string statusFallbackValue, string? source, IdnaTestResultType resultType = IdnaTestResultType.ToAscii)
: this(entry, fallbackValue, statusValue, statusFallbackValue, useValueForStatus: false, resultType, source)
{
}

private ConformanceIdnaTestResult(string entry, string fallbackValue, string statusValue, string statusFallbackValue, bool useValueForStatus, IdnaTestResultType resultType)
private ConformanceIdnaTestResult(string entry, string fallbackValue, string statusValue, string statusFallbackValue, bool useValueForStatus, IdnaTestResultType resultType, string? source)
{
Source = source;
ResultType = resultType;
SetValue(string.IsNullOrEmpty(entry.Trim()) ? fallbackValue : entry);
SetSuccess(useValueForStatus ?
@@ -81,11 +89,120 @@ private void SetSuccess(string statusValue)
}
}

// Fullwidth Full Stop, Ideographic Full Stop, and Halfwidth Ideographic Full Stop
private static char[] AllDots = ['.', '\uFF0E', '\u3002', '\uFF61'];

private const char SoftHyphen = '\u00AD';

private bool IsIgnorableA4_2Rule()
{
if (Source is null)
{
return false;
}

// Check the label lengths for the ASCII
int lastIndex = 0;
int index = Value.IndexOfAny(AllDots);
while (index >= 0)
{
if (index - lastIndex > 63) // 63 max label length
{
return false;
}

lastIndex = index + 1;
index = Value.IndexOfAny(AllDots, lastIndex);
}

if (Value.Length - lastIndex > 63)
{
return false;
}

// Remove Hyphen as it is ignored
if (Source.IndexOf(SoftHyphen) >= 0)
{
Span<char> span = stackalloc char[Source.Length];
int spanIndex = 0;

for (int i = 0; i < Source.Length; i++)
{
if (Source[i] != SoftHyphen)
{
span[spanIndex++] = Source[i];
}
}

Source = span.Slice(0, spanIndex).ToString();
}

// Check the label lengths for the Source
lastIndex = 0;
index = Source.IndexOfAny(AllDots);
while (index >= 0)
{
if (index - lastIndex > 63) // 63 max label length
{
return false;
}

lastIndex = index + 1;
index = Source.IndexOfAny(AllDots, lastIndex);
}

if (Source.Length - lastIndex > 63)
{
return false;
}

if (Source[0] is '.') // Leading dot
{
return false;
}

for (int i = 0; i < Source.Length - 1; i++)
{
// Consequence dots
if ((Source[i] is '.' or '\uFF0E' or '\u3002' or '\uFF61') && (Source[i + 1] is '.' or '\uFF0E' or '\u3002' or '\uFF61'))
{
return false;
}

// Check Historical Ranges
if (Source[i] >= 0x2C00 && Source[i] <= 0x2C5F) // Glagolitic (U+2C00–U+2C5F)
return false;

switch (Source[i])
{
case '\uD800':
if (Source[i + 1] >= 0xDFA0 && Source[i + 1] <= 0xDFDF) return false; // Old Persian (U+103A0–U+103DF)
if (Source[i + 1] >= 0xDF30 && Source[i + 1] <= 0xDF4F) return false; // Gothic (U+10330–U+1034F)
if (Source[i + 1] >= 0xDC00 && Source[i + 1] <= 0xDC7F) return false; // Linear B (U+10000–U+1007F)
break;
case '\uD802':
if (Source[i + 1] >= 0xDD00 && Source[i + 1] <= 0xDD1F) return false; // Phoenician (U+10900–U+1091F)
break;
case '\uD803':
if (Source[i + 1] >= 0xDEA0 && Source[i + 1] <= 0xDEAF) return false; // Elymaic (U+10EA0–U+10EAF)
break;
case '\uD808':
if (Source[i + 1] >= 0xDC00 && Source[i + 1] <= 0xDFFF) return false; // Cuneiform (U+12000–U+123FF)
break;
case '\uD838':
if (Source[i + 1] >= 0xDC00 && Source[i + 1] <= 0xDCDF) return false; // Indic Siyaq Numbers (U+1E800–U+1E8DF)
break;
}
}

return true;
}

private bool IsIgnoredError(string statusCode)
{
// We don't validate for BIDI rule so we can ignore BIDI codes
// If we're validating ToAscii we ignore rule V2 (UIDNA_ERROR_HYPHEN_3_4) for compatibility with windows.
return statusCode.StartsWith('B') || (ResultType == IdnaTestResultType.ToAscii && statusCode == "V2");
return statusCode.StartsWith('B') || (ResultType == IdnaTestResultType.ToAscii && statusCode == "V2") || (statusCode.StartsWith("A4_2") && IsIgnorableA4_2Rule());
}
}
}
Original file line number Diff line number Diff line change
@@ -13,6 +13,12 @@ public ConformanceIdnaUnicodeTestResult(string entry, string fallbackValue, bool
ValidDomainName = validDomainName;
}

public ConformanceIdnaUnicodeTestResult(string entry, string fallbackValue, string statusValue, string statusFallbackValue, string? source, bool validDomainName = true)
: base(entry, fallbackValue, statusValue, statusFallbackValue, source, IdnaTestResultType.ToUnicode)
{
ValidDomainName = validDomainName;
}

public ConformanceIdnaUnicodeTestResult(string entry, string fallbackValue, string statusValue, string statusFallbackValue, bool validDomainName = true)
: base(entry, fallbackValue, statusValue, statusFallbackValue, IdnaTestResultType.ToUnicode)
{
Original file line number Diff line number Diff line change
@@ -26,8 +26,12 @@ private static string RemoveComment(string line)
private static Stream GetIdnaTestTxt()
{
string fileName = null;
if (PlatformDetection.ICUVersion >= new Version(74, 0))
if (PlatformDetection.ICUVersion >= new Version(76, 0))
fileName = "IdnaTest_16.txt";
else if (PlatformDetection.ICUVersion >= new Version(72, 1, 0, 4))
fileName = "IdnaTest_15_1.txt";
else if (PlatformDetection.ICUVersion >= new Version(72, 0))
fileName = "IdnaTest_15_0.txt";
else if (PlatformDetection.ICUVersion >= new Version(66, 0))
fileName = "IdnaTest_13.txt";
else if (PlatformDetection.IsWindows7)
@@ -63,8 +67,12 @@ private static IEnumerable<IConformanceIdnaTest> ParseFile(Stream stream, Func<s

private static IConformanceIdnaTest GetConformanceIdnaTest(string line, int lineCount)
{
if (PlatformDetection.ICUVersion >= new Version(74, 0))
if (PlatformDetection.ICUVersion >= new Version(76, 0))
return new Unicode_16_0_IdnaTest(line, lineCount);
else if (PlatformDetection.ICUVersion >= new Version(72, 1, 0, 4))
return new Unicode_15_1_IdnaTest(line, lineCount);
else if (PlatformDetection.ICUVersion >= new Version(72, 0))
return new Unicode_15_0_IdnaTest(line, lineCount);
else if (PlatformDetection.ICUVersion >= new Version(66, 0))
return new Unicode_13_0_IdnaTest(line, lineCount);
else if (PlatformDetection.IsWindows7)
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.