File: System\Data\Common\DbConnectionOptions.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="DBConnectionOptions.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Data.Common {
 
    using System;
    using System.Collections;
    using System.Data;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Security.Permissions;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Runtime.Versioning;
 
    internal class DbConnectionOptions {
        // instances of this class are intended to be immutable, i.e readonly
        // used by pooling classes so it is much easier to verify correctness
        // when not worried about the class being modified during execution
 
#if DEBUG
        /*private const string ConnectionStringPatternV1 =
             "[\\s;]*"
            +"(?<key>([^=\\s]|\\s+[^=\\s]|\\s+==|==)+)"
            +   "\\s*=(?!=)\\s*"
            +"(?<value>("
            +   "(" + "\"" + "([^\"]|\"\")*" + "\"" + ")"
            +   "|"
            +   "(" + "'" + "([^']|'')*" + "'" + ")"
            +   "|"
            +   "(" + "(?![\"'])" + "([^\\s;]|\\s+[^\\s;])*" + "(?<![\"'])" + ")"
            + "))"
            + "[\\s;]*"
        ;*/
        private const string ConnectionStringPattern =                  // may not contain embedded null except trailing last value
            "([\\s;]*"                                                  // leading whitespace and extra semicolons
            + "(?![\\s;])"                                              // key does not start with space or semicolon
            + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)"  // allow any visible character for keyname except '=' which must quoted as '=='
            + "\\s*=(?!=)\\s*"                                          // the equal sign divides the key and value parts
            + "(?<value>"
            +  "(\"([^\"\u0000]|\"\")*\")"                              // double quoted string, " must be quoted as ""
            +  "|"
            +  "('([^'\u0000]|'')*')"                                   // single quoted string, ' must be quoted as ''
            +  "|"
            +  "((?![\"'\\s])"                                          // unquoted value must not start with " or ' or space, would also like = but too late to change
            +   "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*"                  // control characters must be quoted
            +  "(?<![\"']))"                                            // unquoted value must not stop with " or '
            + ")(\\s*)(;|[\u0000\\s]*$)"                                // whitespace after value up to semicolon or end-of-line
            + ")*"                                                      // repeat the key-value pair
            + "[\\s;]*[\u0000\\s]*"                                     // traling whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end
        ;
 
        private const string ConnectionStringPatternOdbc =              // may not contain embedded null except trailing last value
            "([\\s;]*"                                                  // leading whitespace and extra semicolons
            + "(?![\\s;])"                                              // key does not start with space or semicolon
            + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}])+)"            // allow any visible character for keyname except '='
            + "\\s*=\\s*"                                               // the equal sign divides the key and value parts
            + "(?<value>"
            +  "(\\{([^\\}\u0000]|\\}\\})*\\})"                         // quoted string, starts with { and ends with }
            +  "|"
            +  "((?![\\{\\s])"                                          // unquoted value must not start with { or space, would also like = but too late to change
            +   "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*"                  // control characters must be quoted
 
            +  ")" // VSTFDEVDIV 94761: although the spec does not allow {}
                   // embedded within a value, the retail code does.
            // +  "(?<![\\}]))"                                            // unquoted value must not stop with }
 
            + ")(\\s*)(;|[\u0000\\s]*$)"                               // whitespace after value up to semicolon or end-of-line
            + ")*"                                                      // repeat the key-value pair
            + "[\\s;]*[\u0000\\s]*"                                     // traling whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end
        ;
 
        private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
        private static readonly Regex ConnectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
