diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index b39c6849e8..ac8f2d2200 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -53,6 +53,12 @@ Microsoft\Data\Common\ConnectionString\AttestationProtocolUtilities.cs + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.Debug.cs + Microsoft\Data\Common\ConnectionString\DbConnectionStringDefaults.cs @@ -71,9 +77,6 @@ Microsoft\Data\Common\ConnectionString\PoolBlockingUtilities.cs - - Microsoft\Data\Common\DbConnectionOptions.Common.cs - Microsoft\Data\Common\MultipartIdentifier.cs @@ -728,7 +731,8 @@ System\Diagnostics\CodeAnalysis.cs - + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionOptions.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.netcore.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionOptions.cs rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.netcore.cs index 443b7e6734..903af89b3d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionOptions.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.netcore.cs @@ -5,9 +5,8 @@ using System; using System.IO; using System.Text; -using Microsoft.Data.Common.ConnectionString; -namespace Microsoft.Data.Common +namespace Microsoft.Data.Common.ConnectionString { internal partial class DbConnectionOptions { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index 52112bfe74..45df50badb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -18,6 +18,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Data.SqlClient.Diagnostics; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 3a6ef00793..a2ca3dd6c3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs index c8e782c0b3..133854c1f0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs @@ -9,10 +9,10 @@ using System.Threading; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; - namespace Microsoft.Data.SqlClient { public sealed partial class SqlConnection : DbConnection diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 6d5753baad..0d1da114cc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -9,13 +9,13 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net.Http.Headers; -using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Identity.Client; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index d45c126fdb..39ade1bf39 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -237,6 +237,12 @@ Microsoft\Data\Common\ConnectionString\AttestationProtocolUtilities.cs + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.Debug.cs + Microsoft\Data\Common\ConnectionString\DbConnectionStringDefaults.cs @@ -255,9 +261,6 @@ Microsoft\Data\Common\ConnectionString\PoolBlockingUtilities.cs - - Microsoft\Data\Common\DbConnectionOptions.Common.cs - Microsoft\Data\Common\MultipartIdentifier.cs @@ -938,7 +941,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionOptions.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.netfx.cs similarity index 92% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionOptions.cs rename to src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.netfx.cs index 80af65c2dc..646682c086 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionOptions.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.netfx.cs @@ -8,7 +8,7 @@ using System.Text; using Microsoft.Data.Common.ConnectionString; -namespace Microsoft.Data.Common +namespace Microsoft.Data.Common.ConnectionString { internal partial class DbConnectionOptions { @@ -33,20 +33,18 @@ internal bool HasBlankPassword { if (!ConvertValueToIntegratedSecurity()) { - if (_parsetable.ContainsKey(KEY.Password)) + if (_parsetable.TryGetValue(DbConnectionStringKeywords.Password, out string value)) { - return string.IsNullOrEmpty(_parsetable[KEY.Password]); + return string.IsNullOrEmpty(value); } - else - if (_parsetable.ContainsKey(SYNONYM.Pwd)) + + if (_parsetable.TryGetValue(DbConnectionStringSynonyms.Pwd, out value)) { - return string.IsNullOrEmpty(_parsetable[SYNONYM.Pwd]); // MDAC 83097 - } - else - { - return (_parsetable.ContainsKey(KEY.User_ID) && !string.IsNullOrEmpty(_parsetable[KEY.User_ID])) || - (_parsetable.ContainsKey(SYNONYM.UID) && !string.IsNullOrEmpty(_parsetable[SYNONYM.UID])); + return string.IsNullOrEmpty(value); // MDAC 83097 } + + return (_parsetable.TryGetValue(DbConnectionStringKeywords.UserID, out value) && !string.IsNullOrEmpty(value)) || + (_parsetable.TryGetValue(DbConnectionStringSynonyms.UID, out value) && !string.IsNullOrEmpty(value)); } return false; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index 2be92a6732..04fa51992c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -22,6 +22,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.SqlServer.Server; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index d42a0170aa..bf6f4d759e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -3,13 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Specialized; -using System.Configuration; using System.Data.Common; using System.Diagnostics; using System.IO; -using System.Runtime.Versioning; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Data.SqlClient.Server; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs index 9f8ddb7930..eb5cb511be 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs @@ -2,19 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Runtime.ConstrainedExecution; +using System.Threading; +using System.Transactions; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; + namespace Microsoft.Data.SqlClient { - using System; - using System.Data; - using System.Data.Common; - using System.Diagnostics; - using System.Runtime.ConstrainedExecution; - using System.Threading; - using System.Transactions; - using Microsoft.Data.Common; - using Microsoft.Data.ProviderBase; - using Microsoft.Data.SqlClient.ConnectionPool; - public sealed partial class SqlConnection : DbConnection { private static readonly DbConnectionFactory _connectionFactory = SqlConnectionFactory.SingletonInstance; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 721f13195f..c5bf4fe9db 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -16,6 +16,7 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Identity.Client; @@ -694,7 +695,7 @@ internal override bool IsLockedForBulkCopy } } - internal protected override bool IsNonPoolableTransactionRoot + protected internal override bool IsNonPoolableTransactionRoot { get { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs new file mode 100644 index 0000000000..867deb1ac3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text.RegularExpressions; +using Microsoft.Data.SqlClient; + +namespace Microsoft.Data.Common.ConnectionString +{ + internal partial class DbConnectionOptions + { + #if DEBUG + 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 + + "(?([^=\\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 + + "(?" + + "(\"([^\"\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 + + "(?([^=\\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 + + "(?" + + "(\\{([^\\}\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 + + ")" // although the spec does not allow {} embedded within a value, the retail code does. + + ")(\\s*)(;|[\u0000\\s]*$)" // whitespace after value up to semicolon or end-of-line + + ")*" // repeat the key-value pair + + "[\\s;]*[\u0000\\s]*"; // trailing whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end + private static readonly Regex ConnectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled); + #endif + + [Conditional("DEBUG")] + private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Dictionary synonyms) + { + if (SqlClientEventSource.Log.IsAdvancedTraceOn()) + { + Debug.Assert(string.Equals(keyname, keyname?.ToLower(), StringComparison.InvariantCulture), "missing ToLower"); + string realkeyname = synonyms != null ? synonyms[keyname] : keyname; + + if (!CompareInsensitiveInvariant(DbConnectionStringKeywords.Password, realkeyname) && + !CompareInsensitiveInvariant(DbConnectionStringSynonyms.Pwd, realkeyname)) + { + // don't trace passwords ever! + if (keyvalue != null) + { + SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); + } + else + { + SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}'", keyname); + } + } + } + } + + #if DEBUG + private static void ParseComparison( + Dictionary parseTable, + string connectionString, + Dictionary synonyms, + bool firstKey, + Exception e) + { + try + { + var parsedValues = SplitConnectionString(connectionString, synonyms, firstKey); + foreach (var parsedValue in parsedValues) + { + string key = parsedValue.Key; + string value1 = parsedValue.Value; + + bool parseTableContainsKey = parseTable.TryGetValue(key, out string value2); + Debug.Assert(parseTableContainsKey, $"{nameof(ParseInternal)} code vs. regex mismatch keyname <{key}>"); + Debug.Assert(value1 == value2, $"{nameof(ParseInternal)} code vs. regex mismatch keyvalue <{value1}> <{value2}>"); + } + } + catch (ArgumentException f) + { + if (e != null) + { + 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) + { + // 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.Fail("ParseInternal code vs regex throw mismatch " + f.Message); + } + + e = null; + } + + if (e != null) + { + Debug.Fail("ParseInternal code threw exception vs regex mismatch"); + } + } + #endif + + #if DEBUG + private static Dictionary SplitConnectionString( + string connectionString, + Dictionary synonyms, + bool firstKey) + { + var parseTable = new Dictionary(); + 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 (connectionString != null) + { + 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 = synonyms != null + ? synonyms.TryGetValue(keyName, out string synonym) ? synonym : null + : 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; + } + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs similarity index 63% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs index f9e83b0120..c8a9461f60 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs @@ -8,87 +8,15 @@ using System.Globalization; using System.Text; using System.Text.RegularExpressions; -using Microsoft.Data.Common.ConnectionString; -using Microsoft.Data.SqlClient; -namespace Microsoft.Data.Common +namespace Microsoft.Data.Common.ConnectionString { - partial class DbConnectionOptions + internal partial 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 - // connection string common keywords - private static class KEY - { - internal const string Integrated_Security = DbConnectionStringKeywords.IntegratedSecurity; - internal const string Password = DbConnectionStringKeywords.Password; - internal const string Persist_Security_Info = DbConnectionStringKeywords.PersistSecurityInfo; - internal const string User_ID = DbConnectionStringKeywords.UserID; - internal const string Encrypt = DbConnectionStringKeywords.Encrypt; - } - - // known connection string common synonyms - private static class SYNONYM - { - internal const string Pwd = DbConnectionStringSynonyms.Pwd; - internal const string UID = DbConnectionStringSynonyms.UID; - } - -#if DEBUG - /*private const string ConnectionStringPatternV1 = - "[\\s;]*" - +"(?([^=\\s]|\\s+[^=\\s]|\\s+==|==)+)" - + "\\s*=(?!=)\\s*" - +"(?(" - + "(" + "\"" + "([^\"]|\"\")*" + "\"" + ")" - + "|" - + "(" + "'" + "([^']|'')*" + "'" + ")" - + "|" - + "(" + "(?![\"'])" + "([^\\s;]|\\s+[^\\s;])*" + "(?([^=\\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 - + "(?" - + "(\"([^\"\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 - + "(?([^=\\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 - + "(?" - + "(\\{([^\\}\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 - - + ")" // although the spec does not allow {} - // embedded within a value, the retail code does. - + ")(\\s*)(;|[\u0000\\s]*$)" // whitespace after value up to semicolon or end-of-line - + ")*" // repeat the key-value pair - + "[\\s;]*[\u0000\\s]*" // trailing whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end - ; - - private static readonly Regex s_connectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled); - private static readonly Regex s_connectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled); -#endif private const string ConnectionStringValidKeyPattern = "^(?![;\\s])[^\\p{Cc}]+(? synonyms) { _parsetable = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - _usersConnectionString = connectionString != null ? connectionString : ""; + _usersConnectionString = connectionString ?? ""; // first pass on parsing, initial syntax check - if (0 < _usersConnectionString.Length) + if (_usersConnectionString.Length > 0) { _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, false); - _hasPasswordKeyword = (_parsetable.ContainsKey(KEY.Password) || _parsetable.ContainsKey(SYNONYM.Pwd)); - _hasUserIdKeyword = (_parsetable.ContainsKey(KEY.User_ID) || _parsetable.ContainsKey(SYNONYM.UID)); + _hasPasswordKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.Password) || + _parsetable.ContainsKey(DbConnectionStringSynonyms.Pwd); + _hasUserIdKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.UserID) || + _parsetable.ContainsKey(DbConnectionStringSynonyms.UID); } } @@ -139,29 +69,35 @@ protected DbConnectionOptions(DbConnectionOptions connectionOptions) // same as Boolean, but with SSPI thrown in as valid yes public bool ConvertValueToIntegratedSecurity() { - return _parsetable.TryGetValue(KEY.Integrated_Security, out string value) && value != null ? - ConvertValueToIntegratedSecurityInternal(value) : - false; + return _parsetable.TryGetValue(DbConnectionStringKeywords.IntegratedSecurity, out string value) && value != null + ? ConvertValueToIntegratedSecurityInternal(value) + : false; } 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")) + } + + if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) + { return false; - else + } + + string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. + if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) { - string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. - 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); - } + return true; } + + if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) + { + return false; + } + + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.IntegratedSecurity); } public int ConvertValueToInt32(string keyName, int defaultValue) @@ -208,9 +144,9 @@ private string UsersConnectionString(bool hidePassword, bool forceHidePassword) return connectionString ?? string.Empty; } - internal bool HasPersistablePassword => _hasPasswordKeyword ? - ConvertValueToBoolean(KEY.Persist_Security_Info, DbConnectionStringDefaults.PersistSecurityInfo) : - true; // no password means persistable password so we don't have to munge + internal bool HasPersistablePassword => _hasPasswordKeyword + ? ConvertValueToBoolean(DbConnectionStringKeywords.PersistSecurityInfo, DbConnectionStringDefaults.PersistSecurityInfo) + : true; // no password means persistable password so we don't have to munge public bool ConvertValueToBoolean(string keyName, bool defaultValue) { @@ -243,30 +179,6 @@ internal static bool ConvertValueToBooleanInternal(string keyName, string string private static bool CompareInsensitiveInvariant(string strvalue, string strconst) => (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst)); - [System.Diagnostics.Conditional("DEBUG")] - private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Dictionary synonyms) - { - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - Debug.Assert(string.Equals(keyname, keyname?.ToLower(), StringComparison.InvariantCulture), "missing ToLower"); - string realkeyname = synonyms != null ? synonyms[keyname] : keyname; - - if (!string.Equals(KEY.Password, realkeyname, StringComparison.InvariantCultureIgnoreCase) && - !string.Equals(SYNONYM.Pwd, realkeyname, StringComparison.InvariantCultureIgnoreCase)) - { - // don't trace passwords ever! - if (keyvalue != null) - { - SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); - } - else - { - SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}'", keyname); - } - } - } - } - private static string GetKeyName(StringBuilder buffer) { int count = buffer.Length; @@ -543,130 +455,16 @@ private static bool IsKeyNameValid(string keyname) return false; } -#if DEBUG - private static Dictionary SplitConnectionString(string connectionString, Dictionary synonyms, bool firstKey) - { - var parsetable = new Dictionary(); - Regex parser = (firstKey ? s_connectionStringRegexOdbc : s_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 (connectionString != null) - { - 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 synonym; - string realkeyname = synonyms != null - ? (synonyms.TryGetValue(keyname, out synonym) ? synonym : null) - : 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(Dictionary parsetable, string connectionString, Dictionary synonyms, bool firstKey, Exception e) - { - try - { - var parsedvalues = SplitConnectionString(connectionString, synonyms, firstKey); - foreach (var entry in parsedvalues) - { - string keyname = entry.Key; - string value1 = entry.Value; - string value2; - bool parsetableContainsKey = parsetable.TryGetValue(keyname, out value2); - Debug.Assert(parsetableContainsKey, $"{nameof(ParseInternal)} code vs. regex mismatch keyname <{keyname}>"); - Debug.Assert(value1 == value2, $"{nameof(ParseInternal)} code vs. regex mismatch keyvalue <{value1}> <{value2}>"); - } - } - catch (ArgumentException f) - { - if (e != null) - { - 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) - { - // 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.Fail("ParseInternal code vs regex throw mismatch " + f.Message); - } - e = null; - } - if (e != null) - { - Debug.Fail("ParseInternal code threw exception vs regex mismatch"); - } - } -#endif - private static NameValuePair ParseInternal(Dictionary parsetable, string connectionString, bool buildChain, Dictionary synonyms, bool firstKey) { Debug.Assert(connectionString != null, "null connectionstring"); StringBuilder buffer = new StringBuilder(); NameValuePair localKeychain = null, keychain = null; -#if DEBUG + + #if DEBUG try { -#endif + #endif int nextStartPosition = 0; int endPosition = connectionString.Length; while (nextStartPosition < endPosition) @@ -680,9 +478,8 @@ private static NameValuePair ParseInternal(Dictionary parsetable // if (nextStartPosition != endPosition) { throw; } break; } -#if DEBUG + DebugTraceKeyValuePair(keyname, keyvalue, synonyms); -#endif Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname"); Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue"); @@ -708,7 +505,7 @@ private static NameValuePair ParseInternal(Dictionary parsetable keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition); } } -#if DEBUG + #if DEBUG } catch (ArgumentException e) { @@ -716,7 +513,8 @@ private static NameValuePair ParseInternal(Dictionary parsetable throw; } ParseComparison(parsetable, connectionString, synonyms, firstKey, null); -#endif + #endif + return keychain; } @@ -728,8 +526,8 @@ internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) StringBuilder builder = new StringBuilder(_usersConnectionString.Length); for (NameValuePair current = _keyChain; current != null; current = current.Next) { - if (!string.Equals(KEY.Password, current.Name, StringComparison.InvariantCultureIgnoreCase) && - !string.Equals(SYNONYM.Pwd, current.Name, StringComparison.InvariantCultureIgnoreCase)) + if (!CompareInsensitiveInvariant(DbConnectionStringKeywords.Password, current.Name) && + !CompareInsensitiveInvariant(DbConnectionStringSynonyms.Pwd, current.Name)) { builder.Append(_usersConnectionString, copyPosition, current.Length); if (fakePassword) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs index b8ada9b1bc..d4ea183312 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -2,12 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient.ConnectionPool; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.SqlClient.ConnectionPool; namespace Microsoft.Data.ProviderBase { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs index 6f12c748f2..832b1dc2dc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient.ConnectionPool; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index 53ff96963c..b4dfb4f214 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient.ConnectionPool; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 110f578a85..805beed2b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -2,14 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. - -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; namespace Microsoft.Data.SqlClient.ConnectionPool { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs index e0bbcb8f24..d6647f832e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -7,7 +7,7 @@ using System.Data.Common; using System.Threading.Tasks; using System.Transactions; -using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; namespace Microsoft.Data.SqlClient.ConnectionPool diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 162b533ecc..77d2b3bae1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using static Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolState;