ContentType.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Base / MS / Internal / ContentType.cs / 1305600 / ContentType.cs

                            //------------------------------------------------------------------------------ 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: 
//  ContentType class parses and validates the content-type string. 
//  It provides functionality to compare the type/subtype values.
// 
// Details:
// Grammar which this class follows -
//
// Content-type grammar MUST conform to media-type grammar as per 
// RFC 2616 (ABNF notation):
// 
// media-type     = type "/" subtype *( ";" parameter ) 
// type           = token
// subtype        = token 
// parameter      = attribute "=" value
// attribute      = token
// value          = token | quoted-string
// quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> ) 
// qdtext         = >
// quoted-pair    = "\" CHAR 
// token          = 1* 
// separators     = "(" | ")" | "<" | ">" | "@"
//                  | "," | ";" | ":" | "\" | <"> 
//                  | "/" | "[" | "]" | "?" | "="
//                  | "{" | "}" | SP | HT
// TEXT           = 
// OCTET          =  
// CHAR           = 
// CTL            =  
// CR             =  
// LF             = 
// SP             =  
// HT             = 
// <">            = 
// LWS            = [CRLF] 1*( SP | HT )
// CRLF           = CR LF 
// Linear white space (LWS) MUST NOT be used between the type and subtype, nor
// between an attribute and its value. Leading and trailing LWS are prohibited. 
// 
//
// History: 
//  04/26/2004: SarjanaS: Initial Creation
//-----------------------------------------------------------------------------

using System; 
using System.Collections.Generic;   // For Dictionary
using System.Text;                  // For StringBuilder 
using System.Windows;               // For Exception strings - SRID 
using MS.Internal.WindowsBase;      // For FriendAccessAllowed
using System.Diagnostics;           // For Debug.Assert 

namespace MS.Internal
{
    ///  
    /// Content Type class
    ///  
    [FriendAccessAllowed] 
    internal sealed class ContentType
    { 
        //-----------------------------------------------------
        //
        //  Internal Constructors
        // 
        //-----------------------------------------------------
 
        #region Internal Constructors 

        ///  
        /// This constructor creates a ContentType object that represents
        /// the content-type string. At construction time we validate the
        /// string as per the grammar specified in RFC 2616.
        /// Note: We allow empty strings as valid input. Empty string should 
        /// we used more as an indication of an absent/unknown ContentType.
        ///  
        /// content-type 
        /// If the contentType parameter is null
        /// If the contentType string has leading or 
        /// trailing Linear White Spaces(LWS) characters
        /// If the contentType string invalid CR-LF characters
        internal ContentType(string contentType)
        { 
            if (contentType == null)
                throw new ArgumentNullException("contentType"); 
 
            if (String.CompareOrdinal(contentType, String.Empty) == 0)
            { 
                _contentType = String.Empty;
            }
            else
            { 
                if (IsLinearWhiteSpaceChar(contentType[0]) || IsLinearWhiteSpaceChar(contentType[contentType.Length - 1]))
                    throw new ArgumentException(SR.Get(SRID.ContentTypeCannotHaveLeadingTrailingLWS)); 
 
                //Carriage return can be expressed as '\r\n' or '\n\r'
                //We need to make sure that a \r is accompanied by \n 
                ValidateCarriageReturns(contentType);

                //Begin Parsing
                int semiColonIndex = contentType.IndexOf(_semicolonSeparator); 

                if (semiColonIndex == -1) 
                { 
                    // Parse content type similar to - type/subtype
                    ParseTypeAndSubType(contentType); 
                }
                else
                {
                    // Parse content type similar to - type/subtype ; param1=value1 ; param2=value2 ; param3="value3" 
                    ParseTypeAndSubType(contentType.Substring(0, semiColonIndex));
                    ParseParameterAndValue(contentType.Substring(semiColonIndex)); 
                } 
            }
 
            // keep this untouched for return from OriginalString property
            _originalString = contentType;

            //This variable is used to print out the correct content type string representation 
            //using the ToString method. This is mainly important while debugging and seeing the
            //value of the content type object in the debugger. 
            _isInitialized = true; 
        }
 
        #endregion Internal Constructors

        //------------------------------------------------------
        // 
        //  Internal Methods
        // 
        //----------------------------------------------------- 

        #region Internal Properties 

        /// 
        /// TypeComponent of the Content Type
        /// If the content type is "text/xml". This property will return "text" 
        /// 
        internal string TypeComponent 
        { 
            get
            { 
                return _type;
            }
        }
 
        /// 
        /// SubType component 
        /// If the content type is "text/xml". This property will return "xml" 
        /// 
        internal string SubTypeComponent 
        {
            get
            {
                return _subType; 
            }
        } 
 
        /// 
        /// Enumerator which iterates over the Parameter and Value pairs which are stored 
        /// in a dictionary. We hand out just the enumerator in order to make this property
        /// ReadOnly
        /// Consider following Content type -
        /// type/subtype ; param1=value1 ; param2=value2 ; param3="value3" 
        /// This will return a enumerator over a dictionary of the parameter/value pairs.
        ///  
        internal Dictionary.Enumerator ParameterValuePairs 
        {
            get 
            {
                EnsureParameterDictionary();
                return _parameterDictionary.GetEnumerator();
            } 
        }
 
 
        /// 
        /// Static property that represents a content type that is empty "" 
        /// This is not a valid content type as per the grammar and should be used
        /// in places where the content type is missing or not available.
        /// 
        internal static ContentType Empty 
        {
            get 
            { 
                return _emptyContentType;
            } 
        }

        /// 
        /// Original string provided to constructor 
        /// 
        internal string OriginalString 
        { 
            get
            { 
                return _originalString;
            }
        }
        #endregion Internal Properties 