#endif
        private const string ConnectionStringValidKeyPattern   = "^(?![;\\s])[^\\p{Cc}]+(?<!\\s)$"; // key not allowed to start with semi-colon or space or contain non-visible characters or end with space
        private const string ConnectionStringValidValuePattern = "^[^\u0000]*$";                    // value not allowed to contain embedded null
        private const string ConnectionStringQuoteValuePattern = "^[^\"'=;\\s\\p{Cc}]*$";           // generally do not quote the value if it matches the pattern
        private const string ConnectionStringQuoteOdbcValuePattern = "^\\{([^\\}\u0000]|\\}\\})*\\}$"; // do not quote odbc value if it matches this pattern
        internal const string DataDirectory = "|datadirectory|";
 
        private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
        private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
 
        private static readonly Regex ConnectionStringQuoteValueRegex = new Regex(ConnectionStringQuoteValuePattern, RegexOptions.Compiled);
        private static readonly Regex ConnectionStringQuoteOdbcValueRegex = new Regex(ConnectionStringQuoteOdbcValuePattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
 
        // connection string common keywords
        private static class KEY {
            internal const string Integrated_Security      = "integrated security";
            internal const string Password                 = "password";
            internal const string Persist_Security_Info    = "persist security info";
            internal const string User_ID                  = "user id";
        };
 
        // known connection string common synonyms
        private static class SYNONYM {
            internal const string Pwd = "pwd";
            internal const string UID = "uid";
        };
 
        private readonly string        _usersConnectionString;
        private readonly Hashtable     _parsetable;
        internal readonly NameValuePair KeyChain;
        internal readonly bool HasPasswordKeyword;
        internal readonly bool HasUserIdKeyword;
 
        // differences between OleDb and Odbc
        // ODBC:
        //     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcsqldriverconnect.asp
        //     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbcsql/od_odbc_d_4x4k.asp
        //     do not support == -> = in keywords
        //     first key-value pair wins
        //     quote values using \{ and \}, only driver= and pwd= appear to generically allow quoting
        //     do not strip quotes from value, or add quotes except for driver keyword
        // OLEDB:
        //     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/oledb/htm/oledbconnectionstringsyntax.asp
        //     support == -> = in keywords
        //     last key-value pair wins
        //     quote values using \" or \'
        //     strip quotes from value
        internal readonly bool UseOdbcRules;
 
        private System.Security.PermissionSet _permissionset;
 
        // called by derived classes that may cache based on connectionString
        public DbConnectionOptions(string connectionString)
            : this(connectionString, null, false) {
        }
 
        // synonyms hashtable is meant to be read-only translation of parsed string
        // keywords/synonyms to a known keyword string
        public DbConnectionOptions(string connectionString, Hashtable synonyms, bool useOdbcRules) {
            UseOdbcRules = useOdbcRules;
            _parsetable = new Hashtable();
            _usersConnectionString = ((null != connectionString) ? connectionString : "");
 
            // first pass on parsing, initial syntax check
            if (0 < _usersConnectionString.Length) {
                KeyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, UseOdbcRules);
                HasPasswordKeyword = (_parsetable.ContainsKey(KEY.Password) || _parsetable.ContainsKey(SYNONYM.Pwd));
                HasUserIdKeyword = (_parsetable.ContainsKey(KEY.User_ID) || _parsetable.ContainsKey(SYNONYM.UID));
            }
        }
 
        protected DbConnectionOptions(DbConnectionOptions connectionOptions) { // Clone used by SqlConnectionString
            _usersConnectionString = connectionOptions._usersConnectionString;
            HasPasswordKeyword = connectionOptions.HasPasswordKeyword;
            HasUserIdKeyword = connectionOptions.HasUserIdKeyword;
            UseOdbcRules = connectionOptions.UseOdbcRules;
            _parsetable = connectionOptions._parsetable;
            KeyChain = connectionOptions.KeyChain;
        }
 
 
        public string UsersConnectionString(bool hidePassword) {
            return UsersConnectionString(hidePassword, false);
        }
 
        private string UsersConnectionString(bool hidePassword, bool forceHidePassword) {
            string connectionString = _usersConnectionString;
            if (HasPasswordKeyword && (forceHidePassword || (hidePassword && !HasPersistablePassword))) {
                ReplacePasswordPwd(out connectionString, false);
            }
            return ((null != connectionString) ? connectionString : "");
        }
 
        internal string UsersConnectionStringForTrace() {
            return UsersConnectionString(true, true);
        }
 
        internal bool HasBlankPassword {
            get {
                if (!ConvertValueToIntegratedSecurity()) {
                    if (_parsetable.ContainsKey(KEY.Password)) {
                        return ADP.IsEmpty((string)_parsetable[KEY.Password]);
                    } else
                    if (_parsetable.ContainsKey(SYNONYM.Pwd)) {
                        return ADP.IsEmpty((string)_parsetable[SYNONYM.Pwd]); // MDAC 83097
                    } else {
                        return ((_parsetable.ContainsKey(KEY.User_ID) && !ADP.IsEmpty((string)_parsetable[KEY.User_ID])) || (_parsetable.ContainsKey(SYNONYM.UID) && !ADP.IsEmpty((string)_parsetable[SYNONYM.UID])));
                    }
                }
                return false;
            }
        }
 
        internal bool HasPersistablePassword {
            get {
                if (HasPasswordKeyword) {
                    return ConvertValueToBoolean(KEY.Persist_Security_Info, false);
                }
                return true; // no password means persistable password so we don't have to munge
            }
        }
 
        public bool IsEmpty {
            get { return (null == KeyChain); }
        }
 
        internal Hashtable Parsetable {
            get { return _parsetable; }
        }
 
        public ICollection Keys {
            get { return _parsetable.Keys; }
        }
 
        public string this[string keyword] {
            get { return (string)_parsetable[keyword]; }
        }
 
        internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string keyValue, bool useOdbcRules) {
            ADP.CheckArgumentNull(builder, "builder");
            ADP.CheckArgumentLength(keyName, "keyName");
 
            if ((null == keyName) || !ConnectionStringValidKeyRegex.IsMatch(keyName)) {
                throw ADP.InvalidKeyname(keyName);
            }
            if ((null != keyValue) && !IsValueValidInternal(keyValue)) {
                throw ADP.InvalidValue(keyName);
            }
 
            if ((0 < builder.Length) && (';' != builder[builder.Length-1])) {
                builder.Append(";");
            }
 
            if (useOdbcRules) {
                builder.Append(keyName);
            }
            else {
                builder.Append(keyName.Replace("=", "=="));
            }
            builder.Append("=");
 
            if (null != keyValue) { // else <keyword>=;
                if (useOdbcRules) {
                    if ((0 < keyValue.Length) &&
                        (('{' == keyValue[0]) || (0 <= keyValue.IndexOf(';')) || (0 == String.Compare(DbConnectionStringKeywords.Driver, keyName, StringComparison.OrdinalIgnoreCase))) &&
                        !ConnectionStringQuoteOdbcValueRegex.IsMatch(keyValue))
                    {
                        // always quote Driver value (required for ODBC Version 2.65 and earlier)
                        // always quote values that contain a ';'
                        builder.Append('{').Append(keyValue.Replace("}", "}}")).Append('}');
                    }
                    else {
                        builder.Append(keyValue);
                    }
                }
                else if (ConnectionStringQuoteValueRegex.IsMatch(keyValue)) {
                    // <value> -> <value>
                    builder.Append(keyValue);
                }
                else if ((-1 != keyValue.IndexOf('\"')) && (-1 == keyValue.IndexOf('\''))) {
                    // <val"ue> -> <'val"ue'>
                    builder.Append('\'');
                    builder.Append(keyValue);
                    builder.Append('\'');
                }
                else {
                    // <val'ue> -> <"val'ue">
                    // <=value> -> <"=value">
                    // <;value> -> <";value">
                    // < value> -> <" value">
                    // <va lue> -> <"va lue">
                    // <va'"lue> -> <"va'""lue">
                    builder.Append('\"');
                    builder.Append(keyValue.Replace("\"", "\"\""));
                    builder.Append('\"');
                }
            }
        }
 
        public bool ConvertValueToBoolean(string keyName, bool defaultValue) {
            object value = _parsetable[keyName];
            if (null == value) {
                return defaultValue;
            }
            return ConvertValueToBooleanInternal(keyName, (string) value);
        }
 
        internal static bool ConvertValueToBooleanInternal(string keyName, string stringValue) {
            if (CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
                return true;
            else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
                return false;
            else {
                string tmp = stringValue.Trim();  // Remove leading & trailing white space.
                if (CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
                    return true;
                else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
                    return false;
                else {
                    throw ADP.InvalidConnectionOptionValue(keyName);
                }
            }
        }
 
        // same as Boolean, but with SSPI thrown in as valid yes
        public bool ConvertValueToIntegratedSecurity() {
            object value = _parsetable[KEY.Integrated_Security];
            if (null == value) {
                return false;
            }
            return ConvertValueToIntegratedSecurityInternal((string) value);
        }
 
        internal bool ConvertValueToIntegratedSecurityInternal(string stringValue) {
            if (CompareInsensitiveInvariant(stringValue, "sspi") || CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
                return true;
            else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
                return false;
            else {
                string tmp = stringValue.Trim();  // Remove leading & trailing white space.
                if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
                    return true;
                else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
                    return false;
                else {
                    throw ADP.InvalidConnectionOptionValue(KEY.Integrated_Security);
                }
            }
        }
 
        public int ConvertValueToInt32(string keyName, int defaultValue) {
            object value = _parsetable[keyName];
            if (null == value) {
                return defaultValue;
            }
            return ConvertToInt32Internal(keyName, (string) value);
        }
 
        internal static int ConvertToInt32Internal(string keyname, string stringValue) {
            try {
                return System.Int32.Parse(stringValue, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture);
            }
            catch (FormatException e) {
                throw ADP.InvalidConnectionOptionValue(keyname, e);
            }
            catch (OverflowException e) {
                throw ADP.InvalidConnectionOptionValue(keyname, e);
            }
        }
 
        public string ConvertValueToString(string keyName, string defaultValue) {
            string value = (string)_parsetable[keyName];
            return ((null != value) ? value : defaultValue);
        }
 
        static private bool CompareInsensitiveInvariant(string strvalue, string strconst) {
            return (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst));
        }
 
        public bool ContainsKey(string keyword) {
            return _parsetable.ContainsKey(keyword);
        }
 
        protected internal virtual System.Security.PermissionSet CreatePermissionSet() {
            return null;
        }
 
        internal void DemandPermission() {
            if (null == _permissionset) {
                _permissionset = CreatePermissionSet();
            }
            _permissionset.Demand();
        }
 
        protected internal virtual string Expand() {
            return _usersConnectionString;
        }
 
        // SxS notes:
        // * this method queries "DataDirectory" value from the current AppDomain.
        //   This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource".
        // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out.
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        internal static string ExpandDataDirectory(string keyword, string value, ref string datadir) {
            string fullPath = null;
            if ((null != value) && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase)) {
 
                string rootFolderPath = datadir;
                if (null == rootFolderPath) {
                    // find the replacement path
                    object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
                    rootFolderPath = (rootFolderObject as string);
                    if ((null != rootFolderObject) && (null == rootFolderPath)) {
                        throw ADP.InvalidDataDirectory();
                    }
                    else if (ADP.IsEmpty(rootFolderPath)) {
                        rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
                    }
                    if (null == rootFolderPath) {
                        rootFolderPath = "";
                    }
                    // cache the |DataDir| for ExpandDataDirectories
                    datadir = rootFolderPath;
                }
 
                // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw
                int fileNamePosition = DataDirectory.Length;    // filename starts right after the '|datadirectory|' keyword
                bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length-1] == '\\';
                bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\';
 
                // replace |datadirectory| with root folder path
                if (!rootFolderEndsWith && !fileNameStartsWith) {
                    // need to insert '\'
                    fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
                }
                else if (rootFolderEndsWith && fileNameStartsWith) {
                    // need to strip one out
                    fullPath = rootFolderPath + value.Substring(fileNamePosition+1);
                }
                else {
                    // simply concatenate the strings
                    fullPath = rootFolderPath + value.Substring(fileNamePosition);
                }
 
                // verify root folder path is a real path without unexpected "..\"
                if (!ADP.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal)) {
                    throw ADP.InvalidConnectionOptionValue(keyword);
                }
            }
            return fullPath;
        }
 
        internal string ExpandDataDirectories(ref string filename, ref int position) {
            string value = null;
            StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
            string datadir = null;
 
            int copyPosition = 0;
            bool expanded = false;
 
            for(NameValuePair current = KeyChain; null != current; current = current.Next) {
                value = current.Value;
 
                // remove duplicate keyswords from connectionstring
                //if ((object)this[current.Name] != (object)value) {
                //    expanded = true;
                //    copyPosition += current.Length;
                //    continue;
                //}
 
                // There is a set of keywords we explictly do NOT want to expand |DataDirectory| on
                if (UseOdbcRules) {
                    switch(current.Name) {
                    case DbConnectionOptionKeywords.Driver:
                    case DbConnectionOptionKeywords.Pwd:
                    case DbConnectionOptionKeywords.UID:
                        break;
                    default:
                        value = ExpandDataDirectory(current.Name, value, ref datadir);
                        break;
                    }
                }
                else {
                    switch(current.Name) {
                    case DbConnectionOptionKeywords.Provider:
                    case DbConnectionOptionKeywords.DataProvider:
                    case DbConnectionOptionKeywords.RemoteProvider:
                    case DbConnectionOptionKeywords.ExtendedProperties:
                    case DbConnectionOptionKeywords.UserID:
                    case DbConnectionOptionKeywords.Password:
                    case DbConnectionOptionKeywords.UID:
                    case DbConnectionOptionKeywords.Pwd:
                        break;
                    default:
                        value = ExpandDataDirectory(current.Name, value, ref datadir);
                        break;
                    }
                }
                if (null == value) {
                    value = current.Value;
                }
                if (UseOdbcRules || (DbConnectionOptionKeywords.FileName != current.Name)) {
                    if (value != current.Value) {
                        expanded = true;
                        AppendKeyValuePairBuilder(builder, current.Name, value, UseOdbcRules);
                        builder.Append(';');
                    }
                    else {
                        builder.Append(_usersConnectionString, copyPosition, current.Length);
                    }
                }
                else {
                    // strip out 'File Name=myconnection.udl' for OleDb
                    // remembering is value for which UDL file to open
                    // and where to insert the strnig
                    expanded = true;
                    filename = value;
                    position = builder.Length;
                }
                copyPosition += current.Length;
            }
 
            if (expanded) {
                value = builder.ToString();
            }
            else {
                value = null;
            }
            return value;
        }
 
        internal string ExpandKeyword(string keyword, string replacementValue) {
            // preserve duplicates, updated keyword value with replacement value
            // if keyword not specified, append to end of the string
            bool expanded = false;
            int copyPosition = 0;
 
            StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
            for(NameValuePair current = KeyChain; null != current; current = current.Next) {
                if ((current.Name == keyword) && (current.Value == this[keyword])) {
                    // only replace the parse end-result value instead of all values
                    // so that when duplicate-keywords occur other original values remain in place
                    AppendKeyValuePairBuilder(builder, current.Name, replacementValue, UseOdbcRules);
                    builder.Append(';');
                    expanded = true;
                }
                else {
                    builder.Append(_usersConnectionString, copyPosition, current.Length);
                }
                copyPosition += current.Length;
            }
 
            if (!expanded) {
                // 
                Debug.Assert(!UseOdbcRules, "ExpandKeyword not ready for Odbc");
                AppendKeyValuePairBuilder(builder, keyword, replacementValue, UseOdbcRules);
            }
            return builder.ToString();
        }
 
