536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
534 lines
24 KiB
C#
534 lines
24 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="DbConnectionOptions.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner Microsoft
|
|
// @backupOwner Microsoft
|
|
//---------------------------------------------------------------------
|
|
|
|
namespace System.Data.EntityClient
|
|
{
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Versioning;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
/// <summary>
|
|
/// Copied from System.Data.dll
|
|
/// </summary>
|
|
internal class DbConnectionOptions
|
|
{
|
|
// instances of this class are intended to be immutable, i.e readonly
|
|
// used by pooling classes so it is much easier to verify correctness
|
|
// when not worried about the class being modified during execution
|
|
|
|
#if DEBUG
|
|
private const string ConnectionStringPattern = // may not contain embedded null except trailing last value
|
|
"([\\s;]*" // leading whitespace and extra semicolons
|
|
+ "(?![\\s;])" // key does not start with space or semicolon
|
|
+ "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)" // allow any visible character for keyname except '=' which must quoted as '=='
|
|
+ "\\s*=(?!=)\\s*" // the equal sign divides the key and value parts
|
|
+ "(?<value>"
|
|
+ "(\"([^\"\u0000]|\"\")*\")" // double quoted string, " must be quoted as ""
|
|
+ "|"
|
|
+ "('([^'\u0000]|'')*')" // single quoted string, ' must be quoted as ''
|
|
+ "|"
|
|
+ "((?![\"'\\s])" // unquoted value must not start with " or ' or space, would also like = but too late to change
|
|
+ "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted
|
|
+ "(?<![\"']))" // unquoted value must not stop with " or '
|
|
+ ")(\\s*)(;|\u0000|$)" // whitespace after value up to semicolon or end-of-line
|
|
+ ")*" // repeat the key-value pair
|
|
+ "[\\s;\u0000]*" // traling whitespace/semicolons and embedded nulls (DataSourceLocator)
|
|
;
|
|
|
|
private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
|
|
#endif
|
|
internal const string DataDirectory = "|datadirectory|";
|
|
|
|
#if DEBUG
|
|
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
|
|
private const string ConnectionStringValidValuePattern = "^[^\u0000]*$"; // value not allowed to contain embedded null
|
|
private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
|
|
private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
|
|
#endif
|
|
|
|
private readonly string _usersConnectionString;
|
|
private readonly Hashtable _parsetable;
|
|
internal readonly NameValuePair KeyChain;
|
|
|
|
// synonyms hashtable is meant to be read-only translation of parsed string
|
|
// keywords/synonyms to a known keyword string
|
|
internal DbConnectionOptions(string connectionString, Hashtable synonyms)
|
|
{
|
|
_parsetable = new Hashtable();
|
|
_usersConnectionString = ((null != connectionString) ? connectionString : "");
|
|
|
|
// first pass on parsing, initial syntax check
|
|
if (0 < _usersConnectionString.Length)
|
|
{
|
|
KeyChain = ParseInternal(_parsetable, _usersConnectionString, synonyms);
|
|
}
|
|
}
|
|
|
|
internal string UsersConnectionString
|
|
{
|
|
get
|
|
{
|
|
return _usersConnectionString ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
internal bool IsEmpty
|
|
{
|
|
get { return (null == KeyChain); }
|
|
}
|
|
|
|
internal Hashtable Parsetable
|
|
{
|
|
get { return _parsetable; }
|
|
}
|
|
|
|
internal string this[string keyword]
|
|
{
|
|
get { return (string)_parsetable[keyword]; }
|
|
}
|
|
|
|
// SxS notes:
|
|
// * this method queries "DataDirectory" value from the current AppDomain.
|
|
// This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource".
|
|
// * This method uses GetFullPath to validate that root path is valid, the result is not exposed out.
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
internal static string ExpandDataDirectory(string keyword, string value)
|
|
{
|
|
string fullPath = null;
|
|
if ((null != value) && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// find the replacement path
|
|
object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
|
|
string rootFolderPath = (rootFolderObject as string);
|
|
if ((null != rootFolderObject) && (null == rootFolderPath))
|
|
{
|
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ADP_InvalidDataDirectory);
|
|
}
|
|
else if (rootFolderPath == string.Empty)
|
|
{
|
|
rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
|
|
}
|
|
if (null == rootFolderPath)
|
|
{
|
|
rootFolderPath = "";
|
|
}
|
|
|
|
// We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw
|
|
int fileNamePosition = DataDirectory.Length; // filename starts right after the '|datadirectory|' keyword
|
|
bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length - 1] == '\\';
|
|
bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\';
|
|
|
|
// replace |datadirectory| with root folder path
|
|
if (!rootFolderEndsWith && !fileNameStartsWith)
|
|
{
|
|
// need to insert '\'
|
|
fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
|
|
}
|
|
else if (rootFolderEndsWith && fileNameStartsWith)
|
|
{
|
|
// need to strip one out
|
|
fullPath = rootFolderPath + value.Substring(fileNamePosition + 1);
|
|
}
|
|
else
|
|
{
|
|
// simply concatenate the strings
|
|
fullPath = rootFolderPath + value.Substring(fileNamePosition);
|
|
}
|
|
|
|
// verify root folder path is a real path without unexpected "..\"
|
|
if (!EntityUtil.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal))
|
|
{
|
|
throw EntityUtil.InvalidConnectionOptionValue(keyword);
|
|
}
|
|
}
|
|
return fullPath;
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
|
|
static private string GetKeyName(StringBuilder buffer)
|
|
{
|
|
int count = buffer.Length;
|
|
while ((0 < count) && Char.IsWhiteSpace(buffer[count - 1]))
|
|
{
|
|
count--; // trailing whitespace
|
|
}
|
|
return buffer.ToString(0, count).ToLowerInvariant();
|
|
}
|
|
|
|
static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace)
|
|
{
|
|
int count = buffer.Length;
|
|
int index = 0;
|
|
if (trimWhitespace)
|
|
{
|
|
while ((index < count) && Char.IsWhiteSpace(buffer[index]))
|
|
{
|
|
index++; // leading whitespace
|
|
}
|
|
while ((0 < count) && Char.IsWhiteSpace(buffer[count - 1]))
|
|
{
|
|
count--; // trailing whitespace
|
|
}
|
|
}
|
|
return buffer.ToString(index, count - index);
|
|
}
|
|
|
|
// transistion states used for parsing
|
|
private enum ParserState
|
|
{
|
|
NothingYet = 1, //start point
|
|
Key,
|
|
KeyEqual,
|
|
KeyEnd,
|
|
UnquotedValue,
|
|
DoubleQuoteValue,
|
|
DoubleQuoteValueQuote,
|
|
SingleQuoteValue,
|
|
SingleQuoteValueQuote,
|
|
QuotedValueEnd,
|
|
NullTermination,
|
|
};
|
|
|
|
static private int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, out string keyname, out string keyvalue)
|
|
{
|
|
int startposition = currentPosition;
|
|
|
|
buffer.Length = 0;
|
|
keyname = null;
|
|
keyvalue = null;
|
|
|
|
char currentChar = '\0';
|
|
|
|
ParserState parserState = ParserState.NothingYet;
|
|
int length = connectionString.Length;
|
|
for (; currentPosition < length; ++currentPosition)
|
|
{
|
|
currentChar = connectionString[currentPosition];
|
|
|
|
switch (parserState)
|
|
{
|
|
case ParserState.NothingYet: // [\\s;]*
|
|
if ((';' == currentChar) || Char.IsWhiteSpace(currentChar))
|
|
{
|
|
continue;
|
|
}
|
|
if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
|
|
if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
startposition = currentPosition;
|
|
if ('=' != currentChar)
|
|
{ // MDAC 86902
|
|
parserState = ParserState.Key;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
parserState = ParserState.KeyEqual;
|
|
continue;
|
|
}
|
|
|
|
case ParserState.Key: // (?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)
|
|
if ('=' == currentChar) { parserState = ParserState.KeyEqual; continue; }
|
|
if (Char.IsWhiteSpace(currentChar)) { break; }
|
|
if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
break;
|
|
|
|
case ParserState.KeyEqual: // \\s*=(?!=)\\s*
|
|
if ('=' == currentChar) { parserState = ParserState.Key; break; }
|
|
keyname = GetKeyName(buffer);
|
|
if (string.IsNullOrEmpty(keyname)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
buffer.Length = 0;
|
|
parserState = ParserState.KeyEnd;
|
|
goto case ParserState.KeyEnd;
|
|
|
|
case ParserState.KeyEnd:
|
|
if (Char.IsWhiteSpace(currentChar)) { continue; }
|
|
if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; continue; }
|
|
if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; continue; }
|
|
|
|
if (';' == currentChar) { goto ParserExit; }
|
|
if ('\0' == currentChar) { goto ParserExit; }
|
|
if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
parserState = ParserState.UnquotedValue;
|
|
break;
|
|
|
|
case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(?<![\"']))"
|
|
if (Char.IsWhiteSpace(currentChar)) { break; }
|
|
if (Char.IsControl(currentChar) || ';' == currentChar) { goto ParserExit; }
|
|
break;
|
|
|
|
case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
|
|
if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValueQuote; continue; }
|
|
if ('\0' == currentChar) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
break;
|
|
|
|
case ParserState.DoubleQuoteValueQuote:
|
|
if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; break; }
|
|
keyvalue = GetKeyValue(buffer, false);
|
|
parserState = ParserState.QuotedValueEnd;
|
|
goto case ParserState.QuotedValueEnd;
|
|
|
|
case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
|
|
if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValueQuote; continue; }
|
|
if ('\0' == currentChar) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
break;
|
|
|
|
case ParserState.SingleQuoteValueQuote:
|
|
if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; break; }
|
|
keyvalue = GetKeyValue(buffer, false);
|
|
parserState = ParserState.QuotedValueEnd;
|
|
goto case ParserState.QuotedValueEnd;
|
|
|
|
case ParserState.QuotedValueEnd:
|
|
if (Char.IsWhiteSpace(currentChar)) { continue; }
|
|
if (';' == currentChar) { goto ParserExit; }
|
|
if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
|
|
throw EntityUtil.ConnectionStringSyntax(startposition); // unbalanced single quote
|
|
|
|
case ParserState.NullTermination: // [\\s;\u0000]*
|
|
if ('\0' == currentChar) { continue; }
|
|
if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
|
|
throw EntityUtil.ConnectionStringSyntax(currentPosition);
|
|
|
|
default:
|
|
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidParserState1);
|
|
}
|
|
buffer.Append(currentChar);
|
|
}
|
|
ParserExit:
|
|
switch (parserState)
|
|
{
|
|
case ParserState.Key:
|
|
case ParserState.DoubleQuoteValue:
|
|
case ParserState.SingleQuoteValue:
|
|
// keyword not found/unbalanced double/single quote
|
|
throw EntityUtil.ConnectionStringSyntax(startposition);
|
|
|
|
case ParserState.KeyEqual:
|
|
// equal sign at end of line
|
|
keyname = GetKeyName(buffer);
|
|
if (string.IsNullOrEmpty(keyname)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
|
|
break;
|
|
|
|
case ParserState.UnquotedValue:
|
|
// unquoted value at end of line
|
|
keyvalue = GetKeyValue(buffer, true);
|
|
|
|
char tmpChar = keyvalue[keyvalue.Length - 1];
|
|
if (('\'' == tmpChar) || ('"' == tmpChar))
|
|
{
|
|
throw EntityUtil.ConnectionStringSyntax(startposition); // unquoted value must not end in quote
|
|
}
|
|
break;
|
|
|
|
case ParserState.DoubleQuoteValueQuote:
|
|
case ParserState.SingleQuoteValueQuote:
|
|
case ParserState.QuotedValueEnd:
|
|
// quoted value at end of line
|
|
keyvalue = GetKeyValue(buffer, false);
|
|
break;
|
|
|
|
case ParserState.NothingYet:
|
|
case ParserState.KeyEnd:
|
|
case ParserState.NullTermination:
|
|
// do nothing
|
|
break;
|
|
|
|
default:
|
|
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidParserState2);
|
|
}
|
|
if ((';' == currentChar) && (currentPosition < connectionString.Length))
|
|
{
|
|
currentPosition++;
|
|
}
|
|
return currentPosition;
|
|
}
|
|
|
|
#if DEBUG
|
|
static private bool IsValueValidInternal(string keyvalue)
|
|
{
|
|
if (null != keyvalue)
|
|
{
|
|
|
|
bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
|
|
Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
|
|
return (-1 == keyvalue.IndexOf('\u0000'));
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static private bool IsKeyNameValid(string keyname)
|
|
{
|
|
if (null != keyname)
|
|
{
|
|
#if DEBUG
|
|
bool compValue = ConnectionStringValidKeyRegex.IsMatch(keyname);
|
|
Debug.Assert(((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000'))) == compValue, "IsValueValid mismatch with regex");
|
|
#endif
|
|
return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if DEBUG
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
|
|
private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms)
|
|
{
|
|
Hashtable parsetable = new Hashtable();
|
|
Regex parser = ConnectionStringRegex;
|
|
|
|
const int KeyIndex = 1, ValueIndex = 2;
|
|
Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index");
|
|
Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index");
|
|
|
|
if (null != connectionString)
|
|
{
|
|
Match match = parser.Match(connectionString);
|
|
if (!match.Success || (match.Length != connectionString.Length))
|
|
{
|
|
throw EntityUtil.ConnectionStringSyntax(match.Length);
|
|
}
|
|
int indexValue = 0;
|
|
CaptureCollection keyvalues = match.Groups[ValueIndex].Captures;
|
|
foreach (Capture keypair in match.Groups[KeyIndex].Captures)
|
|
{
|
|
string keyname = keypair.Value.Replace("==", "=").ToLowerInvariant();
|
|
string keyvalue = keyvalues[indexValue++].Value;
|
|
if (0 < keyvalue.Length)
|
|
{
|
|
switch (keyvalue[0])
|
|
{
|
|
case '\"':
|
|
keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\"\"", "\"");
|
|
break;
|
|
case '\'':
|
|
keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\'\'", "\'");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
keyvalue = null;
|
|
}
|
|
|
|
string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
|
|
if (!IsKeyNameValid(realkeyname))
|
|
{
|
|
throw EntityUtil.ADP_KeywordNotSupported(keyname);
|
|
}
|
|
parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
|
|
}
|
|
}
|
|
return parsetable;
|
|
}
|
|
|
|
private static void ParseComparision(Hashtable parsetable, string connectionString, Hashtable synonyms, Exception e)
|
|
{
|
|
try
|
|
{
|
|
Hashtable parsedvalues = SplitConnectionString(connectionString, synonyms);
|
|
foreach (DictionaryEntry entry in parsedvalues)
|
|
{
|
|
string keyname = (string)entry.Key;
|
|
string value1 = (string)entry.Value;
|
|
string value2 = (string)parsetable[keyname];
|
|
Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">");
|
|
Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 + ">");
|
|
}
|
|
|
|
}
|
|
catch (ArgumentException f)
|
|
{
|
|
if (null != e)
|
|
{
|
|
string msg1 = e.Message;
|
|
string msg2 = f.Message;
|
|
if (msg1.StartsWith("Keyword not supported:", StringComparison.Ordinal) && msg2.StartsWith("Format of the initialization string", StringComparison.Ordinal))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// Does not always hold.
|
|
Debug.Assert(msg1 == msg2, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
|
|
}
|
|
e = null;
|
|
}
|
|
if (null != e)
|
|
{
|
|
Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
|
|
}
|
|
}
|
|
#endif
|
|
private static NameValuePair ParseInternal(Hashtable parsetable, string connectionString, Hashtable synonyms)
|
|
{
|
|
Debug.Assert(null != connectionString, "null connectionstring");
|
|
StringBuilder buffer = new StringBuilder();
|
|
NameValuePair localKeychain = null, keychain = null;
|
|
#if DEBUG
|
|
try
|
|
{
|
|
#endif
|
|
int nextStartPosition = 0;
|
|
int endPosition = connectionString.Length;
|
|
while (nextStartPosition < endPosition)
|
|
{
|
|
int startPosition = nextStartPosition;
|
|
|
|
string keyname, keyvalue;
|
|
nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, out keyname, out keyvalue);
|
|
if (string.IsNullOrEmpty(keyname))
|
|
{
|
|
// if (nextStartPosition != endPosition) { throw; }
|
|
break;
|
|
}
|
|
|
|
#if DEBUG
|
|
Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
|
|
Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
|
|
#endif
|
|
string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
|
|
if (!IsKeyNameValid(realkeyname))
|
|
{
|
|
throw EntityUtil.ADP_KeywordNotSupported(keyname);
|
|
}
|
|
parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
|
|
|
|
if (null != localKeychain)
|
|
{
|
|
localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
|
|
}
|
|
else
|
|
{ // first time only - don't contain modified chain from UDL file
|
|
keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
|
|
}
|
|
}
|
|
#if DEBUG
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ParseComparision(parsetable, connectionString, synonyms, e);
|
|
throw;
|
|
}
|
|
ParseComparision(parsetable, connectionString, synonyms, null);
|
|
#endif
|
|
return keychain;
|
|
}
|
|
}
|
|
}
|