2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <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 ;
2017-06-07 13:16:24 +00:00
_keychain = connectionOptions . _keyChain ;
2016-08-03 10:59:49 +00:00
// 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 ] ) ) ;
}
}
}
}
}