#if DEBUG
        [System.Diagnostics.Conditional("DEBUG")]
        private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Hashtable synonyms) {
            if (Bid.AdvancedOn) {
                Debug.Assert(keyname == keyname.ToLower(CultureInfo.InvariantCulture), "missing ToLower");
 
                string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
                if ((KEY.Password != realkeyname) && (SYNONYM.Pwd != realkeyname)) { // don't trace passwords ever!
                    if (null != keyvalue) {
                        Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls', KeyValue='%ls'\n", keyname, keyvalue);
                    }
                    else {
                        Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls'\n", keyname);
                    }
                }
            }
        }
#endif
 
        static private string GetKeyName(StringBuilder buffer) {
            int count = buffer.Length;
            while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
                count--; // trailing whitespace
            }
            return buffer.ToString(0, count).ToLower(CultureInfo.InvariantCulture);
        }
 
        static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace) {
            int count = buffer.Length;
            int index = 0;
            if (trimWhitespace) {
                while ((index < count) && Char.IsWhiteSpace(buffer[index])) {
                    index++; // leading whitespace
                }
                while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
                    count--; // trailing whitespace
                }
            }
            return buffer.ToString(index, count - index);
        }
 
        // transistion states used for parsing
        private enum ParserState {
            NothingYet=1,   //start point
            Key,
            KeyEqual,
            KeyEnd,
            UnquotedValue,
            DoubleQuoteValue,
            DoubleQuoteValueQuote,
            SingleQuoteValue,
            SingleQuoteValueQuote,
            BraceQuoteValue,
            BraceQuoteValueQuote,
            QuotedValueEnd,
            NullTermination,
        };
 
        static internal int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, bool useOdbcRules, out string keyname, out string keyvalue) {
            int startposition = currentPosition;
 
            buffer.Length = 0;
            keyname = null;
            keyvalue = null;
 
            char currentChar = '\0';
 
            ParserState parserState = ParserState.NothingYet;
            int length = connectionString.Length;
            for (; currentPosition < length; ++currentPosition) {
                currentChar = connectionString[currentPosition];
 
                switch(parserState) {
                case ParserState.NothingYet: // [\\s;]*
                    if ((';' == currentChar) || Char.IsWhiteSpace(currentChar)) {
                        continue;
                    }
                    if ('\0' == currentChar)            { parserState = ParserState.NullTermination; continue; } // MDAC 83540
                    if (Char.IsControl(currentChar))    { throw ADP.ConnectionStringSyntax(startposition); }
                    startposition = currentPosition;
                    if ('=' != currentChar) { // MDAC 86902
                        parserState = ParserState.Key;
                        break;
                    }
                    else {
                        parserState = ParserState.KeyEqual;
                        continue;
                    }
 
                case ParserState.Key: // (?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)
                    if ('=' == currentChar)             { parserState = ParserState.KeyEqual;       continue; }
                    if (Char.IsWhiteSpace(currentChar)) { break; }
                    if (Char.IsControl(currentChar))    { throw ADP.ConnectionStringSyntax(startposition); }
                    break;
 
                case ParserState.KeyEqual: // \\s*=(?!=)\\s*
                    if (!useOdbcRules && '=' == currentChar) { parserState = ParserState.Key;            break; }
                    keyname = GetKeyName(buffer);
                    if (ADP.IsEmpty(keyname))           { throw ADP.ConnectionStringSyntax(startposition); }
                    buffer.Length = 0;
                    parserState = ParserState.KeyEnd;
                    goto case ParserState.KeyEnd;
 
                case ParserState.KeyEnd:
                    if (Char.IsWhiteSpace(currentChar)) { continue; }
                    if (useOdbcRules) {
                        if ('{' == currentChar)             { parserState = ParserState.BraceQuoteValue; break; }
                    }
                    else {
                        if ('\'' == currentChar)            { parserState = ParserState.SingleQuoteValue; continue; }
                        if ('"' == currentChar)             { parserState = ParserState.DoubleQuoteValue; continue; }
                    }
                    if (';' == currentChar)             { goto ParserExit; }
                    if ('\0' == currentChar)            { goto ParserExit; }
                    if (Char.IsControl(currentChar))    { throw ADP.ConnectionStringSyntax(startposition); }
                    parserState = ParserState.UnquotedValue;
                    break;
 
                case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(?<![\"']))"
                    if (Char.IsWhiteSpace(currentChar)) { break; }
                    if (Char.IsControl(currentChar) || ';' == currentChar) { goto ParserExit; }
                    break;
 
                case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
                    if ('"' == currentChar)             { parserState = ParserState.DoubleQuoteValueQuote;   continue; }
                    if ('\0' == currentChar)            { throw ADP.ConnectionStringSyntax(startposition); }
                    break;
 
                case ParserState.DoubleQuoteValueQuote:
                    if ('"' == currentChar)             { parserState = ParserState.DoubleQuoteValue;      break; }
                    keyvalue = GetKeyValue(buffer, false);
                    parserState = ParserState.QuotedValueEnd;
                    goto case ParserState.QuotedValueEnd;
 
                case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
                    if ('\'' == currentChar)             { parserState = ParserState.SingleQuoteValueQuote;   continue; }
                    if ('\0' == currentChar)             { throw ADP.ConnectionStringSyntax(startposition); }
                    break;
 
                case ParserState.SingleQuoteValueQuote:
                    if ('\'' == currentChar)             { parserState = ParserState.SingleQuoteValue;      break; }
                    keyvalue = GetKeyValue(buffer, false);
                    parserState = ParserState.QuotedValueEnd;
                    goto case ParserState.QuotedValueEnd;
 
                case ParserState.BraceQuoteValue: // "(\\{([^\\}\u0000]|\\}\\})*\\})"
                    if ('}' == currentChar)             { parserState = ParserState.BraceQuoteValueQuote;   break; }
                    if ('\0' == currentChar)            { throw ADP.ConnectionStringSyntax(startposition); }
                    break;
 
                case ParserState.BraceQuoteValueQuote:
                    if ('}' == currentChar)             { parserState = ParserState.BraceQuoteValue;      break; }
                    keyvalue = GetKeyValue(buffer, false);
                    parserState = ParserState.QuotedValueEnd;
                    goto case ParserState.QuotedValueEnd;
 
                case ParserState.QuotedValueEnd:
                    if (Char.IsWhiteSpace(currentChar)) { continue; }
                    if (';' == currentChar)             { goto ParserExit; }
                    if ('\0' == currentChar)            { parserState = ParserState.NullTermination; continue; } // MDAC 83540
                    throw ADP.ConnectionStringSyntax(startposition);  // unbalanced single quote
 
                case ParserState.NullTermination: // [\\s;\u0000]*
                    if ('\0' == currentChar) { continue; }
                    if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
                    throw ADP.ConnectionStringSyntax(currentPosition);
 
                default:
                    throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState1);
                }
                buffer.Append(currentChar);
            }
        ParserExit:
            switch (parserState) {
            case ParserState.Key:
            case ParserState.DoubleQuoteValue:
            case ParserState.SingleQuoteValue:
            case ParserState.BraceQuoteValue:
                // keyword not found/unbalanced double/single quote
                throw ADP.ConnectionStringSyntax(startposition);
 
            case ParserState.KeyEqual:
                // equal sign at end of line
                keyname = GetKeyName(buffer);
                if (ADP.IsEmpty(keyname))           { throw ADP.ConnectionStringSyntax(startposition); }
                break;
 
            case ParserState.UnquotedValue:
                // unquoted value at end of line
                keyvalue = GetKeyValue(buffer, true);
 
                char tmpChar = keyvalue[keyvalue.Length - 1];
                if (!useOdbcRules && (('\'' == tmpChar) || ('"' == tmpChar))) {
                    throw ADP.ConnectionStringSyntax(startposition);    // unquoted value must not end in quote, except for odbc
                }
                break;
 
            case ParserState.DoubleQuoteValueQuote:
            case ParserState.SingleQuoteValueQuote:
            case ParserState.BraceQuoteValueQuote:
            case ParserState.QuotedValueEnd:
                // quoted value at end of line
                keyvalue = GetKeyValue(buffer, false);
                break;
 
            case ParserState.NothingYet:
            case ParserState.KeyEnd:
            case ParserState.NullTermination:
                // do nothing
                break;
 
            default:
                throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState2);
            }
            if ((';' == currentChar) && (currentPosition < connectionString.Length)) {
                currentPosition++;
            }
            return currentPosition;
        }
 
        static private bool IsValueValidInternal(string keyvalue) {
            if (null != keyvalue)
            {
#if DEBUG
                bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
                Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
#endif
                return (-1 == keyvalue.IndexOf('\u0000'));
            }
            return true;
        }
 
        static private bool IsKeyNameValid(string keyname) {
            if (null != keyname) {
#if DEBUG
                bool compValue = ConnectionStringValidKeyRegex.IsMatch(keyname);
                Debug.Assert(((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000'))) == compValue, "IsValueValid mismatch with regex");
#endif
                return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
            }
            return false;
        }
 
#if DEBUG
        private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms, bool firstKey) {
            Hashtable parsetable = new Hashtable();
            Regex parser = (firstKey ? ConnectionStringRegexOdbc : ConnectionStringRegex);
 
            const int KeyIndex = 1, ValueIndex = 2;
            Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index");
            Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index");
 
            if (null != connectionString) {
                Match match = parser.Match(connectionString);
                if (!match.Success || (match.Length != connectionString.Length)) {
                    throw ADP.ConnectionStringSyntax(match.Length);
                }
                int indexValue = 0;
                CaptureCollection keyvalues = match.Groups[ValueIndex].Captures;
                foreach(Capture keypair in match.Groups[KeyIndex].Captures) {
                    string keyname = (firstKey ? keypair.Value : keypair.Value.Replace("==", "=")).ToLower(CultureInfo.InvariantCulture);
                    string keyvalue = keyvalues[indexValue++].Value;
                    if (0 < keyvalue.Length) {
                        if (!firstKey) {
                            switch(keyvalue[0]) {
                            case '\"':
                                keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\"\"", "\"");
                                break;
                            case '\'':
                                keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\'\'", "\'");
                                break;
                            default:
                                break;
                            }
                        }
                    }
                    else {
                        keyvalue = null;
                    }
                    DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
 
                    string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
                    if (!IsKeyNameValid(realkeyname)) {
                        throw ADP.KeywordNotSupported(keyname);
                    }
                    if (!firstKey || !parsetable.ContainsKey(realkeyname)) {
                        parsetable[realkeyname] =  keyvalue; // last key-value pair wins (or first)
                    }
                }
            }
            return parsetable;
        }
 
        private static void ParseComparison(Hashtable parsetable, string connectionString, Hashtable synonyms, bool firstKey, Exception e) {
            try {
                Hashtable parsedvalues = SplitConnectionString(connectionString, synonyms, firstKey);
                foreach(DictionaryEntry entry in parsedvalues) {
                    string keyname = (string) entry.Key;
                    string value1 = (string) entry.Value;
                    string value2 = (string) parsetable[keyname];
                    Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">");
                    Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 +">");
                }
 
            }
            catch(ArgumentException f) {
                if (null != e) {
                    string msg1 = e.Message;
                    string msg2 = f.Message;
 
                    const string KeywordNotSupportedMessagePrefix = "Keyword not supported:";
                    const string WrongFormatMessagePrefix = "Format of the initialization string";
                    bool isEquivalent = (msg1 == msg2);
                    if (!isEquivalent)
                    {
                        // VSTFDEVDIV 479587: we also accept cases were Regex parser (debug only) reports "wrong format" and 
                        // retail parsing code reports format exception in different location or "keyword not supported"
                        if (msg2.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) {
                            if (msg1.StartsWith(KeywordNotSupportedMessagePrefix, StringComparison.Ordinal) || msg1.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) {
                                isEquivalent = true;
                            }
                        }
                    }
                    Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <"+msg1+"> <"+msg2+">");
                }
                else {
                    Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
                }
                e = null;
            }
            if (null != e) {
                Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
            }
        }