        //------------------------------------------------------ 
        // 
        //  Internal Methods
        // 
        //------------------------------------------------------

        #region Internal Methods
 
        /// 
        /// This method does a strong comparison of the content types, as parameters are not allowed. 
        /// We only compare the type and subType values in an ASCII case-insensitive manner. 
        /// Parameters are not allowed to be present on any of the content type operands.
        ///  
        /// Content type to be compared with
        /// 
        internal bool AreTypeAndSubTypeEqual(ContentType contentType)
        { 
            return AreTypeAndSubTypeEqual(contentType, false);
        } 
 
        /// 
        /// This method does a weak comparison of the content types. We only compare the 
        /// type and subType values in an ASCII case-insensitive manner.
        /// Parameter and value pairs are not used for the comparison.
        /// If you wish to compare the paramters too, then you must get the ParameterValuePairs from
        /// both the ContentType objects and compare each parameter entry. 
        /// The allowParameterValuePairs parameter is used to indicate whether the
        /// comparison is tolerant to parameters being present or no. 
        ///  
        /// Content type to be compared with
        /// If true, allows the presence of parameter value pairs. 
        /// If false, parameter/value pairs cannot be present in the content type string.
        /// In either case, the parameter value pair is not used for the comparison.
        /// 
        internal bool AreTypeAndSubTypeEqual(ContentType contentType, bool allowParameterValuePairs) 
        {
            bool result = false; 
 
            if (contentType != null)
            { 
                if (!allowParameterValuePairs)
                {
                    //Return false if this content type object has parameters
                    if (_parameterDictionary != null && _parameterDictionary.Count > 0) 
                        return false;
 
                    //Return false if the content type object passed in has parameters 
                    Dictionary.Enumerator contentTypeEnumerator;
                    contentTypeEnumerator = contentType.ParameterValuePairs; 
                    contentTypeEnumerator.MoveNext();
                    if (contentTypeEnumerator.Current.Key != null)
                        return false;
                } 

                // Perform a case-insensitive comparison on the type/subtype strings.  This is a 
                // safe comparison because the _type and _subType strings have been restricted to 
                // ASCII characters, digits, and a small set of symbols.  This is not a safe comparison
                // for the broader set of strings that have not been restricted in the same way. 
                result = (String.Compare(_type, contentType.TypeComponent, StringComparison.OrdinalIgnoreCase) == 0 &&
                          String.Compare(_subType, contentType.SubTypeComponent, StringComparison.OrdinalIgnoreCase) == 0);
            }
            return result; 
        }
 
        ///  
        /// ToString - outputs a normalized form of the content type string
        ///  
        /// 
        public override string ToString()
        {
            if (_contentType == null) 
            {
                //This is needed so that while debugging we get the correct 
                //string 
                if (!_isInitialized)
                    return String.Empty; 

                Debug.Assert(String.CompareOrdinal(_type, String.Empty) != 0
                   || String.CompareOrdinal(_subType, String.Empty) != 0);
 
                StringBuilder stringBuilder = new StringBuilder(_type);
                stringBuilder.Append(_forwardSlashSeparator[0]); 
                stringBuilder.Append(_subType); 

                if (_parameterDictionary != null && _parameterDictionary.Count > 0) 
                {
                    foreach (string paramterKey in _parameterDictionary.Keys)
                    {
                        stringBuilder.Append(_LinearWhiteSpaceChars[0]); 
                        stringBuilder.Append(_semicolonSeparator);
                        stringBuilder.Append(_LinearWhiteSpaceChars[0]); 
                        stringBuilder.Append(paramterKey); 
                        stringBuilder.Append(_equalSeparator);
                        stringBuilder.Append(_parameterDictionary[paramterKey]); 
                    }
                }

                _contentType = stringBuilder.ToString(); 
            }
 
            return _contentType; 
        }
 
        #endregion Internal Methods

        //-----------------------------------------------------
        // 
        //  Nested Classes
        // 
        //------------------------------------------------------ 

        #region Nested Classes 

        /// 
        /// Comparer class makes it easier to put ContentType objects in collections.
        /// Only compares type and subtype components of the ContentType.  Could be 
        /// expanded to optionally compare parameters as well.
        ///  
        internal class StrongComparer : IEqualityComparer 
        {
            ///  
            /// This method does a strong comparison of the content types.
            /// Only compares the ContentTypes' type and subtype components.
            /// 
            public bool Equals(ContentType x, ContentType y) 
            {
                if (x == null) 
                { 
                    return (y == null);
                } 
                else
                {
                    return x.AreTypeAndSubTypeEqual(y);
                } 
            }
 
            ///  
            /// We lower case the results of ToString() because it returns the original
            /// casing passed into the constructor.  ContentTypes that are equal (which 
            /// ignores casing) must have the same hash code.
            /// 
            public int GetHashCode(ContentType obj)
            { 
                return obj.ToString().ToUpperInvariant().GetHashCode();
            } 
        } 

        internal class WeakComparer : IEqualityComparer 
        {
            /// 
            /// This method does a weak comparison of the content types.
            /// Parameter and value pairs are not used for the comparison. 
            /// 
            public bool Equals(ContentType x, ContentType y) 
            { 
                if (x == null)
                { 
                    return (y == null);
                }
                else
                { 
                    return x.AreTypeAndSubTypeEqual(y, true);
                } 
            } 

            ///  
            /// We lower case the results of ToString() because it returns the original
            /// casing passed into the constructor.  ContentTypes that are equal (which
            /// ignores casing) must have the same hash code.
            ///  
            public int GetHashCode(ContentType obj)
            { 
                return obj._type.ToUpperInvariant().GetHashCode() ^ obj._subType.ToUpperInvariant().GetHashCode(); 
            }
        } 
        #endregion Nested Classes

        //-----------------------------------------------------
        // 
        //  Private Methods
        // 
        //----------------------------------------------------- 

