Skip to content

Commit 90a8d6d

Browse files
authored
Merge pull request #503 from mjcheetham/curl-envars
Match libcurl lowercase proxy environment variable behaviour
2 parents 3bfcf41 + 81cdb21 commit 90a8d6d

File tree

4 files changed

+125
-29
lines changed

4 files changed

+125
-29
lines changed

docs/netconfig.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,35 @@ GCM Core supports other ways of configuring a proxy for convenience and compatib
4949
1. GCM-specific configuration options (_**only** respected by GCM; **deprecated**_):
5050
- `credential.httpProxy`
5151
- `credential.httpsProxy`
52-
1. cURL environment variables (_also respected by Git_):
53-
- `HTTP_PROXY`
54-
- `HTTPS_PROXY`
55-
- `ALL_PROXY`
56-
1. `GCM_HTTP_PROXY` environment variable (_**only** respected by GCM; **deprecated**_)
52+
2. cURL environment variables (_also respected by Git_):
53+
- `http_proxy`
54+
- `https_proxy`/`HTTPS_PROXY`
55+
- `all_proxy`/`ALL_PROXY`
56+
3. `GCM_HTTP_PROXY` environment variable (_**only** respected by GCM; **deprecated**_)
57+
58+
Note that with the cURL environment variables there are both lowercase and
59+
uppercase variants.
60+
61+
**_Lowercase variants take precedence over the uppercase form._** This is
62+
consistent with how libcurl (and therefore Git) operates.
63+
64+
The `http_proxy` variable exists only in the lowercase variant and libcurl does
65+
_not_ consider any uppercase form. _GCM Core also reflects this behavior._
66+
67+
See <https://everything.curl.dev/usingcurl/proxies#proxy-environment-variables>
68+
for more information.
5769

5870
### Bypassing addresses
5971

