Git fork

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: Jeff King <peff@peff.net>
Signed-off-by: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Ryan Hendrickson
Jeff King
and committed by
Junio C Hamano
0ca365c2 891ee3b9

+128 -3
+2 -2
Documentation/config/http.txt
··· 5 5 proxy string with a user name but no password, in which case git will 6 6 attempt to acquire one in the same way it does for other credentials. See 7 7 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 10 10 + 11 11 Any proxy, however configured, must be completely transparent and must not 12 12 modify, transform, or buffer the request or response in any way. Proxies which
+23 -1
http.c
··· 1227 1227 */ 1228 1228 curl_easy_setopt(result, CURLOPT_PROXY, ""); 1229 1229 } else if (curl_http_proxy) { 1230 + struct strbuf proxy = STRBUF_INIT; 1231 + 1230 1232 if (starts_with(curl_http_proxy, "socks5h")) 1231 1233 curl_easy_setopt(result, 1232 1234 CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); ··· 1265 1267 if (!proxy_auth.host) 1266 1268 die("Invalid proxy URL '%s'", curl_http_proxy); 1267 1269 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 + 1274 + if (ver->version_num < 0x075400) 1275 + die("libcurl 7.84 or later is required to support paths in proxy URLs"); 1276 + 1277 + if (!starts_with(proxy_auth.protocol, "socks")) 1278 + die("Invalid proxy URL '%s': only SOCKS proxies support paths", 1279 + curl_http_proxy); 1280 + 1281 + if (strcasecmp(proxy_auth.host, "localhost")) 1282 + die("Invalid proxy URL '%s': host must be localhost if a path is present", 1283 + curl_http_proxy); 1284 + 1285 + strbuf_addch(&proxy, '/'); 1286 + strbuf_add_percentencode(&proxy, proxy_auth.path, 0); 1287 + } 1288 + curl_easy_setopt(result, CURLOPT_PROXY, proxy.buf); 1289 + strbuf_release(&proxy); 1290 + 1269 1291 var_override(&curl_no_proxy, getenv("NO_PROXY")); 1270 1292 var_override(&curl_no_proxy, getenv("no_proxy")); 1271 1293 curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);
+48
t/socks4-proxy.pl
··· 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 + }
+55
t/t5564-http-proxy.sh
··· 39 39 expect_askpass pass proxuser 40 40 ' 41 41 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 ' 55 + test_have_prereq PERL && 56 + start_socks "$TRASH_DIRECTORY/%30.sock" 57 + ' 58 + 59 + test_atexit ' 60 + test ! -e "$TRASH_DIRECTORY/socks.pid" || 61 + kill "$(cat "$TRASH_DIRECTORY/socks.pid")" 62 + ' 63 + 64 + # The below tests morally ought to be gated on a prerequisite that Git is 65 + # linked with a libcurl that supports Unix socket paths for proxies (7.84 or 66 + # later), but this is not easy to test right now. Instead, we || the tests with 67 + # this function. 68 + old_libcurl_error() { 69 + grep -Fx "fatal: libcurl 7.84 or later is required to support paths in proxy URLs" "$1" 70 + } 71 + 72 + test_expect_success SOCKS_PROXY 'clone via Unix socket' ' 73 + test_when_finished "rm -rf clone" && 74 + test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && { 75 + { 76 + GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err && 77 + grep -i "SOCKS4 request granted" trace 78 + } || 79 + old_libcurl_error err 80 + } 81 + ' 82 + 83 + test_expect_success 'Unix socket requires socks*:' - <<\EOT 84 + ! git clone -c http.proxy=localhost/path https://example.com/repo.git 2>err && { 85 + grep -Fx "fatal: Invalid proxy URL 'localhost/path': only SOCKS proxies support paths" err || 86 + old_libcurl_error err 87 + } 88 + EOT 89 + 90 + test_expect_success 'Unix socket requires localhost' - <<\EOT 91 + ! git clone -c http.proxy=socks4://127.0.0.1/path https://example.com/repo.git 2>err && { 92 + grep -Fx "fatal: Invalid proxy URL 'socks4://127.0.0.1/path': host must be localhost if a path is present" err || 93 + old_libcurl_error err 94 + } 95 + EOT 96 + 42 97 test_done