diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index a6929eed5..b59e1fd65 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ diff --git a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs index 668db5f91..58be6fdf7 100644 --- a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs +++ b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs @@ -3,16 +3,15 @@ */ using System.Net.Http; +using NUnit.Framework; +using Snowflake.Data.Core; +using RichardSzalay.MockHttp; +using System.Threading.Tasks; +using System.Net; +using System; namespace Snowflake.Data.Tests.UnitTests { - using NUnit.Framework; - using Snowflake.Data.Core; - using RichardSzalay.MockHttp; - using System.Threading.Tasks; - using System.Net; - using System; - [TestFixture] class HttpUtilTest { @@ -102,48 +101,100 @@ public void TestGetJitter(int seconds) } [Test] - public void ShouldCreateHttpClientHandlerWithProxy() + public void TestCreateHttpClientHandlerWithExplicitProxy() { // given var config = new HttpClientConfig( + true, true, "snowflake.com", "123", "testUser", "proxyPassword", - "localhost", + "localhost", false, false, 7 ); - + // when var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); - + // then Assert.IsTrue(handler.UseProxy); Assert.IsNotNull(handler.Proxy); } [Test] - public void ShouldCreateHttpClientHandlerWithoutProxy() + public void TestCreateHttpClientHandlerWithDefaultProxy() { // given var config = new HttpClientConfig( true, + true, + null, + null, + null, + null, + null, + false, + false, + 7 + ); + + // when + var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); + + // then + Assert.IsTrue(handler.UseProxy); + Assert.IsNull(handler.Proxy); + } + + [Test] + public void TestCreateHttpClientHandlerWithoutProxy() + { + // given + var config = new HttpClientConfig( + false, + false, + null, null, null, null, null, - null, false, false, 0 ); - + // when var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); - + + // then + Assert.IsFalse(handler.UseProxy); + Assert.IsNull(handler.Proxy); + } + + [Test] + public void TestIgnoreProxyDetailsIfProxyDisabled() + { + // given + var config = new HttpClientConfig( + true, + false, + "snowflake.com", + "123", + "testUser", + "proxyPassword", + "localhost", + false, + false, + 7 + ); + + // when + var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); + // then Assert.IsFalse(handler.UseProxy); Assert.IsNull(handler.Proxy); diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index a57a9fb74..c4e5e7642 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -9,6 +9,7 @@ using Snowflake.Data.Client; using Snowflake.Data.Core.Authenticator; using Snowflake.Data.Core.Tools; +using Snowflake.Data.Tests.Util; namespace Snowflake.Data.Tests.UnitTests { @@ -166,6 +167,58 @@ public void TestResolveConnectionArea(string host, string expectedMessage) Assert.AreEqual(expectedMessage, message); } + [Test] + public void TestFailWhenProxyConfiguredWithoutPort() + { + // arrange + var connectionString = "ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyHost=localhost"; + + // act + var thrown = Assert.Throws(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + + // assert + SnowflakeDbExceptionAssert.HasErrorCode(thrown, SFError.MISSING_CONNECTION_PROPERTY); + Assert.That(thrown.Message, Does.Contain("Required property PROXYPORT is not provided")); + } + + [Test] + public void TestFailWhenProxyUserProvidedWithoutProxyPassword() + { + // arrange + var connectionString = "ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyHost=localhost;proxyPort=1234;proxyUser=testUser"; + + // act + var thrown = Assert.Throws(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + + // assert + SnowflakeDbExceptionAssert.HasErrorCode(thrown, SFError.MISSING_CONNECTION_PROPERTY); + Assert.That(thrown.Message, Does.Contain("Required property PROXYPASSWORD is not provided")); + } + + [Test] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyPort=1234;proxyPassword=xyz", SFSessionProperty.PROXYPORT)] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyUser=testUser;proxyPassword=xyz", SFSessionProperty.PROXYUSER)] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyPassword=xyz", SFSessionProperty.PROXYPASSWORD)] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;nonProxyHosts=xyz", SFSessionProperty.NONPROXYHOSTS)] + public void TestFailWhenConfiguringProxyDetailsWithoutProxyHost(string connectionString, SFSessionProperty unwantedProperty) + { + // act + var thrown = Assert.Throws(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + + // assert + SnowflakeDbExceptionAssert.HasErrorCode(thrown, SFError.INVALID_CONNECTION_STRING); + Assert.That(thrown.Message, Does.Contain($"Proxy property {unwantedProperty.ToString()} provided while PROXYHOST is missing")); + } + + [Test] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;proxyHost=localhost")] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=false;proxyHost=localhost;proxyPort=1234;proxyUser=testUser")] + public void TestProxyValidationsOnlyWhenProxyEnabledAndProxyHostConfigured(string connectionString) + { + // act + Assert.DoesNotThrow(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + } + public static IEnumerable ConnectionStringTestCases() { string defAccount = "testaccount"; diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs index 53941cc27..394f57f63 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs @@ -13,7 +13,7 @@ namespace Snowflake.Data.Tests.UnitTests.Session public class SFHttpClientProxyPropertiesTest { [Test, TestCaseSource(nameof(ProxyPropertiesProvider))] - public void ShouldExtractProxyProperties(ProxyPropertiesTestCase testCase) + public void TestExtractProxyProperties(ProxyPropertiesTestCase testCase) { // given var extractor = new SFSessionHttpClientProxyProperties.Extractor(); @@ -23,6 +23,7 @@ public void ShouldExtractProxyProperties(ProxyPropertiesTestCase testCase) var proxyProperties = extractor.ExtractProperties(properties); // then + Assert.AreEqual(testCase.expectedProperties.useProxy, proxyProperties.useProxy); Assert.AreEqual(testCase.expectedProperties.proxyHost, proxyProperties.proxyHost); Assert.AreEqual(testCase.expectedProperties.proxyPort, proxyProperties.proxyPort); Assert.AreEqual(testCase.expectedProperties.nonProxyHosts, proxyProperties.nonProxyHosts); @@ -37,6 +38,7 @@ public static IEnumerable ProxyPropertiesProvider() conectionString = "account=test;user=test;password=test", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = false, proxyHost = null, proxyPort = null, nonProxyHosts = null, @@ -49,6 +51,20 @@ public static IEnumerable ProxyPropertiesProvider() conectionString = "account=test;user=test;password=test;useProxy=false;proxyHost=snowflake.com;proxyPort=123;nonProxyHosts=localhost;proxyPassword=proxyPassword;proxyUser=Chris", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = false, + proxyHost = null, + proxyPort = null, + nonProxyHosts = null, + proxyPassword = null, + proxyUser = null + } + }; + var proxyPropertiesConfiguredButDisabledCase2 = new ProxyPropertiesTestCase() + { + conectionString = "account=test;user=test;password=test;proxyHost=snowflake.com;proxyPort=123;nonProxyHosts=localhost;proxyPassword=proxyPassword;proxyUser=Chris", + expectedProperties = new SFSessionHttpClientProxyProperties() + { + useProxy = false, proxyHost = null, proxyPort = null, nonProxyHosts = null, @@ -58,11 +74,12 @@ public static IEnumerable ProxyPropertiesProvider() }; var proxyPropertiesConfiguredAndEnabledCase = new ProxyPropertiesTestCase() { - conectionString = "account=test;user=test;password=test;useProxy=true;proxyHost=snowflake.com", + conectionString = "account=test;user=test;password=test;useProxy=true;proxyHost=snowflake.com;proxyPort=1234", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = true, proxyHost = "snowflake.com", - proxyPort = null, + proxyPort = "1234", nonProxyHosts = null, proxyPassword = null, proxyUser = null @@ -74,6 +91,7 @@ public static IEnumerable ProxyPropertiesProvider() "account=test;user=test;password=test;useProxy=true;proxyHost=snowflake.com;proxyPort=123;nonProxyHosts=localhost;proxyPassword=proxyPassword;proxyUser=Chris", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = true, proxyHost = "snowflake.com", proxyPort = "123", nonProxyHosts = "localhost", @@ -81,12 +99,27 @@ public static IEnumerable ProxyPropertiesProvider() proxyUser = "Chris" } }; + var defaultProxyEnabled = new ProxyPropertiesTestCase() + { + conectionString = "account=test;user=test;password=test;useProxy=true;", + expectedProperties = new SFSessionHttpClientProxyProperties() + { + useProxy = true, + proxyHost = null, + proxyPort = null, + nonProxyHosts = null, + proxyPassword = null, + proxyUser = null + } + }; return new [] { noProxyPropertiesCase, proxyPropertiesConfiguredButDisabledCase, + proxyPropertiesConfiguredButDisabledCase2, proxyPropertiesConfiguredAndEnabledCase, - proxyPropertiesAllConfiguredAndEnabled + proxyPropertiesAllConfiguredAndEnabled, + defaultProxyEnabled }; } diff --git a/Snowflake.Data/Core/HttpUtil.cs b/Snowflake.Data/Core/HttpUtil.cs index 531e76fd7..cad58061d 100755 --- a/Snowflake.Data/Core/HttpUtil.cs +++ b/Snowflake.Data/Core/HttpUtil.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ @@ -21,6 +21,7 @@ public class HttpClientConfig { public HttpClientConfig( bool crlCheckEnabled, + bool useProxy, string proxyHost, string proxyPort, string proxyUser, @@ -32,6 +33,7 @@ public HttpClientConfig( bool includeRetryReason = true) { CrlCheckEnabled = crlCheckEnabled; + UseProxy = useProxy; ProxyHost = proxyHost; ProxyPort = proxyPort; ProxyUser = proxyUser; @@ -45,6 +47,7 @@ public HttpClientConfig( ConfKey = string.Join(";", new string[] { crlCheckEnabled.ToString(), + useProxy.ToString(), proxyHost, proxyPort, proxyUser, @@ -57,6 +60,7 @@ public HttpClientConfig( } public readonly bool CrlCheckEnabled; + public readonly bool UseProxy; public readonly string ProxyHost; public readonly string ProxyPort; public readonly string ProxyUser; @@ -87,7 +91,7 @@ public sealed class HttpUtil private HttpUtil() { - // This value is used by AWS SDK and can cause deadlock, + // This value is used by AWS SDK and can cause deadlock, // so we need to increase the default value of 2 // See: https://github.com/aws/aws-sdk-net/issues/152 ServicePointManager.DefaultConnectionLimit = 50; @@ -130,7 +134,7 @@ private HttpClient RegisterNewHttpClientIfNecessary(HttpClientConfig config) internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) { - HttpMessageHandler httpHandler; + HttpClientHandler httpHandler; try { httpHandler = new HttpClientHandler() @@ -140,8 +144,7 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) // Enforce tls v1.2 SslProtocols = SslProtocols.Tls12, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = false, // Disable cookies - UseProxy = false + UseCookies = false // Disable cookies }; } // special logic for .NET framework 4.7.1 that @@ -151,58 +154,66 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) httpHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = false, // Disable cookies - UseProxy = false + UseCookies = false // Disable cookies }; } - // Add a proxy if necessary - if (null != config.ProxyHost) + httpHandler.UseProxy = config.UseProxy; + + if (config.UseProxy && !string.IsNullOrEmpty(config.ProxyHost)) + { + logger.Info("Configuring proxy based on connection properties"); + var proxy = ConfigureWebProxy(config); + httpHandler.Proxy = proxy; + } + else if (config.UseProxy) { - // Proxy needed - WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); + logger.Info("Using a default proxy"); + } - // Add credential if provided - if (!String.IsNullOrEmpty(config.ProxyUser)) - { - ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); - proxy.Credentials = credentials; - } + return httpHandler; + } + + private WebProxy ConfigureWebProxy(HttpClientConfig config) + { + WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); + + // Add credential if provided + if (!String.IsNullOrEmpty(config.ProxyUser)) + { + ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); + proxy.Credentials = credentials; + } - // Add bypasslist if provided - if (!String.IsNullOrEmpty(config.NoProxyList)) + // Add bypasslist if provided + if (!String.IsNullOrEmpty(config.NoProxyList)) + { + string[] bypassList = config.NoProxyList.Split( + new char[] { '|' }, + StringSplitOptions.RemoveEmptyEntries); + // Convert simplified syntax to standard regular expression syntax + string entry = null; + for (int i = 0; i < bypassList.Length; i++) { - string[] bypassList = config.NoProxyList.Split( - new char[] { '|' }, - StringSplitOptions.RemoveEmptyEntries); - // Convert simplified syntax to standard regular expression syntax - string entry = null; - for (int i = 0; i < bypassList.Length; i++) - { - // Get the original entry - entry = bypassList[i].Trim(); - // . -> [.] because . means any char - entry = entry.Replace(".", "[.]"); - // * -> .* because * is a quantifier and need a char or group to apply to - entry = entry.Replace("*", ".*"); - - entry = entry.StartsWith("^") ? entry : $"^{entry}"; - - entry = entry.EndsWith("$") ? entry : $"{entry}$"; - - // Replace with the valid entry syntax - bypassList[i] = entry; + // Get the original entry + entry = bypassList[i].Trim(); + // . -> [.] because . means any char + entry = entry.Replace(".", "[.]"); + // * -> .* because * is a quantifier and need a char or group to apply to + entry = entry.Replace("*", ".*"); - } - proxy.BypassList = bypassList; - } + entry = entry.StartsWith("^") ? entry : $"^{entry}"; - HttpClientHandler httpHandlerWithProxy = (HttpClientHandler)httpHandler; - httpHandlerWithProxy.UseProxy = true; - httpHandlerWithProxy.Proxy = proxy; - return httpHandlerWithProxy; + entry = entry.EndsWith("$") ? entry : $"{entry}$"; + + // Replace with the valid entry syntax + bypassList[i] = entry; + + } + proxy.BypassList = bypassList; } - return httpHandler; + + return proxy; } /// @@ -384,7 +395,7 @@ protected override async Task SendAsync(HttpRequestMessage if (httpTimeout.Ticks == 0) childCts.Cancel(); else - childCts.CancelAfter(httpTimeout); + childCts.CancelAfter(httpTimeout); } response = await base.SendAsync(requestMessage, childCts == null ? cancellationToken : childCts.Token).ConfigureAwait(false); diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index 2d818f8c8..6af6f64aa 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -179,6 +179,7 @@ public HttpClientConfig BuildHttpClientConfig() { return new HttpClientConfig( !insecureMode, + proxyProperties.useProxy, proxyProperties.proxyHost, proxyProperties.proxyPort, proxyProperties.proxyUser, diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index 4266c585d..ce63b098d 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -6,6 +6,7 @@ namespace Snowflake.Data.Core internal class SFSessionHttpClientProxyProperties { + internal bool useProxy = false; internal string proxyHost = null; internal string proxyPort = null; internal string nonProxyHosts = null; @@ -22,7 +23,10 @@ internal class Extractor : IExtractor public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties propertiesDictionary) { var properties = new SFSessionHttpClientProxyProperties(); - if (Boolean.Parse(propertiesDictionary[SFSessionProperty.USEPROXY])) + + properties.useProxy = Boolean.Parse(propertiesDictionary[SFSessionProperty.USEPROXY]); + + if (properties.useProxy) { // Let's try to get the associated RestRequester propertiesDictionary.TryGetValue(SFSessionProperty.PROXYHOST, out properties.proxyHost); @@ -34,7 +38,7 @@ public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties if (!String.IsNullOrEmpty(properties.nonProxyHosts)) { // The list is url-encoded - // Host names are separated with a URL-escaped pipe symbol (%7C). + // Host names are separated with a URL-escaped pipe symbol (%7C). properties.nonProxyHosts = HttpUtility.UrlDecode(properties.nonProxyHosts); } } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index bfbe71a2a..3d850a561 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -220,37 +220,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin } UpdatePropertiesForSpecialCases(properties, connectionString); - - var useProxy = false; - if (properties.ContainsKey(SFSessionProperty.USEPROXY)) - { - try - { - useProxy = Boolean.Parse(properties[SFSessionProperty.USEPROXY]); - } - catch (Exception e) - { - // The useProxy setting is not a valid boolean value - logger.Error("Unable to connect", e); - throw new SnowflakeDbException(e, - SFError.INVALID_CONNECTION_STRING, - e.Message); - } - } - - // Based on which proxy settings have been provided, update the required settings list - if (useProxy) - { - // If useProxy is true, then proxyhost and proxy port are mandatory - SFSessionProperty.PROXYHOST.GetAttribute().required = true; - SFSessionProperty.PROXYPORT.GetAttribute().required = true; - - // If a username is provided, then a password is required - if (properties.ContainsKey(SFSessionProperty.PROXYUSER)) - { - SFSessionProperty.PROXYPASSWORD.GetAttribute().required = true; - } - } + ValidateProxy(properties); if (password != null && password.Length > 0) { @@ -296,6 +266,60 @@ internal static string ResolveConnectionAreaMessage(string host) => ? "Connecting to CHINA Snowflake domain" : "Connecting to GLOBAL Snowflake domain"; + + private static void ValidateProxy(SFSessionProperties properties) + { + var useProxy = false; + if (properties.ContainsKey(SFSessionProperty.USEPROXY)) + { + try + { + useProxy = Boolean.Parse(properties[SFSessionProperty.USEPROXY]); + } + catch (Exception e) + { + // The useProxy setting is not a valid boolean value + logger.Error("Unable to connect", e); + throw new SnowflakeDbException(e, + SFError.INVALID_CONNECTION_STRING, + e.Message); + } + } + + var isProxyHostProvided = properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYHOST); + if (useProxy && isProxyHostProvided) + { + if (!properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYPORT)) + { + throw new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, SFSessionProperty.PROXYPORT.ToString()); + } + if (properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYUSER) && + !properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYPASSWORD)) + { + throw new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, SFSessionProperty.PROXYPASSWORD.ToString()); + } + } + + if (useProxy && !isProxyHostProvided) + { + var property = FindProxyDetailOtherThanHost(properties); + if (property != null) + { + var exception = new Exception($"Proxy property {property.ToString()} provided while {SFSessionProperty.PROXYHOST.ToString()} is missing"); + logger.Error("Unable to connect", exception); + throw new SnowflakeDbException(exception, SFError.INVALID_CONNECTION_STRING, exception.Message); + } + } + } + + private static SFSessionProperty? FindProxyDetailOtherThanHost(SFSessionProperties properties) + { + var proxyProperties = new[] { SFSessionProperty.PROXYPORT, SFSessionProperty.PROXYUSER, SFSessionProperty.PROXYPASSWORD, SFSessionProperty.NONPROXYHOSTS } + .Where(properties.IsNonEmptyValueProvided) + .ToArray(); + return proxyProperties.Length == 0 ? (SFSessionProperty?) null : proxyProperties[0]; + } + private static void ValidateAuthenticator(SFSessionProperties properties) { var knownAuthenticators = new[] { diff --git a/doc/Connecting.md b/doc/Connecting.md index 576120f79..9982da2e6 100644 --- a/doc/Connecting.md +++ b/doc/Connecting.md @@ -34,11 +34,11 @@ The following table lists all valid connection properties: | TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | | INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | | USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | -| PROXYHOST | Depends | The hostname of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | -| PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | -| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | +| PROXYHOST | Depends | The hostname of the proxy server.

Proxy parameters are used only if you enable the proxy by setting USEPROXY to `true`. It is required if you provide your own proxy configuration. Do not provide this value if you want to use a default proxy.

This parameter was introduced in v2.0.4. | +| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true` and PROXYHOST is provided, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYUSER | No | The username for authenticating to the proxy server. Parameter is used only if USEPROXY is set to `true` and PROXYHOST is provided.

This parameter was introduced in v2.0.4. | +| PROXYPASSWORD | Depends | The password for authenticating to the proxy server. Parameter is used only if USEPROXY is set to `true` and PROXYHOST is provided.

If USEPROXY is `true` and PROXYHOST and PROXYUSER are set, you must set this parameter.

This parameter was introduced in v2.0.4. | +| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server.
The parameter is used only if USEPROXY is set to `true` and PROXYHOST is provided.
Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | | FILE_TRANSFER_MEMORY_THRESHOLD | No | The maximum number of bytes to store in memory used in order to provide a file encryption. If encrypting/decrypting file size exceeds provided value a temporary file will be created and the work will be continued in the temporary file instead of memory.
If no value provided 1MB will be used as a default value (that is 1048576 bytes).
It is possible to configure any integer value bigger than zero representing maximal number of bytes to reside in memory. | | CLIENT_CONFIG_FILE | No | The location of the client configuration json file. In this file you can configure easy logging feature. | | ALLOWUNDERSCORESINHOST | No | Specifies whether to allow underscores in account names. This impacts PrivateLink customers whose account names contain underscores. In this situation, you must override the default value by setting allowUnderscoresInHost to true. |