Skip to content

Commit 81cdb21

Browse files
committed
settings: match curl lowercase proxy envar behaviour
libcurl supports multiple different environment variables [1] to configure proxy behaviour: http_proxy, https_proxy, all_proxy, and no_proxy. Unlike most other environment variables these proxy envars are normally _lowercase_, not uppercase. This convention was set by libwww back in the early 1990s. When libcurl was first released however, it was not aware of this schism and only implemented checks for uppercase variants of these envars: HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY. In time, libcurl learned to also read the lowercase variants, and gives them precedence over the uppercase forms (since the former are quasi- standards). However, to further complicate the matter, libcurl no longer reads the uppercase HTTP_PROXY variable specifically. This change was made to address a security concern with some CGI webservers [2]. The problem is that today GCM only reads the uppercase variants of the environment variables! This is inconsistent with libcurl, and therefore Git's behaviour (that we aim to be consistent/co-operative with). We change GCM's behaviour to match that of libcurl/Git in that the lowercase proxy envars are preferred to the uppercase ones, and the uppercase HTTP_PROXY variable is ignored. Dropping support for the HTTP_PROXY uppercase envar is technically a breaking change, but if the user had only set this uppercase envar then Git would not be proxying the actual remote calls, only GCM, which is most likely not what the user wanted. [1] https://everything.curl.dev/usingcurl/proxies#proxy-environment-variables [2] https://everything.curl.dev/usingcurl/proxies#http_proxy-in-lower-case-only
1 parent a391785 commit 81cdb21

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)