Skip to content

Commit a391785

Browse files
authored
Merge pull request #501 from mjcheetham/noproxy-globs
Match `NO_PROXY` formats to libcurl behaviour
2 parents f22fd0b + 8cd79a3 commit a391785

File tree

5 files changed

+223
-73
lines changed

5 files changed

+223
-73
lines changed

docs/netconfig.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,28 @@ addresses. GCM Core supports the cURL environment variable `NO_PROXY` for this
6262
scenariom, as does Git itself.
6363

6464
The `NO_PROXY` environment variable should contain a comma (`,`) or space (` `)
65-
separated list of regular expressions to match hosts that should not be proxied
66-
(should connect directly).
65+
separated list of host names that should not be proxied (should connect
66+
directly).
67+
68+
GCM Core attempts to match [libcurl's behaviour](https://curl.se/libcurl/c/CURLOPT_NOPROXY.html),
69+
which is briefly summarized here:
70+
71+
- a value of `*` disables proxying for all hosts;
72+
- other wildcard use is **not** supported;
73+
- each name in the list is matched as a domain which contains the hostname,
74+
or the hostname itself
75+
- a leading period/dot `.` matches against the provided hostname
76+
77+
For example, setting `NO_PROXY` to `example.com` results in the following:
78+
79+
Hostname|Matches?
80+
-|-
81+
`example.com`|:white_check_mark:
82+
`example.com:80`|:white_check_mark:
83+
`www.example.com`|:white_check_mark:
84+
`notanexample.com`|:x:
85+
`www.notanexample.com`|:x:
86+
`example.com.othertld`|:x:
6787

6888
**Example:**
6989

src/shared/Core.Tests/HttpClientFactoryTests.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithBypass_ReturnsTrueOutProxy
8888
const string repoPath = "/tmp/repos/foo";
8989
const string repoRemote = "https://remote.example.com/foo.git";
9090

91-
var bypassList = new List<string> {"https://contoso.com", ".*fabrikam\\.com"};
91+
var noProxyRaw = "contoso.com,fabrikam.com";
9292
var repoRemoteUri = new Uri(repoRemote);
9393
var proxyConfig = new ProxyConfiguration(
9494
new Uri(proxyUrl),
9595
userName: null,
9696
password: null,
97-
bypassHosts: bypassList);
97+
noProxyRaw: noProxyRaw);
9898

9999
var settings = new TestSettings
100100
{
@@ -117,6 +117,35 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithBypass_ReturnsTrueOutProxy
117117
Assert.False(proxy.IsBypassed(repoRemoteUri));
118118
}
119119

120+
[Fact]
121+
public void HttpClientFactory_TryCreateProxy_ProxyWithWildcardBypass_ReturnsFalse()
122+
{
123+
const string proxyUrl = "https://proxy.example.com/git";
124+
const string repoPath = "/tmp/repos/foo";
125+
const string repoRemote = "https://remote.example.com/foo.git";
126+
127+
var noProxyRaw = "*";
128+
var repoRemoteUri = new Uri(repoRemote);
129+
var proxyConfig = new ProxyConfiguration(
130+
new Uri(proxyUrl),
131+
userName: null,
132+
password: null,
133+
noProxyRaw: noProxyRaw);
134+
135+
var settings = new TestSettings
136+
{
137+
RemoteUri = repoRemoteUri,
138+
RepositoryPath = repoPath,
139+
ProxyConfiguration = proxyConfig
140+
};
141+
var httpFactory = new HttpClientFactory(Mock.Of<IFileSystem>(), Mock.Of<ITrace>(), settings, Mock.Of<IStandardStreams>());
142+
143+
bool result = httpFactory.TryCreateProxy(out IWebProxy proxy);
144+
145+
Assert.False(result);
146+
Assert.Null(proxy);
147+
}
148+
120149
[Fact]
121150
public void HttpClientFactory_TryCreateProxy_ProxyWithCredentials_ReturnsTrueOutProxyWithUrlConfiguredCredentials()
122151
{

src/shared/Core.Tests/SettingsTests.cs

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Net;
45
using GitCredentialManager.Tests.Objects;
56
using Xunit;
67

@@ -427,6 +428,57 @@ public void Settings_IsWindowsIntegratedAuthenticationEnabled_ConfigNonBooleanyV
427428
Assert.True(settings.IsWindowsIntegratedAuthenticationEnabled);
428429
}
429430

