-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
proxy: match NO_PROXY formats to libcurl behaviour
We aim to be compatible with the behaviour of Git as much as possible when it comes to network settings. This enables users to setup Git proxy settings and get the same setup "for free" with GCM. Git uses libcurl to provide it's HTTP interactions. The NO_PROXY setting is used by libcurl to disable proxy settings for specific hosts. We previously attempted to plumb the value of NO_PROXY through to the .NET WebProxy class' list of "bypassed addresses" (the set of hosts that should not be proxied). However, the .NET class expects a set of _regular expressions_ which is unlike libcurl! As a result, libcurl permitted values for NO_PROXY were throwing errors inside of GCM since they are not valid regexs. In this commit we perform a transformation of the NO_PROXY list and construct a set of regular expressions that match addresses in the same way as libcurl does. The transformation is as follows: 1. strip any leading periods '.' or wildcards '*.' 2. escape the remaining input to match literally (e.g.: '.' becomes '\.') 3. prepend a group that matches either a period '.' or a URI scheme delimiter '://' - this prevents partial domain matching 4. append a end-of-string symbol '$' to ensure we only match to the specified TLD and port See the libcurl documentation on NO_PROXY behaviour: https://curl.se/libcurl/c/CURLOPT_NOPROXY.html
- Loading branch information
1 parent
d872294
commit f466150
Showing
5 changed files
with
221 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using Microsoft.Git.CredentialManager.Tests.Objects; | ||
using Xunit; | ||
|
||
|
@@ -427,6 +428,55 @@ public void Settings_IsWindowsIntegratedAuthenticationEnabled_ConfigNonBooleanyV | |
Assert.True(settings.IsWindowsIntegratedAuthenticationEnabled); | ||
} | ||
|
||
[Theory] | ||
[InlineData("", new string[0])] | ||
[InlineData(" ", new string[0])] | ||
[InlineData(",", new string[0])] | ||
[InlineData("example.com", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData("example.com:8080", new[] { @"(\.|\:\/\/)example\.com:8080$" })] | ||
[InlineData("example.com,", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData(",example.com", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData(",example.com,", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData(".example.com", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData("..example.com", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData("*.example.com", new[] { @"(\.|\:\/\/)example\.com$" })] | ||
[InlineData("my.example.com", new[] { @"(\.|\:\/\/)my\.example\.com$" })] | ||
[InlineData("example.com,contoso.com,fabrikam.com", new[] | ||
{ | ||
@"(\.|\:\/\/)example\.com$", | ||
@"(\.|\:\/\/)contoso\.com$", | ||
@"(\.|\:\/\/)fabrikam\.com$" | ||
})] | ||
public void Settings_ProxyConfiguration_ConvertToBypassRegexArray(string input, string[] expected) | ||
{ | ||
string[] actual = ProxyConfiguration.ConvertToBypassRegexArray(input).ToArray(); | ||
Assert.Equal(expected, actual); | ||
} | ||
|
||
[Theory] | ||
[InlineData("example.com", "http://example.com", true)] | ||
[InlineData("example.com", "https://example.com", true)] | ||
[InlineData("example.com", "https://www.example.com", true)] | ||
[InlineData("example.com", "http://www.example.com:80", true)] | ||
[InlineData("example.com", "https://www.example.com:443", true)] | ||
[InlineData("example.com", "https://www.example.com:8080", false)] | ||
[InlineData("example.com", "http://notanexample.com", false)] | ||
[InlineData("example.com", "https://notanexample.com", false)] | ||
[InlineData("example.com", "https://www.notanexample.com", false)] | ||
[InlineData("example.com", "https://example.com.otherltd", false)] | ||
public void Settings_ProxyConfiguration_ConvertToBypassRegexArray_WebProxyBypass(string noProxy, string address, bool expected) | ||
{ | ||
var bypassList = ProxyConfiguration.ConvertToBypassRegexArray(noProxy).ToArray(); | ||
var webProxy = new WebProxy("https://localhost:8080/proxy") | ||
{ | ||
BypassList = bypassList | ||
}; | ||
|
||
bool actual = webProxy.IsBypassed(new Uri(address)); | ||
|
||
Assert.Equal(expected, actual); | ||
} | ||
|
||
[Fact] | ||
public void Settings_ProxyConfiguration_Unset_ReturnsNull() | ||
{ | ||
|
@@ -458,11 +508,11 @@ public void Settings_ProxyConfiguration_GcmHttpConfig_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)} | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy} | ||
}; | ||
var git = new TestGit(); | ||
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()}; | ||
|
@@ -478,7 +528,7 @@ public void Settings_ProxyConfiguration_GcmHttpConfig_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.True(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
@@ -494,11 +544,11 @@ public void Settings_ProxyConfiguration_GcmHttpsConfig_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)} | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy} | ||
}; | ||
var git = new TestGit(); | ||
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()}; | ||
|
@@ -514,7 +564,7 @@ public void Settings_ProxyConfiguration_GcmHttpsConfig_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.True(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
@@ -530,11 +580,11 @@ public void Settings_ProxyConfiguration_GitHttpConfig_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)} | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy} | ||
}; | ||
var git = new TestGit(); | ||
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()}; | ||
|
@@ -550,7 +600,7 @@ public void Settings_ProxyConfiguration_GitHttpConfig_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.False(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
@@ -579,42 +629,6 @@ public void Settings_ProxyConfiguration_GitHttpConfig_EmptyScopedUriUnscoped_Ret | |
Assert.Null(actualConfig); | ||
} | ||
|
||
[Fact] | ||
public void Settings_ProxyConfiguration_NoProxyMixedSplitChar_ReturnsValue() | ||
{ | ||
const string remoteUrl = "http://example.com/foo.git"; | ||
const string section = Constants.GitConfiguration.Http.SectionName; | ||
const string property = Constants.GitConfiguration.Http.Proxy; | ||
var remoteUri = new Uri(remoteUrl); | ||
|
||
const string expectedUserName = "john.doe"; | ||
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com", "example.com"}; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = "contoso.com, fabrikam.com example.com,"} | ||
}; | ||
var git = new TestGit(); | ||
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()}; | ||
|
||
var settings = new Settings(envars, git) | ||
{ | ||
RemoteUri = remoteUri | ||
}; | ||
|
||
ProxyConfiguration actualConfig = settings.GetProxyConfiguration(); | ||
|
||
Assert.NotNull(actualConfig); | ||
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.False(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
[Fact] | ||
public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue() | ||
{ | ||
|
@@ -625,14 +639,14 @@ public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = | ||
{ | ||
[Constants.EnvironmentVariables.CurlHttpProxy] = settingValue.ToString(), | ||
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList) | ||
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy | ||
} | ||
}; | ||
var git = new TestGit(); | ||
|
@@ -648,7 +662,7 @@ public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.False(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
@@ -662,14 +676,14 @@ public void Settings_ProxyConfiguration_CurlHttpsEnvar_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = | ||
{ | ||
[Constants.EnvironmentVariables.CurlHttpsProxy] = settingValue.ToString(), | ||
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList) | ||
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy | ||
} | ||
}; | ||
var git = new TestGit(); | ||
|
@@ -685,7 +699,7 @@ public void Settings_ProxyConfiguration_CurlHttpsEnvar_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.False(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
@@ -699,14 +713,14 @@ public void Settings_TryGetProxy_CurlAllEnvar_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"contoso.com", "fabrikam.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = | ||
{ | ||
[Constants.EnvironmentVariables.CurlAllProxy] = settingValue.ToString(), | ||
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList) | ||
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy | ||
} | ||
}; | ||
var git = new TestGit(); | ||
|
@@ -722,7 +736,7 @@ public void Settings_TryGetProxy_CurlAllEnvar_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.False(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
@@ -736,14 +750,14 @@ public void Settings_ProxyConfiguration_LegacyGcmEnvar_ReturnsValue() | |
const string expectedPassword = "letmein123"; | ||
var expectedAddress = new Uri("http://proxy.example.com"); | ||
var settingValue = new Uri("http://john.doe:[email protected]"); | ||
var bypassList = new List<string> {"https://contoso.com", ".*fabrikam\\.com"}; | ||
var expectedNoProxy = "contoso.com,fabrikam.com"; | ||
|
||
var envars = new TestEnvironment | ||
{ | ||
Variables = | ||
{ | ||
[Constants.EnvironmentVariables.GcmHttpProxy] = settingValue.ToString(), | ||
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList) | ||
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy | ||
} | ||
}; | ||
var git = new TestGit(); | ||
|
@@ -759,7 +773,7 @@ public void Settings_ProxyConfiguration_LegacyGcmEnvar_ReturnsValue() | |
Assert.Equal(expectedAddress, actualConfig.Address); | ||
Assert.Equal(expectedUserName, actualConfig.UserName); | ||
Assert.Equal(expectedPassword, actualConfig.Password); | ||
Assert.Equal(bypassList, actualConfig.BypassHosts); | ||
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw); | ||
Assert.True(actualConfig.IsDeprecatedSource); | ||
} | ||
|
||
|
Oops, something went wrong.