        #region Private Methods 


        /// 
        /// This method validates if the content type string has 
        /// valid CR-LF characters. Specifically we test if '\r' is
        /// accompanied by a '\n' in the string, else its an error. 
        ///  
        /// 
        private static void ValidateCarriageReturns(string contentType) 
        {
            Debug.Assert(!IsLinearWhiteSpaceChar(contentType[0]) && !IsLinearWhiteSpaceChar(contentType[contentType.Length - 1]));

            //Prior to calling this method we have already checked that first and last 
            //character of the content type are not Linear White Spaces. So its safe to
            //assume that the index will be greater than 0 and less that length-2. 
 
            int index = contentType.IndexOf(_LinearWhiteSpaceChars[2]);
 
            while (index != -1)
            {
                if (contentType[index - 1] == _LinearWhiteSpaceChars[1] || contentType[index + 1] == _LinearWhiteSpaceChars[1])
                { 
                    index = contentType.IndexOf(_LinearWhiteSpaceChars[2], ++index);
                } 
                else 
                    throw new ArgumentException(SR.Get(SRID.InvalidLinearWhiteSpaceCharacter));
            } 
        }

        /// 
        /// Parses the type ans subType tokens from the string. 
        /// Also verifies if the Tokens are valid as per the grammar.
        ///  
        /// substring that has the type and subType of the content type 
        /// If the typeAndSubType parameter does not have the "/" character
        private void ParseTypeAndSubType(string typeAndSubType) 
        {
            //okay to trim at this point the end of the string as Linear White Spaces(LWS) chars are allowed here.
            typeAndSubType = typeAndSubType.TrimEnd(_LinearWhiteSpaceChars);
 
            string[] splitBasedOnForwardSlash = typeAndSubType.Split(_forwardSlashSeparator);
 
            if (splitBasedOnForwardSlash.Length != 2) 
                throw new ArgumentException(SR.Get(SRID.InvalidTypeSubType));
 
            _type    = ValidateToken(splitBasedOnForwardSlash[0]);
            _subType = ValidateToken(splitBasedOnForwardSlash[1]);
        }
 
        /// 
        /// Parse the individual parameter=value strings 
        ///  
        /// This string has the parameter and value pair of the form
        /// parameter=value 
        /// If the string does not have the required "="
        private void ParseParameterAndValue(string parameterAndValue)
        {
            while (String.CompareOrdinal(parameterAndValue, String.Empty) != 0) 
            {
                //At this point the first character MUST be a semi-colon 
                //First time through this test is serving more as an assert. 
                if (parameterAndValue[0] != _semicolonSeparator)
                    throw new ArgumentException(SR.Get(SRID.ExpectingSemicolon)); 

                //At this point if we have just one semicolon, then its an error.
                //Also, there can be no trailing LWS characters, as we already checked for that
                //in the constructor. 
                if (parameterAndValue.Length == 1)
                    throw new ArgumentException(SR.Get(SRID.ExpectingParameterValuePairs)); 
 
                //Removing the leading ; from the string
                parameterAndValue = parameterAndValue.Substring(1); 

                //okay to trim start as there can be spaces before the begining
                //of the parameter name.
                parameterAndValue = parameterAndValue.TrimStart(_LinearWhiteSpaceChars); 

                int equalSignIndex = parameterAndValue.IndexOf(_equalSeparator); 
 
                if (equalSignIndex <= 0 || equalSignIndex == (parameterAndValue.Length - 1))
                    throw new ArgumentException(SR.Get(SRID.InvalidParameterValuePair)); 

                int parameterStartIndex = equalSignIndex + 1;

                //Get length of the parameter value 
                int parameterValueLength = GetLengthOfParameterValue(parameterAndValue, parameterStartIndex);
 
                EnsureParameterDictionary(); 

                _parameterDictionary.Add( 
                    ValidateToken(parameterAndValue.Substring(0, equalSignIndex)),
                    ValidateQuotedStringOrToken(parameterAndValue.Substring(parameterStartIndex, parameterValueLength)));

                parameterAndValue = parameterAndValue.Substring(parameterStartIndex + parameterValueLength).TrimStart(_LinearWhiteSpaceChars); 
            }
        } 
 
        /// 
        /// This method returns the length of the first parameter value in the input string. 
        /// 
        /// 
        /// Starting index for parsing
        ///  
        private static int GetLengthOfParameterValue(string s, int startIndex)
        { 
            Debug.Assert(s != null); 

            int length = 0; 

            //if the parameter value does not start with a '"' then,
            //we expect a valid token. So we look for Linear White Spaces or
            //a ';' as the terminator for the token value. 
            if (s[startIndex] != '"')
            { 
                int semicolonIndex = s.IndexOf(_semicolonSeparator, startIndex); 

                if (semicolonIndex != -1) 
                {
                    int lwsIndex = s.IndexOfAny(_LinearWhiteSpaceChars, startIndex);
                    if (lwsIndex != -1 && lwsIndex < semicolonIndex)
                        length = lwsIndex; 
                    else
                        length = semicolonIndex; 
                } 
                else
                    length = semicolonIndex; 

                //If there is no linear white space found we treat the entire remaining string as
                //parameter value.
                if (length == -1) 
                    length = s.Length;
            } 
            else 
            {
                //if the parameter value starts with a '"' then, we need to look for the 
                //pairing '"' that is not preceded by a "\" ["\" is used to escape the '"']
                bool found = false;
                length = startIndex;
 
                while (!found)
                { 
                    length = s.IndexOf('"', ++length); 

                    if (length == -1) 
                        throw new ArgumentException(SR.Get(SRID.InvalidParameterValue));

                    if (s[length - 1] != '\\')
                    { 
                        found = true;
                        length++; 
                    } 
                }
            } 
            return length - startIndex;
        }

