From: "Ryan Hendrickson via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>,
Jeff King <peff@peff.net>,
Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>,
Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
Subject: [PATCH v5] http: do not ignore proxy path
Date: Fri, 02 Aug 2024 05:20:07 +0000 [thread overview]
Message-ID: <pull.1767.v5.git.1722576007398.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.1767.v4.git.1722489776279.gitgitgadget@gmail.com>
From: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
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>
Signed-off-by: Jeff King <peff@peff.net>
---
http: do not ignore proxy path
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1767%2Frhendric%2Frhendric%2Fhttp-proxy-path-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1767/rhendric/rhendric/http-proxy-path-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/1767
Range-diff vs v4:
1: 507fb75c1a6 ! 1: fa101a3b264 http: do not ignore proxy path
@@ Commit message
Co-authored-by: Jeff King <peff@peff.net>
Signed-off-by: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
+ Signed-off-by: Jeff King <peff@peff.net>
## Documentation/config/http.txt ##
@@ Documentation/config/http.txt: http.proxy::
@@ t/t5564-http-proxy.sh: test_expect_success 'clone can prompt for proxy password'
+
+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
++ grep -i "SOCKS4 request granted" trace
+ } ||
-+ grep "^fatal: libcurl 7\.84 or later" err
++ old_libcurl_error err
+ }
+'
+
-+test_expect_success 'Unix socket requires socks*:' '
++test_expect_success 'Unix socket requires socks*:' - <<\EOT
+ ! git clone -c http.proxy=localhost/path https://example.com/repo.git 2>err && {
-+ grep "^fatal: Invalid proxy URL '\''localhost/path'\'': only SOCKS proxies support paths" err ||
-+ grep "^fatal: libcurl 7\.84 or later" 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' '
++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 "^fatal: Invalid proxy URL '\''socks4://127\.0\.0\.1/path'\'': host must be localhost if a path is present" err ||
-+ grep "^fatal: libcurl 7\.84 or later" 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
Documentation/config/http.txt | 4 +--
http.c | 24 ++++++++++++++++-
t/socks4-proxy.pl | 48 ++++++++++++++++++++++++++++++++++
t/t5564-http-proxy.sh | 49 +++++++++++++++++++++++++++++++++++
4 files changed, 122 insertions(+), 3 deletions(-)
create mode 100644 t/socks4-proxy.pl
diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt
index 162b33fc52f..a14371b5c96 100644
--- a/Documentation/config/http.txt
+++ b/Documentation/config/http.txt
@@ -5,8 +5,8 @@ http.proxy::
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
linkgit:gitcredentials[7] for more information. The syntax thus is
- '[protocol://][user[:password]@]proxyhost[:port]'. This can be overridden
- on a per-remote basis; see remote.<name>.proxy
+ '[protocol://][user[:password]@]proxyhost[:port][/path]'. This can be
+ overridden on a per-remote basis; see remote.<name>.proxy
+
Any proxy, however configured, must be completely transparent and must not
modify, transform, or buffer the request or response in any way. Proxies which
diff --git a/http.c b/http.c
index 623ed234891..6c6cc5c822a 100644
--- a/http.c
+++ b/http.c
@@ -1227,6 +1227,8 @@ static CURL *get_curl_handle(void)
*/
curl_easy_setopt(result, CURLOPT_PROXY, "");
} else if (curl_http_proxy) {
+ struct strbuf proxy = STRBUF_INIT;
+
if (starts_with(curl_http_proxy, "socks5h"))
curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
@@ -1265,7 +1267,27 @@ static CURL *get_curl_handle(void)
if (!proxy_auth.host)
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"));
curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);
diff --git a/t/socks4-proxy.pl b/t/socks4-proxy.pl
new file mode 100644
index 00000000000..4c3a35c0083
--- /dev/null
+++ b/t/socks4-proxy.pl
@@ -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;
+ }
+ }
+ }
+}
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh
index bb35b87071d..0d6cfebbfab 100755
--- a/t/t5564-http-proxy.sh
+++ b/t/t5564-http-proxy.sh
@@ -39,4 +39,53 @@ test_expect_success 'clone can prompt for proxy password' '
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
base-commit: 406f326d271e0bacecdb00425422c5fa3f314930
--
gitgitgadget
next prev parent reply other threads:[~2024-08-02 5:20 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-07-26 15:52 [PATCH] http: do not ignore proxy path Ryan Hendrickson via GitGitGadget
2024-07-26 16:29 ` Junio C Hamano
2024-07-26 17:12 ` Ryan Hendrickson
2024-07-26 17:45 ` Junio C Hamano
2024-07-26 21:11 ` Jeff King
2024-07-26 22:43 ` Ryan Hendrickson
2024-07-29 19:31 ` Jeff King
2024-07-27 6:44 ` [PATCH v2] " Ryan Hendrickson via GitGitGadget
2024-07-29 20:09 ` Jeff King
2024-07-31 15:33 ` Ryan Hendrickson
2024-07-31 16:01 ` [PATCH v3] " Ryan Hendrickson via GitGitGadget
2024-07-31 22:24 ` Junio C Hamano
2024-08-01 3:44 ` Ryan Hendrickson
2024-08-01 5:21 ` Junio C Hamano
2024-08-01 5:45 ` Jeff King
2024-08-01 14:40 ` Junio C Hamano
2024-08-01 5:22 ` [PATCH v4] " Ryan Hendrickson via GitGitGadget
2024-08-01 6:04 ` Jeff King
2024-08-01 17:04 ` Junio C Hamano
2024-08-02 5:20 ` Ryan Hendrickson via GitGitGadget [this message]
2024-08-02 15:52 ` [PATCH v5] " Junio C Hamano
2024-08-02 16:43 ` Ryan Hendrickson
2024-08-02 17:10 ` Junio C Hamano
2024-08-02 18:03 ` Ryan Hendrickson
2024-08-02 19:28 ` Junio C Hamano
2024-08-02 19:39 ` Ryan Hendrickson
2024-08-02 21:13 ` Junio C Hamano
2024-08-02 21:26 ` Ryan Hendrickson
2024-08-02 21:43 ` Junio C Hamano
2024-08-02 21:47 ` Junio C Hamano
2024-08-02 22:14 ` Ryan Hendrickson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.1767.v5.git.1722576007398.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=peff@peff.net \
--cc=ryan.hendrickson@alum.mit.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).