#endif
        private static NameValuePair ParseInternal(Hashtable parsetable, string connectionString, bool buildChain, Hashtable synonyms, bool firstKey) {
            Debug.Assert(null != connectionString, "null connectionstring");
            StringBuilder buffer = new StringBuilder();
            NameValuePair localKeychain = null, keychain = null;
#if DEBUG
            try {
#endif
                int nextStartPosition = 0;
                int endPosition = connectionString.Length;
                while (nextStartPosition < endPosition) {
                    int startPosition = nextStartPosition;
 
                    string keyname, keyvalue;
                    nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, firstKey, out keyname, out keyvalue);
                    if (ADP.IsEmpty(keyname)) {
                        // if (nextStartPosition != endPosition) { throw; }
                        break;
                    }
#if DEBUG
                    DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
 
                    Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
                    Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
#endif
                    string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
                    if (!IsKeyNameValid(realkeyname)) {
                        throw ADP.KeywordNotSupported(keyname);
                    }
                    if (!firstKey || !parsetable.Contains(realkeyname)) {
                        parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
                    }
 
                    if(null != localKeychain) {
                        localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
                    }
                    else if (buildChain) { // first time only - don't contain modified chain from UDL file
                        keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
                    }
                }
#if DEBUG
            }
            catch(ArgumentException e) {
                ParseComparison(parsetable, connectionString, synonyms, firstKey, e);
                throw;
            }
            ParseComparison(parsetable, connectionString, synonyms, firstKey, null);
