public string SanitizeUrl()

in src/assets/Azure.Core.Shared/HttpMessageSanitizer.cs [47:171]


    public string SanitizeUrl(string url)
    {
        if (_logFullQueries)
        {
            return url;
        }

#if NET5_0_OR_GREATER
        int indexOfQuerySeparator = url.IndexOf('?', StringComparison.Ordinal);
#else
        int indexOfQuerySeparator = url.IndexOf('?');
#endif

        if (indexOfQuerySeparator == -1)
        {
            return url;
        }

        // PERF: Avoid allocations in this heavily-used method:
        // 1. Use ReadOnlySpan<char> to avoid creating substrings.
        // 2. Defer creating a StringBuilder until absolutely necessary.
        // 3. Use a rented StringBuilder to avoid allocating a new one
        //    each time.

        // Create the StringBuilder only when necessary (when we encounter
        // a query parameter that needs to be redacted)
        StringBuilder? stringBuilder = null;

        // Keeps track of the number of characters we've processed so far
        // so that, if we need to create a StringBuilder, we know how many
        // characters to copy over from the original URL.
        int lengthSoFar = indexOfQuerySeparator + 1;

        ReadOnlySpan<char> query = url.AsSpan(indexOfQuerySeparator + 1); // +1 to skip the '?'

        while (query.Length > 0)
        {
            int endOfParameterValue = query.IndexOf('&');
            int endOfParameterName = query.IndexOf('=');
            bool noValue = false;

            // Check if we have parameter without value
            if ((endOfParameterValue == -1 && endOfParameterName == -1) ||
                (endOfParameterValue != -1 && (endOfParameterName == -1 || endOfParameterName > endOfParameterValue)))
            {
                endOfParameterName = endOfParameterValue;
                noValue = true;
            }

            if (endOfParameterName == -1)
            {
                endOfParameterName = query.Length;
            }

            if (endOfParameterValue == -1)
            {
                endOfParameterValue = query.Length;
            }
            else
            {
                // include the separator
                endOfParameterValue++;
            }

            ReadOnlySpan<char> parameterName = query.Slice(0, endOfParameterName);

            bool isAllowed = false;
            foreach (string name in _allowedQueryParameters)
            {
                if (parameterName.Equals(name.AsSpan(), StringComparison.OrdinalIgnoreCase))
                {
                    isAllowed = true;
                    break;
                }
            }

            int valueLength = endOfParameterValue;
            int nameLength = endOfParameterName;

            if (isAllowed || noValue)
            {
                if (stringBuilder is null)
                {
                    lengthSoFar += valueLength;
                }
                else
                {
                    AppendReadOnlySpan(stringBuilder, query.Slice(0, valueLength));
                }
            }
            else
            {
                // Encountered a query value that needs to be redacted.
                // Create the StringBuilder if we haven't already.
                stringBuilder ??= RentStringBuilder(url.Length).Append(url, 0, lengthSoFar);

                AppendReadOnlySpan(stringBuilder, query.Slice(0, nameLength))
                    .Append('=')
                    .Append(_redactedPlaceholder);

                if (query[endOfParameterValue - 1] == '&')
                {
                    stringBuilder.Append('&');
                }
            }

            query = query.Slice(valueLength);
        }

        return stringBuilder is null ? url : ToStringAndReturnStringBuilder(stringBuilder);

        static StringBuilder AppendReadOnlySpan(StringBuilder builder, ReadOnlySpan<char> span)
        {
#if NET6_0_OR_GREATER
            return builder.Append(span);
#else
            foreach (char c in span)
            {
                builder.Append(c);
            }

            return builder;
#endif
        }
    }