Merge branch 'rh/http-proxy-path'

The value of http.proxy can have "path" at the end for a socks
proxy that listens to a unix-domain socket, but we started to
discard it when we taught proxy auth code path to use the
credential helpers, which has been corrected.

* rh/http-proxy-path:
  http: do not ignore proxy path
This commit is contained in:
Junio C Hamano
2024-08-14 14:54:48 -07:00
4 changed files with 128 additions and 3 deletions

View File

@ -5,8 +5,8 @@ http.proxy::
proxy string with a user name but no password, in which case git will proxy string with a user name but no password, in which case git will
attempt to acquire one in the same way it does for other credentials. See attempt to acquire one in the same way it does for other credentials. See
linkgit:gitcredentials[7] for more information. The syntax thus is linkgit:gitcredentials[7] for more information. The syntax thus is
'[protocol://][user[:password]@]proxyhost[:port]'. This can be overridden '[protocol://][user[:password]@]proxyhost[:port][/path]'. This can be
on a per-remote basis; see remote.<name>.proxy overridden on a per-remote basis; see remote.<name>.proxy
+ +
Any proxy, however configured, must be completely transparent and must not Any proxy, however configured, must be completely transparent and must not
modify, transform, or buffer the request or response in any way. Proxies which modify, transform, or buffer the request or response in any way. Proxies which

24
http.c
View File

@ -1227,6 +1227,8 @@ static CURL *get_curl_handle(void)
*/ */
curl_easy_setopt(result, CURLOPT_PROXY, ""); curl_easy_setopt(result, CURLOPT_PROXY, "");
} else if (curl_http_proxy) { } else if (curl_http_proxy) {
struct strbuf proxy = STRBUF_INIT;
if (starts_with(curl_http_proxy, "socks5h")) if (starts_with(curl_http_proxy, "socks5h"))
curl_easy_setopt(result, curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
@ -1265,7 +1267,27 @@ static CURL *get_curl_handle(void)
if (!proxy_auth.host) if (!proxy_auth.host)
die("Invalid proxy URL '%s'", curl_http_proxy); die("Invalid proxy URL '%s'", curl_http_proxy);
curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host); strbuf_addstr(&proxy, proxy_auth.host);
if (proxy_auth.path) {
curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW);
if (ver->version_num < 0x075400)
die("libcurl 7.84 or later is required to support paths in proxy URLs");
if (!starts_with(proxy_auth.protocol, "socks"))
die("Invalid proxy URL '%s': only SOCKS proxies support paths",
curl_http_proxy);
if (strcasecmp(proxy_auth.host, "localhost"))
die("Invalid proxy URL '%s': host must be localhost if a path is present",
curl_http_proxy);
strbuf_addch(&proxy, '/');
strbuf_add_percentencode(&proxy, proxy_auth.path, 0);
}
curl_easy_setopt(result, CURLOPT_PROXY, proxy.buf);
strbuf_release(&proxy);
var_override(&curl_no_proxy, getenv("NO_PROXY")); var_override(&curl_no_proxy, getenv("NO_PROXY"));
var_override(&curl_no_proxy, getenv("no_proxy")); var_override(&curl_no_proxy, getenv("no_proxy"));
curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy); curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);

48
t/socks4-proxy.pl Normal file
View File

@ -0,0 +1,48 @@
use strict;
use IO::Select;
use IO::Socket::UNIX;
use IO::Socket::INET;
my $path = shift;
unlink($path);
my $server = IO::Socket::UNIX->new(Listen => 1, Local => $path)
or die "unable to listen on $path: $!";
$| = 1;
print "ready\n";
while (my $client = $server->accept()) {
sysread $client, my $buf, 8;
my ($version, $cmd, $port, $ip) = unpack 'CCnN', $buf;
next unless $version == 4; # socks4
next unless $cmd == 1; # TCP stream connection
# skip NUL-terminated id
while (sysread $client, my $char, 1) {
last unless ord($char);
}
# version(0), reply(5a == granted), port (ignored), ip (ignored)
syswrite $client, "\x00\x5a\x00\x00\x00\x00\x00\x00";
my $remote = IO::Socket::INET->new(PeerHost => $ip, PeerPort => $port)
or die "unable to connect to $ip/$port: $!";
my $io = IO::Select->new($client, $remote);
while ($io->count) {
for my $fh ($io->can_read(0)) {
for my $pair ([$client, $remote], [$remote, $client]) {
my ($from, $to) = @$pair;
next unless $fh == $from;
my $r = sysread $from, my $buf, 1024;
if (!defined $r || $r <= 0) {
$io->remove($from);
next;
}
syswrite $to, $buf;
}
}
}
}

View File

@ -39,4 +39,59 @@ test_expect_success 'clone can prompt for proxy password' '
expect_askpass pass proxuser expect_askpass pass proxuser
' '
start_socks() {
mkfifo socks_output &&
{
"$PERL_PATH" "$TEST_DIRECTORY/socks4-proxy.pl" "$1" >socks_output &
echo $! > "$TRASH_DIRECTORY/socks.pid"
} &&
read line <socks_output &&
test "$line" = ready
}
# The %30 tests that the correct amount of percent-encoding is applied to the
# proxy string passed to curl.
test_lazy_prereq SOCKS_PROXY '
test_have_prereq PERL &&
start_socks "$TRASH_DIRECTORY/%30.sock"
'
test_atexit '
test ! -e "$TRASH_DIRECTORY/socks.pid" ||
kill "$(cat "$TRASH_DIRECTORY/socks.pid")"
'
# The below tests morally ought to be gated on a prerequisite that Git is
# linked with a libcurl that supports Unix socket paths for proxies (7.84 or
# later), but this is not easy to test right now. Instead, we || the tests with
# this function.
old_libcurl_error() {
grep -Fx "fatal: libcurl 7.84 or later is required to support paths in proxy URLs" "$1"
}
test_expect_success SOCKS_PROXY 'clone via Unix socket' '
test_when_finished "rm -rf clone" &&
test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && {
{
GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
grep -i "SOCKS4 request granted" trace
} ||
old_libcurl_error err
}
'
test_expect_success 'Unix socket requires socks*:' - <<\EOT
! git clone -c http.proxy=localhost/path https://example.com/repo.git 2>err && {
grep -Fx "fatal: Invalid proxy URL 'localhost/path': only SOCKS proxies support paths" err ||
old_libcurl_error err
}
EOT
test_expect_success 'Unix socket requires localhost' - <<\EOT
! git clone -c http.proxy=socks4://127.0.0.1/path https://example.com/repo.git 2>err && {
grep -Fx "fatal: Invalid proxy URL 'socks4://127.0.0.1/path': host must be localhost if a path is present" err ||
old_libcurl_error err
}
EOT
test_done test_done