966bba02bb
Former-commit-id: bb0468d0f257ff100aa895eb5fe583fb5dfbf900
477 lines
23 KiB
C#
477 lines
23 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="DBConnectionString.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</owner>
|
|
// <owner current="true" primary="false">[....]</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Data.Common {
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Runtime.Serialization;
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
[Serializable] // MDAC 83147
|
|
internal sealed class DBConnectionString {
|
|
// instances of this class are intended to be immutable, i.e readonly
|
|
// used by permission classes so it is much easier to verify correctness
|
|
// when not worried about the class being modified during execution
|
|
|
|
private static class KEY {
|
|
internal const string Password = "password";
|
|
internal const string PersistSecurityInfo = "persist security info";
|
|
internal const string Pwd = "pwd";
|
|
};
|
|
|
|
// this class is serializable with Everett, so ugly field names can't be changed
|
|
readonly private string _encryptedUsersConnectionString;
|
|
|
|
// hash of unique keys to values
|
|
readonly private Hashtable _parsetable;
|
|
|
|
// a linked list of key/value and their length in _encryptedUsersConnectionString
|
|
readonly private NameValuePair _keychain;
|
|
|
|
// track the existance of "password" or "pwd" in the connection string
|
|
// not used for anything anymore but must keep it set correct for V1.1 serialization
|
|
readonly private bool _hasPassword;
|
|
|
|
readonly private string[] _restrictionValues;
|
|
readonly private string _restrictions;
|
|
|
|
readonly private KeyRestrictionBehavior _behavior;
|
|
|
|
#pragma warning disable 169
|
|
// this field is no longer used, hence the warning was disabled
|
|
// however, it can not be removed or it will break serialization with V1.1
|
|
readonly private string _encryptedActualConnectionString;
|
|
#pragma warning restore 169
|
|
|
|
internal DBConnectionString(string value, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool useOdbcRules)
|
|
: this(new DbConnectionOptions(value, synonyms, useOdbcRules), restrictions, behavior, synonyms, false)
|
|
{
|
|
// useOdbcRules is only used to parse the connection string, not to parse restrictions because values don't apply there
|
|
// the hashtable doesn't need clone since it isn't shared with anything else
|
|
}
|
|
|
|
internal DBConnectionString(DbConnectionOptions connectionOptions)
|
|
: this(connectionOptions, (string)null, KeyRestrictionBehavior.AllowOnly, (Hashtable)null, true)
|
|
{
|
|
// used by DBDataPermission to convert from DbConnectionOptions to DBConnectionString
|
|
// since backward compatability requires Everett level classes
|
|
}
|
|
|
|
private DBConnectionString(DbConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool mustCloneDictionary) { // used by DBDataPermission
|
|
Debug.Assert(null != connectionOptions, "null connectionOptions");
|
|
switch(behavior) {
|
|
case KeyRestrictionBehavior.PreventUsage:
|
|
case KeyRestrictionBehavior.AllowOnly:
|
|
_behavior = behavior;
|
|
break;
|
|
default:
|
|
throw ADP.InvalidKeyRestrictionBehavior(behavior);
|
|
}
|
|
|
|
// grab all the parsed details from DbConnectionOptions
|
|
_encryptedUsersConnectionString = connectionOptions.UsersConnectionString(false);
|
|
_hasPassword = connectionOptions.HasPasswordKeyword;
|
|
_parsetable = connectionOptions.Parsetable;
|
|
_keychain = connectionOptions._keyChain;
|
|
|
|
// we do not want to serialize out user password unless directed so by "persist security info=true"
|
|
// otherwise all instances of user's password will be replaced with "*"
|
|
if (_hasPassword && !connectionOptions.HasPersistablePassword) {
|
|
|
|
if (mustCloneDictionary) {
|
|
// clone the hashtable to replace user's password/pwd value with "*"
|
|
// we only need to clone if coming from DbConnectionOptions and password exists
|
|
_parsetable = (Hashtable) _parsetable.Clone();
|
|
}
|
|
|
|
// different than Everett in that instead of removing password/pwd from
|
|
// the hashtable, we replace the value with '*'. This is okay since we
|
|
// serialize out with '*' so already knows what we do. Better this way
|
|
// than to treat password specially later on which causes problems.
|
|
const string star = "*";
|
|
if (_parsetable.ContainsKey(KEY.Password)) {
|
|
_parsetable[KEY.Password] = star;
|
|
}
|
|
if (_parsetable.ContainsKey(KEY.Pwd)) {
|
|
_parsetable[KEY.Pwd] = star;
|
|
}
|
|
|
|
// replace user's password/pwd value with "*" in the linked list and build a new string
|
|
_keychain = connectionOptions.ReplacePasswordPwd(out _encryptedUsersConnectionString, true);
|
|
}
|
|
|
|
if (!ADP.IsEmpty(restrictions)) {
|
|
_restrictionValues = ParseRestrictions(restrictions, synonyms);
|
|
_restrictions = restrictions;
|
|
}
|
|
}
|
|
|
|
private DBConnectionString(DBConnectionString connectionString, string[] restrictionValues, KeyRestrictionBehavior behavior) {
|
|
// used by intersect for two equal connection strings with different restrictions
|
|
_encryptedUsersConnectionString = connectionString._encryptedUsersConnectionString;
|
|
_parsetable = connectionString._parsetable;
|
|
_keychain = connectionString._keychain;
|
|
_hasPassword = connectionString._hasPassword;
|
|
|
|
_restrictionValues = restrictionValues;
|
|
_restrictions = null;
|
|
_behavior = behavior;
|
|
|
|
Verify(restrictionValues);
|
|
}
|
|
|
|
internal KeyRestrictionBehavior Behavior {
|
|
get { return _behavior; }
|
|
}
|
|
|
|
internal string ConnectionString {
|
|
get { return _encryptedUsersConnectionString; }
|
|
}
|
|
|
|
internal bool IsEmpty {
|
|
get { return (null == _keychain); }
|
|
}
|
|
|
|
internal NameValuePair KeyChain {
|
|
get { return _keychain; }
|
|
}
|
|
|
|
internal string Restrictions {
|
|
get {
|
|
string restrictions = _restrictions;
|
|
if (null == restrictions) {
|
|
string[] restrictionValues = _restrictionValues;
|
|
if ((null != restrictionValues) && (0 < restrictionValues.Length)) {
|
|
StringBuilder builder = new StringBuilder();
|
|
for(int i = 0; i < restrictionValues.Length; ++i) {
|
|
if (!ADP.IsEmpty(restrictionValues[i])) {
|
|
builder.Append(restrictionValues[i]);
|
|
builder.Append("=;");
|
|
}
|
|
#if DEBUG
|
|
else {
|
|
Debug.Assert(false, "empty restriction");
|
|
}
|
|
#endif
|
|
}
|
|
restrictions = builder.ToString();
|
|
}
|
|
}
|
|
return ((null != restrictions) ? restrictions: "");
|
|
}
|
|
}
|
|
|
|
internal string this[string keyword] {
|
|
get { return (string)_parsetable[keyword]; }
|
|
}
|
|
|
|
internal bool ContainsKey(string keyword) {
|
|
return _parsetable.ContainsKey(keyword);
|
|
}
|
|
|
|
internal DBConnectionString Intersect(DBConnectionString entry) {
|
|
KeyRestrictionBehavior behavior = _behavior;
|
|
string[] restrictionValues = null;
|
|
|
|
if (null == entry) {
|
|
//Debug.WriteLine("0 entry AllowNothing");
|
|
behavior = KeyRestrictionBehavior.AllowOnly;
|
|
}
|
|
else if (this._behavior != entry._behavior) { // subset of the AllowOnly array
|
|
behavior = KeyRestrictionBehavior.AllowOnly;
|
|
|
|
if (KeyRestrictionBehavior.AllowOnly == entry._behavior) { // this PreventUsage and entry AllowOnly
|
|
if (!ADP.IsEmptyArray(_restrictionValues)) {
|
|
if (!ADP.IsEmptyArray(entry._restrictionValues)) {
|
|
//Debug.WriteLine("1 this PreventUsage with restrictions and entry AllowOnly with restrictions");
|
|
restrictionValues = NewRestrictionAllowOnly(entry._restrictionValues, _restrictionValues);
|
|
}
|
|
else {
|
|
//Debug.WriteLine("2 this PreventUsage with restrictions and entry AllowOnly with no restrictions");
|
|
}
|
|
}
|
|
else {
|
|
//Debug.WriteLine("3/4 this PreventUsage with no restrictions and entry AllowOnly");
|
|
restrictionValues = entry._restrictionValues;
|
|
}
|
|
}
|
|
else if (!ADP.IsEmptyArray(_restrictionValues)) { // this AllowOnly and entry PreventUsage
|
|
if (!ADP.IsEmptyArray(entry._restrictionValues)) {
|
|
//Debug.WriteLine("5 this AllowOnly with restrictions and entry PreventUsage with restrictions");
|
|
restrictionValues = NewRestrictionAllowOnly(_restrictionValues, entry._restrictionValues);
|
|
}
|
|
else {
|
|
//Debug.WriteLine("6 this AllowOnly and entry PreventUsage with no restrictions");
|
|
restrictionValues = _restrictionValues;
|
|
}
|
|
}
|
|
else {
|
|
//Debug.WriteLine("7/8 this AllowOnly with no restrictions and entry PreventUsage");
|
|
}
|
|
}
|
|
else if (KeyRestrictionBehavior.PreventUsage == this._behavior) { // both PreventUsage
|
|
if (ADP.IsEmptyArray(_restrictionValues)) {
|
|
//Debug.WriteLine("9/10 both PreventUsage and this with no restrictions");
|
|
restrictionValues = entry._restrictionValues;
|
|
}
|
|
else if (ADP.IsEmptyArray(entry._restrictionValues)) {
|
|
//Debug.WriteLine("11 both PreventUsage and entry with no restrictions");
|
|
restrictionValues = _restrictionValues;
|
|
}
|
|
else {
|
|
//Debug.WriteLine("12 both PreventUsage with restrictions");
|
|
restrictionValues = NoDuplicateUnion(_restrictionValues, entry._restrictionValues);
|
|
}
|
|
}
|
|
else if (!ADP.IsEmptyArray(_restrictionValues) && !ADP.IsEmptyArray(entry._restrictionValues)) { // both AllowOnly with restrictions
|
|
if (this._restrictionValues.Length <= entry._restrictionValues.Length) {
|
|
//Debug.WriteLine("13a this AllowOnly with restrictions and entry AllowOnly with restrictions");
|
|
restrictionValues = NewRestrictionIntersect(_restrictionValues, entry._restrictionValues);
|
|
}
|
|
else {
|
|
//Debug.WriteLine("13b this AllowOnly with restrictions and entry AllowOnly with restrictions");
|
|
restrictionValues = NewRestrictionIntersect(entry._restrictionValues, _restrictionValues);
|
|
}
|
|
}
|
|
else { // both AllowOnly
|
|
//Debug.WriteLine("14/15/16 this AllowOnly and entry AllowOnly but no restrictions");
|
|
}
|
|
|
|
// verify _hasPassword & _parsetable are in [....] between Everett/Whidbey
|
|
Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this");
|
|
Debug.Assert(null == entry || !entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry");
|
|
|
|
DBConnectionString value = new DBConnectionString(this, restrictionValues, behavior);
|
|
ValidateCombinedSet(this, value);
|
|
ValidateCombinedSet(entry, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void ValidateCombinedSet(DBConnectionString componentSet, DBConnectionString combinedSet) {
|
|
Debug.Assert(combinedSet != null, "The combined connection string should not be null");
|
|
if ((componentSet != null) && (combinedSet._restrictionValues != null) && (componentSet._restrictionValues != null)) {
|
|
if (componentSet._behavior == KeyRestrictionBehavior.AllowOnly) {
|
|
if (combinedSet._behavior == KeyRestrictionBehavior.AllowOnly) {
|
|
// Component==Allow, Combined==Allow
|
|
// All values in the Combined Set should also be in the Component Set
|
|
// Combined - Component == null
|
|
Debug.Assert(combinedSet._restrictionValues.Except(componentSet._restrictionValues).Count() == 0, "Combined set allows values not allowed by component set");
|
|
}
|
|
else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
|
|
// Component==Allow, Combined==PreventUsage
|
|
// Preventions override allows, so there is nothing to check here
|
|
}
|
|
else {
|
|
Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
|
|
}
|
|
}
|
|
else if (componentSet._behavior == KeyRestrictionBehavior.PreventUsage) {
|
|
if (combinedSet._behavior == KeyRestrictionBehavior.AllowOnly) {
|
|
// Component==PreventUsage, Combined==Allow
|
|
// There shouldn't be any of the values from the Component Set in the Combined Set
|
|
// Intersect(Component, Combined) == null
|
|
Debug.Assert(combinedSet._restrictionValues.Intersect(componentSet._restrictionValues).Count() == 0, "Combined values allows values prevented by component set");
|
|
}
|
|
else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
|
|
// Component==PreventUsage, Combined==PreventUsage
|
|
// All values in the Component Set should also be in the Combined Set
|
|
// Component - Combined == null
|
|
Debug.Assert(componentSet._restrictionValues.Except(combinedSet._restrictionValues).Count() == 0, "Combined values does not prevent all of the values prevented by the component set");
|
|
}
|
|
else {
|
|
Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
|
|
}
|
|
}
|
|
else {
|
|
Debug.Assert(false, string.Format("Unknown behavior for component set: {0}", componentSet._behavior));
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsRestrictedKeyword(string key) {
|
|
// restricted if not found
|
|
return ((null == _restrictionValues) || (0 > Array.BinarySearch(_restrictionValues, key, StringComparer.Ordinal)));
|
|
}
|
|
|
|
internal bool IsSupersetOf(DBConnectionString entry) {
|
|
Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this");
|
|
Debug.Assert(!entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry");
|
|
|
|
switch(_behavior) {
|
|
case KeyRestrictionBehavior.AllowOnly:
|
|
// every key must either be in the resticted connection string or in the allowed keywords
|
|
// keychain may contain duplicates, but it is better than GetEnumerator on _parsetable.Keys
|
|
for(NameValuePair current = entry.KeyChain; null != current; current = current.Next) {
|
|
if (!ContainsKey(current.Name) && IsRestrictedKeyword(current.Name)) {
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case KeyRestrictionBehavior.PreventUsage:
|
|
// every key can not be in the restricted keywords (even if in the restricted connection string)
|
|
if (null != _restrictionValues) {
|
|
foreach(string restriction in _restrictionValues) {
|
|
if (entry.ContainsKey(restriction)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
Debug.Assert(false, "invalid KeyRestrictionBehavior");
|
|
throw ADP.InvalidKeyRestrictionBehavior(_behavior);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static private string[] NewRestrictionAllowOnly(string[] allowonly, string[] preventusage) {
|
|
List<string> newlist = null;
|
|
for (int i = 0; i < allowonly.Length; ++i) {
|
|
if (0 > Array.BinarySearch(preventusage, allowonly[i], StringComparer.Ordinal)) {
|
|
if (null == newlist) {
|
|
newlist = new List<string>();
|
|
}
|
|
newlist.Add(allowonly[i]);
|
|
}
|
|
}
|
|
string[] restrictionValues = null;
|
|
if (null != newlist) {
|
|
restrictionValues = newlist.ToArray();
|
|
}
|
|
Verify(restrictionValues);
|
|
return restrictionValues;
|
|
}
|
|
|
|
static private string[] NewRestrictionIntersect(string[] a, string[] b) {
|
|
List<string> newlist = null;
|
|
for (int i = 0; i < a.Length; ++i) {
|
|
if (0 <= Array.BinarySearch(b, a[i], StringComparer.Ordinal)) {
|
|
if (null == newlist) {
|
|
newlist = new List<string>();
|
|
}
|
|
newlist.Add(a[i]);
|
|
}
|
|
}
|
|
string[] restrictionValues = null;
|
|
if (newlist != null) {
|
|
restrictionValues = newlist.ToArray();
|
|
}
|
|
Verify(restrictionValues);
|
|
return restrictionValues;
|
|
}
|
|
|
|
static private string[] NoDuplicateUnion(string[] a, string[] b) {
|
|
#if DEBUG
|
|
Debug.Assert(null != a && 0 < a.Length, "empty a");
|
|
Debug.Assert(null != b && 0 < b.Length, "empty b");
|
|
Verify(a);
|
|
Verify(b);
|
|
#endif
|
|
List<string> newlist = new List<string>(a.Length + b.Length);
|
|
for(int i = 0; i < a.Length; ++i) {
|
|
newlist.Add(a[i]);
|
|
}
|
|
for(int i = 0; i < b.Length; ++i) { // find duplicates
|
|
if (0 > Array.BinarySearch(a, b[i], StringComparer.Ordinal)) {
|
|
newlist.Add(b[i]);
|
|
}
|
|
}
|
|
string[] restrictionValues = newlist.ToArray();
|
|
Array.Sort(restrictionValues, StringComparer.Ordinal);
|
|
Verify(restrictionValues);
|
|
return restrictionValues;
|
|
}
|
|
|
|
private static string[] ParseRestrictions(string restrictions, Hashtable synonyms) {
|
|
#if DEBUG
|
|
if (Bid.AdvancedOn) {
|
|
Bid.Trace("<comm.DBConnectionString|INFO|ADV> Restrictions='%ls'\n", restrictions);
|
|
}
|
|
#endif
|
|
List<string> restrictionValues = new List<string>();
|
|
StringBuilder buffer = new StringBuilder(restrictions.Length);
|
|
|
|
int nextStartPosition = 0;
|
|
int endPosition = restrictions.Length;
|
|
while (nextStartPosition < endPosition) {
|
|
int startPosition = nextStartPosition;
|
|
|
|
string keyname, keyvalue; // since parsing restrictions ignores values, it doesn't matter if we use ODBC rules or OLEDB rules
|
|
nextStartPosition = DbConnectionOptions.GetKeyValuePair(restrictions, startPosition, buffer, false, out keyname, out keyvalue);
|
|
if (!ADP.IsEmpty(keyname)) {
|
|
#if DEBUG
|
|
if (Bid.AdvancedOn) {
|
|
Bid.Trace("<comm.DBConnectionString|INFO|ADV> KeyName='%ls'\n", keyname);
|
|
}
|
|
#endif
|
|
string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname); // MDAC 85144
|
|
if (ADP.IsEmpty(realkeyname)) {
|
|
throw ADP.KeywordNotSupported(keyname);
|
|
}
|
|
restrictionValues.Add(realkeyname);
|
|
}
|
|
}
|
|
return RemoveDuplicates(restrictionValues.ToArray());
|
|
|
|
}
|
|
|
|
static internal string[] RemoveDuplicates(string[] restrictions) {
|
|
int count = restrictions.Length;
|
|
if (0 < count) {
|
|
Array.Sort(restrictions, StringComparer.Ordinal);
|
|
|
|
for (int i = 1; i < restrictions.Length; ++i) {
|
|
string prev = restrictions[i-1];
|
|
if ((0 == prev.Length) || (prev == restrictions[i])) {
|
|
restrictions[i-1] = null;
|
|
count--;
|
|
}
|
|
}
|
|
if (0 == restrictions[restrictions.Length-1].Length) {
|
|
restrictions[restrictions.Length-1] = null;
|
|
count--;
|
|
}
|
|
if (count != restrictions.Length) {
|
|
string[] tmp = new String[count];
|
|
count = 0;
|
|
for (int i = 0; i < restrictions.Length; ++i) {
|
|
if (null != restrictions[i]) {
|
|
tmp[count++] = restrictions[i];
|
|
}
|
|
}
|
|
restrictions = tmp;
|
|
}
|
|
}
|
|
Verify(restrictions);
|
|
return restrictions;
|
|
}
|
|
|
|
[ConditionalAttribute("DEBUG")]
|
|
private static void Verify(string[] restrictionValues) {
|
|
if (null != restrictionValues) {
|
|
for (int i = 1; i < restrictionValues.Length; ++i) {
|
|
Debug.Assert(!ADP.IsEmpty(restrictionValues[i-1]), "empty restriction");
|
|
Debug.Assert(!ADP.IsEmpty(restrictionValues[i]), "empty restriction");
|
|
Debug.Assert(0 >= StringComparer.Ordinal.Compare(restrictionValues[i-1], restrictionValues[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|