431+
[Theory]
432+
[InlineData("", new string[0])]
433+
[InlineData(" ", new string[0])]
434+
[InlineData(",", new string[0])]
435+
[InlineData("example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
436+
[InlineData("example.com:8080", new[] { @"(\.|\:\/\/)example\.com:8080$" })]
437+
[InlineData("example.com,", new[] { @"(\.|\:\/\/)example\.com$" })]
438+
[InlineData(",example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
439+
[InlineData(",example.com,", new[] { @"(\.|\:\/\/)example\.com$" })]
440+
[InlineData(".example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
441+
[InlineData("..example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
442+
[InlineData("*.example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
443+
[InlineData("my.example.com", new[] { @"(\.|\:\/\/)my\.example\.com$" })]
444+
[InlineData("example.com,contoso.com,fabrikam.com", new[]
445+
{
446+
@"(\.|\:\/\/)example\.com$",
447+
@"(\.|\:\/\/)contoso\.com$",
448+
@"(\.|\:\/\/)fabrikam\.com$"
449+
})]
450+
public void Settings_ProxyConfiguration_ConvertToBypassRegexArray(string input, string[] expected)
451+
{
452+
string[] actual = ProxyConfiguration.ConvertToBypassRegexArray(input).ToArray();
453+
Assert.Equal(expected, actual);
454+
}
455+
456+
[Theory]
457+
[InlineData("example.com", "http://example.com", true)]
458+
[InlineData("example.com", "https://example.com", true)]
459+
[InlineData("example.com", "https://www.example.com", true)]
460+
[InlineData("example.com", "http://www.example.com:80", true)]
461+
[InlineData("example.com", "https://www.example.com:443", true)]
462+
[InlineData("example.com", "https://www.example.com:8080", false)]
463+
[InlineData("example.com", "http://notanexample.com", false)]
464+
[InlineData("example.com", "https://notanexample.com", false)]
465+
[InlineData("example.com", "https://www.notanexample.com", false)]
466+
[InlineData("example.com", "https://example.com.otherltd", false)]
467+
[InlineData("example.com:8080", "http://example.com", false)]
468+
[InlineData("my.example.com", "http://example.com", false)]
469+
public void Settings_ProxyConfiguration_ConvertToBypassRegexArray_WebProxyBypass(string noProxy, string address, bool expected)
470+
{
471+
var bypassList = ProxyConfiguration.ConvertToBypassRegexArray(noProxy).ToArray();
472+
var webProxy = new WebProxy("https://localhost:8080/proxy")
473+
{
474+
BypassList = bypassList
475+
};
476+
477+
bool actual = webProxy.IsBypassed(new Uri(address));
478+
479+
Assert.Equal(expected, actual);
480+
}
481+
430482
[Fact]
431483
public void Settings_ProxyConfiguration_Unset_ReturnsNull()
432484
{
@@ -458,11 +510,11 @@ public void Settings_ProxyConfiguration_GcmHttpConfig_ReturnsValue()
458510
const string expectedPassword = "letmein123";
459511
var expectedAddress = new Uri("http://proxy.example.com");
460512
var settingValue = new Uri("http://john.doe:[email protected]");
461-
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
513+
var expectedNoProxy = "contoso.com,fabrikam.com";
462514

463515
var envars = new TestEnvironment
464516
{
465-
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)}
517+
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy}
466518
};
467519
var git = new TestGit();
468520
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
@@ -478,7 +530,7 @@ public void Settings_ProxyConfiguration_GcmHttpConfig_ReturnsValue()
478530
Assert.Equal(expectedAddress, actualConfig.Address);
479531
Assert.Equal(expectedUserName, actualConfig.UserName);
480532
Assert.Equal(expectedPassword, actualConfig.Password);
481-
Assert.Equal(bypassList, actualConfig.BypassHosts);
533+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
482534
Assert.True(actualConfig.IsDeprecatedSource);
483535
}
484536

@@ -494,11 +546,11 @@ public void Settings_ProxyConfiguration_GcmHttpsConfig_ReturnsValue()
494546
const string expectedPassword = "letmein123";
495547
var expectedAddress = new Uri("http://proxy.example.com");
496548
var settingValue = new Uri("http://john.doe:[email protected]");
497-
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
549+
var expectedNoProxy = "contoso.com,fabrikam.com";
498550

499551
var envars = new TestEnvironment
500552
{
501-
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)}
553+
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy}
502554
};
503555
var git = new TestGit();
504556
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
@@ -514,7 +566,7 @@ public void Settings_ProxyConfiguration_GcmHttpsConfig_ReturnsValue()
514566
Assert.Equal(expectedAddress, actualConfig.Address);
515567
Assert.Equal(expectedUserName, actualConfig.UserName);
516568
Assert.Equal(expectedPassword, actualConfig.Password);
517-
Assert.Equal(bypassList, actualConfig.BypassHosts);
569+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
518570
Assert.True(actualConfig.IsDeprecatedSource);
519571
}
520572

@@ -530,11 +582,11 @@ public void Settings_ProxyConfiguration_GitHttpConfig_ReturnsValue()
530582
const string expectedPassword = "letmein123";
531583
var expectedAddress = new Uri("http://proxy.example.com");
532584
var settingValue = new Uri("http://john.doe:[email protected]");
533-
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
585+
var expectedNoProxy = "contoso.com,fabrikam.com";
534586

535587
var envars = new TestEnvironment
536588
{
537-
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)}
589+
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy}
538590
};
539591
var git = new TestGit();
540592
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
@@ -550,7 +602,7 @@ public void Settings_ProxyConfiguration_GitHttpConfig_ReturnsValue()
550602
Assert.Equal(expectedAddress, actualConfig.Address);
551603
Assert.Equal(expectedUserName, actualConfig.UserName);
552604
Assert.Equal(expectedPassword, actualConfig.Password);
553-
Assert.Equal(bypassList, actualConfig.BypassHosts);
605+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
554606
Assert.False(actualConfig.IsDeprecatedSource);
555607
}
556608

