File: System\Data\Services\Serializers\DataStringEscapeBuilder.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="DataStringEscapeBuilder.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------
 
namespace System.Data.Services.Serializers
{
    using System.Diagnostics;
    using System.Text;
 
    /// <summary>
    /// Take a URI string and escape the data portion of it
    /// </summary>
    internal class DataStringEscapeBuilder
    {
        /// <summary>
        /// Sensitive characters that we should always skip
        /// This should be the set of Http control characters intersecting with 
        /// the set of characters OData literal format allows outside of strings
        /// (In .NET 4.5: only +, as used in double literals ex. 3E+8)
        /// </summary>
        private const String SensitiveCharacters = "+";
 
        /// <summary>
        /// input string
        /// </summary>
        private readonly string input;
 
        /// <summary>
        /// output string
        /// </summary>
        private readonly StringBuilder output = new StringBuilder();
 
        /// <summary>
        /// the current index
        /// </summary>
        private int index;
 
        /// <summary>
        /// current quoted data string
        /// </summary>
        private StringBuilder quotedDataBuilder;
 
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="dataString">The string to be escaped.</param>
        private DataStringEscapeBuilder(string dataString)
        {
            this.input = dataString;
        }
 
        /// <summary>
        /// Escape a URI string's data string portion
        /// </summary>
        /// <param name="input">The input string</param>
        /// <returns>The escaped string</returns>
        internal static string EscapeDataString(string input)
        {
            DataStringEscapeBuilder builder = new DataStringEscapeBuilder(input);
            return builder.Build();
        }
 
        /// <summary>
        /// Build a new escaped string
        /// </summary>
        /// <returns>The escaped string</returns>
        private string Build()
        {
            Debug.Assert(this.index == 0, "Expected this.index to be 0, because Build can only be called once for an instance of DataStringEscapeBuilder.");
            Debug.Assert(this.output.Length == 0, "Expected this.output.Length to be 0, because Build can only be called once for an instance of DataStringEscapeBuilder.");
 
            for (this.index = 0; this.index < this.input.Length; ++this.index)
            {
                char current = this.input[this.index];
                if (current == '\'' || current == '"')
                {
                    this.ReadQuotedString(current);
                }
                else if (SensitiveCharacters.IndexOf(current) >= 0)
                {
                    this.output.Append(Uri.EscapeDataString(current.ToString()));
                }
                else
                {
                    this.output.Append(current);
                }
            }
 
            return this.output.ToString();
        }
 
        /// <summary>
        /// Read quoted string
        /// </summary>
        /// <param name="quoteStart">The character that started the quote</param>
        private void ReadQuotedString(char quoteStart)
        {
            if (this.quotedDataBuilder == null)
            {
                this.quotedDataBuilder = new StringBuilder();
            }
#if DEBUG
            else
            {
                Debug.Assert(this.quotedDataBuilder.Length == 0, "Expected quotedDataBuilder to have been cleared by previous call to ReadQuotedString");
            }
#endif
 
            this.output.Append(quoteStart);
            while (++this.index < this.input.Length)
            {
                if (this.input[this.index] == quoteStart)
                {
                    this.output.Append(Uri.EscapeDataString(this.quotedDataBuilder.ToString()));
                    this.output.Append(quoteStart);
                    this.quotedDataBuilder.Clear();
 
                    break;
                }
 
                this.quotedDataBuilder.Append(this.input[this.index]);
            }
 
            if (this.quotedDataBuilder.Length > 0)
            {
                // unterminated quote, should have validated before. We should not fail here.
                Debug.Assert(false, "unterminated quote in uri.");
                this.output.Append(Uri.EscapeDataString(this.quotedDataBuilder.ToString()));
                this.quotedDataBuilder.Clear();
            }
        }
    }
}