6072
In some circumstances you may wish to bypass a configured proxy for specific
61-
addresses. GCM Core supports the cURL environment variable `NO_PROXY` for this
62-
scenariom, as does Git itself.
73+
addresses. GCM Core supports the cURL environment variable `no_proxy` (and
74+
`NO_PROXY`) for this scenario, as does Git itself.
75+
76+
Like with the [other cURL proxy environment variables](#other-proxy-options),
77+
the lowercase variant will take precedence over the uppercase form.
6378

64-
The `NO_PROXY` environment variable should contain a comma (`,`) or space (` `)
65-
separated list of host names that should not be proxied (should connect
66-
directly).
79+
This environment variable should contain a comma (`,`) or space (` `) separated
80+
list of host names that should not be proxied (should connect directly).
6781

6882
GCM Core attempts to match [libcurl's behaviour](https://curl.se/libcurl/c/CURLOPT_NOPROXY.html),
6983
which is briefly summarized here:
@@ -88,7 +102,7 @@ Hostname|Matches?
88102
**Example:**
89103

90104
```text
91-
NO_PROXY="contoso.com,www.fabrikam.com"
105+
no_proxy="contoso.com,www.fabrikam.com"
92106
```
93107

94108
## TLS Verification

src/shared/Core.Tests/SettingsTests.cs

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -788,8 +788,10 @@ public void Settings_ProxyConfiguration_Precedence_ReturnsValue()
788788
// 2. Standard Git configuration
789789
// http.proxy
790790
// 3. cURL environment variables
791+
// http_proxy
791792
// HTTPS_PROXY
792-
// HTTP_PROXY
793+
// http_proxy (note that uppercase HTTP_PROXY is not supported by libcurl)
794+
// all_proxy
793795
// ALL_PROXY
794796
// 4. GCM proxy environment variable (deprecated)
795797
// GCM_HTTP_PROXY
@@ -820,15 +822,71 @@ void RunTest(Uri expectedValue)
820822
envars.Variables[Constants.EnvironmentVariables.CurlHttpProxy] = value2.ToString();
821823
RunTest(value2);
822824

823-
// Test case 2: http.proxy > cURL environment variables
824-
string httpProxyKey = $"{Constants.GitConfiguration.Http.SectionName}.{Constants.GitConfiguration.Http.Proxy}";
825-
git.Configuration.Global[httpProxyKey] = new[] {value3.ToString()};
826-
RunTest(value3);
825+
// Test case 2: http.proxy > cURL environment variables
826+
string httpProxyKey = $"{Constants.GitConfiguration.Http.SectionName}.{Constants.GitConfiguration.Http.Proxy}";
827+
git.Configuration.Global[httpProxyKey] = new[] {value3.ToString()};
828+
RunTest(value3);
827829

828-
// Test case 3: credential.httpProxy > http.proxy
829-
string credentialProxyKey = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.HttpProxy}";
830-
git.Configuration.Global[credentialProxyKey] = new[] {value4.ToString()};
831-
RunTest(value4);
830+
// Test case 3: credential.httpProxy > http.proxy
831+
string credentialProxyKey = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.HttpProxy}";
832+
git.Configuration.Global[credentialProxyKey] = new[] {value4.ToString()};
833+
RunTest(value4);
834+
}
835+
836+
[Fact]
837+
public void Settings_ProxyConfiguration_CurlEnvarPrecedence_PrefersLowercase()
838+
{
839+
// Expected precedence:
840+
// https_proxy
841+
// HTTPS_PROXY
842+
// http_proxy (note that uppercase HTTP_PROXY is not supported by libcurl)
843+
// all_proxy
844+
// ALL_PROXY
845+
846+
const string remoteUrl = "https://example.com/foo.git";
847+
var remoteUri = new Uri(remoteUrl);
848+
849+
var value1 = new Uri("http://proxy1.example.com");
850+
var value2 = new Uri("http://proxy2.example.com");
851+
852+
// The differentiation between upper- and lowercase environment variables is not possible
853+
// on some platforms (Windows) so force the comparer to case-sensitive in the test so we
854+
// can run this on all platforms.
855+
var envars = new TestEnvironment(envarComparer: StringComparer.Ordinal);
856+
var git = new TestGit();
857+
858+
void RunTest(Uri expectedValue)
859+
{
860+
var settings = new Settings(envars, git)
861+
{
862+
RemoteUri = remoteUri
863+
};
864+
ProxyConfiguration actualConfig = settings.GetProxyConfiguration();
865+
Assert.Equal(expectedValue, actualConfig.Address);
866+
}
867+
868+
// Test case 1: https_proxy > HTTPS_PROXY
869+
envars.Variables[Constants.EnvironmentVariables.CurlHttpsProxy] = value1.ToString();
870+
envars.Variables[Constants.EnvironmentVariables.CurlHttpsProxyUpper] = value2.ToString();
871+
RunTest(value1);
872+
873+
// Test case 2a: https_proxy > http_proxy
874+
envars.Variables.Clear();
875+
envars.Variables[Constants.EnvironmentVariables.CurlHttpsProxy] = value1.ToString();
876+
envars.Variables[Constants.EnvironmentVariables.CurlHttpProxy] = value2.ToString();
877+
RunTest(value1);
878+
879+
// Test case 2b: HTTPS_PROXY > http_proxy
880+
envars.Variables.Clear();
881+
envars.Variables[Constants.EnvironmentVariables.CurlHttpsProxyUpper] = value1.ToString();
882+
envars.Variables[Constants.EnvironmentVariables.CurlHttpProxy] = value2.ToString();
883+
RunTest(value1);
884+
885+
// Test case 3: all_proxy > ALL_PROXY
886+
envars.Variables.Clear();
887+
envars.Variables[Constants.EnvironmentVariables.CurlAllProxy] = value1.ToString();
888+
envars.Variables[Constants.EnvironmentVariables.CurlAllProxyUpper] = value2.ToString();
889+
RunTest(value1);
832890
}
833891

834892
[Fact]

