DBConnectionString.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DBConnectionString.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">Microsoft</owner>
  6. // <owner current="true" primary="false">Microsoft</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data.Common {
  9. using System;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using System.Data;
  13. using System.Data.Common;
  14. using System.Diagnostics;
  15. using System.Globalization;
  16. using System.Linq;
  17. using System.Runtime.Serialization;
  18. using System.Security.Permissions;
  19. using System.Text;
  20. using System.Text.RegularExpressions;
  21. [Serializable] // MDAC 83147
  22. internal sealed class DBConnectionString {
  23. // instances of this class are intended to be immutable, i.e readonly
  24. // used by permission classes so it is much easier to verify correctness
  25. // when not worried about the class being modified during execution
  26. private static class KEY {
  27. internal const string Password = "password";
  28. internal const string PersistSecurityInfo = "persist security info";
  29. internal const string Pwd = "pwd";
  30. };
  31. // this class is serializable with Everett, so ugly field names can't be changed
  32. readonly private string _encryptedUsersConnectionString;
  33. // hash of unique keys to values
  34. readonly private Hashtable _parsetable;
  35. // a linked list of key/value and their length in _encryptedUsersConnectionString
  36. readonly private NameValuePair _keychain;
  37. // track the existance of "password" or "pwd" in the connection string
  38. // not used for anything anymore but must keep it set correct for V1.1 serialization
  39. readonly private bool _hasPassword;
  40. readonly private string[] _restrictionValues;
  41. readonly private string _restrictions;
  42. readonly private KeyRestrictionBehavior _behavior;
  43. #pragma warning disable 169
  44. // this field is no longer used, hence the warning was disabled
  45. // however, it can not be removed or it will break serialization with V1.1
  46. readonly private string _encryptedActualConnectionString;
  47. #pragma warning restore 169
  48. internal DBConnectionString(string value, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool useOdbcRules)
  49. : this(new DbConnectionOptions(value, synonyms, useOdbcRules), restrictions, behavior, synonyms, false)
  50. {
  51. // useOdbcRules is only used to parse the connection string, not to parse restrictions because values don't apply there
  52. // the hashtable doesn't need clone since it isn't shared with anything else
  53. }
  54. internal DBConnectionString(DbConnectionOptions connectionOptions)
  55. : this(connectionOptions, (string)null, KeyRestrictionBehavior.AllowOnly, (Hashtable)null, true)
  56. {
  57. // used by DBDataPermission to convert from DbConnectionOptions to DBConnectionString
  58. // since backward compatability requires Everett level classes
  59. }
  60. private DBConnectionString(DbConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, Hashtable synonyms, bool mustCloneDictionary) { // used by DBDataPermission
  61. Debug.Assert(null != connectionOptions, "null connectionOptions");
  62. switch(behavior) {
  63. case KeyRestrictionBehavior.PreventUsage:
  64. case KeyRestrictionBehavior.AllowOnly:
  65. _behavior = behavior;
  66. break;
  67. default:
  68. throw ADP.InvalidKeyRestrictionBehavior(behavior);
  69. }
  70. // grab all the parsed details from DbConnectionOptions
  71. _encryptedUsersConnectionString = connectionOptions.UsersConnectionString(false);
  72. _hasPassword = connectionOptions.HasPasswordKeyword;
  73. _parsetable = connectionOptions.Parsetable;
  74. _keychain = connectionOptions._keyChain;
  75. // we do not want to serialize out user password unless directed so by "persist security info=true"
  76. // otherwise all instances of user's password will be replaced with "*"
  77. if (_hasPassword && !connectionOptions.HasPersistablePassword) {
  78. if (mustCloneDictionary) {
  79. // clone the hashtable to replace user's password/pwd value with "*"
  80. // we only need to clone if coming from DbConnectionOptions and password exists
  81. _parsetable = (Hashtable) _parsetable.Clone();
  82. }
  83. // different than Everett in that instead of removing password/pwd from
  84. // the hashtable, we replace the value with '*'. This is okay since we
  85. // serialize out with '*' so already knows what we do. Better this way
  86. // than to treat password specially later on which causes problems.
  87. const string star = "*";
  88. if (_parsetable.ContainsKey(KEY.Password)) {
  89. _parsetable[KEY.Password] = star;
  90. }
  91. if (_parsetable.ContainsKey(KEY.Pwd)) {
  92. _parsetable[KEY.Pwd] = star;
  93. }
  94. // replace user's password/pwd value with "*" in the linked list and build a new string
  95. _keychain = connectionOptions.ReplacePasswordPwd(out _encryptedUsersConnectionString, true);
  96. }
  97. if (!ADP.IsEmpty(restrictions)) {
  98. _restrictionValues = ParseRestrictions(restrictions, synonyms);
  99. _restrictions = restrictions;
  100. }
  101. }
  102. private DBConnectionString(DBConnectionString connectionString, string[] restrictionValues, KeyRestrictionBehavior behavior) {
  103. // used by intersect for two equal connection strings with different restrictions
  104. _encryptedUsersConnectionString = connectionString._encryptedUsersConnectionString;
  105. _parsetable = connectionString._parsetable;
  106. _keychain = connectionString._keychain;
  107. _hasPassword = connectionString._hasPassword;
  108. _restrictionValues = restrictionValues;
  109. _restrictions = null;
  110. _behavior = behavior;
  111. Verify(restrictionValues);
  112. }
  113. internal KeyRestrictionBehavior Behavior {
  114. get { return _behavior; }
  115. }
  116. internal string ConnectionString {
  117. get { return _encryptedUsersConnectionString; }
  118. }
  119. internal bool IsEmpty {
  120. get { return (null == _keychain); }
  121. }
  122. internal NameValuePair KeyChain {
  123. get { return _keychain; }
  124. }
  125. internal string Restrictions {
  126. get {
  127. string restrictions = _restrictions;
  128. if (null == restrictions) {
  129. string[] restrictionValues = _restrictionValues;
  130. if ((null != restrictionValues) && (0 < restrictionValues.Length)) {
  131. StringBuilder builder = new StringBuilder();
  132. for(int i = 0; i < restrictionValues.Length; ++i) {
  133. if (!ADP.IsEmpty(restrictionValues[i])) {
  134. builder.Append(restrictionValues[i]);
  135. builder.Append("=;");
  136. }
  137. #if DEBUG
  138. else {
  139. Debug.Assert(false, "empty restriction");
  140. }
  141. #endif
  142. }
  143. restrictions = builder.ToString();
  144. }
  145. }
  146. return ((null != restrictions) ? restrictions: "");
  147. }
  148. }
  149. internal string this[string keyword] {
  150. get { return (string)_parsetable[keyword]; }
  151. }
  152. internal bool ContainsKey(string keyword) {
  153. return _parsetable.ContainsKey(keyword);
  154. }
  155. internal DBConnectionString Intersect(DBConnectionString entry) {
  156. KeyRestrictionBehavior behavior = _behavior;
  157. string[] restrictionValues = null;
  158. if (null == entry) {
  159. //Debug.WriteLine("0 entry AllowNothing");
  160. behavior = KeyRestrictionBehavior.AllowOnly;
  161. }
  162. else if (this._behavior != entry._behavior) { // subset of the AllowOnly array
  163. behavior = KeyRestrictionBehavior.AllowOnly;
  164. if (KeyRestrictionBehavior.AllowOnly == entry._behavior) { // this PreventUsage and entry AllowOnly
  165. if (!ADP.IsEmptyArray(_restrictionValues)) {
  166. if (!ADP.IsEmptyArray(entry._restrictionValues)) {
  167. //Debug.WriteLine("1 this PreventUsage with restrictions and entry AllowOnly with restrictions");
  168. restrictionValues = NewRestrictionAllowOnly(entry._restrictionValues, _restrictionValues);
  169. }
  170. else {
  171. //Debug.WriteLine("2 this PreventUsage with restrictions and entry AllowOnly with no restrictions");
  172. }
  173. }
  174. else {
  175. //Debug.WriteLine("3/4 this PreventUsage with no restrictions and entry AllowOnly");
  176. restrictionValues = entry._restrictionValues;
  177. }
  178. }
  179. else if (!ADP.IsEmptyArray(_restrictionValues)) { // this AllowOnly and entry PreventUsage
  180. if (!ADP.IsEmptyArray(entry._restrictionValues)) {
  181. //Debug.WriteLine("5 this AllowOnly with restrictions and entry PreventUsage with restrictions");
  182. restrictionValues = NewRestrictionAllowOnly(_restrictionValues, entry._restrictionValues);
  183. }
  184. else {
  185. //Debug.WriteLine("6 this AllowOnly and entry PreventUsage with no restrictions");
  186. restrictionValues = _restrictionValues;
  187. }
  188. }
  189. else {
  190. //Debug.WriteLine("7/8 this AllowOnly with no restrictions and entry PreventUsage");
  191. }
  192. }
  193. else if (KeyRestrictionBehavior.PreventUsage == this._behavior) { // both PreventUsage
  194. if (ADP.IsEmptyArray(_restrictionValues)) {
  195. //Debug.WriteLine("9/10 both PreventUsage and this with no restrictions");
  196. restrictionValues = entry._restrictionValues;
  197. }
  198. else if (ADP.IsEmptyArray(entry._restrictionValues)) {
  199. //Debug.WriteLine("11 both PreventUsage and entry with no restrictions");
  200. restrictionValues = _restrictionValues;
  201. }
  202. else {
  203. //Debug.WriteLine("12 both PreventUsage with restrictions");
  204. restrictionValues = NoDuplicateUnion(_restrictionValues, entry._restrictionValues);
  205. }
  206. }
  207. else if (!ADP.IsEmptyArray(_restrictionValues) && !ADP.IsEmptyArray(entry._restrictionValues)) { // both AllowOnly with restrictions
  208. if (this._restrictionValues.Length <= entry._restrictionValues.Length) {
  209. //Debug.WriteLine("13a this AllowOnly with restrictions and entry AllowOnly with restrictions");
  210. restrictionValues = NewRestrictionIntersect(_restrictionValues, entry._restrictionValues);
  211. }
  212. else {
  213. //Debug.WriteLine("13b this AllowOnly with restrictions and entry AllowOnly with restrictions");
  214. restrictionValues = NewRestrictionIntersect(entry._restrictionValues, _restrictionValues);
  215. }
  216. }
  217. else { // both AllowOnly
  218. //Debug.WriteLine("14/15/16 this AllowOnly and entry AllowOnly but no restrictions");
  219. }
  220. // verify _hasPassword & _parsetable are in sync between Everett/Whidbey
  221. Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this");
  222. Debug.Assert(null == entry || !entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry");
  223. DBConnectionString value = new DBConnectionString(this, restrictionValues, behavior);
  224. ValidateCombinedSet(this, value);
  225. ValidateCombinedSet(entry, value);
  226. return value;
  227. }
  228. [Conditional("DEBUG")]
  229. private void ValidateCombinedSet(DBConnectionString componentSet, DBConnectionString combinedSet) {
  230. Debug.Assert(combinedSet != null, "The combined connection string should not be null");
  231. if ((componentSet != null) && (combinedSet._restrictionValues != null) && (componentSet._restrictionValues != null)) {
  232. if (componentSet._behavior == KeyRestrictionBehavior.AllowOnly) {
  233. if (combinedSet._behavior == KeyRestrictionBehavior.AllowOnly) {
  234. // Component==Allow, Combined==Allow
  235. // All values in the Combined Set should also be in the Component Set
  236. // Combined - Component == null
  237. Debug.Assert(combinedSet._restrictionValues.Except(componentSet._restrictionValues).Count() == 0, "Combined set allows values not allowed by component set");
  238. }
  239. else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
  240. // Component==Allow, Combined==PreventUsage
  241. // Preventions override allows, so there is nothing to check here
  242. }
  243. else {
  244. Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
  245. }
  246. }
  247. else if (componentSet._behavior == KeyRestrictionBehavior.PreventUsage) {
  248. if (combinedSet._behavior == KeyRestrictionBehavior.AllowOnly) {
  249. // Component==PreventUsage, Combined==Allow
  250. // There shouldn't be any of the values from the Component Set in the Combined Set
  251. // Intersect(Component, Combined) == null
  252. Debug.Assert(combinedSet._restrictionValues.Intersect(componentSet._restrictionValues).Count() == 0, "Combined values allows values prevented by component set");
  253. }
  254. else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) {
  255. // Component==PreventUsage, Combined==PreventUsage
  256. // All values in the Component Set should also be in the Combined Set
  257. // Component - Combined == null
  258. Debug.Assert(componentSet._restrictionValues.Except(combinedSet._restrictionValues).Count() == 0, "Combined values does not prevent all of the values prevented by the component set");
  259. }
  260. else {
  261. Debug.Assert(false, string.Format("Unknown behavior for combined set: {0}", combinedSet._behavior));
  262. }
  263. }
  264. else {
  265. Debug.Assert(false, string.Format("Unknown behavior for component set: {0}", componentSet._behavior));
  266. }
  267. }
  268. }
  269. private bool IsRestrictedKeyword(string key) {
  270. // restricted if not found
  271. return ((null == _restrictionValues) || (0 > Array.BinarySearch(_restrictionValues, key, StringComparer.Ordinal)));
  272. }
  273. internal bool IsSupersetOf(DBConnectionString entry) {
  274. Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this");
  275. Debug.Assert(!entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry");
  276. switch(_behavior) {
  277. case KeyRestrictionBehavior.AllowOnly:
  278. // every key must either be in the resticted connection string or in the allowed keywords
  279. // keychain may contain duplicates, but it is better than GetEnumerator on _parsetable.Keys
  280. for(NameValuePair current = entry.KeyChain; null != current; current = current.Next) {
  281. if (!ContainsKey(current.Name) && IsRestrictedKeyword(current.Name)) {
  282. return false;
  283. }
  284. }
  285. break;
  286. case KeyRestrictionBehavior.PreventUsage:
  287. // every key can not be in the restricted keywords (even if in the restricted connection string)
  288. if (null != _restrictionValues) {
  289. foreach(string restriction in _restrictionValues) {
  290. if (entry.ContainsKey(restriction)) {
  291. return false;
  292. }
  293. }
  294. }
  295. break;
  296. default:
  297. Debug.Assert(false, "invalid KeyRestrictionBehavior");
  298. throw ADP.InvalidKeyRestrictionBehavior(_behavior);
  299. }
  300. return true;
  301. }
  302. static private string[] NewRestrictionAllowOnly(string[] allowonly, string[] preventusage) {
  303. List<string> newlist = null;
  304. for (int i = 0; i < allowonly.Length; ++i) {
  305. if (0 > Array.BinarySearch(preventusage, allowonly[i], StringComparer.Ordinal)) {
  306. if (null == newlist) {
  307. newlist = new List<string>();
  308. }
  309. newlist.Add(allowonly[i]);
  310. }
  311. }
  312. string[] restrictionValues = null;
  313. if (null != newlist) {
  314. restrictionValues = newlist.ToArray();
  315. }
  316. Verify(restrictionValues);
  317. return restrictionValues;
  318. }
  319. static private string[] NewRestrictionIntersect(string[] a, string[] b) {
  320. List<string> newlist = null;
  321. for (int i = 0; i < a.Length; ++i) {
  322. if (0 <= Array.BinarySearch(b, a[i], StringComparer.Ordinal)) {
  323. if (null == newlist) {
  324. newlist = new List<string>();
  325. }
  326. newlist.Add(a[i]);
  327. }
  328. }
  329. string[] restrictionValues = null;
  330. if (newlist != null) {
  331. restrictionValues = newlist.ToArray();
  332. }
  333. Verify(restrictionValues);
  334. return restrictionValues;
  335. }
  336. static private string[] NoDuplicateUnion(string[] a, string[] b) {
  337. #if DEBUG
  338. Debug.Assert(null != a && 0 < a.Length, "empty a");
  339. Debug.Assert(null != b && 0 < b.Length, "empty b");
  340. Verify(a);
  341. Verify(b);
  342. #endif
  343. List<string> newlist = new List<string>(a.Length + b.Length);
  344. for(int i = 0; i < a.Length; ++i) {
  345. newlist.Add(a[i]);
  346. }
  347. for(int i = 0; i < b.Length; ++i) { // find duplicates
  348. if (0 > Array.BinarySearch(a, b[i], StringComparer.Ordinal)) {
  349. newlist.Add(b[i]);
  350. }
  351. }
  352. string[] restrictionValues = newlist.ToArray();
  353. Array.Sort(restrictionValues, StringComparer.Ordinal);
  354. Verify(restrictionValues);
  355. return restrictionValues;
  356. }
  357. private static string[] ParseRestrictions(string restrictions, Hashtable synonyms) {
  358. #if DEBUG
  359. if (Bid.AdvancedOn) {
  360. Bid.Trace("<comm.DBConnectionString|INFO|ADV> Restrictions='%ls'\n", restrictions);
  361. }
  362. #endif
  363. List<string> restrictionValues = new List<string>();
  364. StringBuilder buffer = new StringBuilder(restrictions.Length);
  365. int nextStartPosition = 0;
  366. int endPosition = restrictions.Length;
  367. while (nextStartPosition < endPosition) {
  368. int startPosition = nextStartPosition;
  369. string keyname, keyvalue; // since parsing restrictions ignores values, it doesn't matter if we use ODBC rules or OLEDB rules
  370. nextStartPosition = DbConnectionOptions.GetKeyValuePair(restrictions, startPosition, buffer, false, out keyname, out keyvalue);
  371. if (!ADP.IsEmpty(keyname)) {
  372. #if DEBUG
  373. if (Bid.AdvancedOn) {
  374. Bid.Trace("<comm.DBConnectionString|INFO|ADV> KeyName='%ls'\n", keyname);
  375. }
  376. #endif
  377. string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname); // MDAC 85144
  378. if (ADP.IsEmpty(realkeyname)) {
  379. throw ADP.KeywordNotSupported(keyname);
  380. }
  381. restrictionValues.Add(realkeyname);
  382. }
  383. }
  384. return RemoveDuplicates(restrictionValues.ToArray());
  385. }
  386. static internal string[] RemoveDuplicates(string[] restrictions) {
  387. int count = restrictions.Length;
  388. if (0 < count) {
  389. Array.Sort(restrictions, StringComparer.Ordinal);
  390. for (int i = 1; i < restrictions.Length; ++i) {
  391. string prev = restrictions[i-1];
  392. if ((0 == prev.Length) || (prev == restrictions[i])) {
  393. restrictions[i-1] = null;
  394. count--;
  395. }
  396. }
  397. if (0 == restrictions[restrictions.Length-1].Length) {
  398. restrictions[restrictions.Length-1] = null;
  399. count--;
  400. }
  401. if (count != restrictions.Length) {
  402. string[] tmp = new String[count];
  403. count = 0;
  404. for (int i = 0; i < restrictions.Length; ++i) {
  405. if (null != restrictions[i]) {
  406. tmp[count++] = restrictions[i];
  407. }
  408. }
  409. restrictions = tmp;
  410. }
  411. }
  412. Verify(restrictions);
  413. return restrictions;
  414. }
  415. [ConditionalAttribute("DEBUG")]
  416. private static void Verify(string[] restrictionValues) {
  417. if (null != restrictionValues) {
  418. for (int i = 1; i < restrictionValues.Length; ++i) {
  419. Debug.Assert(!ADP.IsEmpty(restrictionValues[i-1]), "empty restriction");
  420. Debug.Assert(!ADP.IsEmpty(restrictionValues[i]), "empty restriction");
  421. Debug.Assert(0 >= StringComparer.Ordinal.Compare(restrictionValues[i-1], restrictionValues[i]));
  422. }
  423. }
  424. }
  425. }
  426. }