DbConnectionOptions.cs 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DBConnectionOptions.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.Data;
  12. using System.Diagnostics;
  13. using System.Globalization;
  14. using System.Runtime.Serialization;
  15. using System.Security.Permissions;
  16. using System.Text;
  17. using System.Text.RegularExpressions;
  18. using System.Runtime.Versioning;
  19. internal class DbConnectionOptions {
  20. // instances of this class are intended to be immutable, i.e readonly
  21. // used by pooling classes so it is much easier to verify correctness
  22. // when not worried about the class being modified during execution
  23. #if DEBUG
  24. /*private const string ConnectionStringPatternV1 =
  25. "[\\s;]*"
  26. +"(?<key>([^=\\s]|\\s+[^=\\s]|\\s+==|==)+)"
  27. + "\\s*=(?!=)\\s*"
  28. +"(?<value>("
  29. + "(" + "\"" + "([^\"]|\"\")*" + "\"" + ")"
  30. + "|"
  31. + "(" + "'" + "([^']|'')*" + "'" + ")"
  32. + "|"
  33. + "(" + "(?![\"'])" + "([^\\s;]|\\s+[^\\s;])*" + "(?<![\"'])" + ")"
  34. + "))"
  35. + "[\\s;]*"
  36. ;*/
  37. private const string ConnectionStringPattern = // may not contain embedded null except trailing last value
  38. "([\\s;]*" // leading whitespace and extra semicolons
  39. + "(?![\\s;])" // key does not start with space or semicolon
  40. + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)" // allow any visible character for keyname except '=' which must quoted as '=='
  41. + "\\s*=(?!=)\\s*" // the equal sign divides the key and value parts
  42. + "(?<value>"
  43. + "(\"([^\"\u0000]|\"\")*\")" // double quoted string, " must be quoted as ""
  44. + "|"
  45. + "('([^'\u0000]|'')*')" // single quoted string, ' must be quoted as ''
  46. + "|"
  47. + "((?![\"'\\s])" // unquoted value must not start with " or ' or space, would also like = but too late to change
  48. + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted
  49. + "(?<![\"']))" // unquoted value must not stop with " or '
  50. + ")(\\s*)(;|[\u0000\\s]*$)" // whitespace after value up to semicolon or end-of-line
  51. + ")*" // repeat the key-value pair
  52. + "[\\s;]*[\u0000\\s]*" // traling whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end
  53. ;
  54. private const string ConnectionStringPatternOdbc = // may not contain embedded null except trailing last value
  55. "([\\s;]*" // leading whitespace and extra semicolons
  56. + "(?![\\s;])" // key does not start with space or semicolon
  57. + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}])+)" // allow any visible character for keyname except '='
  58. + "\\s*=\\s*" // the equal sign divides the key and value parts
  59. + "(?<value>"
  60. + "(\\{([^\\}\u0000]|\\}\\})*\\})" // quoted string, starts with { and ends with }
  61. + "|"
  62. + "((?![\\{\\s])" // unquoted value must not start with { or space, would also like = but too late to change
  63. + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted
  64. + ")" // VSTFDEVDIV 94761: although the spec does not allow {}
  65. // embedded within a value, the retail code does.
  66. // + "(?<![\\}]))" // unquoted value must not stop with }
  67. + ")(\\s*)(;|[\u0000\\s]*$)" // whitespace after value up to semicolon or end-of-line
  68. + ")*" // repeat the key-value pair
  69. + "[\\s;]*[\u0000\\s]*" // traling whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end
  70. ;
  71. private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
  72. private static readonly Regex ConnectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
  73. #endif
  74. private const string ConnectionStringValidKeyPattern = "^(?![;\\s])[^\\p{Cc}]+(?<!\\s)$"; // key not allowed to start with semi-colon or space or contain non-visible characters or end with space
  75. private const string ConnectionStringValidValuePattern = "^[^\u0000]*$"; // value not allowed to contain embedded null
  76. private const string ConnectionStringQuoteValuePattern = "^[^\"'=;\\s\\p{Cc}]*$"; // generally do not quote the value if it matches the pattern
  77. private const string ConnectionStringQuoteOdbcValuePattern = "^\\{([^\\}\u0000]|\\}\\})*\\}$"; // do not quote odbc value if it matches this pattern
  78. internal const string DataDirectory = "|datadirectory|";
  79. private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
  80. private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
  81. private static readonly Regex ConnectionStringQuoteValueRegex = new Regex(ConnectionStringQuoteValuePattern, RegexOptions.Compiled);
  82. private static readonly Regex ConnectionStringQuoteOdbcValueRegex = new Regex(ConnectionStringQuoteOdbcValuePattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
  83. // connection string common keywords
  84. private static class KEY {
  85. internal const string Integrated_Security = "integrated security";
  86. internal const string Password = "password";
  87. internal const string Persist_Security_Info = "persist security info";
  88. internal const string User_ID = "user id";
  89. };
  90. // known connection string common synonyms
  91. private static class SYNONYM {
  92. internal const string Pwd = "pwd";
  93. internal const string UID = "uid";
  94. };
  95. private readonly string _usersConnectionString;
  96. private readonly Hashtable _parsetable;
  97. internal readonly NameValuePair _keyChain;
  98. internal readonly bool HasPasswordKeyword;
  99. internal readonly bool HasUserIdKeyword;
  100. // differences between OleDb and Odbc
  101. // ODBC:
  102. // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcsqldriverconnect.asp
  103. // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbcsql/od_odbc_d_4x4k.asp
  104. // do not support == -> = in keywords
  105. // first key-value pair wins
  106. // quote values using \{ and \}, only driver= and pwd= appear to generically allow quoting
  107. // do not strip quotes from value, or add quotes except for driver keyword
  108. // OLEDB:
  109. // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/oledb/htm/oledbconnectionstringsyntax.asp
  110. // support == -> = in keywords
  111. // last key-value pair wins
  112. // quote values using \" or \'
  113. // strip quotes from value
  114. internal readonly bool UseOdbcRules;
  115. private System.Security.PermissionSet _permissionset;
  116. // called by derived classes that may cache based on connectionString
  117. public DbConnectionOptions(string connectionString)
  118. : this(connectionString, null, false) {
  119. }
  120. // synonyms hashtable is meant to be read-only translation of parsed string
  121. // keywords/synonyms to a known keyword string
  122. public DbConnectionOptions(string connectionString, Hashtable synonyms, bool useOdbcRules) {
  123. UseOdbcRules = useOdbcRules;
  124. _parsetable = new Hashtable();
  125. _usersConnectionString = ((null != connectionString) ? connectionString : "");
  126. // first pass on parsing, initial syntax check
  127. if (0 < _usersConnectionString.Length) {
  128. _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, UseOdbcRules);
  129. HasPasswordKeyword = (_parsetable.ContainsKey(KEY.Password) || _parsetable.ContainsKey(SYNONYM.Pwd));
  130. HasUserIdKeyword = (_parsetable.ContainsKey(KEY.User_ID) || _parsetable.ContainsKey(SYNONYM.UID));
  131. }
  132. }
  133. protected DbConnectionOptions(DbConnectionOptions connectionOptions) { // Clone used by SqlConnectionString
  134. _usersConnectionString = connectionOptions._usersConnectionString;
  135. HasPasswordKeyword = connectionOptions.HasPasswordKeyword;
  136. HasUserIdKeyword = connectionOptions.HasUserIdKeyword;
  137. UseOdbcRules = connectionOptions.UseOdbcRules;
  138. _parsetable = connectionOptions._parsetable;
  139. _keyChain = connectionOptions._keyChain;
  140. }
  141. public string UsersConnectionString(bool hidePassword) {
  142. return UsersConnectionString(hidePassword, false);
  143. }
  144. private string UsersConnectionString(bool hidePassword, bool forceHidePassword) {
  145. string connectionString = _usersConnectionString;
  146. if (HasPasswordKeyword && (forceHidePassword || (hidePassword && !HasPersistablePassword))) {
  147. ReplacePasswordPwd(out connectionString, false);
  148. }
  149. return ((null != connectionString) ? connectionString : "");
  150. }
  151. internal string UsersConnectionStringForTrace() {
  152. return UsersConnectionString(true, true);
  153. }
  154. internal bool HasBlankPassword {
  155. get {
  156. if (!ConvertValueToIntegratedSecurity()) {
  157. if (_parsetable.ContainsKey(KEY.Password)) {
  158. return ADP.IsEmpty((string)_parsetable[KEY.Password]);
  159. } else
  160. if (_parsetable.ContainsKey(SYNONYM.Pwd)) {
  161. return ADP.IsEmpty((string)_parsetable[SYNONYM.Pwd]); // MDAC 83097
  162. } else {
  163. return ((_parsetable.ContainsKey(KEY.User_ID) && !ADP.IsEmpty((string)_parsetable[KEY.User_ID])) || (_parsetable.ContainsKey(SYNONYM.UID) && !ADP.IsEmpty((string)_parsetable[SYNONYM.UID])));
  164. }
  165. }
  166. return false;
  167. }
  168. }
  169. internal bool HasPersistablePassword {
  170. get {
  171. if (HasPasswordKeyword) {
  172. return ConvertValueToBoolean(KEY.Persist_Security_Info, false);
  173. }
  174. return true; // no password means persistable password so we don't have to munge
  175. }
  176. }
  177. public bool IsEmpty {
  178. get { return (null == _keyChain); }
  179. }
  180. internal Hashtable Parsetable {
  181. get { return _parsetable; }
  182. }
  183. public ICollection Keys {
  184. get { return _parsetable.Keys; }
  185. }
  186. public string this[string keyword] {
  187. get { return (string)_parsetable[keyword]; }
  188. }
  189. internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string keyValue, bool useOdbcRules) {
  190. ADP.CheckArgumentNull(builder, "builder");
  191. ADP.CheckArgumentLength(keyName, "keyName");
  192. if ((null == keyName) || !ConnectionStringValidKeyRegex.IsMatch(keyName)) {
  193. throw ADP.InvalidKeyname(keyName);
  194. }
  195. if ((null != keyValue) && !IsValueValidInternal(keyValue)) {
  196. throw ADP.InvalidValue(keyName);
  197. }
  198. if ((0 < builder.Length) && (';' != builder[builder.Length-1])) {
  199. builder.Append(";");
  200. }
  201. if (useOdbcRules) {
  202. builder.Append(keyName);
  203. }
  204. else {
  205. builder.Append(keyName.Replace("=", "=="));
  206. }
  207. builder.Append("=");
  208. if (null != keyValue) { // else <keyword>=;
  209. if (useOdbcRules) {
  210. if ((0 < keyValue.Length) &&
  211. (('{' == keyValue[0]) || (0 <= keyValue.IndexOf(';')) || (0 == String.Compare(DbConnectionStringKeywords.Driver, keyName, StringComparison.OrdinalIgnoreCase))) &&
  212. !ConnectionStringQuoteOdbcValueRegex.IsMatch(keyValue))
  213. {
  214. // always quote Driver value (required for ODBC Version 2.65 and earlier)
  215. // always quote values that contain a ';'
  216. builder.Append('{').Append(keyValue.Replace("}", "}}")).Append('}');
  217. }
  218. else {
  219. builder.Append(keyValue);
  220. }
  221. }
  222. else if (ConnectionStringQuoteValueRegex.IsMatch(keyValue)) {
  223. // <value> -> <value>
  224. builder.Append(keyValue);
  225. }
  226. else if ((-1 != keyValue.IndexOf('\"')) && (-1 == keyValue.IndexOf('\''))) {
  227. // <val"ue> -> <'val"ue'>
  228. builder.Append('\'');
  229. builder.Append(keyValue);
  230. builder.Append('\'');
  231. }
  232. else {
  233. // <val'ue> -> <"val'ue">
  234. // <=value> -> <"=value">
  235. // <;value> -> <";value">
  236. // < value> -> <" value">
  237. // <va lue> -> <"va lue">
  238. // <va'"lue> -> <"va'""lue">
  239. builder.Append('\"');
  240. builder.Append(keyValue.Replace("\"", "\"\""));
  241. builder.Append('\"');
  242. }
  243. }
  244. }
  245. public bool ConvertValueToBoolean(string keyName, bool defaultValue) {
  246. object value = _parsetable[keyName];
  247. if (null == value) {
  248. return defaultValue;
  249. }
  250. return ConvertValueToBooleanInternal(keyName, (string) value);
  251. }
  252. internal static bool ConvertValueToBooleanInternal(string keyName, string stringValue) {
  253. if (CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
  254. return true;
  255. else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
  256. return false;
  257. else {
  258. string tmp = stringValue.Trim(); // Remove leading & trailing white space.
  259. if (CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
  260. return true;
  261. else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
  262. return false;
  263. else {
  264. throw ADP.InvalidConnectionOptionValue(keyName);
  265. }
  266. }
  267. }
  268. // same as Boolean, but with SSPI thrown in as valid yes
  269. public bool ConvertValueToIntegratedSecurity() {
  270. object value = _parsetable[KEY.Integrated_Security];
  271. if (null == value) {
  272. return false;
  273. }
  274. return ConvertValueToIntegratedSecurityInternal((string) value);
  275. }
  276. internal bool ConvertValueToIntegratedSecurityInternal(string stringValue) {
  277. if (CompareInsensitiveInvariant(stringValue, "sspi") || CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes"))
  278. return true;
  279. else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no"))
  280. return false;
  281. else {
  282. string tmp = stringValue.Trim(); // Remove leading & trailing white space.
  283. if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes"))
  284. return true;
  285. else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no"))
  286. return false;
  287. else {
  288. throw ADP.InvalidConnectionOptionValue(KEY.Integrated_Security);
  289. }
  290. }
  291. }
  292. public int ConvertValueToInt32(string keyName, int defaultValue) {
  293. object value = _parsetable[keyName];
  294. if (null == value) {
  295. return defaultValue;
  296. }
  297. return ConvertToInt32Internal(keyName, (string) value);
  298. }
  299. internal static int ConvertToInt32Internal(string keyname, string stringValue) {
  300. try {
  301. return System.Int32.Parse(stringValue, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture);
  302. }
  303. catch (FormatException e) {
  304. throw ADP.InvalidConnectionOptionValue(keyname, e);
  305. }
  306. catch (OverflowException e) {
  307. throw ADP.InvalidConnectionOptionValue(keyname, e);
  308. }
  309. }
  310. public string ConvertValueToString(string keyName, string defaultValue) {
  311. string value = (string)_parsetable[keyName];
  312. return ((null != value) ? value : defaultValue);
  313. }
  314. static private bool CompareInsensitiveInvariant(string strvalue, string strconst) {
  315. return (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst));
  316. }
  317. public bool ContainsKey(string keyword) {
  318. return _parsetable.ContainsKey(keyword);
  319. }
  320. protected internal virtual System.Security.PermissionSet CreatePermissionSet() {
  321. return null;
  322. }
  323. internal void DemandPermission() {
  324. if (null == _permissionset) {
  325. _permissionset = CreatePermissionSet();
  326. }
  327. _permissionset.Demand();
  328. }
  329. protected internal virtual string Expand() {
  330. return _usersConnectionString;
  331. }
  332. // SxS notes:
  333. // * this method queries "DataDirectory" value from the current AppDomain.
  334. // This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource".
  335. // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out.
  336. [ResourceExposure(ResourceScope.None)]
  337. [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
  338. internal static string ExpandDataDirectory(string keyword, string value, ref string datadir) {
  339. string fullPath = null;
  340. if ((null != value) && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase)) {
  341. string rootFolderPath = datadir;
  342. if (null == rootFolderPath) {
  343. // find the replacement path
  344. object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
  345. rootFolderPath = (rootFolderObject as string);
  346. if ((null != rootFolderObject) && (null == rootFolderPath)) {
  347. throw ADP.InvalidDataDirectory();
  348. }
  349. else if (ADP.IsEmpty(rootFolderPath)) {
  350. rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
  351. }
  352. if (null == rootFolderPath) {
  353. rootFolderPath = "";
  354. }
  355. // cache the |DataDir| for ExpandDataDirectories
  356. datadir = rootFolderPath;
  357. }
  358. // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw
  359. int fileNamePosition = DataDirectory.Length; // filename starts right after the '|datadirectory|' keyword
  360. bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length-1] == '\\';
  361. bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\';
  362. // replace |datadirectory| with root folder path
  363. if (!rootFolderEndsWith && !fileNameStartsWith) {
  364. // need to insert '\'
  365. fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
  366. }
  367. else if (rootFolderEndsWith && fileNameStartsWith) {
  368. // need to strip one out
  369. fullPath = rootFolderPath + value.Substring(fileNamePosition+1);
  370. }
  371. else {
  372. // simply concatenate the strings
  373. fullPath = rootFolderPath + value.Substring(fileNamePosition);
  374. }
  375. // verify root folder path is a real path without unexpected "..\"
  376. if (!ADP.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal)) {
  377. throw ADP.InvalidConnectionOptionValue(keyword);
  378. }
  379. }
  380. return fullPath;
  381. }
  382. internal string ExpandDataDirectories(ref string filename, ref int position) {
  383. string value = null;
  384. StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
  385. string datadir = null;
  386. int copyPosition = 0;
  387. bool expanded = false;
  388. for(NameValuePair current = _keyChain; null != current; current = current.Next) {
  389. value = current.Value;
  390. // remove duplicate keyswords from connectionstring
  391. //if ((object)this[current.Name] != (object)value) {
  392. // expanded = true;
  393. // copyPosition += current.Length;
  394. // continue;
  395. //}
  396. // There is a set of keywords we explictly do NOT want to expand |DataDirectory| on
  397. if (UseOdbcRules) {
  398. switch(current.Name) {
  399. case DbConnectionOptionKeywords.Driver:
  400. case DbConnectionOptionKeywords.Pwd:
  401. case DbConnectionOptionKeywords.UID:
  402. break;
  403. default:
  404. value = ExpandDataDirectory(current.Name, value, ref datadir);
  405. break;
  406. }
  407. }
  408. else {
  409. switch(current.Name) {
  410. case DbConnectionOptionKeywords.Provider:
  411. case DbConnectionOptionKeywords.DataProvider:
  412. case DbConnectionOptionKeywords.RemoteProvider:
  413. case DbConnectionOptionKeywords.ExtendedProperties:
  414. case DbConnectionOptionKeywords.UserID:
  415. case DbConnectionOptionKeywords.Password:
  416. case DbConnectionOptionKeywords.UID:
  417. case DbConnectionOptionKeywords.Pwd:
  418. break;
  419. default:
  420. value = ExpandDataDirectory(current.Name, value, ref datadir);
  421. break;
  422. }
  423. }
  424. if (null == value) {
  425. value = current.Value;
  426. }
  427. if (UseOdbcRules || (DbConnectionOptionKeywords.FileName != current.Name)) {
  428. if (value != current.Value) {
  429. expanded = true;
  430. AppendKeyValuePairBuilder(builder, current.Name, value, UseOdbcRules);
  431. builder.Append(';');
  432. }
  433. else {
  434. builder.Append(_usersConnectionString, copyPosition, current.Length);
  435. }
  436. }
  437. else {
  438. // strip out 'File Name=myconnection.udl' for OleDb
  439. // remembering is value for which UDL file to open
  440. // and where to insert the strnig
  441. expanded = true;
  442. filename = value;
  443. position = builder.Length;
  444. }
  445. copyPosition += current.Length;
  446. }
  447. if (expanded) {
  448. value = builder.ToString();
  449. }
  450. else {
  451. value = null;
  452. }
  453. return value;
  454. }
  455. internal string ExpandKeyword(string keyword, string replacementValue) {
  456. // preserve duplicates, updated keyword value with replacement value
  457. // if keyword not specified, append to end of the string
  458. bool expanded = false;
  459. int copyPosition = 0;
  460. StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
  461. for(NameValuePair current = _keyChain; null != current; current = current.Next) {
  462. if ((current.Name == keyword) && (current.Value == this[keyword])) {
  463. // only replace the parse end-result value instead of all values
  464. // so that when duplicate-keywords occur other original values remain in place
  465. AppendKeyValuePairBuilder(builder, current.Name, replacementValue, UseOdbcRules);
  466. builder.Append(';');
  467. expanded = true;
  468. }
  469. else {
  470. builder.Append(_usersConnectionString, copyPosition, current.Length);
  471. }
  472. copyPosition += current.Length;
  473. }
  474. if (!expanded) {
  475. //
  476. Debug.Assert(!UseOdbcRules, "ExpandKeyword not ready for Odbc");
  477. AppendKeyValuePairBuilder(builder, keyword, replacementValue, UseOdbcRules);
  478. }
  479. return builder.ToString();
  480. }
  481. #if DEBUG
  482. [System.Diagnostics.Conditional("DEBUG")]
  483. private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Hashtable synonyms) {
  484. if (Bid.AdvancedOn) {
  485. Debug.Assert(keyname == keyname.ToLower(CultureInfo.InvariantCulture), "missing ToLower");
  486. string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
  487. if ((KEY.Password != realkeyname) && (SYNONYM.Pwd != realkeyname)) { // don't trace passwords ever!
  488. if (null != keyvalue) {
  489. Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls', KeyValue='%ls'\n", keyname, keyvalue);
  490. }
  491. else {
  492. Bid.Trace("<comm.DbConnectionOptions|INFO|ADV> KeyName='%ls'\n", keyname);
  493. }
  494. }
  495. }
  496. }
  497. #endif
  498. static private string GetKeyName(StringBuilder buffer) {
  499. int count = buffer.Length;
  500. while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
  501. count--; // trailing whitespace
  502. }
  503. return buffer.ToString(0, count).ToLower(CultureInfo.InvariantCulture);
  504. }
  505. static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace) {
  506. int count = buffer.Length;
  507. int index = 0;
  508. if (trimWhitespace) {
  509. while ((index < count) && Char.IsWhiteSpace(buffer[index])) {
  510. index++; // leading whitespace
  511. }
  512. while ((0 < count) && Char.IsWhiteSpace(buffer[count-1])) {
  513. count--; // trailing whitespace
  514. }
  515. }
  516. return buffer.ToString(index, count - index);
  517. }
  518. // transistion states used for parsing
  519. private enum ParserState {
  520. NothingYet=1, //start point
  521. Key,
  522. KeyEqual,
  523. KeyEnd,
  524. UnquotedValue,
  525. DoubleQuoteValue,
  526. DoubleQuoteValueQuote,
  527. SingleQuoteValue,
  528. SingleQuoteValueQuote,
  529. BraceQuoteValue,
  530. BraceQuoteValueQuote,
  531. QuotedValueEnd,
  532. NullTermination,
  533. };
  534. static internal int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, bool useOdbcRules, out string keyname, out string keyvalue) {
  535. int startposition = currentPosition;
  536. buffer.Length = 0;
  537. keyname = null;
  538. keyvalue = null;
  539. char currentChar = '\0';
  540. ParserState parserState = ParserState.NothingYet;
  541. int length = connectionString.Length;
  542. for (; currentPosition < length; ++currentPosition) {
  543. currentChar = connectionString[currentPosition];
  544. switch(parserState) {
  545. case ParserState.NothingYet: // [\\s;]*
  546. if ((';' == currentChar) || Char.IsWhiteSpace(currentChar)) {
  547. continue;
  548. }
  549. if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
  550. if (Char.IsControl(currentChar)) { throw ADP.ConnectionStringSyntax(startposition); }
  551. startposition = currentPosition;
  552. if ('=' != currentChar) { // MDAC 86902
  553. parserState = ParserState.Key;
  554. break;
  555. }
  556. else {
  557. parserState = ParserState.KeyEqual;
  558. continue;
  559. }
  560. case ParserState.Key: // (?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)
  561. if ('=' == currentChar) { parserState = ParserState.KeyEqual; continue; }
  562. if (Char.IsWhiteSpace(currentChar)) { break; }
  563. if (Char.IsControl(currentChar)) { throw ADP.ConnectionStringSyntax(startposition); }
  564. break;
  565. case ParserState.KeyEqual: // \\s*=(?!=)\\s*
  566. if (!useOdbcRules && '=' == currentChar) { parserState = ParserState.Key; break; }
  567. keyname = GetKeyName(buffer);
  568. if (ADP.IsEmpty(keyname)) { throw ADP.ConnectionStringSyntax(startposition); }
  569. buffer.Length = 0;
  570. parserState = ParserState.KeyEnd;
  571. goto case ParserState.KeyEnd;
  572. case ParserState.KeyEnd:
  573. if (Char.IsWhiteSpace(currentChar)) { continue; }
  574. if (useOdbcRules) {
  575. if ('{' == currentChar) { parserState = ParserState.BraceQuoteValue; break; }
  576. }
  577. else {
  578. if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; continue; }
  579. if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; continue; }
  580. }
  581. if (';' == currentChar) { goto ParserExit; }
  582. if ('\0' == currentChar) { goto ParserExit; }
  583. if (Char.IsControl(currentChar)) { throw ADP.ConnectionStringSyntax(startposition); }
  584. parserState = ParserState.UnquotedValue;
  585. break;
  586. case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(?<![\"']))"
  587. if (Char.IsWhiteSpace(currentChar)) { break; }
  588. if (Char.IsControl(currentChar) || ';' == currentChar) { goto ParserExit; }
  589. break;
  590. case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
  591. if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValueQuote; continue; }
  592. if ('\0' == currentChar) { throw ADP.ConnectionStringSyntax(startposition); }
  593. break;
  594. case ParserState.DoubleQuoteValueQuote:
  595. if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; break; }
  596. keyvalue = GetKeyValue(buffer, false);
  597. parserState = ParserState.QuotedValueEnd;
  598. goto case ParserState.QuotedValueEnd;
  599. case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
  600. if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValueQuote; continue; }
  601. if ('\0' == currentChar) { throw ADP.ConnectionStringSyntax(startposition); }
  602. break;
  603. case ParserState.SingleQuoteValueQuote:
  604. if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; break; }
  605. keyvalue = GetKeyValue(buffer, false);
  606. parserState = ParserState.QuotedValueEnd;
  607. goto case ParserState.QuotedValueEnd;
  608. case ParserState.BraceQuoteValue: // "(\\{([^\\}\u0000]|\\}\\})*\\})"
  609. if ('}' == currentChar) { parserState = ParserState.BraceQuoteValueQuote; break; }
  610. if ('\0' == currentChar) { throw ADP.ConnectionStringSyntax(startposition); }
  611. break;
  612. case ParserState.BraceQuoteValueQuote:
  613. if ('}' == currentChar) { parserState = ParserState.BraceQuoteValue; break; }
  614. keyvalue = GetKeyValue(buffer, false);
  615. parserState = ParserState.QuotedValueEnd;
  616. goto case ParserState.QuotedValueEnd;
  617. case ParserState.QuotedValueEnd:
  618. if (Char.IsWhiteSpace(currentChar)) { continue; }
  619. if (';' == currentChar) { goto ParserExit; }
  620. if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
  621. throw ADP.ConnectionStringSyntax(startposition); // unbalanced single quote
  622. case ParserState.NullTermination: // [\\s;\u0000]*
  623. if ('\0' == currentChar) { continue; }
  624. if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
  625. throw ADP.ConnectionStringSyntax(currentPosition);
  626. default:
  627. throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState1);
  628. }
  629. buffer.Append(currentChar);
  630. }
  631. ParserExit:
  632. switch (parserState) {
  633. case ParserState.Key:
  634. case ParserState.DoubleQuoteValue:
  635. case ParserState.SingleQuoteValue:
  636. case ParserState.BraceQuoteValue:
  637. // keyword not found/unbalanced double/single quote
  638. throw ADP.ConnectionStringSyntax(startposition);
  639. case ParserState.KeyEqual:
  640. // equal sign at end of line
  641. keyname = GetKeyName(buffer);
  642. if (ADP.IsEmpty(keyname)) { throw ADP.ConnectionStringSyntax(startposition); }
  643. break;
  644. case ParserState.UnquotedValue:
  645. // unquoted value at end of line
  646. keyvalue = GetKeyValue(buffer, true);
  647. char tmpChar = keyvalue[keyvalue.Length - 1];
  648. if (!useOdbcRules && (('\'' == tmpChar) || ('"' == tmpChar))) {
  649. throw ADP.ConnectionStringSyntax(startposition); // unquoted value must not end in quote, except for odbc
  650. }
  651. break;
  652. case ParserState.DoubleQuoteValueQuote:
  653. case ParserState.SingleQuoteValueQuote:
  654. case ParserState.BraceQuoteValueQuote:
  655. case ParserState.QuotedValueEnd:
  656. // quoted value at end of line
  657. keyvalue = GetKeyValue(buffer, false);
  658. break;
  659. case ParserState.NothingYet:
  660. case ParserState.KeyEnd:
  661. case ParserState.NullTermination:
  662. // do nothing
  663. break;
  664. default:
  665. throw ADP.InternalError(ADP.InternalErrorCode.InvalidParserState2);
  666. }
  667. if ((';' == currentChar) && (currentPosition < connectionString.Length)) {
  668. currentPosition++;
  669. }
  670. return currentPosition;
  671. }
  672. static private bool IsValueValidInternal(string keyvalue) {
  673. if (null != keyvalue)
  674. {
  675. #if DEBUG
  676. bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
  677. Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
  678. #endif
  679. return (-1 == keyvalue.IndexOf('\u0000'));
  680. }
  681. return true;
  682. }
  683. static private bool IsKeyNameValid(string keyname) {
  684. if (null != keyname) {
  685. #if DEBUG
  686. bool compValue = ConnectionStringValidKeyRegex.IsMatch(keyname);
  687. Debug.Assert(((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000'))) == compValue, "IsValueValid mismatch with regex");
  688. #endif
  689. return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
  690. }
  691. return false;
  692. }
  693. #if DEBUG
  694. private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms, bool firstKey) {
  695. Hashtable parsetable = new Hashtable();
  696. Regex parser = (firstKey ? ConnectionStringRegexOdbc : ConnectionStringRegex);
  697. const int KeyIndex = 1, ValueIndex = 2;
  698. Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index");
  699. Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index");
  700. if (null != connectionString) {
  701. Match match = parser.Match(connectionString);
  702. if (!match.Success || (match.Length != connectionString.Length)) {
  703. throw ADP.ConnectionStringSyntax(match.Length);
  704. }
  705. int indexValue = 0;
  706. CaptureCollection keyvalues = match.Groups[ValueIndex].Captures;
  707. foreach(Capture keypair in match.Groups[KeyIndex].Captures) {
  708. string keyname = (firstKey ? keypair.Value : keypair.Value.Replace("==", "=")).ToLower(CultureInfo.InvariantCulture);
  709. string keyvalue = keyvalues[indexValue++].Value;
  710. if (0 < keyvalue.Length) {
  711. if (!firstKey) {
  712. switch(keyvalue[0]) {
  713. case '\"':
  714. keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\"\"", "\"");
  715. break;
  716. case '\'':
  717. keyvalue = keyvalue.Substring(1, keyvalue.Length-2).Replace("\'\'", "\'");
  718. break;
  719. default:
  720. break;
  721. }
  722. }
  723. }
  724. else {
  725. keyvalue = null;
  726. }
  727. DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
  728. string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
  729. if (!IsKeyNameValid(realkeyname)) {
  730. throw ADP.KeywordNotSupported(keyname);
  731. }
  732. if (!firstKey || !parsetable.ContainsKey(realkeyname)) {
  733. parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
  734. }
  735. }
  736. }
  737. return parsetable;
  738. }
  739. private static void ParseComparison(Hashtable parsetable, string connectionString, Hashtable synonyms, bool firstKey, Exception e) {
  740. try {
  741. Hashtable parsedvalues = SplitConnectionString(connectionString, synonyms, firstKey);
  742. foreach(DictionaryEntry entry in parsedvalues) {
  743. string keyname = (string) entry.Key;
  744. string value1 = (string) entry.Value;
  745. string value2 = (string) parsetable[keyname];
  746. Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">");
  747. Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 +">");
  748. }
  749. }
  750. catch(ArgumentException f) {
  751. if (null != e) {
  752. string msg1 = e.Message;
  753. string msg2 = f.Message;
  754. const string KeywordNotSupportedMessagePrefix = "Keyword not supported:";
  755. const string WrongFormatMessagePrefix = "Format of the initialization string";
  756. bool isEquivalent = (msg1 == msg2);
  757. if (!isEquivalent)
  758. {
  759. // VSTFDEVDIV 479587: we also accept cases were Regex parser (debug only) reports "wrong format" and
  760. // retail parsing code reports format exception in different location or "keyword not supported"
  761. if (msg2.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) {
  762. if (msg1.StartsWith(KeywordNotSupportedMessagePrefix, StringComparison.Ordinal) || msg1.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) {
  763. isEquivalent = true;
  764. }
  765. }
  766. }
  767. Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <"+msg1+"> <"+msg2+">");
  768. }
  769. else {
  770. Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
  771. }
  772. e = null;
  773. }
  774. if (null != e) {
  775. Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
  776. }
  777. }
  778. #endif
  779. private static NameValuePair ParseInternal(Hashtable parsetable, string connectionString, bool buildChain, Hashtable synonyms, bool firstKey) {
  780. Debug.Assert(null != connectionString, "null connectionstring");
  781. StringBuilder buffer = new StringBuilder();
  782. NameValuePair localKeychain = null, keychain = null;
  783. #if DEBUG
  784. try {
  785. #endif
  786. int nextStartPosition = 0;
  787. int endPosition = connectionString.Length;
  788. while (nextStartPosition < endPosition) {
  789. int startPosition = nextStartPosition;
  790. string keyname, keyvalue;
  791. nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, firstKey, out keyname, out keyvalue);
  792. if (ADP.IsEmpty(keyname)) {
  793. // if (nextStartPosition != endPosition) { throw; }
  794. break;
  795. }
  796. #if DEBUG
  797. DebugTraceKeyValuePair(keyname, keyvalue, synonyms);
  798. Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
  799. Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
  800. #endif
  801. string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
  802. if (!IsKeyNameValid(realkeyname)) {
  803. throw ADP.KeywordNotSupported(keyname);
  804. }
  805. if (!firstKey || !parsetable.Contains(realkeyname)) {
  806. parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
  807. }
  808. if(null != localKeychain) {
  809. localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
  810. }
  811. else if (buildChain) { // first time only - don't contain modified chain from UDL file
  812. keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
  813. }
  814. }
  815. #if DEBUG
  816. }
  817. catch(ArgumentException e) {
  818. ParseComparison(parsetable, connectionString, synonyms, firstKey, e);
  819. throw;
  820. }
  821. ParseComparison(parsetable, connectionString, synonyms, firstKey, null);
  822. #endif
  823. return keychain;
  824. }
  825. internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) {
  826. bool expanded = false;
  827. int copyPosition = 0;
  828. NameValuePair head = null, tail = null, next = null;
  829. StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
  830. for(NameValuePair current = _keyChain; null != current; current = current.Next) {
  831. if ((KEY.Password != current.Name) && (SYNONYM.Pwd != current.Name)) {
  832. builder.Append(_usersConnectionString, copyPosition, current.Length);
  833. if (fakePassword) {
  834. next = new NameValuePair(current.Name, current.Value, current.Length);
  835. }
  836. }
  837. else if (fakePassword) { // replace user password/pwd value with *
  838. const string equalstar = "=*;";
  839. builder.Append(current.Name).Append(equalstar);
  840. next = new NameValuePair(current.Name, "*", current.Name.Length + equalstar.Length);
  841. expanded = true;
  842. }
  843. else { // drop the password/pwd completely in returning for user
  844. expanded = true;
  845. }
  846. if (fakePassword) {
  847. if (null != tail) {
  848. tail = tail.Next = next;
  849. }
  850. else {
  851. tail = head = next;
  852. }
  853. }
  854. copyPosition += current.Length;
  855. }
  856. Debug.Assert(expanded, "password/pwd was not removed");
  857. constr = builder.ToString();
  858. return head;
  859. }
  860. internal static void ValidateKeyValuePair(string keyword, string value) {
  861. if ((null == keyword) || !ConnectionStringValidKeyRegex.IsMatch(keyword)) {
  862. throw ADP.InvalidKeyname(keyword);
  863. }
  864. if ((null != value) && !ConnectionStringValidValueRegex.IsMatch(value)) {
  865. throw ADP.InvalidValue(keyword);
  866. }
  867. }
  868. }
  869. }