<commit_before>// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
    /// <summary>
    /// A <see cref="IValueProviderFactory"/> for <see cref="JQueryFormValueProvider"/>.
    /// </summary>
    public class JQueryFormValueProviderFactory : IValueProviderFactory
    {
        /// <inheritdoc />
        public Task<IValueProvider> GetValueProviderAsync(ActionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            
            var request = context.HttpContext.Request;
            if (request.HasFormContentType)
            {
                return CreateValueProviderAsync(request);
            }

            return TaskCache<IValueProvider>.DefaultCompletedTask;
        }

        private static async Task<IValueProvider> CreateValueProviderAsync(HttpRequest request)
        {
            return new JQueryFormValueProvider(
                BindingSource.Form,
                await GetValueCollectionAsync(request),
                CultureInfo.CurrentCulture);
        }

        private static async Task<IDictionary<string, StringValues>> GetValueCollectionAsync(HttpRequest request)
        {
            var formCollection = await request.ReadFormAsync();

            var dictionary = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
            foreach (var entry in formCollection)
            {
                var key = NormalizeJQueryToMvc(entry.Key);
                dictionary[key] = entry.Value;
            }

            return dictionary;
        }

        // This is a helper method for Model Binding over a JQuery syntax.
        // Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys.
        // x[] --> x
        // [] --> ""
        // x[12] --> x[12]
        // x[field]  --> x.field, where field is not a number
        private static string NormalizeJQueryToMvc(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                return string.Empty;
            }

            var indexOpen = key.IndexOf('[');
            if (indexOpen == -1)
            {

                // Fast path, no normalization needed.
                // This skips string conversion and allocating the string builder.
                return key;
            }

            var builder = new StringBuilder();
            var position = 0;
            while (position < key.Length)
            {
                if (indexOpen == -1)
                {
                    // No more brackets.
                    builder.Append(key, position, key.Length - position);
                    break;
                }
<commit_msg>Be smarter about stringbuilder usage<commit_after>// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
    /// <summary>
    /// A <see cref="IValueProviderFactory"/> for <see cref="JQueryFormValueProvider"/>.
    /// </summary>
    public class JQueryFormValueProviderFactory : IValueProviderFactory
    {
        /// <inheritdoc />
        public Task<IValueProvider> GetValueProviderAsync(ActionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var request = context.HttpContext.Request;
            if (request.HasFormContentType)
            {
                return CreateValueProviderAsync(request);
            }

            return TaskCache<IValueProvider>.DefaultCompletedTask;
        }

        private static async Task<IValueProvider> CreateValueProviderAsync(HttpRequest request)
        {
            return new JQueryFormValueProvider(
                BindingSource.Form,
                await GetValueCollectionAsync(request),
                CultureInfo.CurrentCulture);
        }

        private static async Task<IDictionary<string, StringValues>> GetValueCollectionAsync(HttpRequest request)
        {
            var formCollection = await request.ReadFormAsync();

            var builder = new StringBuilder();
            var dictionary = new Dictionary<string, StringValues>(
                formCollection.Count,
                StringComparer.OrdinalIgnoreCase);
            foreach (var entry in formCollection)
            {
                var key = NormalizeJQueryToMvc(builder, entry.Key);
                builder.Clear();

                dictionary[key] = entry.Value;
            }

            return dictionary;
        }

        // This is a helper method for Model Binding over a JQuery syntax.
        // Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys.
        // x[] --> x
        // [] --> ""
        // x[12] --> x[12]
        // x[field]  --> x.field, where field is not a number
        private static string NormalizeJQueryToMvc(StringBuilder builder, string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                return string.Empty;
            }

            var indexOpen = key.IndexOf('[');
            if (indexOpen == -1)
            {

                // Fast path, no normalization needed.
                // This skips string conversion and allocating the string builder.
                return key;
            }

            var position = 0;
            while (position < key.Length)
            {
                if (indexOpen == -1)
                {
                    // No more brackets.
                    builder.Append(key, position, key.Length - position);
                    break;
                }

{'fraction_non_alphanumeric': 0.08526413345690455, 'fraction_numerical': 0.0027803521779425394, 'mean_word_length': 1.7623720136518772, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6892599', 'n_tokens_mistral': 1561, 'n_tokens_neox': 1510, 'n_words': 422}