File: src\libraries\System.Private.CoreLib\src\System\Globalization\CompareInfo.Utf8.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Text;
using System.Text.Unicode;
 
namespace System.Globalization
{
    public partial class CompareInfo
    {
        /// <summary>
        /// Determines whether a UTF-8 string starts with a specific prefix.
        /// </summary>
        /// <param name="source">The UTF-8 string to search within.</param>
        /// <param name="prefix">The prefix to attempt to match at the start of <paramref name="source"/>.</param>
        /// <param name="options">The <see cref="CompareOptions"/> to use during the match.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="prefix"/> occurs at the start of <paramref name="source"/>;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// <paramref name="options"/> contains an unsupported combination of flags.
        /// </exception>
        internal bool IsPrefixUtf8(ReadOnlySpan<byte> source, ReadOnlySpan<byte> prefix, CompareOptions options = CompareOptions.None)
        {
            // The empty UTF-8 string is trivially a prefix of every other string. For compat with
            // earlier versions of the Framework we'll early-exit here before validating the
            // 'options' argument.
 
            if (prefix.IsEmpty)
            {
                return true;
            }
 
            if ((options & ValidIndexMaskOffFlags) == 0)
            {
                // Common case: caller is attempting to perform a linguistic search.
                // Pass the flags down to NLS or ICU unless we're running in invariant
                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 
                if (!GlobalizationMode.Invariant)
                {
                    return StartsWithCoreUtf8(source, prefix, options);
                }
 
                if ((options & CompareOptions.IgnoreCase) == 0)
                {
                    return source.StartsWith(prefix);
                }
 
                return source.StartsWithOrdinalIgnoreCaseUtf8(prefix);
            }
            else
            {
                // Less common case: caller is attempting to perform non-linguistic comparison,
                // or an invalid combination of flags was supplied.
 
                if (options == CompareOptions.Ordinal)
                {
                    return source.StartsWith(prefix);
                }
 
                if (options == CompareOptions.OrdinalIgnoreCase)
                {
                    return source.StartsWithOrdinalIgnoreCaseUtf8(prefix);
                }
 
                ThrowCompareOptionsCheckFailed(options);
 
                return false; // make the compiler happy;
            }
        }
 
        private unsafe bool StartsWithCoreUtf8(ReadOnlySpan<byte> source, ReadOnlySpan<byte> prefix, CompareOptions options)
        {
            // NLS/ICU doesn't provide native UTF-8 support so we need to convert to UTF-16 and compare that way
 
            // Convert source using stackalloc for <= 256 characters and ArrayPool otherwise
 
            char[]? sourceUtf16Array;
            scoped Span<char> sourceUtf16;
            int sourceMaxCharCount = Encoding.UTF8.GetMaxCharCount(source.Length);
 
            if ((uint)sourceMaxCharCount <= 256)
            {
                sourceUtf16Array = null;
                sourceUtf16 = stackalloc char[256];
            }
            else
            {
                sourceUtf16Array = ArrayPool<char>.Shared.Rent(sourceMaxCharCount);
                sourceUtf16 = sourceUtf16Array.AsSpan(0, sourceMaxCharCount);
            }
 
            OperationStatus sourceStatus = Utf8.ToUtf16PreservingReplacement(source, sourceUtf16, out _, out int sourceUtf16Length, replaceInvalidSequences: true);
 
            if (sourceStatus != OperationStatus.Done)
            {
                if (sourceUtf16Array != null)
                {
                    // Return rented buffers if necessary
                    ArrayPool<char>.Shared.Return(sourceUtf16Array);
                }
 
                return false;
            }
            sourceUtf16 = sourceUtf16.Slice(0, sourceUtf16Length);
 
            // Convert prefix using stackalloc for <= 256 characters and ArrayPool otherwise
 
            char[]? prefixUtf16Array;
            scoped Span<char> prefixUtf16;
            int prefixMaxCharCount = Encoding.UTF8.GetMaxCharCount(prefix.Length);
 
            if ((uint)prefixMaxCharCount < 256)
            {
                prefixUtf16Array = null;
                prefixUtf16 = stackalloc char[256];
            }
            else
            {
                prefixUtf16Array = ArrayPool<char>.Shared.Rent(prefixMaxCharCount);
                prefixUtf16 = prefixUtf16Array.AsSpan(0, prefixMaxCharCount);
            }
 
            OperationStatus prefixStatus = Utf8.ToUtf16PreservingReplacement(prefix, prefixUtf16, out _, out int prefixUtf16Length, replaceInvalidSequences: true);
 
            if (prefixStatus != OperationStatus.Done)
            {
                // Return rented buffers if necessary
 
                if (prefixUtf16Array != null)
                {
                    ArrayPool<char>.Shared.Return(prefixUtf16Array);
                }
 
                if (sourceUtf16Array != null)
                {
                    ArrayPool<char>.Shared.Return(sourceUtf16Array);
                }
 
                return false;
            }
            prefixUtf16 = prefixUtf16.Slice(0, prefixUtf16Length);
 
            // Actual operation
 
            bool result = StartsWithCore(sourceUtf16, prefixUtf16, options, matchLengthPtr: null);
 
            // Return rented buffers if necessary
 
            if (prefixUtf16Array != null)
            {
                ArrayPool<char>.Shared.Return(prefixUtf16Array);
            }
 
            if (sourceUtf16Array != null)
            {
                ArrayPool<char>.Shared.Return(sourceUtf16Array);
            }
 
            return result;
        }
    }
}