Skip to content

Commit

Permalink
Fix | Support |DataDirectory| macro in AttachDBFilename for .NET Core (
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikEJ authored and cheenamalhotra committed Nov 14, 2019
1 parent ffd47e9 commit 1a0748e
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ internal static Exception InvalidConnectionOptionValue(string key, Exception inn
{
return Argument(System.SRHelper.Format(SR.ADP_InvalidConnectionOptionValue, key), inner);
}
static internal InvalidOperationException InvalidDataDirectory()
{
InvalidOperationException e = new InvalidOperationException(SR.ADP_InvalidDataDirectory);
return e;
}

//
// Generic Data Provider Collection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ private static class KEY
internal const string Password = "password";
internal const string Persist_Security_Info = "persist security info";
internal const string User_ID = "user id";
internal const string AttachDBFileName = "attachdbfilename";
}

// known connection string common synonyms
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,18 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D

if (null == userConnectionOptions)
{ // we only allow one expansion on the connection string

userConnectionOptions = connectionOptions;
string expandedConnectionString = connectionOptions.Expand();

// if the expanded string is same instance (default implementation), then use the already created options
if ((object)expandedConnectionString != (object)key.ConnectionString)
{
// CONSIDER: caching the original string to reduce future parsing
DbConnectionPoolKey newKey = (DbConnectionPoolKey)((ICloneable)key).Clone();
newKey.ConnectionString = expandedConnectionString;
return GetConnectionPoolGroup(newKey, null, ref userConnectionOptions);
}
}

// We don't support connection pooling on Win9x
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;

namespace Microsoft.Data.Common
{
Expand Down Expand Up @@ -105,5 +107,70 @@ public bool ContainsKey(string keyword)
{
return _parsetable.ContainsKey(keyword);
}

protected internal virtual string Expand()
{
return _usersConnectionString;
}

// 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.
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");
var rootFolderPath = (rootFolderObject as string);
if ((null != rootFolderObject) && (null == rootFolderPath))
{
throw ADP.InvalidDataDirectory();
}
else if (string.IsNullOrEmpty(rootFolderPath))
{
rootFolderPath = AppDomain.CurrentDomain.BaseDirectory ?? string.Empty;
}

var fileName = value.Substring(DataDirectory.Length);

if (Path.IsPathRooted(fileName))
{
fileName = fileName.TrimStart(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
}

fullPath = Path.Combine(rootFolderPath, fileName);

// verify root folder path is a real path without unexpected "..\"
if (!Path.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal))
{
throw ADP.InvalidConnectionOptionValue(keyword);
}
}
return fullPath;
}

internal string ExpandAttachDbFileName(string replacementValue)
{
int copyPosition = 0;

StringBuilder builder = new StringBuilder(_usersConnectionString.Length);
for (NameValuePair current = _keyChain; null != current; current = current.Next)
{
if (current.Name == KEY.AttachDBFileName)
{
builder.Append($"{KEY.AttachDBFileName}={replacementValue};");
}
else
{
builder.Append(_usersConnectionString, copyPosition, current.Length);
}
copyPosition += current.Length;
}

return builder.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading;
using Microsoft.Data.Common;

Expand Down Expand Up @@ -218,6 +220,8 @@ internal static class TRANSACTIONBINDING
private static readonly Version constTypeSystemAsmVersion10 = new Version("10.0.0.0");
private static readonly Version constTypeSystemAsmVersion11 = new Version("11.0.0.0");

private readonly string _expandedAttachDBFilename; // expanded during construction so that CreatePermissionSet & Expand are consistent

internal SqlConnectionString(string connectionString) : base(connectionString, GetParseSynonyms())
{
ThrowUnsupportedIfKeywordSet(KEY.AsynchronousProcessing);
Expand Down Expand Up @@ -332,15 +336,31 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
}
}

if (0 <= _attachDBFileName.IndexOf('|'))
// expand during construction so that CreatePermissionSet and Expand are consistent
_expandedAttachDBFilename = ExpandDataDirectory(KEY.AttachDBFilename, _attachDBFileName);
if (null != _expandedAttachDBFilename)
{
if (0 <= _expandedAttachDBFilename.IndexOf('|'))
{
throw ADP.InvalidConnectionOptionValue(KEY.AttachDBFilename);
}
ValidateValueLength(_expandedAttachDBFilename, TdsEnums.MAXLEN_ATTACHDBFILE, KEY.AttachDBFilename);
if (_localDBInstance == null)
{
// fail fast to verify LocalHost when using |DataDirectory|
// still must check again at connect time
string host = _dataSource;
VerifyLocalHostAndFixup(ref host, true, false /*don't fix-up*/);
}
}
else if (0 <= _attachDBFileName.IndexOf('|'))
{
throw ADP.InvalidConnectionOptionValue(KEY.AttachDBFilename);
}
else
{
ValidateValueLength(_attachDBFileName, TdsEnums.MAXLEN_ATTACHDBFILE, KEY.AttachDBFilename);
}

_typeSystemAssemblyVersion = constTypeSystemAsmVersion10;

if (true == _userInstance && !string.IsNullOrEmpty(_failoverPartner))
Expand Down Expand Up @@ -471,6 +491,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
_password = connectionOptions._password;
_userID = connectionOptions._userID;
_workstationId = connectionOptions._workstationId;
_expandedAttachDBFilename = connectionOptions._expandedAttachDBFilename;
_typeSystemVersion = connectionOptions._typeSystemVersion;
_transactionBinding = connectionOptions._transactionBinding;
_applicationIntent = connectionOptions._applicationIntent;
Expand Down Expand Up @@ -531,6 +552,52 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS

internal TransactionBindingEnum TransactionBinding { get { return _transactionBinding; } }