        ///  
        /// Validating the given token
        /// The following checks are being made - 
        /// 1. If all the characters in the token are either ASCII letter or digit. 
        /// 2. If all the characters in the token are either from the remaining allowed cha----ter set.
        ///  
        /// string token
        /// validated string token
        /// If the token is Empty
        private static string ValidateToken(string token) 
        {
            if (String.CompareOrdinal(token, String.Empty)==0) 
                throw new ArgumentException(SR.Get(SRID.InvalidToken)); 

            for (int i = 0; i < token.Length; i++) 
            {
                if (IsAsciiLetterOrDigit(token[i]))
                    continue;
                else 
                    if (IsAllowedCharacter(token[i]))
                        continue; 
                    else 
                        throw new ArgumentException(SR.Get(SRID.InvalidToken));
            } 

            return token;
        }
 
        /// 
        /// Validating if the value of a parameter is either a valid token or a 
        /// valid quoted string 
        /// 
        /// paramter value string 
        /// validate parameter value string
        /// If the paramter value is empty
        private static string ValidateQuotedStringOrToken(string parameterValue)
        { 
            if (String.CompareOrdinal(parameterValue, String.Empty) == 0)
                throw new ArgumentException(SR.Get(SRID.InvalidParameterValue)); 
 
            if (parameterValue.Length >= 2 &&
                parameterValue.StartsWith(_quote, StringComparison.Ordinal) && 
                parameterValue.EndsWith(_quote, StringComparison.Ordinal))
                ValidateQuotedText(parameterValue.Substring(1, parameterValue.Length-2));
            else
                ValidateToken(parameterValue); 

            return parameterValue; 
        } 

        ///  
        /// This method validates if the text in the quoted string
        /// 
        /// 
        private static void ValidateQuotedText(string quotedText) 
        {
            //empty is okay 
 
            for (int i = 0; i < quotedText.Length; i++)
            { 
                if (IsLinearWhiteSpaceChar(quotedText[i]))
                    continue;

                if (quotedText[i] <= ' ' || quotedText[i] >= 0xFF) 
                    throw new ArgumentException(SR.Get(SRID.InvalidParameterValue));
                else 
                    if (quotedText[i] == '"' && 
                        (i==0 || quotedText[i-1] != '\\'))
                        throw new ArgumentException(SR.Get(SRID.InvalidParameterValue)); 
            }
        }

        ///  
        /// Returns true if the input character is an allowed character
        /// Returns false if the input cha----ter is not an allowed character 
        ///  
        /// input character
        ///  
        private static bool IsAllowedCharacter(char character)
        {
            //We did not use any of the .Contains methods as
            //it will result in boxing costs. 
            foreach (char c in _allowedCharacters)
            { 
                if (c == character) 
                    return true;
            } 
            return false;
        }

        ///  
        /// Returns true if the input character is an ASCII digit or letter
        /// Returns false if the input character is not an ASCII digit or letter 
        ///  
        /// input character
        ///  
        private static bool IsAsciiLetterOrDigit(char character)
        {
            if (IsAsciiLetter(character))
            { 
                return true;
            } 
            if (character >= '0') 
            {
                return (character <= '9'); 
            }
            return false;
        }
 
        /// 
        /// Returns true if the input character is an ASCII letter 
        /// Returns false if the input character is not an ASCII letter 
        /// 
        /// input character 
        /// 
        private static bool IsAsciiLetter(char character)
        {
            if ((character >= 'a') && (character <= 'z')) 
            {
                return true; 
            } 
            if (character >= 'A')
            { 
                return (character <= 'Z');
            }
            return false;
        } 

        ///  
        /// Returns true if the input character is one of the Linear White Space characters - 
        /// ' ', '\t', '\n', '\r'
        /// Returns false if the input character is none of the above 
        /// 
        /// input character
        /// 
        private static bool IsLinearWhiteSpaceChar(char ch) 
        {
            if (ch > ' ') 
            { 
                return false;
            } 

            foreach (char c in _LinearWhiteSpaceChars)
            {
                if (ch == c) 
                    return true;
            } 
 
            return false;
        } 

        /// 
        /// Lazy initialization for the ParameterDictionary
        ///  
        private void EnsureParameterDictionary()
        { 
            if (_parameterDictionary == null) 
            {
                _parameterDictionary = new Dictionary(); //initial size 0 
            }
        }

        #endregion Private Methods 

        //----------------------------------------------------- 
        // 
        //  Private Members
        // 
        //------------------------------------------------------
        #region Private Members

        private string _contentType = null; 
        private string _type    = String.Empty;
        private string _subType = String.Empty; 
        private string _originalString; 
        private Dictionary _parameterDictionary = null;
        private bool   _isInitialized = false; 

        private const string     _quote              = "\"";
        private const char       _semicolonSeparator = ';';
        private const char       _equalSeparator     = '='; 

        //This array is sorted by the ascii value of these characters. 
        private static readonly char[] _allowedCharacters = 
         { '!' /*33*/, '#' /*35*/ , '$'  /*36*/,
           '%' /*37*/, '&' /*38*/ , '\'' /*39*/, 
           '*' /*42*/, '+' /*43*/ , '-'  /*45*/,
           '.' /*46*/, '^' /*94*/ , '_'  /*95*/,
           '`' /*96*/, '|' /*124*/, '~'  /*126*/,
         }; 

        private static readonly char[]     _forwardSlashSeparator = { '/' }; 
 
        //Linear White Space characters
        private static readonly char[]     _LinearWhiteSpaceChars = 
         { ' ',  // space           - \x20
           '\n', // new line        - \x0A
           '\r', // carriage return - \x0D
           '\t'  // horizontal tab  - \x09 
         };
 
        private static readonly ContentType _emptyContentType = new ContentType(""); 

