1 //---------------------------------------------------------------------
2 // <copyright file="DbConnectionOptions.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.EntityClient
12 using System.Collections;
13 using System.Diagnostics;
14 using System.Runtime.Versioning;
16 using System.Text.RegularExpressions;
19 /// Copied from System.Data.dll
21 internal class DbConnectionOptions
23 // instances of this class are intended to be immutable, i.e readonly
24 // used by pooling classes so it is much easier to verify correctness
25 // when not worried about the class being modified during execution
28 private const string ConnectionStringPattern = // may not contain embedded null except trailing last value
29 "([\\s;]*" // leading whitespace and extra semicolons
30 + "(?![\\s;])" // key does not start with space or semicolon
31 + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)" // allow any visible character for keyname except '=' which must quoted as '=='
32 + "\\s*=(?!=)\\s*" // the equal sign divides the key and value parts
34 + "(\"([^\"\u0000]|\"\")*\")" // double quoted string, " must be quoted as ""
36 + "('([^'\u0000]|'')*')" // single quoted string, ' must be quoted as ''
38 + "((?![\"'\\s])" // unquoted value must not start with " or ' or space, would also like = but too late to change
39 + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted
40 + "(?<![\"']))" // unquoted value must not stop with " or '
41 + ")(\\s*)(;|\u0000|$)" // whitespace after value up to semicolon or end-of-line
42 + ")*" // repeat the key-value pair
43 + "[\\s;\u0000]*" // traling whitespace/semicolons and embedded nulls (DataSourceLocator)
46 private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
48 internal const string DataDirectory = "|datadirectory|";
51 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
52 private const string ConnectionStringValidValuePattern = "^[^\u0000]*$"; // value not allowed to contain embedded null
53 private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
54 private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
57 private readonly string _usersConnectionString;
58 private readonly Hashtable _parsetable;
59 internal readonly NameValuePair KeyChain;
61 // synonyms hashtable is meant to be read-only translation of parsed string
62 // keywords/synonyms to a known keyword string
63 internal DbConnectionOptions(string connectionString, Hashtable synonyms)
65 _parsetable = new Hashtable();
66 _usersConnectionString = ((null != connectionString) ? connectionString : "");
68 // first pass on parsing, initial syntax check
69 if (0 < _usersConnectionString.Length)
71 KeyChain = ParseInternal(_parsetable, _usersConnectionString, synonyms);
75 internal string UsersConnectionString
79 return _usersConnectionString ?? string.Empty;
85 get { return (null == KeyChain); }
88 internal Hashtable Parsetable
90 get { return _parsetable; }
93 internal string this[string keyword]
95 get { return (string)_parsetable[keyword]; }
99 // * this method queries "DataDirectory" value from the current AppDomain.
100 // This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource".
101 // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out.
102 [ResourceExposure(ResourceScope.None)]
103 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
104 internal static string ExpandDataDirectory(string keyword, string value)
106 string fullPath = null;
107 if ((null != value) && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase))
109 // find the replacement path
110 object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
111 string rootFolderPath = (rootFolderObject as string);
112 if ((null != rootFolderObject) && (null == rootFolderPath))
114 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ADP_InvalidDataDirectory);
116 else if (rootFolderPath == string.Empty)
118 rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
120 if (null == rootFolderPath)
125 // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw
126 int fileNamePosition = DataDirectory.Length; // filename starts right after the '|datadirectory|' keyword
127 bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length - 1] == '\\';
128 bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\';
130 // replace |datadirectory| with root folder path
131 if (!rootFolderEndsWith && !fileNameStartsWith)
133 // need to insert '\'
134 fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
136 else if (rootFolderEndsWith && fileNameStartsWith)
138 // need to strip one out
139 fullPath = rootFolderPath + value.Substring(fileNamePosition + 1);
143 // simply concatenate the strings
144 fullPath = rootFolderPath + value.Substring(fileNamePosition);
147 // verify root folder path is a real path without unexpected "..\"
148 if (!EntityUtil.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal))
150 throw EntityUtil.InvalidConnectionOptionValue(keyword);
156 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
157 static private string GetKeyName(StringBuilder buffer)
159 int count = buffer.Length;
160 while ((0 < count) && Char.IsWhiteSpace(buffer[count - 1]))
162 count--; // trailing whitespace
164 return buffer.ToString(0, count).ToLowerInvariant();
167 static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace)
169 int count = buffer.Length;
173 while ((index < count) && Char.IsWhiteSpace(buffer[index]))
175 index++; // leading whitespace
177 while ((0 < count) && Char.IsWhiteSpace(buffer[count - 1]))
179 count--; // trailing whitespace
182 return buffer.ToString(index, count - index);
185 // transistion states used for parsing
186 private enum ParserState
188 NothingYet = 1, //start point
194 DoubleQuoteValueQuote,
196 SingleQuoteValueQuote,
201 static private int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, out string keyname, out string keyvalue)
203 int startposition = currentPosition;
209 char currentChar = '\0';
211 ParserState parserState = ParserState.NothingYet;
212 int length = connectionString.Length;
213 for (; currentPosition < length; ++currentPosition)
215 currentChar = connectionString[currentPosition];
219 case ParserState.NothingYet: // [\\s;]*
220 if ((';' == currentChar) || Char.IsWhiteSpace(currentChar))
224 if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
225 if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
226 startposition = currentPosition;
227 if ('=' != currentChar)
229 parserState = ParserState.Key;
234 parserState = ParserState.KeyEqual;
238 case ParserState.Key: // (?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)
239 if ('=' == currentChar) { parserState = ParserState.KeyEqual; continue; }
240 if (Char.IsWhiteSpace(currentChar)) { break; }
241 if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
244 case ParserState.KeyEqual: // \\s*=(?!=)\\s*
245 if ('=' == currentChar) { parserState = ParserState.Key; break; }
246 keyname = GetKeyName(buffer);
247 if (string.IsNullOrEmpty(keyname)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
249 parserState = ParserState.KeyEnd;
250 goto case ParserState.KeyEnd;
252 case ParserState.KeyEnd:
253 if (Char.IsWhiteSpace(currentChar)) { continue; }
254 if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; continue; }
255 if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; continue; }
257 if (';' == currentChar) { goto ParserExit; }
258 if ('\0' == currentChar) { goto ParserExit; }
259 if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
260 parserState = ParserState.UnquotedValue;
263 case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(?<![\"']))"
264 if (Char.IsWhiteSpace(currentChar)) { break; }
265 if (Char.IsControl(currentChar) || ';' == currentChar) { goto ParserExit; }
268 case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
269 if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValueQuote; continue; }
270 if ('\0' == currentChar) { throw EntityUtil.ConnectionStringSyntax(startposition); }
273 case ParserState.DoubleQuoteValueQuote:
274 if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; break; }
275 keyvalue = GetKeyValue(buffer, false);
276 parserState = ParserState.QuotedValueEnd;
277 goto case ParserState.QuotedValueEnd;
279 case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
280 if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValueQuote; continue; }
281 if ('\0' == currentChar) { throw EntityUtil.ConnectionStringSyntax(startposition); }
284 case ParserState.SingleQuoteValueQuote:
285 if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; break; }
286 keyvalue = GetKeyValue(buffer, false);
287 parserState = ParserState.QuotedValueEnd;
288 goto case ParserState.QuotedValueEnd;
290 case ParserState.QuotedValueEnd:
291 if (Char.IsWhiteSpace(currentChar)) { continue; }
292 if (';' == currentChar) { goto ParserExit; }
293 if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
294 throw EntityUtil.ConnectionStringSyntax(startposition); // unbalanced single quote
296 case ParserState.NullTermination: // [\\s;\u0000]*
297 if ('\0' == currentChar) { continue; }
298 if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
299 throw EntityUtil.ConnectionStringSyntax(currentPosition);
302 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidParserState1);
304 buffer.Append(currentChar);
309 case ParserState.Key:
310 case ParserState.DoubleQuoteValue:
311 case ParserState.SingleQuoteValue:
312 // keyword not found/unbalanced double/single quote
313 throw EntityUtil.ConnectionStringSyntax(startposition);
315 case ParserState.KeyEqual:
316 // equal sign at end of line
317 keyname = GetKeyName(buffer);
318 if (string.IsNullOrEmpty(keyname)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
321 case ParserState.UnquotedValue:
322 // unquoted value at end of line
323 keyvalue = GetKeyValue(buffer, true);
325 char tmpChar = keyvalue[keyvalue.Length - 1];
326 if (('\'' == tmpChar) || ('"' == tmpChar))
328 throw EntityUtil.ConnectionStringSyntax(startposition); // unquoted value must not end in quote
332 case ParserState.DoubleQuoteValueQuote:
333 case ParserState.SingleQuoteValueQuote:
334 case ParserState.QuotedValueEnd:
335 // quoted value at end of line
336 keyvalue = GetKeyValue(buffer, false);
339 case ParserState.NothingYet:
340 case ParserState.KeyEnd:
341 case ParserState.NullTermination:
346 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidParserState2);
348 if ((';' == currentChar) && (currentPosition < connectionString.Length))
352 return currentPosition;
356 static private bool IsValueValidInternal(string keyvalue)
358 if (null != keyvalue)
361 bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
362 Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
363 return (-1 == keyvalue.IndexOf('\u0000'));
369 static private bool IsKeyNameValid(string keyname)
374 bool compValue = ConnectionStringValidKeyRegex.IsMatch(keyname);
375 Debug.Assert(((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000'))) == compValue, "IsValueValid mismatch with regex");
377 return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
383 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
384 private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms)
386 Hashtable parsetable = new Hashtable();
387 Regex parser = ConnectionStringRegex;
389 const int KeyIndex = 1, ValueIndex = 2;
390 Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index");
391 Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index");
393 if (null != connectionString)
395 Match match = parser.Match(connectionString);
396 if (!match.Success || (match.Length != connectionString.Length))
398 throw EntityUtil.ConnectionStringSyntax(match.Length);
401 CaptureCollection keyvalues = match.Groups[ValueIndex].Captures;
402 foreach (Capture keypair in match.Groups[KeyIndex].Captures)
404 string keyname = keypair.Value.Replace("==", "=").ToLowerInvariant();
405 string keyvalue = keyvalues[indexValue++].Value;
406 if (0 < keyvalue.Length)
411 keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\"\"", "\"");
414 keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\'\'", "\'");
425 string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
426 if (!IsKeyNameValid(realkeyname))
428 throw EntityUtil.ADP_KeywordNotSupported(keyname);
430 parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
436 private static void ParseComparision(Hashtable parsetable, string connectionString, Hashtable synonyms, Exception e)
440 Hashtable parsedvalues = SplitConnectionString(connectionString, synonyms);
441 foreach (DictionaryEntry entry in parsedvalues)
443 string keyname = (string)entry.Key;
444 string value1 = (string)entry.Value;
445 string value2 = (string)parsetable[keyname];
446 Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">");
447 Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 + ">");
451 catch (ArgumentException f)
455 string msg1 = e.Message;
456 string msg2 = f.Message;
457 if (msg1.StartsWith("Keyword not supported:", StringComparison.Ordinal) && msg2.StartsWith("Format of the initialization string", StringComparison.Ordinal))
462 // Does not always hold.
463 Debug.Assert(msg1 == msg2, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">");
468 Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
474 Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
478 private static NameValuePair ParseInternal(Hashtable parsetable, string connectionString, Hashtable synonyms)
480 Debug.Assert(null != connectionString, "null connectionstring");
481 StringBuilder buffer = new StringBuilder();
482 NameValuePair localKeychain = null, keychain = null;
487 int nextStartPosition = 0;
488 int endPosition = connectionString.Length;
489 while (nextStartPosition < endPosition)
491 int startPosition = nextStartPosition;
493 string keyname, keyvalue;
494 nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, out keyname, out keyvalue);
495 if (string.IsNullOrEmpty(keyname))
497 // if (nextStartPosition != endPosition) { throw; }
502 Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
503 Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
505 string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
506 if (!IsKeyNameValid(realkeyname))
508 throw EntityUtil.ADP_KeywordNotSupported(keyname);
510 parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
512 if (null != localKeychain)
514 localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
517 { // first time only - don't contain modified chain from UDL file
518 keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
523 catch (ArgumentException e)
525 ParseComparision(parsetable, connectionString, synonyms, e);
528 ParseComparision(parsetable, connectionString, synonyms, null);