1 //------------------------------------------------------------------------------
2 // <copyright file="DBConnectionString.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;
13 using System.Collections.Generic;
15 using System.Data.Common;
16 using System.Diagnostics;
17 using System.Globalization;
19 using System.Runtime.Serialization;
20 using System.Security.Permissions;
22 using System.Text.RegularExpressions;
24 [Serializable] // MDAC 83147
25 internal sealed class DBConnectionString {
26 // instances of this class are intended to be immutable, i.e readonly
27 // used by permission classes so it is much easier to verify correctness
28 // when not worried about the class being modified during execution
30 private static class KEY {
31 internal const string Password = "password";
32 internal const string PersistSecurityInfo = "persist security info";
33 internal const string Pwd = "pwd";
36 // this class is serializable with Everett, so ugly field names can't be changed
37 readonly private string _encryptedUsersConnectionString;
39 // hash of unique keys to values
40 readonly private Hashtable _parsetable;
42 // a linked list of key/value and their length in _encryptedUsersConnectionString
43 readonly private NameValuePair _keychain;
45 // track the existance of "password" or "pwd" in the connection string
46 // not used for anything anymore but must keep it set correct for V1.1 serialization
47 readonly private bool _hasPassword;
49 readonly private string[] _restrictionValues;
50 readonly private string _restrictions;
52 readonly private KeyRestrictionBehavior _behavior;
54 #pragma warning disable 169
55 // this field is no longer used, hence the warning was disabled
56 // however, it can not be removed or it will break serialization with V1.1
57 readonly private string _encryptedActualConnectionString;
58 #pragma warning restore 169
60 internal DBConnectionString(string value, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool useOdbcRules)
61 : this(new DbConnectionOptions(value, synonyms, useOdbcRules), restrictions, behavior, synonyms, false)
63 // useOdbcRules is only used to parse the connection string, not to parse restrictions because values don't apply there
64 // the hashtable doesn't need clone since it isn't shared with anything else
67 internal DBConnectionString(DbConnectionOptions connectionOptions)
68 : this(connectionOptions, (string)null, KeyRestrictionBehavior.AllowOnly, (Hashtable)null, true)
70 // used by DBDataPermission to convert from DbConnectionOptions to DBConnectionString
71 // since backward compatability requires Everett level classes
74 private DBConnectionString(DbConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool mustCloneDictionary) { // used by DBDataPermission
75 Debug.Assert(null != connectionOptions, "null connectionOptions");
77 case KeyRestrictionBehavior.PreventUsage:
78 case KeyRestrictionBehavior.AllowOnly:
82 throw ADP.InvalidKeyRestrictionBehavior(behavior);
85 // grab all the parsed details from DbConnectionOptions
86 _encryptedUsersConnectionString = connectionOptions.UsersConnectionString(false);
87 _hasPassword = connectionOptions.HasPasswordKeyword;
88 _parsetable = connectionOptions.Parsetable;
89 _keychain = connectionOptions.KeyChain;
91 // we do not want to serialize out user password unless directed so by "persist security info=true"
92 // otherwise all instances of user's password will be replaced with "*"
93 if (_hasPassword && !connectionOptions.HasPersistablePassword) {
95 if (mustCloneDictionary) {
96 // clone the hashtable to replace user's password/pwd value with "*"
97 // we only need to clone if coming from DbConnectionOptions and password exists
98 _parsetable = (Hashtable) _parsetable.Clone();
101 // different than Everett in that instead of removing password/pwd from
102 // the hashtable, we replace the value with '*'. This is okay since we
103 // serialize out with '*' so already knows what we do. Better this way
104 // than to treat password specially later on which causes problems.
105 const string star = "*";
106 if (_parsetable.ContainsKey(KEY.Password)) {
107 _parsetable[KEY.Password] = star;
109 if (_parsetable.ContainsKey(KEY.Pwd)) {
110 _parsetable[KEY.Pwd] = star;
113 // replace user's password/pwd value with "*" in the linked list and build a new string
114 _keychain = connectionOptions.ReplacePasswordPwd(out _encryptedUsersConnectionString, true);
117 if (!ADP.IsEmpty(restrictions)) {
118 _restrictionValues = ParseRestrictions(restrictions, synonyms);
119 _restrictions = restrictions;
123 private DBConnectionString(DBConnectionString connectionString, string[] restrictionValues, KeyRestrictionBehavior behavior) {
124 // used by intersect for two equal connection strings with different restrictions
125 _encryptedUsersConnectionString = connectionString._encryptedUsersConnectionString;
126 _parsetable = connectionString._parsetable;
127 _keychain = connectionString._keychain;
128 _hasPassword = connectionString._hasPassword;
130 _restrictionValues = restrictionValues;
131 _restrictions = null;
132 _behavior = behavior;
134 Verify(restrictionValues);
137 internal KeyRestrictionBehavior Behavior {
138 get { return _behavior; }
141 internal string ConnectionString {
142 get { return _encryptedUsersConnectionString; }
145 internal bool IsEmpty {
146 get { return (null == _keychain); }
149 internal NameValuePair KeyChain {
150 get { return _keychain; }
153 internal string Restrictions {
155 string restrictions = _restrictions;
156 if (null == restrictions) {
157 string[] restrictionValues = _restrictionValues;
158 if ((null != restrictionValues) && (0 < restrictionValues.Length)) {
159 StringBuilder builder = new StringBuilder();
160 for(int i = 0; i < restrictionValues.Length; ++i) {
161 if (!ADP.IsEmpty(restrictionValues[i])) {
162 builder.Append(restrictionValues[i]);
163 builder.Append("=;");
167 Debug.Assert(false, "empty restriction");
171 restrictions = builder.ToString();
174 return ((null != restrictions) ? restrictions: "");
178 internal string this[string keyword] {
179 get { return (string)_parsetable[keyword]; }
182 internal bool ContainsKey(string keyword) {
183 return _parsetable.ContainsKey(keyword);
186 internal DBConnectionString Intersect(DBConnectionString entry) {
187 KeyRestrictionBehavior behavior = _behavior;
188 string[] restrictionValues = null;
191 //Debug.WriteLine("0 entry AllowNothing");
192 behavior = KeyRestrictionBehavior.AllowOnly;
194 else if (this._behavior != entry._behavior) { // subset of the AllowOnly array
195 behavior = KeyRestrictionBehavior.AllowOnly;
197 if (KeyRestrictionBehavior.AllowOnly == entry._behavior) { // this PreventUsage and entry AllowOnly
198 if (!ADP.IsEmptyArray(_restrictionValues)) {
199 if (!ADP.IsEmptyArray(entry._restrictionValues)) {
200 //Debug.WriteLine("1 this PreventUsage with restrictions and entry AllowOnly with restrictions");
201 restrictionValues = NewRestrictionAllowOnly(entry._restrictionValues, _restrictionValues);
204 //Debug.WriteLine("2 this PreventUsage with restrictions and entry AllowOnly with no restrictions");
208 //Debug.WriteLine("3/4 this PreventUsage with no restrictions and entry AllowOnly");
209 restrictionValues = entry._restrictionValues;
212 else if (!ADP.IsEmptyArray(_restrictionValues)) { // this AllowOnly and entry PreventUsage
213 if (!ADP.IsEmptyArray(entry._restrictionValues)) {
214 //Debug.WriteLine("5 this AllowOnly with restrictions and entry PreventUsage with restrictions");
215 restrictionValues = NewRestrictionAllowOnly(_restrictionValues, entry._restrictionValues);
218 //Debug.WriteLine("6 this AllowOnly and entry PreventUsage with no restrictions");
219 restrictionValues = _restrictionValues;
223 //Debug.WriteLine("7/8 this AllowOnly with no restrictions and entry PreventUsage");
226 else if (KeyRestrictionBehavior.PreventUsage == this._behavior) { // both PreventUsage
227 if (ADP.IsEmptyArray(_restrictionValues)) {
228 //Debug.WriteLine("9/10 both PreventUsage and this with no restrictions");
229 restrictionValues = entry._restrictionValues;
231 else if (ADP.IsEmptyArray(entry._restrictionValues)) {
232 //Debug.WriteLine("11 both PreventUsage and entry with no restrictions");
233 restrictionValues = _restrictionValues;
236 //Debug.WriteLine("12 both PreventUsage with restrictions");
237 restrictionValues = NoDuplicateUnion(_restrictionValues, entry._restrictionValues);
240 else if (!ADP.IsEmptyArray(_restrictionValues) && !ADP.IsEmptyArray(entry._restrictionValues)) { // both AllowOnly with restrictions
241 if (this._restrictionValues.Length <= entry._restrictionValues.Length) {
242 //Debug.WriteLine("13a this AllowOnly with restrictions and entry AllowOnly with restrictions");
243 restrictionValues = NewRestrictionIntersect(_restrictionValues, entry._restrictionValues);
246 //Debug.WriteLine("13b this AllowOnly with restrictions and entry AllowOnly with restrictions");
247 restrictionValues = NewRestrictionIntersect(entry._restrictionValues, _restrictionValues);
250 else { // both AllowOnly
251 //Debug.WriteLine("14/15/16 this AllowOnly and entry AllowOnly but no restrictions");
254 // verify _hasPassword & _parsetable are in [....] between Everett/Whidbey
255 Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this");
256 Debug.Assert(null == entry || !entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry");
258 DBConnectionString value = new DBConnectionString(this, restrictionValues, behavior);
259 ValidateCombinedSet(this, value);
260 ValidateCombinedSet(entry, value);
265 [Conditional("DEBUG")]
266 private void ValidateCombinedSet(DBConnectionString componentSet, DBConnectionString combinedSet) {
267 Debug.Assert(combinedSet != null, "The combined connection string should not be null");
268 if ((componentSet != null) && (combinedSet._restrictionValues != null) && (componentSet._restrictionValues != null)) {
269 if (componentSet._behavior == KeyRestrictionBehavior.AllowOnly) {
270 if (combinedSet._behavior == KeyRestrictionBehavior.AllowOnly) {
271 // Component==Allow, Combined==Allow
272 // All values in the Combined Set should also be in the Component Set
273 // Combined - Component == null
274 Debug.Assert(combinedSet._restrictionValues.Except(componentSet._restrictionValues).Count() == 0, "Combined set allows values not allowed by component set");
276 else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
277 // Component==Allow, Combined==PreventUsage
278 // Preventions override allows, so there is nothing to check here
281 Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
284 else if (componentSet._behavior == KeyRestrictionBehavior.PreventUsage) {
285 if (combinedSet._behavior == KeyRestrictionBehavior.AllowOnly) {
286 // Component==PreventUsage, Combined==Allow
287 // There shouldn't be any of the values from the Component Set in the Combined Set
288 // Intersect(Component, Combined) == null
289 Debug.Assert(combinedSet._restrictionValues.Intersect(componentSet._restrictionValues).Count() == 0, "Combined values allows values prevented by component set");
291 else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
292 // Component==PreventUsage, Combined==PreventUsage
293 // All values in the Component Set should also be in the Combined Set
294 // Component - Combined == null
295 Debug.Assert(componentSet._restrictionValues.Except(combinedSet._restrictionValues).Count() == 0, "Combined values does not prevent all of the values prevented by the component set");
298 Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
302 Debug.Assert(false, string.Format("Unknown behavior for component set: {0}", componentSet._behavior));
307 private bool IsRestrictedKeyword(string key) {
308 // restricted if not found
309 return ((null == _restrictionValues) || (0 > Array.BinarySearch(_restrictionValues, key, StringComparer.Ordinal)));
312 internal bool IsSupersetOf(DBConnectionString entry) {
313 Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this");
314 Debug.Assert(!entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry");
317 case KeyRestrictionBehavior.AllowOnly:
318 // every key must either be in the resticted connection string or in the allowed keywords
319 // keychain may contain duplicates, but it is better than GetEnumerator on _parsetable.Keys
320 for(NameValuePair current = entry.KeyChain; null != current; current = current.Next) {
321 if (!ContainsKey(current.Name) && IsRestrictedKeyword(current.Name)) {
326 case KeyRestrictionBehavior.PreventUsage:
327 // every key can not be in the restricted keywords (even if in the restricted connection string)
328 if (null != _restrictionValues) {
329 foreach(string restriction in _restrictionValues) {
330 if (entry.ContainsKey(restriction)) {
337 Debug.Assert(false, "invalid KeyRestrictionBehavior");
338 throw ADP.InvalidKeyRestrictionBehavior(_behavior);
343 static private string[] NewRestrictionAllowOnly(string[] allowonly, string[] preventusage) {
344 List<string> newlist = null;
345 for (int i = 0; i < allowonly.Length; ++i) {
346 if (0 > Array.BinarySearch(preventusage, allowonly[i], StringComparer.Ordinal)) {
347 if (null == newlist) {
348 newlist = new List<string>();
350 newlist.Add(allowonly[i]);
353 string[] restrictionValues = null;
354 if (null != newlist) {
355 restrictionValues = newlist.ToArray();
357 Verify(restrictionValues);
358 return restrictionValues;
361 static private string[] NewRestrictionIntersect(string[] a, string[] b) {
362 List<string> newlist = null;
363 for (int i = 0; i < a.Length; ++i) {
364 if (0 <= Array.BinarySearch(b, a[i], StringComparer.Ordinal)) {
365 if (null == newlist) {
366 newlist = new List<string>();
371 string[] restrictionValues = null;
372 if (newlist != null) {
373 restrictionValues = newlist.ToArray();
375 Verify(restrictionValues);
376 return restrictionValues;
379 static private string[] NoDuplicateUnion(string[] a, string[] b) {
381 Debug.Assert(null != a && 0 < a.Length, "empty a");
382 Debug.Assert(null != b && 0 < b.Length, "empty b");
386 List<string> newlist = new List<string>(a.Length + b.Length);
387 for(int i = 0; i < a.Length; ++i) {
390 for(int i = 0; i < b.Length; ++i) { // find duplicates
391 if (0 > Array.BinarySearch(a, b[i], StringComparer.Ordinal)) {
395 string[] restrictionValues = newlist.ToArray();
396 Array.Sort(restrictionValues, StringComparer.Ordinal);
397 Verify(restrictionValues);
398 return restrictionValues;
401 private static string[] ParseRestrictions(string restrictions, Hashtable synonyms) {
403 if (Bid.AdvancedOn) {
404 Bid.Trace("<comm.DBConnectionString|INFO|ADV> Restrictions='%ls'\n", restrictions);
407 List<string> restrictionValues = new List<string>();
408 StringBuilder buffer = new StringBuilder(restrictions.Length);
410 int nextStartPosition = 0;
411 int endPosition = restrictions.Length;
412 while (nextStartPosition < endPosition) {
413 int startPosition = nextStartPosition;
415 string keyname, keyvalue; // since parsing restrictions ignores values, it doesn't matter if we use ODBC rules or OLEDB rules
416 nextStartPosition = DbConnectionOptions.GetKeyValuePair(restrictions, startPosition, buffer, false, out keyname, out keyvalue);
417 if (!ADP.IsEmpty(keyname)) {
419 if (Bid.AdvancedOn) {
420 Bid.Trace("<comm.DBConnectionString|INFO|ADV> KeyName='%ls'\n", keyname);
423 string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname); // MDAC 85144
424 if (ADP.IsEmpty(realkeyname)) {
425 throw ADP.KeywordNotSupported(keyname);
427 restrictionValues.Add(realkeyname);
430 return RemoveDuplicates(restrictionValues.ToArray());
434 static internal string[] RemoveDuplicates(string[] restrictions) {
435 int count = restrictions.Length;
437 Array.Sort(restrictions, StringComparer.Ordinal);
439 for (int i = 1; i < restrictions.Length; ++i) {
440 string prev = restrictions[i-1];
441 if ((0 == prev.Length) || (prev == restrictions[i])) {
442 restrictions[i-1] = null;
446 if (0 == restrictions[restrictions.Length-1].Length) {
447 restrictions[restrictions.Length-1] = null;
450 if (count != restrictions.Length) {
451 string[] tmp = new String[count];
453 for (int i = 0; i < restrictions.Length; ++i) {
454 if (null != restrictions[i]) {
455 tmp[count++] = restrictions[i];
461 Verify(restrictions);
465 [ConditionalAttribute("DEBUG")]
466 private static void Verify(string[] restrictionValues) {
467 if (null != restrictionValues) {
468 for (int i = 1; i < restrictionValues.Length; ++i) {
469 Debug.Assert(!ADP.IsEmpty(restrictionValues[i-1]), "empty restriction");
470 Debug.Assert(!ADP.IsEmpty(restrictionValues[i]), "empty restriction");
471 Debug.Assert(0 >= StringComparer.Ordinal.Compare(restrictionValues[i-1], restrictionValues[i]));