[threads] Don't ignore abort requests in abort protected blocks
[mono.git] / mcs / class / referencesource / System.Data / System / Data / Common / DBConnectionString.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DBConnectionString.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data.Common {
10
11     using System;
12     using System.Collections;
13     using System.Collections.Generic;
14     using System.Data;
15     using System.Data.Common;
16     using System.Diagnostics;
17     using System.Globalization;
18     using System.Linq;
19     using System.Runtime.Serialization;
20     using System.Security.Permissions;
21     using System.Text;
22     using System.Text.RegularExpressions;
23
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
29
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";
34         };
35
36         // this class is serializable with Everett, so ugly field names can't be changed
37         readonly private string        _encryptedUsersConnectionString;
38
39         // hash of unique keys to values
40         readonly private Hashtable     _parsetable;
41
42         // a linked list of key/value and their length in _encryptedUsersConnectionString
43         readonly private NameValuePair _keychain;
44
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;
48
49         readonly private string[] _restrictionValues;
50         readonly private string   _restrictions;
51
52         readonly private KeyRestrictionBehavior _behavior;
53
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
59
60         internal DBConnectionString(string value, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool useOdbcRules)
61             : this(new DbConnectionOptions(value, synonyms, useOdbcRules), restrictions, behavior, synonyms, false)
62         {
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
65         }
66
67         internal DBConnectionString(DbConnectionOptions connectionOptions)
68             : this(connectionOptions, (string)null, KeyRestrictionBehavior.AllowOnly, (Hashtable)null, true)
69         {
70             // used by DBDataPermission to convert from DbConnectionOptions to DBConnectionString
71             // since backward compatability requires Everett level classes
72         }
73
74         private DBConnectionString(DbConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool mustCloneDictionary) { // used by DBDataPermission
75             Debug.Assert(null != connectionOptions, "null connectionOptions");
76             switch(behavior) {
77             case KeyRestrictionBehavior.PreventUsage:
78             case KeyRestrictionBehavior.AllowOnly:
79                 _behavior = behavior;
80                 break;
81             default:
82                 throw ADP.InvalidKeyRestrictionBehavior(behavior);
83             }
84
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;
90
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) {
94
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();
99                 }
100
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;
108                 }
109                 if (_parsetable.ContainsKey(KEY.Pwd)) {
110                     _parsetable[KEY.Pwd] = star;
111                 }
112
113                 // replace user's password/pwd value with "*" in the linked list and build a new string
114                 _keychain = connectionOptions.ReplacePasswordPwd(out _encryptedUsersConnectionString, true);
115             }
116
117             if (!ADP.IsEmpty(restrictions)) {
118                 _restrictionValues = ParseRestrictions(restrictions, synonyms);
119                 _restrictions = restrictions;
120             }
121         }
122
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;
129
130             _restrictionValues = restrictionValues;
131             _restrictions = null;
132             _behavior = behavior;
133
134             Verify(restrictionValues);
135         }
136
137         internal KeyRestrictionBehavior Behavior {
138             get { return _behavior; }
139         }
140
141         internal string ConnectionString {
142             get { return _encryptedUsersConnectionString; }
143         }
144
145         internal bool IsEmpty {
146             get { return (null == _keychain); }
147         }
148
149         internal NameValuePair KeyChain {
150             get { return _keychain; }
151         }
152
153         internal string Restrictions {
154             get {
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("=;");
164                             }
165 #if DEBUG
166                             else {
167                                 Debug.Assert(false, "empty restriction");
168                             }
169 #endif
170                         }
171                         restrictions = builder.ToString();
172                     }
173                 }
174                 return ((null != restrictions) ? restrictions: "");
175             }
176         }
177
178         internal string this[string keyword] {
179             get { return (string)_parsetable[keyword]; }
180         }
181
182         internal bool ContainsKey(string keyword) {
183             return _parsetable.ContainsKey(keyword);
184         }
185
186         internal DBConnectionString Intersect(DBConnectionString entry) {
187             KeyRestrictionBehavior behavior = _behavior;
188             string[] restrictionValues = null;
189
190             if (null == entry) {
191                 //Debug.WriteLine("0 entry AllowNothing");
192                 behavior = KeyRestrictionBehavior.AllowOnly;
193             }
194             else if (this._behavior != entry._behavior) { // subset of the AllowOnly array
195                 behavior = KeyRestrictionBehavior.AllowOnly;
196
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);
202                         }
203                         else {
204                             //Debug.WriteLine("2 this PreventUsage with restrictions and entry AllowOnly with no restrictions");
205                         }
206                     }
207                     else {
208                         //Debug.WriteLine("3/4 this PreventUsage with no restrictions and entry AllowOnly");
209                         restrictionValues = entry._restrictionValues;
210                     }
211                 }
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);
216                     }
217                     else {
218                         //Debug.WriteLine("6 this AllowOnly and entry PreventUsage with no restrictions");
219                         restrictionValues = _restrictionValues;
220                     }
221                 }
222                 else {
223                     //Debug.WriteLine("7/8 this AllowOnly with no restrictions and entry PreventUsage");
224                 }
225             }
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;
230                 }
231                 else if (ADP.IsEmptyArray(entry._restrictionValues)) {
232                     //Debug.WriteLine("11 both PreventUsage and entry with no restrictions");
233                     restrictionValues = _restrictionValues;
234                 }
235                 else {
236                     //Debug.WriteLine("12 both PreventUsage with restrictions");
237                     restrictionValues = NoDuplicateUnion(_restrictionValues, entry._restrictionValues);
238                 }
239             }
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);
244                 }
245                 else {
246                     //Debug.WriteLine("13b this AllowOnly with restrictions and entry AllowOnly with restrictions");
247                     restrictionValues = NewRestrictionIntersect(entry._restrictionValues, _restrictionValues);
248                 }
249             }
250             else { // both AllowOnly
251                 //Debug.WriteLine("14/15/16 this AllowOnly and entry AllowOnly but no restrictions");
252             }
253
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");
257
258             DBConnectionString value = new DBConnectionString(this, restrictionValues, behavior);
259             ValidateCombinedSet(this, value);
260             ValidateCombinedSet(entry, value);
261             
262             return value;
263         }
264
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");
275                     }
276                     else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
277                         // Component==Allow, Combined==PreventUsage
278                         // Preventions override allows, so there is nothing to check here
279                     }
280                     else {
281                         Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
282                     }
283                 }
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");
290                     }
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");
296                     }
297                     else {
298                         Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
299                     }
300                 }
301                 else {
302                     Debug.Assert(false, string.Format("Unknown behavior for component set: {0}", componentSet._behavior));
303                 }
304             }
305         }
306
307         private bool IsRestrictedKeyword(string key) {
308             // restricted if not found
309             return ((null == _restrictionValues) || (0 > Array.BinarySearch(_restrictionValues, key, StringComparer.Ordinal)));
310         }
311
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");
315
316             switch(_behavior) {
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)) {
322                         return false;
323                     }
324                 }
325                 break;
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)) {
331                             return false;
332                         }
333                     }
334                 }
335                 break;
336             default:
337                 Debug.Assert(false, "invalid KeyRestrictionBehavior");
338                 throw ADP.InvalidKeyRestrictionBehavior(_behavior);
339             }
340             return true;
341         }
342
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>();
349                     }
350                     newlist.Add(allowonly[i]);
351                 }
352             }
353             string[] restrictionValues = null;
354             if (null != newlist) {
355                 restrictionValues = newlist.ToArray();
356             }
357             Verify(restrictionValues);
358             return restrictionValues;
359         }
360
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>();
367                     }
368                     newlist.Add(a[i]);
369                 }
370             }
371             string[] restrictionValues = null;
372             if (newlist != null) {
373                 restrictionValues = newlist.ToArray();
374             }
375             Verify(restrictionValues);
376             return restrictionValues;
377         }
378
379         static private string[] NoDuplicateUnion(string[] a, string[] b) {
380 #if DEBUG
381             Debug.Assert(null != a && 0 < a.Length, "empty a");
382             Debug.Assert(null != b && 0 < b.Length, "empty b");
383             Verify(a);
384             Verify(b);
385 #endif
386             List<string> newlist = new List<string>(a.Length + b.Length);
387             for(int i = 0; i < a.Length; ++i) {
388                 newlist.Add(a[i]);
389             }
390             for(int i = 0; i < b.Length; ++i) { // find duplicates
391                 if (0 > Array.BinarySearch(a, b[i], StringComparer.Ordinal)) {
392                     newlist.Add(b[i]);
393                 }
394             }
395             string[] restrictionValues = newlist.ToArray();
396             Array.Sort(restrictionValues, StringComparer.Ordinal);
397             Verify(restrictionValues);
398             return restrictionValues;
399         }
400
401         private static string[] ParseRestrictions(string restrictions, Hashtable synonyms) {
402 #if DEBUG
403             if (Bid.AdvancedOn) {
404                 Bid.Trace("<comm.DBConnectionString|INFO|ADV> Restrictions='%ls'\n", restrictions);
405             }
406 #endif
407             List<string> restrictionValues = new List<string>();
408             StringBuilder buffer = new StringBuilder(restrictions.Length);
409
410             int nextStartPosition = 0;
411             int endPosition = restrictions.Length;
412             while (nextStartPosition < endPosition) {
413                 int startPosition = nextStartPosition;
414
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)) {
418 #if DEBUG
419                     if (Bid.AdvancedOn) {
420                         Bid.Trace("<comm.DBConnectionString|INFO|ADV> KeyName='%ls'\n", keyname);
421                     }
422 #endif
423                     string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname); // MDAC 85144
424                     if (ADP.IsEmpty(realkeyname)) {
425                         throw ADP.KeywordNotSupported(keyname);
426                     }
427                     restrictionValues.Add(realkeyname);
428                 }
429             }
430             return RemoveDuplicates(restrictionValues.ToArray());
431
432         }
433
434         static internal string[] RemoveDuplicates(string[] restrictions) {
435             int count = restrictions.Length;
436             if (0 < count) {
437                 Array.Sort(restrictions, StringComparer.Ordinal);
438
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;
443                         count--;
444                     }
445                 }
446                 if (0 == restrictions[restrictions.Length-1].Length) {
447                     restrictions[restrictions.Length-1] = null;
448                     count--;
449                 }
450                 if (count != restrictions.Length) {
451                     string[] tmp = new String[count];
452                     count = 0;
453                     for (int i = 0; i < restrictions.Length; ++i) {
454                         if (null != restrictions[i]) {
455                             tmp[count++] = restrictions[i];
456                         }
457                     }
458                     restrictions = tmp;
459                 }
460             }
461             Verify(restrictions);
462             return restrictions;
463         }
464
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]));
472                 }
473             }
474         }
475     }
476 }