1 //------------------------------------------------------------------------------
2 // <copyright file="DBConnectionOptions.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.Common {
12 using System.Collections;
14 using System.Diagnostics;
15 using System.Globalization;
16 using System.Runtime.Serialization;
17 using System.Security.Permissions;
19 using System.Text.RegularExpressions;
20 using System.Runtime.Versioning;
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
28 /*private const string ConnectionStringPatternV1 =
30 +"(?<key>([^=\\s]|\\s+[^=\\s]|\\s+==|==)+)"
33 + "(" + "\"" + "([^\"]|\"\")*" + "\"" + ")"
35 + "(" + "'" + "([^']|'')*" + "'" + ")"
37 + "(" + "(?![\"'])" + "([^\\s;]|\\s+[^\\s;])*" + "(?<![\"'])" + ")"
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
47 + "(\"([^\"\u0000]|\"\")*\")" // double quoted string, " must be quoted as ""
49 + "('([^'\u0000]|'')*')" // single quoted string, ' must be quoted as ''
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
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
65 + "(\\{([^\\}\u0000]|\\}\\})*\\})" // quoted string, starts with { and ends with }
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
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 }
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
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);
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|";
88 private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
89 private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
91 private static readonly Regex ConnectionStringQuoteValueRegex = new Regex(ConnectionStringQuoteValuePattern, RegexOptions.Compiled);
92 private static readonly Regex ConnectionStringQuoteOdbcValueRegex = new Regex(ConnectionStringQuoteOdbcValuePattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
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";
102 // known connection string common synonyms
103 private static class SYNONYM {
104 internal const string Pwd = "pwd";
105 internal const string UID = "uid";
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;
114 // differences between OleDb and 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
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;
130 private System.Security.PermissionSet _permissionset;
132 // called by derived classes that may cache based on connectionString
133 public DbConnectionOptions(string connectionString)
134 : this(connectionString, null, false) {
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 : "");
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));
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;
162 public string UsersConnectionString(bool hidePassword) {
163 return UsersConnectionString(hidePassword, false);
166 private string UsersConnectionString(bool hidePassword, bool forceHidePassword) {
167 string connectionString = _usersConnectionString;
168 if (HasPasswordKeyword && (forceHidePassword || (hidePassword && !HasPersistablePassword))) {
169 ReplacePasswordPwd(out connectionString, false);
171 return ((null != connectionString) ? connectionString : "");
174 internal string UsersConnectionStringForTrace() {
175 return UsersConnectionString(true, true);
178 internal bool HasBlankPassword {
180 if (!ConvertValueToIntegratedSecurity()) {
181 if (_parsetable.ContainsKey(KEY.Password)) {
182 return ADP.IsEmpty((string)_parsetable[KEY.Password]);
184 if (_parsetable.ContainsKey(SYNONYM.Pwd)) {
185 return ADP.IsEmpty((string)_parsetable[SYNONYM.Pwd]); // MDAC 83097
187 return ((_parsetable.ContainsKey(KEY.User_ID) && !ADP.IsEmpty((string)_parsetable[KEY.User_ID])) || (_parsetable.ContainsKey(SYNONYM.UID) && !ADP.IsEmpty((string)_parsetable[SYNONYM.UID])));
194 internal bool HasPersistablePassword {
196 if (HasPasswordKeyword) {
197 return ConvertValueToBoolean(KEY.Persist_Security_Info, false);
199 return true; // no password means persistable password so we don't have to munge
203 public bool IsEmpty {
204 get { return (null == _keyChain); }
207 internal Hashtable Parsetable {
208 get { return _parsetable; }
211 public ICollection Keys {
212 get { return _parsetable.Keys; }
215 public string this[string keyword] {
216 get { return (string)_parsetable[keyword]; }
219 internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string keyValue, bool useOdbcRules) {
220 ADP.CheckArgumentNull(builder, "builder");
221 ADP.CheckArgumentLength(keyName, "keyName");
223 if ((null == keyName) || !ConnectionStringValidKeyRegex.IsMatch(keyName)) {
224 throw ADP.InvalidKeyname(keyName);
226 if ((null != keyValue) && !IsValueValidInternal(keyValue)) {
227 throw ADP.InvalidValue(keyName);
230 if ((0 < builder.Length) && (';' != builder[builder.Length-1])) {
235 builder.Append(keyName);
238 builder.Append(keyName.Replace("=", "=="));
242 if (null != keyValue) { // else <keyword>=;
244 if ((0 < keyValue.Length) &&
245 (('{' == keyValue[0]) || (0 <= keyValue.IndexOf(';')) || (0 == String.Compare(DbConnectionStringKeywords.Driver, keyName, StringComparison.OrdinalIgnoreCase))) &&
246 !ConnectionStringQuoteOdbcValueRegex.IsMatch(keyValue))
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('}');
253 builder.Append(keyValue);
256 else if (ConnectionStringQuoteValueRegex.IsMatch(keyValue)) {
257 // <value> -> <value>
258 builder.Append(keyValue);
260 else if ((-1 != keyValue.IndexOf('\"')) && (-1 == keyValue.IndexOf('\''))) {
261 // <val"ue> -> <'val"ue'>
262 builder.Append('\'');
263 builder.Append(keyValue);
264 builder.Append('\'');
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('\"');
280 public bool ConvertValueToBoolean(string keyName, bool defaultValue) {
281 object value = _parsetable[keyName];
285 return ConvertValueToBooleanInternal(keyName, (string) value);
288 internal static bool ConvertValueToBooleanInternal(string keyName, string stringValue) {
289 if (CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
291 else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
294 string tmp = stringValue.Trim(); // Remove leading & trailing white space.
295 if (CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
297 else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
300 throw ADP.InvalidConnectionOptionValue(keyName);
305 // same as Boolean, but with SSPI thrown in as valid yes
306 public bool ConvertValueToIntegratedSecurity() {
307 object value = _parsetable[KEY.Integrated_Security];
311 return ConvertValueToIntegratedSecurityInternal((string) value);
314 internal bool ConvertValueToIntegratedSecurityInternal(string stringValue) {
315 if (CompareInsensitiveInvariant(stringValue, "sspi") || CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
317 else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
320 string tmp = stringValue.Trim(); // Remove leading & trailing white space.
321 if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
323 else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
326 throw ADP.InvalidConnectionOptionValue(KEY.Integrated_Security);
331 public int ConvertValueToInt32(string keyName, int defaultValue) {
332 object value = _parsetable[keyName];
336 return ConvertToInt32Internal(keyName, (string) value);
339 internal static int ConvertToInt32Internal(string keyname, string stringValue) {
341 return System.Int32.Parse(stringValue, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture);
343 catch (FormatException e) {
344 throw ADP.InvalidConnectionOptionValue(keyname, e);
346 catch (OverflowException e) {
347 throw ADP.InvalidConnectionOptionValue(keyname, e);
351 public string ConvertValueToString(string keyName, string defaultValue) {
352 string value = (string)_parsetable[keyName];
353 return ((null != value) ? value : defaultValue);
356 static private bool CompareInsensitiveInvariant(string strvalue, string strconst) {
357 return (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst));
360 public bool ContainsKey(string keyword) {
361 return _parsetable.ContainsKey(keyword);
364 protected internal virtual System.Security.PermissionSet CreatePermissionSet() {
368 internal void DemandPermission() {
369 if (null == _permissionset) {
370 _permissionset = CreatePermissionSet();
372 _permissionset.Demand();
375 protected internal virtual string Expand() {
376 return _usersConnectionString;
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)) {
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();
397 else if (ADP.IsEmpty(rootFolderPath)) {
398 rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
400 if (null == rootFolderPath) {
403 // cache the |DataDir| for ExpandDataDirectories
404 datadir = rootFolderPath;
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] == '\\';
412 // replace |datadirectory| with root folder path
413 if (!rootFolderEndsWith && !fileNameStartsWith) {
414 // need to insert '\'
415 fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
417 else if (rootFolderEndsWith && fileNameStartsWith) {
418 // need to strip one out
419 fullPath = rootFolderPath + value.Substring(fileNamePosition+1);
422 // simply concatenate the strings
423 fullPath = rootFolderPath + value.Substring(fileNamePosition);
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);
434 internal string ExpandDataDirectories(ref string filename, ref int position) {
436 StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
437 string datadir = null;
439 int copyPosition = 0;
440 bool expanded = false;
442 for(NameValuePair current = _keyChain; null != current; current = current.Next) {
443 value = current.Value;
445 // remove duplicate keyswords from connectionstring
446 //if ((object)this[current.Name] != (object)value) {
448 // copyPosition += current.Length;
452 // There is a set of keywords we explictly do NOT want to expand |DataDirectory| on
454 switch(current.Name) {
455 case DbConnectionOptionKeywords.Driver:
456 case DbConnectionOptionKeywords.Pwd:
457 case DbConnectionOptionKeywords.UID:
460 value = ExpandDataDirectory(current.Name, value, ref datadir);
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:
476 value = ExpandDataDirectory(current.Name, value, ref datadir);
481 value = current.Value;
483 if (UseOdbcRules || (DbConnectionOptionKeywords.FileName != current.Name)) {
484 if (value != current.Value) {
486 AppendKeyValuePairBuilder(builder, current.Name, value, UseOdbcRules);
490 builder.Append(_usersConnectionString, copyPosition, current.Length);
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
499 position = builder.Length;
501 copyPosition += current.Length;
505 value = builder.ToString();
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;
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);
529 builder.Append(_usersConnectionString, copyPosition, current.Length);
531 copyPosition += current.Length;
536 Debug.Assert(!UseOdbcRules, "ExpandKeyword not ready for Odbc");
537 AppendKeyValuePairBuilder(builder, keyword, replacementValue, UseOdbcRules);
539 return builder.ToString();
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");
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);
554 Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls'\n", keyname);
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
566 return buffer.ToString(0, count).ToLower(CultureInfo.InvariantCulture);
569 static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace) {
570 int count = buffer.Length;
572 if (trimWhitespace) {
573 while ((index < count) && Char.IsWhiteSpace(buffer[index])) {
574 index++; // leading whitespace
576 while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
577 count--; // trailing whitespace
580 return buffer.ToString(index, count - index);
583 // transistion states used for parsing
584 private enum ParserState {
585 NothingYet=1, //start point
591 DoubleQuoteValueQuote,
593 SingleQuoteValueQuote,
595 BraceQuoteValueQuote,
600 static internal int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, bool useOdbcRules, out string keyname, out string keyvalue) {
601 int startposition = currentPosition;
607 char currentChar = '\0';
609 ParserState parserState = ParserState.NothingYet;
610 int length = connectionString.Length;
611 for (; currentPosition < length; ++currentPosition) {
612 currentChar = connectionString[currentPosition];
614 switch(parserState) {
615 case ParserState.NothingYet: // [\\s;]*
616 if ((';' == currentChar) || Char.IsWhiteSpace(currentChar)) {
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;
627 parserState = ParserState.KeyEqual;
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); }
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); }
642 parserState = ParserState.KeyEnd;
643 goto case ParserState.KeyEnd;
645 case ParserState.KeyEnd:
646 if (Char.IsWhiteSpace(currentChar)) { continue; }
648 if ('{' == currentChar) { parserState = ParserState.BraceQuoteValue; break; }
651 if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; continue; }
652 if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; continue; }
654 if (';' == currentChar) { goto ParserExit; }
655 if ('\0' == currentChar) { goto ParserExit; }
656 if (Char.IsControl(currentChar)) { throw ADP.ConnectionStringSyntax(startposition); }
657 parserState = ParserState.UnquotedValue;
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; }
665 case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
666 if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValueQuote; continue; }
667 if ('\0' == currentChar) { throw ADP.ConnectionStringSyntax(startposition); }
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;
676 case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
677 if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValueQuote; continue; }
678 if ('\0' == currentChar) { throw ADP.ConnectionStringSyntax(startposition); }
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;
687 case ParserState.BraceQuoteValue: // "(\\{([^\\}\u0000]|\\}\\})*\\})"
688 if ('}' == currentChar) { parserState = ParserState.BraceQuoteValueQuote; break; }
689 if ('\0' == currentChar) { throw ADP.ConnectionStringSyntax(startposition); }
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;
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
704 case ParserState.NullTermination: // [\\s;\u0000]*
705 if ('\0' == currentChar) { continue; }
706 if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
707 throw ADP.ConnectionStringSyntax(currentPosition);
710 throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState1);
712 buffer.Append(currentChar);
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);
723 case ParserState.KeyEqual:
724 // equal sign at end of line
725 keyname = GetKeyName(buffer);
726 if (ADP.IsEmpty(keyname)) { throw ADP.ConnectionStringSyntax(startposition); }
729 case ParserState.UnquotedValue:
730 // unquoted value at end of line
731 keyvalue = GetKeyValue(buffer, true);
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
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);
747 case ParserState.NothingYet:
748 case ParserState.KeyEnd:
749 case ParserState.NullTermination:
754 throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState2);
756 if ((';' == currentChar) && (currentPosition < connectionString.Length)) {
759 return currentPosition;
762 static private bool IsValueValidInternal(string keyvalue) {
763 if (null != keyvalue)
766 bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
767 Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
769 return (-1 == keyvalue.IndexOf('\u0000'));
774 static private bool IsKeyNameValid(string keyname) {
775 if (null != keyname) {
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");
780 return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
786 private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms, bool firstKey) {
787 Hashtable parsetable = new Hashtable();
788 Regex parser = (firstKey ? ConnectionStringRegexOdbc : ConnectionStringRegex);
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");
794 if (null != connectionString) {
795 Match match = parser.Match(connectionString);
796 if (!match.Success || (match.Length != connectionString.Length)) {
797 throw ADP.ConnectionStringSyntax(match.Length);
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) {
806 switch(keyvalue[0]) {
808 keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\"\"", "\"");
811 keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\'\'", "\'");
821 DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
823 string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
824 if (!IsKeyNameValid(realkeyname)) {
825 throw ADP.KeywordNotSupported(keyname);
827 if (!firstKey || !parsetable.ContainsKey(realkeyname)) {
828 parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
835 private static void ParseComparison(Hashtable parsetable, string connectionString, Hashtable synonyms, bool firstKey, Exception e) {
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 +">");
847 catch(ArgumentException f) {
849 string msg1 = e.Message;
850 string msg2 = f.Message;
852 const string KeywordNotSupportedMessagePrefix = "Keyword not supported:";
853 const string WrongFormatMessagePrefix = "Format of the initialization string";
854 bool isEquivalent = (msg1 == msg2);
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)) {
865 Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <"+msg1+"> <"+msg2+">");
868 Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
873 Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
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;
884 int nextStartPosition = 0;
885 int endPosition = connectionString.Length;
886 while (nextStartPosition < endPosition) {
887 int startPosition = nextStartPosition;
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; }
896 DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
898 Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
899 Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
901 string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
902 if (!IsKeyNameValid(realkeyname)) {
903 throw ADP.KeywordNotSupported(keyname);
905 if (!firstKey || !parsetable.Contains(realkeyname)) {
906 parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
909 if(null != localKeychain) {
910 localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
912 else if (buildChain) { // first time only - don't contain modified chain from UDL file
913 keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
918 catch(ArgumentException e) {
919 ParseComparison(parsetable, connectionString, synonyms, firstKey, e);
922 ParseComparison(parsetable, connectionString, synonyms, firstKey, null);
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);
936 next = new NameValuePair(current.Name, current.Value, current.Length);
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);
945 else { // drop the password/pwd completely in returning for user
951 tail = tail.Next = next;
957 copyPosition += current.Length;
959 Debug.Assert(expanded, "password/pwd was not removed");
960 constr = builder.ToString();
964 internal static void ValidateKeyValuePair(string keyword, string value) {
965 if ((null == keyword) || !ConnectionStringValidKeyRegex.IsMatch(keyword)) {
966 throw ADP.InvalidKeyname(keyword);
968 if ((null != value) && !ConnectionStringValidValueRegex.IsMatch(value)) {
969 throw ADP.InvalidValue(keyword);