#endif
            return keychain;
        }
 
        internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) {
            bool expanded = false;
            int copyPosition = 0;
            NameValuePair head = null, tail = null, next = null;
            StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
            for(NameValuePair current = KeyChain; null != current; current = current.Next) {
                if ((KEY.Password != current.Name) && (SYNONYM.Pwd != current.Name)) {
                    builder.Append(_usersConnectionString, copyPosition, current.Length);
                    if (fakePassword) {
                        next = new NameValuePair(current.Name, current.Value, current.Length);
                    }
                }
                else if (fakePassword) { // replace user password/pwd value with *
                    const string equalstar = "=*;";
                    builder.Append(current.Name).Append(equalstar);
                    next = new NameValuePair(current.Name, "*", current.Name.Length + equalstar.Length);
                    expanded = true;
                }
                else { // drop the password/pwd completely in returning for user
                    expanded = true;
                }
 
                if (fakePassword) {
                    if (null != tail) {
                        tail = tail.Next = next;
                    }
                    else {
                        tail = head = next;
                    }
                }
                copyPosition += current.Length;
            }
            Debug.Assert(expanded, "password/pwd was not removed");
            constr = builder.ToString();
            return head;
        }
 
        internal static void ValidateKeyValuePair(string keyword, string value) {
            if ((null == keyword) || !ConnectionStringValidKeyRegex.IsMatch(keyword)) {
                throw ADP.InvalidKeyname(keyword);
            }
            if ((null != value) && !ConnectionStringValidValueRegex.IsMatch(value)) {
                throw ADP.InvalidValue(keyword);
            }
        }
    }
}