        #endregion Private Members 
    }
}


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------ 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: 
//  ContentType class parses and validates the content-type string. 
//  It provides functionality to compare the type/subtype values.
// 
// Details:
// Grammar which this class follows -
//
// Content-type grammar MUST conform to media-type grammar as per 
// RFC 2616 (ABNF notation):
// 
// media-type     = type "/" subtype *( ";" parameter ) 
// type           = token
// subtype        = token 
// parameter      = attribute "=" value
// attribute      = token
// value          = token | quoted-string
// quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> ) 
// qdtext         = >
// quoted-pair    = "\" CHAR 
// token          = 1* 
// separators     = "(" | ")" | "<" | ">" | "@"
//                  | "," | ";" | ":" | "\" | <"> 
//                  | "/" | "[" | "]" | "?" | "="
//                  | "{" | "}" | SP | HT
// TEXT           = 
// OCTET          =  
// CHAR           = 
// CTL            =  
// CR             =  
// LF             = 
// SP             =  
// HT             = 
// <">            = 
// LWS            = [CRLF] 1*( SP | HT )
// CRLF           = CR LF 
// Linear white space (LWS) MUST NOT be used between the type and subtype, nor
// between an attribute and its value. Leading and trailing LWS are prohibited. 
// 
//
// History: 
//  04/26/2004: SarjanaS: Initial Creation
//-----------------------------------------------------------------------------

using System; 
using System.Collections.Generic;   // For Dictionary
using System.Text;                  // For StringBuilder 
using System.Windows;               // For Exception strings - SRID 
using MS.Internal.WindowsBase;      // For FriendAccessAllowed
using System.Diagnostics;           // For Debug.Assert 

namespace MS.Internal
{
    ///  
    /// Content Type class
    ///  
    [FriendAccessAllowed] 
    internal sealed class ContentType
    { 
        //-----------------------------------------------------
        //
        //  Internal Constructors
        // 
        //-----------------------------------------------------
 
        #region Internal Constructors 

        ///  
        /// This constructor creates a ContentType object that represents
        /// the content-type string. At construction time we validate the
        /// string as per the grammar specified in RFC 2616.
        /// Note: We allow empty strings as valid input. Empty string should 
        /// we used more as an indication of an absent/unknown ContentType.
        ///  
        /// content-type 
        /// If the contentType parameter is null
        /// If the contentType string has leading or 
        /// trailing Linear White Spaces(LWS) characters
        /// If the contentType string invalid CR-LF characters
        internal ContentType(string contentType)
        { 
            if (contentType == null)
                throw new ArgumentNullException("contentType"); 
 
            if (String.CompareOrdinal(contentType, String.Empty) == 0)
            { 
                _contentType = String.Empty;
            }
            else
            { 
                if (IsLinearWhiteSpaceChar(contentType[0]) || IsLinearWhiteSpaceChar(contentType[contentType.Length - 1]))
                    throw new ArgumentException(SR.Get(SRID.ContentTypeCannotHaveLeadingTrailingLWS)); 
 
                //Carriage return can be expressed as '\r\n' or '\n\r'
                //We need to make sure that a \r is accompanied by \n 
                ValidateCarriageReturns(contentType);

                //Begin Parsing
                int semiColonIndex = contentType.IndexOf(_semicolonSeparator); 

                if (semiColonIndex == -1) 
                { 
                    // Parse content type similar to - type/subtype
                    ParseTypeAndSubType(contentType); 
                }
                else
                {
                    // Parse content type similar to - type/subtype ; param1=value1 ; param2=value2 ; param3="value3" 
                    ParseTypeAndSubType(contentType.Substring(0, semiColonIndex));
                    ParseParameterAndValue(contentType.Substring(semiColonIndex)); 
                } 
            }
 
            // keep this untouched for return from OriginalString property
            _originalString = contentType;

            //This variable is used to print out the correct content type string representation 
            //using the ToString method. This is mainly important while debugging and seeing the
            //value of the content type object in the debugger. 
            _isInitialized = true; 
        }
 
        #endregion Internal Constructors

        //------------------------------------------------------
        // 
        //  Internal Methods
        // 
        //----------------------------------------------------- 

        #region Internal Properties 

        /// 
        /// TypeComponent of the Content Type
        /// If the content type is "text/xml". This property will return "text" 
        /// 
        internal string TypeComponent 
        { 
            get
            { 
                return _type;
            }
        }
 
        /// 
        /// SubType component 
        /// If the content type is "text/xml". This property will return "xml" 
        /// 
        internal string SubTypeComponent 
        {
            get
            {
                return _subType; 
            }
        } 
 
        /// 
        /// Enumerator which iterates over the Parameter and Value pairs which are stored 
        /// in a dictionary. We hand out just the enumerator in order to make this property
        /// ReadOnly
        /// Consider following Content type -
        /// type/subtype ; param1=value1 ; param2=value2 ; param3="value3" 
        /// This will return a enumerator over a dictionary of the parameter/value pairs.
        ///  
        internal Dictionary.Enumerator ParameterValuePairs 
        {
            get 
            {
                EnsureParameterDictionary();
                return _parameterDictionary.GetEnumerator();
            } 
        }
 
 
        /// 
        /// Static property that represents a content type that is empty "" 
        /// This is not a valid content type as per the grammar and should be used
        /// in places where the content type is missing or not available.
        /// 
        internal static ContentType Empty 
        {
            get 
            { 
                return _emptyContentType;
            } 
        }

        /// 
        /// Original string provided to constructor 
        /// 
        internal string OriginalString 
        { 
            get
            { 
                return _originalString;
            }
        }
        #endregion Internal Properties 

        //------------------------------------------------------ 
        // 
        //  Internal Methods
        // 
        //------------------------------------------------------

