Skip to content

Commit 5669e55

Browse files
rhendricpeff
andcommitted
http: do not ignore proxy path
The documentation for `http.proxy` describes that option, and the environment variables it overrides, as supporting "the syntax understood by curl". curl allows SOCKS proxies to use a path to a Unix domain socket, like `socks5h://localhost/path/to/socket.sock`. Git should therefore include, if present, the path part of the proxy URL in what it passes to libcurl. Co-authored-by: Jeff King <peff@peff.net> Signed-off-by: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
1 parent 39bf06a commit 5669e55

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed

Documentation/config/http.txt

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

http.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,8 @@ static CURL *get_curl_handle(void)
12271227
*/
12281228
curl_easy_setopt(result, CURLOPT_PROXY, "");
12291229
} else if (curl_http_proxy) {
1230+
struct strbuf proxy = STRBUF_INIT;
1231+
12301232
if (starts_with(curl_http_proxy, "socks5h"))
12311233
curl_easy_setopt(result,
12321234
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
@@ -1265,7 +1267,26 @@ static CURL *get_curl_handle(void)
12651267
if (!proxy_auth.host)
12661268
die("Invalid proxy URL '%s'", curl_http_proxy);
12671269

1268-
curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
1270+
strbuf_addstr(&proxy, proxy_auth.host);
1271+
if (proxy_auth.path) {
1272+
curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW);
1273+
if (ver->version_num < 0x075400)
1274+
die("libcurl 7.84 or later is required to support paths in proxy URLs");
1275+
1276+
if (!starts_with(proxy_auth.protocol, "socks"))
1277+
die("Invalid proxy URL '%s': only SOCKS proxies support paths",
1278+
curl_http_proxy);
1279+
1280+
if (strcasecmp(proxy_auth.host, "localhost"))
1281+
die("Invalid proxy URL '%s': host must be localhost if a path is present",
1282+
curl_http_proxy);
1283+
1284+
strbuf_addch(&proxy, '/');
1285+
strbuf_add_percentencode(&proxy, proxy_auth.path, 0);
1286+
}
1287+
curl_easy_setopt(result, CURLOPT_PROXY, proxy.buf);
1288+
strbuf_release(&proxy);
1289+
12691290
var_override(&curl_no_proxy, getenv("NO_PROXY"));
12701291
var_override(&curl_no_proxy, getenv("no_proxy"));
12711292
curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);

t/socks4-proxy.pl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use strict;
2+
use IO::Select;
3+
use IO::Socket::UNIX;
4+
use IO::Socket::INET;
5+
6+
my $path = shift;
7+
8+
unlink($path);
9+
my $server = IO::Socket::UNIX->new(Listen => 1, Local => $path)
10+
or die "unable to listen on $path: $!";
11+
12+
$| = 1;
13+
print "ready\n";
14+
15+
while (my $client = $server->accept()) {
16+
sysread $client, my $buf, 8;
17+
my ($version, $cmd, $port, $ip) = unpack 'CCnN', $buf;
18+
next unless $version == 4; # socks4
19+
next unless $cmd == 1; # TCP stream connection
20+
21+
# skip NUL-terminated id
22+
while (sysread $client, my $char, 1) {
23+
last unless ord($char);
24+
}
25+
26+
# version(0), reply(5a == granted), port (ignored), ip (ignored)
27+
syswrite $client, "\x00\x5a\x00\x00\x00\x00\x00\x00";
28+
29+
my $remote = IO::Socket::INET->new(PeerHost => $ip, PeerPort => $port)
30+
or die "unable to connect to $ip/$port: $!";
31+
32+
my $io = IO::Select->new($client, $remote);
33+
while ($io->count) {
34+
for my $fh ($io->can_read(0)) {
35+
for my $pair ([$client, $remote], [$remote, $client]) {
36+
my ($from, $to) = @$pair;
37+
next unless $fh == $from;
38+
39+
my $r = sysread $from, my $buf, 1024;
40+
if (!defined $r || $r <= 0) {
41+
$io->remove($from);
42+
next;
43+
}
44+
syswrite $to, $buf;
45+
}
46+
}
47+
}
48+
}

t/t5564-http-proxy.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,45 @@ test_expect_success 'clone can prompt for proxy password' '
3939
expect_askpass pass proxuser
4040
'
4141

42+
start_socks() {
43+
mkfifo socks_output &&
44+
{
45+
"$PERL_PATH" "$TEST_DIRECTORY/socks4-proxy.pl" "$1" >socks_output &
46+
echo $! > "$TRASH_DIRECTORY/socks.pid"
47+
} &&
48+
read line <socks_output &&
49+
test "$line" = ready
50+
}
51+
52+
# The %30 tests that the correct amount of percent-encoding is applied to the
53+
# proxy string passed to curl.
54+
test_lazy_prereq SOCKS_PROXY 'test_have_prereq PERL && start_socks "$TRASH_DIRECTORY/%30.sock"'
55+
56+
test_atexit 'test ! -e "$TRASH_DIRECTORY/socks.pid" || kill "$(cat "$TRASH_DIRECTORY/socks.pid")"'
57+
58+
test_expect_success SOCKS_PROXY 'clone via Unix socket' '
59+
test_when_finished "rm -rf clone" &&
60+
test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && {
61+
{
62+
GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
63+
grep -i "SOCKS4 request granted." trace
64+
} ||
65+
grep "^fatal: libcurl 7\.84 or later" err
66+
}
67+
'
68+
69+
test_expect_success 'Unix socket requires socks*:' '
70+
! git clone -c http.proxy=localhost/path https://example.com/repo.git 2>err && {
71+
grep "^fatal: Invalid proxy URL '\''localhost/path'\'': only SOCKS proxies support paths" err ||
72+
grep "^fatal: libcurl 7\.84 or later" err
73+
}
74+
'
75+
76+
test_expect_success 'Unix socket requires localhost' '
77+
! git clone -c http.proxy=socks4://127.0.0.1/path https://example.com/repo.git 2>err && {
78+
grep "^fatal: Invalid proxy URL '\''socks4://127\.0\.0\.1/path'\'': host must be localhost if a path is present" err ||
79+
grep "^fatal: libcurl 7\.84 or later" err
80+
}
81+
'
82+
4283
test_done

0 commit comments

Comments
 (0)