| File: System\Data\Common\DbConnectionOptions.cs | Web Access |
| Project: src\runtime\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; namespace System.Data.Common { internal sealed partial class DbConnectionOptions { // instances of this class are intended to be immutable, i.e readonly // used by pooling classes so it is easier to verify correctness // without the risk of the objects of the class being modified during execution. // differences between OleDb and Odbc // ODBC: // https://learn.microsoft.com/sql/odbc/reference/syntax/sqldriverconnect-function // do not support == -> = in keywords // first key-value pair wins // quote values using \{ and \}, only driver= and pwd= appear to generically allow quoting // do not strip quotes from value, or add quotes except for driver keyword // OLEDB: // https://learn.microsoft.com/dotnet/framework/data/adonet/connection-string-syntax#oledb-connection-string-syntax // support == -> = in keywords // last key-value pair wins // quote values using \" or \' // strip quotes from value internal readonly bool _useOdbcRules; internal readonly bool _hasUserIdKeyword; // synonyms hashtable is meant to be read-only translation of parsed string // keywords/synonyms to a known keyword string public DbConnectionOptions(string? connectionString, Dictionary<string, string>? synonyms, bool useOdbcRules) { _useOdbcRules = useOdbcRules; _parsetable = new Dictionary<string, string?>(); _usersConnectionString = connectionString ?? ""; // first pass on parsing, initial syntax check if (0 < _usersConnectionString.Length) { _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, _useOdbcRules); _hasPasswordKeyword = (_parsetable.ContainsKey(KEY.Password) || _parsetable.ContainsKey(SYNONYM.Pwd)); _hasUserIdKeyword = (_parsetable.ContainsKey(KEY.User_ID) || _parsetable.ContainsKey(SYNONYM.UID)); } } internal Dictionary<string, string?> Parsetable => _parsetable; public string? this[string keyword] => _parsetable[keyword]; internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string? keyValue, bool useOdbcRules) { ADP.CheckArgumentNull(builder, nameof(builder)); ADP.CheckArgumentLength(keyName, nameof(keyName)); if ((null == keyName) || !ConnectionStringValidKeyRegex.IsMatch(keyName)) { throw ADP.InvalidKeyname(keyName); } if ((null != keyValue) && !IsValueValidInternal(keyValue)) { throw ADP.InvalidValue(keyName); } if ((0 < builder.Length) && (';' != builder[builder.Length - 1])) { builder.Append(';'); } if (useOdbcRules) { builder.Append(keyName); } else { builder.Append(keyName.Replace("=", "==")); } builder.Append('='); if (null != keyValue) { // else <keyword>=; if (useOdbcRules) { if ((0 < keyValue.Length) && (('{' == keyValue[0]) || keyValue.Contains(';') || string.Equals(DbConnectionStringKeywords.Driver, keyName, StringComparison.OrdinalIgnoreCase)) && !ConnectionStringQuoteOdbcValueRegex.IsMatch(keyValue)) { // always quote Driver value (required for ODBC Version 2.65 and earlier) // always quote values that contain a ';' builder.Append('{').Append(keyValue.Replace("}", "}}")).Append('}'); } else { builder.Append(keyValue); } } else if (ConnectionStringQuoteValueRegex.IsMatch(keyValue)) { // <value> -> <value> builder.Append(keyValue); } else if ((keyValue.Contains('\"')) && (!keyValue.Contains('\''))) { // <val"ue> -> <'val"ue'> builder.Append('\''); builder.Append(keyValue); builder.Append('\''); } else { // <val'ue> -> <"val'ue"> // <=value> -> <"=value"> // <;value> -> <";value"> // < value> -> <" value"> // <va lue> -> <"va lue"> // <va'"lue> -> <"va'""lue"> builder.Append('\"'); builder.Append(keyValue.Replace("\"", "\"\"")); builder.Append('\"'); } } } internal string Expand() => _usersConnectionString; internal string ExpandKeyword(string keyword, string replacementValue) { // preserve duplicates, updated keyword value with replacement value // if keyword not specified, append to end of the string bool expanded = false; int copyPosition = 0; var builder = new StringBuilder(_usersConnectionString.Length); for (NameValuePair? current = _keyChain; null != current; current = current.Next) { if ((current.Name == keyword) && (current.Value == this[keyword])) { // only replace the parse end-result value instead of all values // so that when duplicate-keywords occur other original values remain in place AppendKeyValuePairBuilder(builder, current.Name, replacementValue, _useOdbcRules); builder.Append(';'); expanded = true; } else { builder.Append(_usersConnectionString, copyPosition, current.Length); } copyPosition += current.Length; } if (!expanded) { Debug.Assert(!_useOdbcRules, "ExpandKeyword not ready for Odbc"); AppendKeyValuePairBuilder(builder, keyword, replacementValue, _useOdbcRules); } return builder.ToString(); } [Conditional("DEBUG")] static partial void DebugTraceKeyValuePair(string keyname, string? keyvalue, Dictionary<string, string>? synonyms) { Debug.Assert(keyname == keyname.ToLowerInvariant(), "missing ToLower"); string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname); if ((KEY.Password != realkeyname) && (SYNONYM.Pwd != realkeyname)) { // don't trace passwords ever! if (null != keyvalue) { DataCommonEventSource.Log.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); } else { DataCommonEventSource.Log.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='{0}'", keyname); } } } internal static void ValidateKeyValuePair(string keyword, string value) { if ((null == keyword) || !ConnectionStringValidKeyRegex.IsMatch(keyword)) { throw ADP.InvalidKeyname(keyword); } if ((null != value) && value.Contains('\0')) { throw ADP.InvalidValue(keyword); } } } }