        #region Internal Methods
 
        /// 
        /// This method does a strong comparison of the content types, as parameters are not allowed. 
        /// We only compare the type and subType values in an ASCII case-insensitive manner. 
        /// Parameters are not allowed to be present on any of the content type operands.
        ///  
        /// Content type to be compared with
        /// 
        internal bool AreTypeAndSubTypeEqual(ContentType contentType)
        { 
            return AreTypeAndSubTypeEqual(contentType, false);
        } 
 
        /// 
        /// This method does a weak comparison of the content types. We only compare the 
        /// type and subType values in an ASCII case-insensitive manner.
        /// Parameter and value pairs are not used for the comparison.
        /// If you wish to compare the paramters too, then you must get the ParameterValuePairs from
        /// both the ContentType objects and compare each parameter entry. 
        /// The allowParameterValuePairs parameter is used to indicate whether the
        /// comparison is tolerant to parameters being present or no. 
        ///  
        /// Content type to be compared with
        /// If true, allows the presence of parameter value pairs. 
        /// If false, parameter/value pairs cannot be present in the content type string.
        /// In either case, the parameter value pair is not used for the comparison.
        /// 
        internal bool AreTypeAndSubTypeEqual(ContentType contentType, bool allowParameterValuePairs) 
        {
            bool result = false; 
 
            if (contentType != null)
            { 
                if (!allowParameterValuePairs)
                {
                    //Return false if this content type object has parameters
                    if (_parameterDictionary != null && _parameterDictionary.Count > 0) 
                        return false;
 
                    //Return false if the content type object passed in has parameters 
                    Dictionary.Enumerator contentTypeEnumerator;
                    contentTypeEnumerator = contentType.ParameterValuePairs; 
                    contentTypeEnumerator.MoveNext();
                    if (contentTypeEnumerator.Current.Key != null)
                        return false;
                } 

                // Perform a case-insensitive comparison on the type/subtype strings.  This is a 
                // safe comparison because the _type and _subType strings have been restricted to 
                // ASCII characters, digits, and a small set of symbols.  This is not a safe comparison
                // for the broader set of strings that have not been restricted in the same way. 
                result = (String.Compare(_type, contentType.TypeComponent, StringComparison.OrdinalIgnoreCase) == 0 &&
                          String.Compare(_subType, contentType.SubTypeComponent, StringComparison.OrdinalIgnoreCase) == 0);
            }
            return result; 
        }
 
        ///  
        /// ToString - outputs a normalized form of the content type string
        ///  
        /// 
        public override string ToString()
        {
            if (_contentType == null) 
            {
                //This is needed so that while debugging we get the correct 
                //string 
                if (!_isInitialized)
                    return String.Empty; 

                Debug.Assert(String.CompareOrdinal(_type, String.Empty) != 0
                   || String.CompareOrdinal(_subType, String.Empty) != 0);
 
                StringBuilder stringBuilder = new StringBuilder(_type);
                stringBuilder.Append(_forwardSlashSeparator[0]); 
                stringBuilder.Append(_subType); 

                if (_parameterDictionary != null && _parameterDictionary.Count > 0) 
                {
                    foreach (string paramterKey in _parameterDictionary.Keys)
                    {
                        stringBuilder.Append(_LinearWhiteSpaceChars[0]); 
                        stringBuilder.Append(_semicolonSeparator);
                        stringBuilder.Append(_LinearWhiteSpaceChars[0]); 
                        stringBuilder.Append(paramterKey); 
                        stringBuilder.Append(_equalSeparator);
                        stringBuilder.Append(_parameterDictionary[paramterKey]); 
                    }
                }

                _contentType = stringBuilder.ToString(); 
            }
 
            return _contentType; 
        }
 
        #endregion Internal Methods

        //-----------------------------------------------------
        // 
        //  Nested Classes
        // 
        //------------------------------------------------------ 

        #region Nested Classes 

        /// 
        /// Comparer class makes it easier to put ContentType objects in collections.
        /// Only compares type and subtype components of the ContentType.  Could be 
        /// expanded to optionally compare parameters as well.
        ///  
        internal class StrongComparer : IEqualityComparer 
        {
            ///  
            /// This method does a strong comparison of the content types.
            /// Only compares the ContentTypes' type and subtype components.
            /// 
            public bool Equals(ContentType x, ContentType y) 
            {
                if (x == null) 
                { 
                    return (y == null);
                } 
                else
                {
                    return x.AreTypeAndSubTypeEqual(y);
                } 
            }
 
            ///  
            /// We lower case the results of ToString() because it returns the original
            /// casing passed into the constructor.  ContentTypes that are equal (which 
            /// ignores casing) must have the same hash code.
            /// 
            public int GetHashCode(ContentType obj)
            { 
                return obj.ToString().ToUpperInvariant().GetHashCode();
            } 
        } 

        internal class WeakComparer : IEqualityComparer 
        {
            /// 
            /// This method does a weak comparison of the content types.
            /// Parameter and value pairs are not used for the comparison. 
            /// 
            public bool Equals(ContentType x, ContentType y) 
            { 
                if (x == null)
                { 
                    return (y == null);
                }
                else
                { 
                    return x.AreTypeAndSubTypeEqual(y, true);
                } 
            } 

            ///  
            /// We lower case the results of ToString() because it returns the original
            /// casing passed into the constructor.  ContentTypes that are equal (which
            /// ignores casing) must have the same hash code.
            ///  
            public int GetHashCode(ContentType obj)
            { 
                return obj._type.ToUpperInvariant().GetHashCode() ^ obj._subType.ToUpperInvariant().GetHashCode(); 
            }
        } 
        #endregion Nested Classes

        //-----------------------------------------------------
        // 
        //  Private Methods
        // 
        //----------------------------------------------------- 

        #region Private Methods 


        /// 
        /// This method validates if the content type string has 
        /// valid CR-LF characters. Specifically we test if '\r' is
        /// accompanied by a '\n' in the string, else its an error. 
        ///  
        /// 
        private static void ValidateCarriageReturns(string contentType) 
        {
            Debug.Assert(!IsLinearWhiteSpaceChar(contentType[0]) && !IsLinearWhiteSpaceChar(contentType[contentType.Length - 1]));

            //Prior to calling this method we have already checked that first and last 
            //character of the content type are not Linear White Spaces. So its safe to
            //assume that the index will be greater than 0 and less that length-2. 
 
            int index = contentType.IndexOf(_LinearWhiteSpaceChars[2]);
 
            while (index != -1)
            {
                if (contentType[index - 1] == _LinearWhiteSpaceChars[1] || contentType[index + 1] == _LinearWhiteSpaceChars[1])
                { 
                    index = contentType.IndexOf(_LinearWhiteSpaceChars[2], ++index);
                } 
                else 
                    throw new ArgumentException(SR.Get(SRID.InvalidLinearWhiteSpaceCharacter));
            } 
        }

        /// 
        /// Parses the type ans subType tokens from the string. 
        /// Also verifies if the Tokens are valid as per the grammar.
        ///  
        /// substring that has the type and subType of the content type 
        /// If the typeAndSubType parameter does not have the "/" character
        private void ParseTypeAndSubType(string typeAndSubType) 
        {
            //okay to trim at this point the end of the string as Linear White Spaces(LWS) chars are allowed here.
            typeAndSubType = typeAndSubType.TrimEnd(_LinearWhiteSpaceChars);
 
            string[] splitBasedOnForwardSlash = typeAndSubType.Split(_forwardSlashSeparator);
 
            if (splitBasedOnForwardSlash.Length != 2) 
                throw new ArgumentException(SR.Get(SRID.InvalidTypeSubType));
 
            _type    = ValidateToken(splitBasedOnForwardSlash[0]);
            _subType = ValidateToken(splitBasedOnForwardSlash[1]);
        }
 
        /// 
        /// Parse the individual parameter=value strings 
        ///  
        /// This string has the parameter and value pair of the form
        /// parameter=value 
        /// If the string does not have the required "="
        private void ParseParameterAndValue(string parameterAndValue)
        {
            while (String.CompareOrdinal(parameterAndValue, String.Empty) != 0) 
            {
                //At this point the first character MUST be a semi-colon 
                //First time through this test is serving more as an assert. 
                if (parameterAndValue[0] != _semicolonSeparator)
                    throw new ArgumentException(SR.Get(SRID.ExpectingSemicolon)); 

                //At this point if we have just one semicolon, then its an error.
                //Also, there can be no trailing LWS characters, as we already checked for that
                //in the constructor. 
                if (parameterAndValue.Length == 1)
                    throw new ArgumentException(SR.Get(SRID.ExpectingParameterValuePairs)); 
 
                //Removing the leading ; from the string
                parameterAndValue = parameterAndValue.Substring(1); 

                //okay to trim start as there can be spaces before the begining
                //of the parameter name.
                parameterAndValue = parameterAndValue.TrimStart(_LinearWhiteSpaceChars); 

                int equalSignIndex = parameterAndValue.IndexOf(_equalSeparator); 
 
                if (equalSignIndex <= 0 || equalSignIndex == (parameterAndValue.Length - 1))
                    throw new ArgumentException(SR.Get(SRID.InvalidParameterValuePair)); 

                int parameterStartIndex = equalSignIndex + 1;

                //Get length of the parameter value 
                int parameterValueLength = GetLengthOfParameterValue(parameterAndValue, parameterStartIndex);
 
                EnsureParameterDictionary(); 

                _parameterDictionary.Add( 
                    ValidateToken(parameterAndValue.Substring(0, equalSignIndex)),
                    ValidateQuotedStringOrToken(parameterAndValue.Substring(parameterStartIndex, parameterValueLength)));

                parameterAndValue = parameterAndValue.Substring(parameterStartIndex + parameterValueLength).TrimStart(_LinearWhiteSpaceChars); 
            }
        } 
 
        /// 
        /// This method returns the length of the first parameter value in the input string. 
        /// 
        /// 
        /// Starting index for parsing
        ///  
        private static int GetLengthOfParameterValue(string s, int startIndex)
        { 
            Debug.Assert(s != null); 

            int length = 0; 

            //if the parameter value does not start with a '"' then,
            //we expect a valid token. So we look for Linear White Spaces or
            //a ';' as the terminator for the token value. 
            if (s[startIndex] != '"')
            { 
                int semicolonIndex = s.IndexOf(_semicolonSeparator, startIndex); 

                if (semicolonIndex != -1) 
                {
                    int lwsIndex = s.IndexOfAny(_LinearWhiteSpaceChars, startIndex);
                    if (lwsIndex != -1 && lwsIndex < semicolonIndex)
                        length = lwsIndex; 
                    else
                        length = semicolonIndex; 
                } 
                else
                    length = semicolonIndex; 

                //If there is no linear white space found we treat the entire remaining string as
                //parameter value.
                if (length == -1) 
                    length = s.Length;
            } 
            else 
            {
                //if the parameter value starts with a '"' then, we need to look for the 
                //pairing '"' that is not preceded by a "\" ["\" is used to escape the '"']
                bool found = false;
                length = startIndex;
 
                while (!found)
                { 
                    length = s.IndexOf('"', ++length); 

                    if (length == -1) 
                        throw new ArgumentException(SR.Get(SRID.InvalidParameterValue));

                    if (s[length - 1] != '\\')
                    { 
                        found = true;
                        length++; 
                    } 
                }
            } 
            return length - startIndex;
        }