@@ -579,42 +631,6 @@ public void Settings_ProxyConfiguration_GitHttpConfig_EmptyScopedUriUnscoped_Ret
579631
Assert.Null(actualConfig);
580632
}
581633

582-
[Fact]
583-
public void Settings_ProxyConfiguration_NoProxyMixedSplitChar_ReturnsValue()
584-
{
585-
const string remoteUrl = "http://example.com/foo.git";
586-
const string section = Constants.GitConfiguration.Http.SectionName;
587-
const string property = Constants.GitConfiguration.Http.Proxy;
588-
var remoteUri = new Uri(remoteUrl);
589-
590-
const string expectedUserName = "john.doe";
591-
const string expectedPassword = "letmein123";
592-
var expectedAddress = new Uri("http://proxy.example.com");
593-
var settingValue = new Uri("http://john.doe:[email protected]");
594-
var bypassList = new List<string> {"contoso.com", "fabrikam.com", "example.com"};
595-
596-
var envars = new TestEnvironment
597-
{
598-
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = "contoso.com, fabrikam.com example.com,"}
599-
};
600-
var git = new TestGit();
601-
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
602-
603-
var settings = new Settings(envars, git)
604-
{
605-
RemoteUri = remoteUri
606-
};
607-
608-
ProxyConfiguration actualConfig = settings.GetProxyConfiguration();
609-
610-
Assert.NotNull(actualConfig);
611-
Assert.Equal(expectedAddress, actualConfig.Address);
612-
Assert.Equal(expectedUserName, actualConfig.UserName);
613-
Assert.Equal(expectedPassword, actualConfig.Password);
614-
Assert.Equal(bypassList, actualConfig.BypassHosts);
615-
Assert.False(actualConfig.IsDeprecatedSource);
616-
}
617-
618634
[Fact]
619635
public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue()
620636
{
@@ -625,14 +641,14 @@ public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue()
625641
const string expectedPassword = "letmein123";
626642
var expectedAddress = new Uri("http://proxy.example.com");
627643
var settingValue = new Uri("http://john.doe:[email protected]");
628-
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
644+
var expectedNoProxy = "contoso.com,fabrikam.com";
629645

630646
var envars = new TestEnvironment
631647
{
632648
Variables =
633649
{
634650
[Constants.EnvironmentVariables.CurlHttpProxy] = settingValue.ToString(),
635-
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
651+
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
636652
}
637653
};
638654
var git = new TestGit();
@@ -648,7 +664,7 @@ public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue()
648664
Assert.Equal(expectedAddress, actualConfig.Address);
649665
Assert.Equal(expectedUserName, actualConfig.UserName);
650666
Assert.Equal(expectedPassword, actualConfig.Password);
651-
Assert.Equal(bypassList, actualConfig.BypassHosts);
667+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
652668
Assert.False(actualConfig.IsDeprecatedSource);
653669
}
654670

@@ -662,14 +678,14 @@ public void Settings_ProxyConfiguration_CurlHttpsEnvar_ReturnsValue()
662678
const string expectedPassword = "letmein123";
663679
var expectedAddress = new Uri("http://proxy.example.com");
664680
var settingValue = new Uri("http://john.doe:[email protected]");
665-
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
681+
var expectedNoProxy = "contoso.com,fabrikam.com";
666682

667683
var envars = new TestEnvironment
668684
{
669685
Variables =
670686
{
671687
[Constants.EnvironmentVariables.CurlHttpsProxy] = settingValue.ToString(),
672-
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
688+
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
673689
}
674690
};
675691
var git = new TestGit();
@@ -685,7 +701,7 @@ public void Settings_ProxyConfiguration_CurlHttpsEnvar_ReturnsValue()
685701
Assert.Equal(expectedAddress, actualConfig.Address);
686702
Assert.Equal(expectedUserName, actualConfig.UserName);
687703
Assert.Equal(expectedPassword, actualConfig.Password);
688-
Assert.Equal(bypassList, actualConfig.BypassHosts);
704+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
689705
Assert.False(actualConfig.IsDeprecatedSource);
690706
}
691707

@@ -699,14 +715,14 @@ public void Settings_TryGetProxy_CurlAllEnvar_ReturnsValue()
699715
const string expectedPassword = "letmein123";
700716
var expectedAddress = new Uri("http://proxy.example.com");
701717
var settingValue = new Uri("http://john.doe:[email protected]");
702-
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
718+
var expectedNoProxy = "contoso.com,fabrikam.com";
703719

704720
var envars = new TestEnvironment
705721
{
706722
Variables =
707723
{
708724
[Constants.EnvironmentVariables.CurlAllProxy] = settingValue.ToString(),
709-
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
725+
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
710726
}
711727
};
712728
var git = new TestGit();
@@ -722,7 +738,7 @@ public void Settings_TryGetProxy_CurlAllEnvar_ReturnsValue()
722738
Assert.Equal(expectedAddress, actualConfig.Address);
723739
Assert.Equal(expectedUserName, actualConfig.UserName);
724740
Assert.Equal(expectedPassword, actualConfig.Password);
725-
Assert.Equal(bypassList, actualConfig.BypassHosts);
741+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
726742
Assert.False(actualConfig.IsDeprecatedSource);
727743
}
728744

@@ -736,14 +752,14 @@ public void Settings_ProxyConfiguration_LegacyGcmEnvar_ReturnsValue()
736752
const string expectedPassword = "letmein123";
737753
var expectedAddress = new Uri("http://proxy.example.com");
738754
var settingValue = new Uri("http://john.doe:[email protected]");
739-
var bypassList = new List<string> {"https://contoso.com", ".*fabrikam\\.com"};
755+
var expectedNoProxy = "contoso.com,fabrikam.com";
740756

741757
var envars = new TestEnvironment
742758
{
743759
Variables =
744760
{
745761
[Constants.EnvironmentVariables.GcmHttpProxy] = settingValue.ToString(),
746-
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
762+
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
747763
}
748764
};
749765
var git = new TestGit();
@@ -759,7 +775,7 @@ public void Settings_ProxyConfiguration_LegacyGcmEnvar_ReturnsValue()
759775
Assert.Equal(expectedAddress, actualConfig.Address);
760776
Assert.Equal(expectedUserName, actualConfig.UserName);
761777
Assert.Equal(expectedPassword, actualConfig.Password);
762-
Assert.Equal(bypassList, actualConfig.BypassHosts);
778+
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
763779
Assert.True(actualConfig.IsDeprecatedSource);
764780
}
765781

0 commit comments

Comments
 (0)