src/shared/Core/Constants.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,24 @@ public static class EnvironmentVariables
5353
public const string GcmAuthority = "GCM_AUTHORITY";
5454
public const string GitTerminalPrompts = "GIT_TERMINAL_PROMPT";
5555
public const string GcmAllowWia = "GCM_ALLOW_WINDOWSAUTH";
56-
public const string CurlNoProxy = "NO_PROXY";
57-
public const string CurlAllProxy = "ALL_PROXY";
58-
public const string CurlHttpProxy = "HTTP_PROXY";
59-
public const string CurlHttpsProxy = "HTTPS_PROXY";
56+
57+
/*
58+
* Unlike other environment variables, these proxy variables are normally lowercase only.
59+
* However, libcurl also implemented checks for the uppercase variants! The lowercase
60+
* variants should take precedence over the uppercase ones since the former are quasi-standard.
61+
*
62+
* One exception to this is that libcurl does not even look for the uppercase variant of
63+
* http_proxy (for some security reasons).
64+
*/
65+
public const string CurlNoProxy = "no_proxy";
66+
public const string CurlNoProxyUpper = "NO_PROXY";
67+
public const string CurlHttpsProxy = "https_proxy";
68+
public const string CurlHttpsProxyUpper = "HTTPS_PROXY";
69+
public const string CurlHttpProxy = "http_proxy";
70+
// Note there is no uppercase variant of the http_proxy since libcurl doesn't use it
71+
public const string CurlAllProxy = "all_proxy";
72+
public const string CurlAllProxyUpper = "ALL_PROXY";
73+
6074
public const string GcmHttpProxy = "GCM_HTTP_PROXY";
6175
public const string GitSslNoVerify = "GIT_SSL_NO_VERIFY";
6276
public const string GitSslCaInfo = "GIT_SSL_CAINFO";

src/shared/Core/Settings.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,11 @@ ProxyConfiguration CreateConfiguration(Uri uri, bool isLegacy = false)
580580
uri.TryGetUserInfo(out string userName, out string password);
581581

582582
// Get the proxy bypass host names
583-
_environment.Variables.TryGetValue(KnownEnvars.CurlNoProxy, out string noProxyStr);
583+
// Check both lowercase "no_proxy" and uppercase "NO_PROXY" variants (preferring the former)
584+
if (!_environment.Variables.TryGetValue(KnownEnvars.CurlNoProxy, out string noProxyStr))
585+
{
586+
_environment.Variables.TryGetValue(KnownEnvars.CurlNoProxyUpper, out noProxyStr);
587+
}
584588

585589
return new ProxyConfiguration(address, userName, password, noProxyStr, isLegacy);
586590
}
@@ -598,8 +602,10 @@ ProxyConfiguration CreateConfiguration(Uri uri, bool isLegacy = false)
598602
* http.proxy
599603
*
600604
* 3. cURL environment variables
605+
* https_proxy
601606
* HTTPS_PROXY
602-
* HTTP_PROXY
607+
* http_proxy (note that uppercase HTTP_PROXY is not supported by libcurl)
608+
* all_proxy
603609
* ALL_PROXY
604610
*
605611
* 4. GCM proxy environment variable (deprecated)
@@ -610,7 +616,7 @@ ProxyConfiguration CreateConfiguration(Uri uri, bool isLegacy = false)
610616
*
611617
* For HTTP URIs we only check the HTTP variants.
612618
*
613-
* We also support the cURL "NO_PROXY" environment variable in conjunction with any
619+
* We also support the cURL "no_proxy" / "NO_PROXY" environment variables in conjunction with any
614620
* of the above supported proxy address configurations. This comma separated list of
615621
* host names (or host name wildcards) should be respected and the proxy should NOT
616622
* be used for these addresses.
@@ -634,10 +640,14 @@ ProxyConfiguration CreateConfiguration(Uri uri, bool isLegacy = false)
634640
return CreateConfiguration(proxyUri);
635641
}
636642

637-
// 3. cURL environment variables
643+
// 3. cURL environment variables (both lower- and uppercase variants)
644+
// Prefer the lowercase variants as these are quasi-standard.
638645
if (isHttps && TryGetUriSetting(KnownEnvars.CurlHttpsProxy, null, null, out proxyUri) ||
646+
isHttps && TryGetUriSetting(KnownEnvars.CurlHttpsProxyUpper, null, null, out proxyUri) ||
639647
TryGetUriSetting(KnownEnvars.CurlHttpProxy, null, null, out proxyUri) ||
640-
TryGetUriSetting(KnownEnvars.CurlAllProxy, null, null, out proxyUri))
648+
// Note that the uppercase HTTP_PROXY is not recognized by libcurl
649+
TryGetUriSetting(KnownEnvars.CurlAllProxy, null, null, out proxyUri) ||
650+
TryGetUriSetting(KnownEnvars.CurlAllProxyUpper, null, null, out proxyUri))
641651
{
642652
return CreateConfiguration(proxyUri);
643653
}

0 commit comments

Comments
 (0)