        ///  
        /// Validating the given token
        /// The following checks are being made - 
        /// 1. If all the characters in the token are either ASCII letter or digit. 
        /// 2. If all the characters in the token are either from the remaining allowed cha----ter set.
        ///  
        /// string token
        /// validated string token
        /// If the token is Empty
        private static string ValidateToken(string token) 
        {
            if (String.CompareOrdinal(token, String.Empty)==0) 
                throw new ArgumentException(SR.Get(SRID.InvalidToken)); 

            for (int i = 0; i < token.Length; i++) 
            {
                if (IsAsciiLetterOrDigit(token[i]))
                    continue;
                else 
                    if (IsAllowedCharacter(token[i]))
                        continue; 
                    else 
                        throw new ArgumentException(SR.Get(SRID.InvalidToken));
            } 

            return token;
        }
 
        /// 
        /// Validating if the value of a parameter is either a valid token or a 
        /// valid quoted string 
        /// 
        /// paramter value string 
        /// validate parameter value string
        /// If the paramter value is empty
        private static string ValidateQuotedStringOrToken(string parameterValue)
        { 
            if (String.CompareOrdinal(parameterValue, String.Empty) == 0)
                throw new ArgumentException(SR.Get(SRID.InvalidParameterValue)); 
 
            if (parameterValue.Length >= 2 &&
                parameterValue.StartsWith(_quote, StringComparison.Ordinal) && 
                parameterValue.EndsWith(_quote, StringComparison.Ordinal))
                ValidateQuotedText(parameterValue.Substring(1, parameterValue.Length-2));
            else
                ValidateToken(parameterValue); 

            return parameterValue; 
        } 

        ///  
        /// This method validates if the text in the quoted string
        /// 
        /// 
        private static void ValidateQuotedText(string quotedText) 
        {
            //empty is okay 
 
            for (int i = 0; i < quotedText.Length; i++)
            { 
                if (IsLinearWhiteSpaceChar(quotedText[i]))
                    continue;

                if (quotedText[i] <= ' ' || quotedText[i] >= 0xFF) 
                    throw new ArgumentException(SR.Get(SRID.InvalidParameterValue));
                else 
                    if (quotedText[i] == '"' && 
                        (i==0 || quotedText[i-1] != '\\'))
                        throw new ArgumentException(SR.Get(SRID.InvalidParameterValue)); 
            }
        }

        ///  
        /// Returns true if the input character is an allowed character
        /// Returns false if the input cha----ter is not an allowed character 
        ///  
        /// input character
        ///  
        private static bool IsAllowedCharacter(char character)
        {
            //We did not use any of the .Contains methods as
            //it will result in boxing costs. 
            foreach (char c in _allowedCharacters)
            { 
                if (c == character) 
                    return true;
            } 
            return false;
        }

        ///  
        /// Returns true if the input character is an ASCII digit or letter
        /// Returns false if the input character is not an ASCII digit or letter 
        ///  
        /// input character
        ///  
        private static bool IsAsciiLetterOrDigit(char character)
        {
            if (IsAsciiLetter(character))
            { 
                return true;
            } 
            if (character >= '0') 
            {
                return (character <= '9'); 
            }
            return false;
        }
 
        /// 
        /// Returns true if the input character is an ASCII letter 
        /// Returns false if the input character is not an ASCII letter 
        /// 
        /// input character 
        /// 
        private static bool IsAsciiLetter(char character)
        {
            if ((character >= 'a') && (character <= 'z')) 
            {
                return true; 
            } 
            if (character >= 'A')
            { 
                return (character <= 'Z');
            }
            return false;
        } 

        ///  
        /// Returns true if the input character is one of the Linear White Space characters - 
        /// ' ', '\t', '\n', '\r'
        /// Returns false if the input character is none of the above 
        /// 
        /// input character
        /// 
        private static bool IsLinearWhiteSpaceChar(char ch) 
        {
            if (ch > ' ') 
            { 
                return false;
            } 

            foreach (char c in _LinearWhiteSpaceChars)
            {
                if (ch == c) 
                    return true;
            } 
 
            return false;
        } 

        /// 
        /// Lazy initialization for the ParameterDictionary
        ///  
        private void EnsureParameterDictionary()
        { 
            if (_parameterDictionary == null) 
            {
                _parameterDictionary = new Dictionary(); //initial size 0 
            }
        }

        #endregion Private Methods 

        //----------------------------------------------------- 
        // 
        //  Private Members
        // 
        //------------------------------------------------------
        #region Private Members

        private string _contentType = null; 
        private string _type    = String.Empty;
        private string _subType = String.Empty; 
        private string _originalString; 
        private Dictionary _parameterDictionary = null;
        private bool   _isInitialized = false; 

        private const string     _quote              = "\"";
        private const char       _semicolonSeparator = ';';
        private const char       _equalSeparator     = '='; 

        //This array is sorted by the ascii value of these characters. 
        private static readonly char[] _allowedCharacters = 
         { '!' /*33*/, '#' /*35*/ , '$'  /*36*/,
           '%' /*37*/, '&' /*38*/ , '\'' /*39*/, 
           '*' /*42*/, '+' /*43*/ , '-'  /*45*/,
           '.' /*46*/, '^' /*94*/ , '_'  /*95*/,
           '`' /*96*/, '|' /*124*/, '~'  /*126*/,
         }; 

        private static readonly char[]     _forwardSlashSeparator = { '/' }; 
 
        //Linear White Space characters
        private static readonly char[]     _LinearWhiteSpaceChars = 
         { ' ',  // space           - \x20
           '\n', // new line        - \x0A
           '\r', // carriage return - \x0D
           '\t'  // horizontal tab  - \x09 
         };
 
        private static readonly ContentType _emptyContentType = new ContentType(""); 

        #endregion Private Members 
    }
}


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK