Merge pull request #4419 from BrzVlad/fix-oom-nre
[mono.git] / mcs / class / referencesource / System.Data / System / Data / Common / DbConnectionOptions.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DBConnectionOptions.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data.Common {
10
11     using System;
12     using System.Collections;
13     using System.Data;
14     using System.Diagnostics;
15     using System.Globalization;
16     using System.Runtime.Serialization;
17     using System.Security.Permissions;
18     using System.Text;
19     using System.Text.RegularExpressions;
20     using System.Runtime.Versioning;
21
22     internal class DbConnectionOptions {
23         // instances of this class are intended to be immutable, i.e readonly
24         // used by pooling classes so it is much easier to verify correctness
25         // when not worried about the class being modified during execution
26
27 #if DEBUG
28         /*private const string ConnectionStringPatternV1 =
29              "[\\s;]*"
30             +"(?<key>([^=\\s]|\\s+[^=\\s]|\\s+==|==)+)"
31             +   "\\s*=(?!=)\\s*"
32             +"(?<value>("
33             +   "(" + "\"" + "([^\"]|\"\")*" + "\"" + ")"
34             +   "|"
35             +   "(" + "'" + "([^']|'')*" + "'" + ")"
36             +   "|"
37             +   "(" + "(?![\"'])" + "([^\\s;]|\\s+[^\\s;])*" + "(?<![\"'])" + ")"
38             + "))"
39             + "[\\s;]*"
40         ;*/
41         private const string ConnectionStringPattern =                  // may not contain embedded null except trailing last value
42             "([\\s;]*"                                                  // leading whitespace and extra semicolons
43             + "(?![\\s;])"                                              // key does not start with space or semicolon
44             + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)"  // allow any visible character for keyname except '=' which must quoted as '=='
45             + "\\s*=(?!=)\\s*"                                          // the equal sign divides the key and value parts
46             + "(?<value>"
47             +  "(\"([^\"\u0000]|\"\")*\")"                              // double quoted string, " must be quoted as ""
48             +  "|"
49             +  "('([^'\u0000]|'')*')"                                   // single quoted string, ' must be quoted as ''
50             +  "|"
51             +  "((?![\"'\\s])"                                          // unquoted value must not start with " or ' or space, would also like = but too late to change
52             +   "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*"                  // control characters must be quoted
53             +  "(?<![\"']))"                                            // unquoted value must not stop with " or '
54             + ")(\\s*)(;|[\u0000\\s]*$)"                                // whitespace after value up to semicolon or end-of-line
55             + ")*"                                                      // repeat the key-value pair
56             + "[\\s;]*[\u0000\\s]*"                                     // traling whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end
57         ;
58
59         private const string ConnectionStringPatternOdbc =              // may not contain embedded null except trailing last value
60             "([\\s;]*"                                                  // leading whitespace and extra semicolons
61             + "(?![\\s;])"                                              // key does not start with space or semicolon
62             + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}])+)"            // allow any visible character for keyname except '='
63             + "\\s*=\\s*"                                               // the equal sign divides the key and value parts
64             + "(?<value>"
65             +  "(\\{([^\\}\u0000]|\\}\\})*\\})"                         // quoted string, starts with { and ends with }
66             +  "|"
67             +  "((?![\\{\\s])"                                          // unquoted value must not start with { or space, would also like = but too late to change
68             +   "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*"                  // control characters must be quoted
69
70             +  ")" // VSTFDEVDIV 94761: although the spec does not allow {}
71                    // embedded within a value, the retail code does.
72             // +  "(?<![\\}]))"                                            // unquoted value must not stop with }
73
74             + ")(\\s*)(;|[\u0000\\s]*$)"                               // whitespace after value up to semicolon or end-of-line
75             + ")*"                                                      // repeat the key-value pair
76             + "[\\s;]*[\u0000\\s]*"                                     // traling whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end
77         ;
78
79         private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
80         private static readonly Regex ConnectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
81 #endif
82         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
83         private const string ConnectionStringValidValuePattern = "^[^\u0000]*$";                    // value not allowed to contain embedded null
84         private const string ConnectionStringQuoteValuePattern = "^[^\"'=;\\s\\p{Cc}]*$";           // generally do not quote the value if it matches the pattern
85         private const string ConnectionStringQuoteOdbcValuePattern = "^\\{([^\\}\u0000]|\\}\\})*\\}$"; // do not quote odbc value if it matches this pattern
86         internal const string DataDirectory = "|datadirectory|";
87
88         private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
89         private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
90
91         private static readonly Regex ConnectionStringQuoteValueRegex = new Regex(ConnectionStringQuoteValuePattern, RegexOptions.Compiled);
92         private static readonly Regex ConnectionStringQuoteOdbcValueRegex = new Regex(ConnectionStringQuoteOdbcValuePattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
93
94         // connection string common keywords
95         private static class KEY {
96             internal const string Integrated_Security      = "integrated security";
97             internal const string Password                 = "password";
98             internal const string Persist_Security_Info    = "persist security info";
99             internal const string User_ID                  = "user id";
100         };
101
102         // known connection string common synonyms
103         private static class SYNONYM {
104             internal const string Pwd = "pwd";
105             internal const string UID = "uid";
106         };
107
108         private readonly string        _usersConnectionString;
109         private readonly Hashtable     _parsetable;
110         internal readonly NameValuePair _keyChain;
111         internal readonly bool HasPasswordKeyword;
112         internal readonly bool HasUserIdKeyword;
113
114         // differences between OleDb and Odbc
115         // ODBC:
116         //     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcsqldriverconnect.asp
117         //     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbcsql/od_odbc_d_4x4k.asp
118         //     do not support == -> = in keywords
119         //     first key-value pair wins
120         //     quote values using \{ and \}, only driver= and pwd= appear to generically allow quoting
121         //     do not strip quotes from value, or add quotes except for driver keyword
122         // OLEDB:
123         //     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/oledb/htm/oledbconnectionstringsyntax.asp
124         //     support == -> = in keywords
125         //     last key-value pair wins
126         //     quote values using \" or \'
127         //     strip quotes from value
128         internal readonly bool UseOdbcRules;
129
130         private System.Security.PermissionSet _permissionset;
131
132         // called by derived classes that may cache based on connectionString
133         public DbConnectionOptions(string connectionString)
134             : this(connectionString, null, false) {
135         }
136
137         // synonyms hashtable is meant to be read-only translation of parsed string
138         // keywords/synonyms to a known keyword string
139         public DbConnectionOptions(string connectionString, Hashtable synonyms, bool useOdbcRules) {
140             UseOdbcRules = useOdbcRules;
141             _parsetable = new Hashtable();
142             _usersConnectionString = ((null != connectionString) ? connectionString : "");
143
144             // first pass on parsing, initial syntax check
145             if (0 < _usersConnectionString.Length) {
146                 _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, UseOdbcRules);
147                 HasPasswordKeyword = (_parsetable.ContainsKey(KEY.Password) || _parsetable.ContainsKey(SYNONYM.Pwd));
148                 HasUserIdKeyword = (_parsetable.ContainsKey(KEY.User_ID) || _parsetable.ContainsKey(SYNONYM.UID));
149             }
150         }
151
152         protected DbConnectionOptions(DbConnectionOptions connectionOptions) { // Clone used by SqlConnectionString
153             _usersConnectionString = connectionOptions._usersConnectionString;
154             HasPasswordKeyword = connectionOptions.HasPasswordKeyword;
155             HasUserIdKeyword = connectionOptions.HasUserIdKeyword;
156             UseOdbcRules = connectionOptions.UseOdbcRules;
157             _parsetable = connectionOptions._parsetable;
158             _keyChain = connectionOptions._keyChain;
159         }
160
161
162         public string UsersConnectionString(bool hidePassword) {
163             return UsersConnectionString(hidePassword, false);
164         }
165
166         private string UsersConnectionString(bool hidePassword, bool forceHidePassword) {
167             string connectionString = _usersConnectionString;
168             if (HasPasswordKeyword && (forceHidePassword || (hidePassword && !HasPersistablePassword))) {
169                 ReplacePasswordPwd(out connectionString, false);
170             }
171             return ((null != connectionString) ? connectionString : "");
172         }
173
174         internal string UsersConnectionStringForTrace() {
175             return UsersConnectionString(true, true);
176         }
177
178         internal bool HasBlankPassword {
179             get {
180                 if (!ConvertValueToIntegratedSecurity()) {
181                     if (_parsetable.ContainsKey(KEY.Password)) {
182                         return ADP.IsEmpty((string)_parsetable[KEY.Password]);
183                     } else
184                     if (_parsetable.ContainsKey(SYNONYM.Pwd)) {
185                         return ADP.IsEmpty((string)_parsetable[SYNONYM.Pwd]); // MDAC 83097
186                     } else {
187                         return ((_parsetable.ContainsKey(KEY.User_ID) && !ADP.IsEmpty((string)_parsetable[KEY.User_ID])) || (_parsetable.ContainsKey(SYNONYM.UID) && !ADP.IsEmpty((string)_parsetable[SYNONYM.UID])));
188                     }
189                 }
190                 return false;
191             }
192         }
193
194         internal bool HasPersistablePassword {
195             get {
196                 if (HasPasswordKeyword) {
197                     return ConvertValueToBoolean(KEY.Persist_Security_Info, false);
198                 }
199                 return true; // no password means persistable password so we don't have to munge
200             }
201         }
202
203         public bool IsEmpty {
204             get { return (null == _keyChain); }
205         }
206
207         internal Hashtable Parsetable {
208             get { return _parsetable; }
209         }
210
211         public ICollection Keys {
212             get { return _parsetable.Keys; }
213         }
214
215         public string this[string keyword] {
216             get { return (string)_parsetable[keyword]; }
217         }
218
219         internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string keyValue, bool useOdbcRules) {
220             ADP.CheckArgumentNull(builder, "builder");
221             ADP.CheckArgumentLength(keyName, "keyName");
222
223             if ((null == keyName) || !ConnectionStringValidKeyRegex.IsMatch(keyName)) {
224                 throw ADP.InvalidKeyname(keyName);
225             }
226             if ((null != keyValue) && !IsValueValidInternal(keyValue)) {
227                 throw ADP.InvalidValue(keyName);
228             }
229
230             if ((0 < builder.Length) && (';' != builder[builder.Length-1])) {
231                 builder.Append(";");
232             }
233
234             if (useOdbcRules) {
235                 builder.Append(keyName);
236             }
237             else {
238                 builder.Append(keyName.Replace("=", "=="));
239             }
240             builder.Append("=");
241
242             if (null != keyValue) { // else <keyword>=;
243                 if (useOdbcRules) {
244                     if ((0 < keyValue.Length) &&
245                         (('{' == keyValue[0]) || (0 <= keyValue.IndexOf(';')) || (0 == String.Compare(DbConnectionStringKeywords.Driver, keyName, StringComparison.OrdinalIgnoreCase))) &&
246                         !ConnectionStringQuoteOdbcValueRegex.IsMatch(keyValue))
247                     {
248                         // always quote Driver value (required for ODBC Version 2.65 and earlier)
249                         // always quote values that contain a ';'
250                         builder.Append('{').Append(keyValue.Replace("}", "}}")).Append('}');
251                     }
252                     else {
253                         builder.Append(keyValue);
254                     }
255                 }
256                 else if (ConnectionStringQuoteValueRegex.IsMatch(keyValue)) {
257                     // <value> -> <value>
258                     builder.Append(keyValue);
259                 }
260                 else if ((-1 != keyValue.IndexOf('\"')) && (-1 == keyValue.IndexOf('\''))) {
261                     // <val"ue> -> <'val"ue'>
262                     builder.Append('\'');
263                     builder.Append(keyValue);
264                     builder.Append('\'');
265                 }
266                 else {
267                     // <val'ue> -> <"val'ue">
268                     // <=value> -> <"=value">
269                     // <;value> -> <";value">
270                     // < value> -> <" value">
271                     // <va lue> -> <"va lue">
272                     // <va'"lue> -> <"va'""lue">
273                     builder.Append('\"');
274                     builder.Append(keyValue.Replace("\"", "\"\""));
275                     builder.Append('\"');
276                 }
277             }
278         }
279
280         public bool ConvertValueToBoolean(string keyName, bool defaultValue) {
281             object value = _parsetable[keyName];
282             if (null == value) {
283                 return defaultValue;
284             }
285             return ConvertValueToBooleanInternal(keyName, (string) value);
286         }
287
288         internal static bool ConvertValueToBooleanInternal(string keyName, string stringValue) {
289             if (CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
290                 return true;
291             else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
292                 return false;
293             else {
294                 string tmp = stringValue.Trim();  // Remove leading & trailing white space.
295                 if (CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
296                     return true;
297                 else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
298                     return false;
299                 else {
300                     throw ADP.InvalidConnectionOptionValue(keyName);
301                 }
302             }
303         }
304
305         // same as Boolean, but with SSPI thrown in as valid yes
306         public bool ConvertValueToIntegratedSecurity() {
307             object value = _parsetable[KEY.Integrated_Security];
308             if (null == value) {
309                 return false;
310             }
311             return ConvertValueToIntegratedSecurityInternal((string) value);
312         }
313
314         internal bool ConvertValueToIntegratedSecurityInternal(string stringValue) {
315             if (CompareInsensitiveInvariant(stringValue, "sspi") || CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
316                 return true;
317             else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
318                 return false;
319             else {
320                 string tmp = stringValue.Trim();  // Remove leading & trailing white space.
321                 if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
322                     return true;
323                 else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
324                     return false;
325                 else {
326                     throw ADP.InvalidConnectionOptionValue(KEY.Integrated_Security);
327                 }
328             }
329         }
330
331         public int ConvertValueToInt32(string keyName, int defaultValue) {
332             object value = _parsetable[keyName];
333             if (null == value) {
334                 return defaultValue;
335             }
336             return ConvertToInt32Internal(keyName, (string) value);
337         }
338
339         internal static int ConvertToInt32Internal(string keyname, string stringValue) {
340             try {
341                 return System.Int32.Parse(stringValue, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture);
342             }
343             catch (FormatException e) {
344                 throw ADP.InvalidConnectionOptionValue(keyname, e);
345             }
346             catch (OverflowException e) {
347                 throw ADP.InvalidConnectionOptionValue(keyname, e);
348             }
349         }
350
351         public string ConvertValueToString(string keyName, string defaultValue) {
352             string value = (string)_parsetable[keyName];
353             return ((null != value) ? value : defaultValue);
354         }
355
356         static private bool CompareInsensitiveInvariant(string strvalue, string strconst) {
357             return (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst));
358         }
359
360         public bool ContainsKey(string keyword) {
361             return _parsetable.ContainsKey(keyword);
362         }
363
364         protected internal virtual System.Security.PermissionSet CreatePermissionSet() {
365             return null;
366         }
367
368         internal void DemandPermission() {
369             if (null == _permissionset) {
370                 _permissionset = CreatePermissionSet();
371             }
372             _permissionset.Demand();
373         }
374
375         protected internal virtual string Expand() {
376             return _usersConnectionString;
377         }
378
379         // SxS notes:
380         // * this method queries "DataDirectory" value from the current AppDomain.
381         //   This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource".
382         // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out.
383         [ResourceExposure(ResourceScope.None)]
384         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
385         internal static string ExpandDataDirectory(string keyword, string value, ref string datadir) {
386             string fullPath = null;
387             if ((null != value) && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase)) {
388
389                 string rootFolderPath = datadir;
390                 if (null == rootFolderPath) {
391                     // find the replacement path
392                     object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
393                     rootFolderPath = (rootFolderObject as string);
394                     if ((null != rootFolderObject) && (null == rootFolderPath)) {
395                         throw ADP.InvalidDataDirectory();
396                     }
397                     else if (ADP.IsEmpty(rootFolderPath)) {
398                         rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
399                     }
400                     if (null == rootFolderPath) {
401                         rootFolderPath = "";
402                     }
403                     // cache the |DataDir| for ExpandDataDirectories
404                     datadir = rootFolderPath;
405                 }
406
407                 // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw
408                 int fileNamePosition = DataDirectory.Length;    // filename starts right after the '|datadirectory|' keyword
409                 bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length-1] == '\\';
410                 bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\';
411
412                 // replace |datadirectory| with root folder path
413                 if (!rootFolderEndsWith && !fileNameStartsWith) {
414                     // need to insert '\'
415                     fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
416                 }
417                 else if (rootFolderEndsWith && fileNameStartsWith) {
418                     // need to strip one out
419                     fullPath = rootFolderPath + value.Substring(fileNamePosition+1);
420                 }
421                 else {
422                     // simply concatenate the strings
423                     fullPath = rootFolderPath + value.Substring(fileNamePosition);
424                 }
425
426                 // verify root folder path is a real path without unexpected "..\"
427                 if (!ADP.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal)) {
428                     throw ADP.InvalidConnectionOptionValue(keyword);
429                 }
430             }
431             return fullPath;
432         }
433
434         internal string ExpandDataDirectories(ref string filename, ref int position) {
435             string value = null;
436             StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
437             string datadir = null;
438
439             int copyPosition = 0;
440             bool expanded = false;
441
442             for(NameValuePair current = _keyChain; null != current; current = current.Next) {
443                 value = current.Value;
444
445                 // remove duplicate keyswords from connectionstring
446                 //if ((object)this[current.Name] != (object)value) {
447                 //    expanded = true;
448                 //    copyPosition += current.Length;
449                 //    continue;
450                 //}
451
452                 // There is a set of keywords we explictly do NOT want to expand |DataDirectory| on
453                 if (UseOdbcRules) {
454                     switch(current.Name) {
455                     case DbConnectionOptionKeywords.Driver:
456                     case DbConnectionOptionKeywords.Pwd:
457                     case DbConnectionOptionKeywords.UID:
458                         break;
459                     default:
460                         value = ExpandDataDirectory(current.Name, value, ref datadir);
461                         break;
462                     }
463                 }
464                 else {
465                     switch(current.Name) {
466                     case DbConnectionOptionKeywords.Provider:
467                     case DbConnectionOptionKeywords.DataProvider:
468                     case DbConnectionOptionKeywords.RemoteProvider:
469                     case DbConnectionOptionKeywords.ExtendedProperties:
470                     case DbConnectionOptionKeywords.UserID:
471                     case DbConnectionOptionKeywords.Password:
472                     case DbConnectionOptionKeywords.UID:
473                     case DbConnectionOptionKeywords.Pwd:
474                         break;
475                     default:
476                         value = ExpandDataDirectory(current.Name, value, ref datadir);
477                         break;
478                     }
479                 }
480                 if (null == value) {
481                     value = current.Value;
482                 }
483                 if (UseOdbcRules || (DbConnectionOptionKeywords.FileName != current.Name)) {
484                     if (value != current.Value) {
485                         expanded = true;
486                         AppendKeyValuePairBuilder(builder, current.Name, value, UseOdbcRules);
487                         builder.Append(';');
488                     }
489                     else {
490                         builder.Append(_usersConnectionString, copyPosition, current.Length);
491                     }
492                 }
493                 else {
494                     // strip out 'File Name=myconnection.udl' for OleDb
495                     // remembering is value for which UDL file to open
496                     // and where to insert the strnig
497                     expanded = true;
498                     filename = value;
499                     position = builder.Length;
500                 }
501                 copyPosition += current.Length;
502             }
503
504             if (expanded) {
505                 value = builder.ToString();
506             }
507             else {
508                 value = null;
509             }
510             return value;
511         }
512
513         internal string ExpandKeyword(string keyword, string replacementValue) {
514             // preserve duplicates, updated keyword value with replacement value
515             // if keyword not specified, append to end of the string
516             bool expanded = false;
517             int copyPosition = 0;
518
519             StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
520             for(NameValuePair current = _keyChain; null != current; current = current.Next) {
521                 if ((current.Name == keyword) && (current.Value == this[keyword])) {
522                     // only replace the parse end-result value instead of all values
523                     // so that when duplicate-keywords occur other original values remain in place
524                     AppendKeyValuePairBuilder(builder, current.Name, replacementValue, UseOdbcRules);
525                     builder.Append(';');
526                     expanded = true;
527                 }
528                 else {
529                     builder.Append(_usersConnectionString, copyPosition, current.Length);
530                 }
531                 copyPosition += current.Length;
532             }
533
534             if (!expanded) {
535                 // 
536                 Debug.Assert(!UseOdbcRules, "ExpandKeyword not ready for Odbc");
537                 AppendKeyValuePairBuilder(builder, keyword, replacementValue, UseOdbcRules);
538             }
539             return builder.ToString();
540         }
541
542 #if DEBUG
543         [System.Diagnostics.Conditional("DEBUG")]
544         private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Hashtable synonyms) {
545             if (Bid.AdvancedOn) {
546                 Debug.Assert(keyname == keyname.ToLower(CultureInfo.InvariantCulture), "missing ToLower");
547
548                 string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
549                 if ((KEY.Password != realkeyname) && (SYNONYM.Pwd != realkeyname)) { // don't trace passwords ever!
550                     if (null != keyvalue) {
551                         Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls', KeyValue='%ls'\n", keyname, keyvalue);
552                     }
553                     else {
554                         Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls'\n", keyname);
555                     }
556                 }
557             }
558         }
559 #endif
560
561         static private string GetKeyName(StringBuilder buffer) {
562             int count = buffer.Length;
563             while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
564                 count--; // trailing whitespace
565             }
566             return buffer.ToString(0, count).ToLower(CultureInfo.InvariantCulture);
567         }
568
569         static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace) {
570             int count = buffer.Length;
571             int index = 0;
572             if (trimWhitespace) {
573                 while ((index < count) && Char.IsWhiteSpace(buffer[index])) {
574                     index++; // leading whitespace
575                 }
576                 while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
577                     count--; // trailing whitespace
578                 }
579             }
580             return buffer.ToString(index, count - index);
581         }
582
583         // transistion states used for parsing
584         private enum ParserState {
585             NothingYet=1,   //start point
586             Key,
587             KeyEqual,
588             KeyEnd,
589             UnquotedValue,
590             DoubleQuoteValue,
591             DoubleQuoteValueQuote,
592             SingleQuoteValue,
593             SingleQuoteValueQuote,
594             BraceQuoteValue,
595             BraceQuoteValueQuote,
596             QuotedValueEnd,
597             NullTermination,
598         };
599
600         static internal int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, bool useOdbcRules, out string keyname, out string keyvalue) {
601             int startposition = currentPosition;
602
603             buffer.Length = 0;
604             keyname = null;
605             keyvalue = null;
606
607             char currentChar = '\0';
608
609             ParserState parserState = ParserState.NothingYet;
610             int length = connectionString.Length;
611             for (; currentPosition < length; ++currentPosition) {
612                 currentChar = connectionString[currentPosition];
613
614                 switch(parserState) {
615                 case ParserState.NothingYet: // [\\s;]*
616                     if ((';' == currentChar) || Char.IsWhiteSpace(currentChar)) {
617                         continue;
618                     }
619                     if ('\0' == currentChar)            { parserState = ParserState.NullTermination; continue; } // MDAC 83540
620                     if (Char.IsControl(currentChar))    { throw ADP.ConnectionStringSyntax(startposition); }
621                     startposition = currentPosition;
622                     if ('=' != currentChar) { // MDAC 86902
623                         parserState = ParserState.Key;
624                         break;
625                     }
626                     else {
627                         parserState = ParserState.KeyEqual;
628                         continue;
629                     }
630
631                 case ParserState.Key: // (?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)
632                     if ('=' == currentChar)             { parserState = ParserState.KeyEqual;       continue; }
633                     if (Char.IsWhiteSpace(currentChar)) { break; }
634                     if (Char.IsControl(currentChar))    { throw ADP.ConnectionStringSyntax(startposition); }
635                     break;
636
637                 case ParserState.KeyEqual: // \\s*=(?!=)\\s*
638                     if (!useOdbcRules && '=' == currentChar) { parserState = ParserState.Key;            break; }
639                     keyname = GetKeyName(buffer);
640                     if (ADP.IsEmpty(keyname))           { throw ADP.ConnectionStringSyntax(startposition); }
641                     buffer.Length = 0;
642                     parserState = ParserState.KeyEnd;
643                     goto case ParserState.KeyEnd;
644
645                 case ParserState.KeyEnd:
646                     if (Char.IsWhiteSpace(currentChar)) { continue; }
647                     if (useOdbcRules) {
648                         if ('{' == currentChar)             { parserState = ParserState.BraceQuoteValue; break; }
649                     }
650                     else {
651                         if ('\'' == currentChar)            { parserState = ParserState.SingleQuoteValue; continue; }
652                         if ('"' == currentChar)             { parserState = ParserState.DoubleQuoteValue; continue; }
653                     }
654                     if (';' == currentChar)             { goto ParserExit; }
655                     if ('\0' == currentChar)            { goto ParserExit; }
656                     if (Char.IsControl(currentChar))    { throw ADP.ConnectionStringSyntax(startposition); }
657                     parserState = ParserState.UnquotedValue;
658                     break;
659
660                 case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(?<![\"']))"
661                     if (Char.IsWhiteSpace(currentChar)) { break; }
662                     if (Char.IsControl(currentChar) || ';' == currentChar) { goto ParserExit; }
663                     break;
664
665                 case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
666                     if ('"' == currentChar)             { parserState = ParserState.DoubleQuoteValueQuote;   continue; }
667                     if ('\0' == currentChar)            { throw ADP.ConnectionStringSyntax(startposition); }
668                     break;
669
670                 case ParserState.DoubleQuoteValueQuote:
671                     if ('"' == currentChar)             { parserState = ParserState.DoubleQuoteValue;      break; }
672                     keyvalue = GetKeyValue(buffer, false);
673                     parserState = ParserState.QuotedValueEnd;
674                     goto case ParserState.QuotedValueEnd;
675
676                 case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
677                     if ('\'' == currentChar)             { parserState = ParserState.SingleQuoteValueQuote;   continue; }
678                     if ('\0' == currentChar)             { throw ADP.ConnectionStringSyntax(startposition); }
679                     break;
680
681                 case ParserState.SingleQuoteValueQuote:
682                     if ('\'' == currentChar)             { parserState = ParserState.SingleQuoteValue;      break; }
683                     keyvalue = GetKeyValue(buffer, false);
684                     parserState = ParserState.QuotedValueEnd;
685                     goto case ParserState.QuotedValueEnd;
686
687                 case ParserState.BraceQuoteValue: // "(\\{([^\\}\u0000]|\\}\\})*\\})"
688                     if ('}' == currentChar)             { parserState = ParserState.BraceQuoteValueQuote;   break; }
689                     if ('\0' == currentChar)            { throw ADP.ConnectionStringSyntax(startposition); }
690                     break;
691
692                 case ParserState.BraceQuoteValueQuote:
693                     if ('}' == currentChar)             { parserState = ParserState.BraceQuoteValue;      break; }
694                     keyvalue = GetKeyValue(buffer, false);
695                     parserState = ParserState.QuotedValueEnd;
696                     goto case ParserState.QuotedValueEnd;
697
698                 case ParserState.QuotedValueEnd:
699                     if (Char.IsWhiteSpace(currentChar)) { continue; }
700                     if (';' == currentChar)             { goto ParserExit; }
701                     if ('\0' == currentChar)            { parserState = ParserState.NullTermination; continue; } // MDAC 83540
702                     throw ADP.ConnectionStringSyntax(startposition);  // unbalanced single quote
703
704                 case ParserState.NullTermination: // [\\s;\u0000]*
705                     if ('\0' == currentChar) { continue; }
706                     if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
707                     throw ADP.ConnectionStringSyntax(currentPosition);
708
709                 default:
710                     throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState1);
711                 }
712                 buffer.Append(currentChar);
713             }
714         ParserExit:
715             switch (parserState) {
716             case ParserState.Key:
717             case ParserState.DoubleQuoteValue:
718             case ParserState.SingleQuoteValue:
719             case ParserState.BraceQuoteValue:
720                 // keyword not found/unbalanced double/single quote
721                 throw ADP.ConnectionStringSyntax(startposition);
722
723             case ParserState.KeyEqual:
724                 // equal sign at end of line
725                 keyname = GetKeyName(buffer);
726                 if (ADP.IsEmpty(keyname))           { throw ADP.ConnectionStringSyntax(startposition); }
727                 break;
728
729             case ParserState.UnquotedValue:
730                 // unquoted value at end of line
731                 keyvalue = GetKeyValue(buffer, true);
732
733                 char tmpChar = keyvalue[keyvalue.Length - 1];
734                 if (!useOdbcRules && (('\'' == tmpChar) || ('"' == tmpChar))) {
735                     throw ADP.ConnectionStringSyntax(startposition);    // unquoted value must not end in quote, except for odbc
736                 }
737                 break;
738
739             case ParserState.DoubleQuoteValueQuote:
740             case ParserState.SingleQuoteValueQuote:
741             case ParserState.BraceQuoteValueQuote:
742             case ParserState.QuotedValueEnd:
743                 // quoted value at end of line
744                 keyvalue = GetKeyValue(buffer, false);
745                 break;
746
747             case ParserState.NothingYet:
748             case ParserState.KeyEnd:
749             case ParserState.NullTermination:
750                 // do nothing
751                 break;
752
753             default:
754                 throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState2);
755             }
756             if ((';' == currentChar) && (currentPosition < connectionString.Length)) {
757                 currentPosition++;
758             }
759             return currentPosition;
760         }
761
762         static private bool IsValueValidInternal(string keyvalue) {
763             if (null != keyvalue)
764             {
765 #if DEBUG
766                 bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
767                 Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
768 #endif
769                 return (-1 == keyvalue.IndexOf('\u0000'));
770             }
771             return true;
772         }
773
774         static private bool IsKeyNameValid(string keyname) {
775             if (null != keyname) {
776 #if DEBUG
777                 bool compValue = ConnectionStringValidKeyRegex.IsMatch(keyname);
778                 Debug.Assert(((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000'))) == compValue, "IsValueValid mismatch with regex");
779 #endif
780                 return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
781             }
782             return false;
783         }
784
785 #if DEBUG
786         private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms, bool firstKey) {
787             Hashtable parsetable = new Hashtable();
788             Regex parser = (firstKey ? ConnectionStringRegexOdbc : ConnectionStringRegex);
789
790             const int KeyIndex = 1, ValueIndex = 2;
791             Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index");
792             Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index");
793
794             if (null != connectionString) {
795                 Match match = parser.Match(connectionString);
796                 if (!match.Success || (match.Length != connectionString.Length)) {
797                     throw ADP.ConnectionStringSyntax(match.Length);
798                 }
799                 int indexValue = 0;
800                 CaptureCollection keyvalues = match.Groups[ValueIndex].Captures;
801                 foreach(Capture keypair in match.Groups[KeyIndex].Captures) {
802                     string keyname = (firstKey ? keypair.Value : keypair.Value.Replace("==", "=")).ToLower(CultureInfo.InvariantCulture);
803                     string keyvalue = keyvalues[indexValue++].Value;
804                     if (0 < keyvalue.Length) {
805                         if (!firstKey) {
806                             switch(keyvalue[0]) {
807                             case '\"':
808                                 keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\"\"", "\"");
809                                 break;
810                             case '\'':
811                                 keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\'\'", "\'");
812                                 break;
813                             default:
814                                 break;
815                             }
816                         }
817                     }
818                     else {
819                         keyvalue = null;
820                     }
821                     DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
822
823                     string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
824                     if (!IsKeyNameValid(realkeyname)) {
825                         throw ADP.KeywordNotSupported(keyname);
826                     }
827                     if (!firstKey || !parsetable.ContainsKey(realkeyname)) {
828                         parsetable[realkeyname] =  keyvalue; // last key-value pair wins (or first)
829                     }
830                 }
831             }
832             return parsetable;
833         }
834
835         private static void ParseComparison(Hashtable parsetable, string connectionString, Hashtable synonyms, bool firstKey, Exception e) {
836             try {
837                 Hashtable parsedvalues = SplitConnectionString(connectionString, synonyms, firstKey);
838                 foreach(DictionaryEntry entry in parsedvalues) {
839                     string keyname = (string) entry.Key;
840                     string value1 = (string) entry.Value;
841                     string value2 = (string) parsetable[keyname];
842                     Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">");
843                     Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 +">");
844                 }
845
846             }
847             catch(ArgumentException f) {
848                 if (null != e) {
849                     string msg1 = e.Message;
850                     string msg2 = f.Message;
851
852                     const string KeywordNotSupportedMessagePrefix = "Keyword not supported:";
853                     const string WrongFormatMessagePrefix = "Format of the initialization string";
854                     bool isEquivalent = (msg1 == msg2);
855                     if (!isEquivalent)
856                     {
857                         // VSTFDEVDIV 479587: we also accept cases were Regex parser (debug only) reports "wrong format" and 
858                         // retail parsing code reports format exception in different location or "keyword not supported"
859                         if (msg2.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) {
860                             if (msg1.StartsWith(KeywordNotSupportedMessagePrefix, StringComparison.Ordinal) || msg1.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) {
861                                 isEquivalent = true;
862                             }
863                         }
864                     }
865                     Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <"+msg1+"> <"+msg2+">");
866                 }
867                 else {
868                     Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
869                 }
870                 e = null;
871             }
872             if (null != e) {
873                 Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
874             }
875         }
876 #endif
877         private static NameValuePair ParseInternal(Hashtable parsetable, string connectionString, bool buildChain, Hashtable synonyms, bool firstKey) {
878             Debug.Assert(null != connectionString, "null connectionstring");
879             StringBuilder buffer = new StringBuilder();
880             NameValuePair localKeychain = null, keychain = null;
881 #if DEBUG
882             try {
883 #endif
884                 int nextStartPosition = 0;
885                 int endPosition = connectionString.Length;
886                 while (nextStartPosition < endPosition) {
887                     int startPosition = nextStartPosition;
888
889                     string keyname, keyvalue;
890                     nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, firstKey, out keyname, out keyvalue);
891                     if (ADP.IsEmpty(keyname)) {
892                         // if (nextStartPosition != endPosition) { throw; }
893                         break;
894                     }
895 #if DEBUG
896                     DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
897
898                     Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
899                     Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
900 #endif
901                     string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
902                     if (!IsKeyNameValid(realkeyname)) {
903                         throw ADP.KeywordNotSupported(keyname);
904                     }
905                     if (!firstKey || !parsetable.Contains(realkeyname)) {
906                         parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
907                     }
908
909                     if(null != localKeychain) {
910                         localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
911                     }
912                     else if (buildChain) { // first time only - don't contain modified chain from UDL file
913                         keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
914                     }
915                 }
916 #if DEBUG
917             }
918             catch(ArgumentException e) {
919                 ParseComparison(parsetable, connectionString, synonyms, firstKey, e);
920                 throw;
921             }
922             ParseComparison(parsetable, connectionString, synonyms, firstKey, null);
923 #endif
924             return keychain;
925         }
926
927         internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) {
928             bool expanded = false;
929             int copyPosition = 0;
930             NameValuePair head = null, tail = null, next = null;
931             StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
932             for(NameValuePair current = _keyChain; null != current; current = current.Next) {
933                 if ((KEY.Password != current.Name) && (SYNONYM.Pwd != current.Name)) {
934                     builder.Append(_usersConnectionString, copyPosition, current.Length);
935                     if (fakePassword) {
936                         next = new NameValuePair(current.Name, current.Value, current.Length);
937                     }
938                 }
939                 else if (fakePassword) { // replace user password/pwd value with *
940                     const string equalstar = "=*;";
941                     builder.Append(current.Name).Append(equalstar);
942                     next = new NameValuePair(current.Name, "*", current.Name.Length + equalstar.Length);
943                     expanded = true;
944                 }
945                 else { // drop the password/pwd completely in returning for user
946                     expanded = true;
947                 }
948
949                 if (fakePassword) {
950                     if (null != tail) {
951                         tail = tail.Next = next;
952                     }
953                     else {
954                         tail = head = next;
955                     }
956                 }
957                 copyPosition += current.Length;
958             }
959             Debug.Assert(expanded, "password/pwd was not removed");
960             constr = builder.ToString();
961             return head;
962         }
963
964         internal static void ValidateKeyValuePair(string keyword, string value) {
965             if ((null == keyword) || !ConnectionStringValidKeyRegex.IsMatch(keyword)) {
966                 throw ADP.InvalidKeyname(keyword);
967             }
968             if ((null != value) && !ConnectionStringValidValueRegex.IsMatch(value)) {
969                 throw ADP.InvalidValue(keyword);
970             }
971         }
972     }
973 }