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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.