internal bool EnforceLocalHost
{
get
{
// so tdsparser.connect can determine if SqlConnection.UserConnectionOptions
// needs to enfoce local host after datasource alias lookup
return (null != _expandedAttachDBFilename) && (null == _localDBInstance);
}
}

protected internal override string Expand()
{
if (null != _expandedAttachDBFilename)
{
return ExpandAttachDbFileName(_expandedAttachDBFilename);
}
else
{
return base.Expand();
}
}

private static bool CompareHostName(ref string host, string name, bool fixup)
{
// same computer name or same computer name + "\named instance"
bool equal = false;

if (host.Equals(name, StringComparison.OrdinalIgnoreCase))
{
if (fixup)
{
host = ".";
}
equal = true;
}
else if (host.StartsWith(name + @"\", StringComparison.OrdinalIgnoreCase))
{
if (fixup)
{
host = "." + host.Substring(name.Length);
}
equal = true;
}
return equal;
}

// This dictionary is meant to be read-only translation of parsed string
// keywords/synonyms to a known keyword string.
internal static Dictionary<string, string> GetParseSynonyms()
Expand Down Expand Up @@ -636,6 +703,49 @@ private void ValidateValueLength(string value, int limit, string key)
}
}

internal static void VerifyLocalHostAndFixup(ref string host, bool enforceLocalHost, bool fixup)
{
if (string.IsNullOrEmpty(host))
{
if (fixup)
{
host = ".";
}
}
else if (!CompareHostName(ref host, @".", fixup) &&
!CompareHostName(ref host, @"(local)", fixup))
{
// Fix-up completed in CompareHostName if return value true.
string name = GetComputerNameDnsFullyQualified(); // i.e, machine.location.corp.company.com
if (!CompareHostName(ref host, name, fixup))
{
int separatorPos = name.IndexOf('.'); // to compare just 'machine' part
if ((separatorPos <= 0) || !CompareHostName(ref host, name.Substring(0, separatorPos), fixup))
{
if (enforceLocalHost)
{
throw ADP.InvalidConnectionOptionValue(KEY.AttachDBFilename);
}
}
}
}
}

private static string GetComputerNameDnsFullyQualified()
{
try
{
var domainName = "." + IPGlobalProperties.GetIPGlobalProperties().DomainName;
var hostName = Dns.GetHostName();
if (domainName != "." && !hostName.EndsWith(domainName))
hostName += domainName;
return hostName;
}
catch (System.Net.Sockets.SocketException)
{
return Environment.MachineName;
}
}

internal ApplicationIntent ConvertValueToApplicationIntent()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,12 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup,
string host = serverInfo.UserServerName;
string protocol = serverInfo.UserProtocol;

//TODO: fix local host enforcement with datadirectory and failover
if (options.EnforceLocalHost)
{
// verify LocalHost for |DataDirectory| usage
SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
}

serverInfo.SetDerivedNames(protocol, host);
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@
<data name="ADP_InternalConnectionError" xml:space="preserve">
<value>Internal DbConnection Error: {0}</value>
</data>
<data name="ADP_InvalidDataDirectory" xml:space="preserve">
<value>The DataDirectory substitute is not a string.</value>
</data>
<data name="ADP_InvalidEnumerationValue" xml:space="preserve">
<value>The {0} enumeration value, {1}, is invalid.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D
userConnectionOptions = connectionOptions;
expandedConnectionString = connectionOptions.Expand();

// if the expanded string is same instance (default implementation), the use the already created options
// if the expanded string is same instance (default implementation), then use the already created options
if ((object)expandedConnectionString != (object)key.ConnectionString)
{
// CONSIDER: caching the original string to reduce future parsing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,45 @@ public void UnexpectedKeywordRetrieval()
Assert.Throws<ArgumentException>(() => builder["RandomKeyword"]);
}

[Theory]
[InlineData(@"C:\test\attach.mdf", "AttachDbFilename=C:\\test\\attach.mdf")]
[InlineData(@"C:\test\attach.mdf;", "AttachDbFilename=\"C:\\test\\attach.mdf;\"")]
public void ConnectionString_AttachDbFileName_Plain(string value, string expected)
{
var builder = new SqlConnectionStringBuilder();
builder.AttachDBFilename = value;
Assert.Equal(expected, builder.ConnectionString);
}

[Theory]
[PlatformSpecific(TestPlatforms.Windows)]
[InlineData(@"|DataDirectory|\attach.mdf",
@"AttachDbFilename=|DataDirectory|\attach.mdf",
@"C:\test\")]
[InlineData(@"|DataDirectory|\attach.mdf",
@"AttachDbFilename=|DataDirectory|\attach.mdf",
@"C:\test")]
[InlineData(@"|DataDirectory|attach.mdf",
@"AttachDbFilename=|DataDirectory|attach.mdf",
@"C:\test")]
[InlineData(@"|DataDirectory|attach.mdf",
@"AttachDbFilename=|DataDirectory|attach.mdf",
@"C:\test\")]
[InlineData(@" |DataDirectory|attach.mdf",
"AttachDbFilename=\" |DataDirectory|attach.mdf\"",
@"C:\test\")]
[InlineData(@"|DataDirectory|attach.mdf ",
"AttachDbFilename=\"|DataDirectory|attach.mdf \"",
@"C:\test\")]
public void ConnectionStringBuilder_AttachDbFileName_DataDirectory(string value, string expected, string dataDirectory)
{
AppDomain.CurrentDomain.SetData("DataDirectory", dataDirectory);

var builder = new SqlConnectionStringBuilder();
builder.AttachDBFilename = value;
Assert.Equal(expected, builder.ConnectionString);
}

private void ExecuteConnectionStringTests(string connectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString);
Expand Down

0 comments on commit 1a0748e

Please sign in to comment.