Skip to content

Commit 515daea

Browse files
committed
remote: introduce follow_redirects connect option
Give callers the ability to select how to handle redirects - either supporting redirects during the initial connection (so that, for example, `git.example.com/repo` can redirect to `github.com/example/repo`) or all/no redirects. This is for compatibility with git.
1 parent 342e55a commit 515daea

File tree

6 files changed

+138
-4
lines changed

6 files changed

+138
-4
lines changed

ci/test.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then
101101
java -jar poxyproxy.jar --address 127.0.0.1 --port 8090 --credentials foo:bar --auth-type ntlm --quiet &
102102
fi
103103

104-
if [ -z "$SKIP_NTLM_TESTS" ]; then
104+
if [ -z "$SKIP_NTLM_TESTS" -o -z "$SKIP_ONLINE_TESTS" ]; then
105105
curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.5.1/poxygit-0.5.1.jar >poxygit.jar
106106

107107
echo ""
@@ -188,7 +188,11 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then
188188
echo "## Running (online) tests"
189189
echo "##############################################################################"
190190

191+
export GITTEST_REMOTE_REDIRECT_INITIAL="http://localhost:9000/initial-redirect/libgit2/TestGitRepository"
192+
export GITTEST_REMOTE_REDIRECT_SUBSEQUENT="http://localhost:9000/subsequent-redirect/libgit2/TestGitRepository"
191193
run_test online
194+
unset GITTEST_REMOTE_REDIRECT_INITIAL
195+
unset GITTEST_REMOTE_REDIRECT_SUBSEQUENT
192196

193197
# Run the online tests that immutably change global state separately
194198
# to avoid polluting the test environment.

include/git2/remote.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@ GIT_EXTERN(int) git_remote_create(
4141
const char *name,
4242
const char *url);
4343

44+
/**
45+
* Remote redirection settings; whether redirects to another host
46+
* are permitted. By default, git will follow a redirect on the
47+
* initial request (`/info/refs`), but not subsequent requests.
48+
*/
49+
typedef enum {
50+
/**
51+
* Do not follow any off-site redirects at any stage of
52+
* the fetch or push.
53+
*/
54+
GIT_REMOTE_REDIRECT_NONE = (1 << 0),
55+
56+
/**
57+
* Allow off-site redirects only upon the initial request.
58+
* This is the default.
59+
*/
60+
GIT_REMOTE_REDIRECT_INITIAL = (1 << 1),
61+
62+
/**
63+
* Allow redirects at any stage in the fetch or push.
64+
*/
65+
GIT_REMOTE_REDIRECT_ALL = (1 << 2)
66+
} git_remote_redirect_t;
67+
4468
/**
4569
* Remote creation options flags
4670
*/
@@ -717,6 +741,13 @@ typedef struct {
717741
*/
718742
git_proxy_options proxy_opts;
719743

744+
/**
745+
* Whether to allow off-site redirects. If this is not
746+
* specified, the `http.followRedirects` configuration setting
747+
* will be consulted.
748+
*/
749+
git_remote_redirect_t follow_redirects;
750+
720751
/**
721752
* Extra headers for this fetch operation
722753
*/
@@ -768,6 +799,13 @@ typedef struct {
768799
*/
769800
git_proxy_options proxy_opts;
770801

802+
/**
803+
* Whether to allow off-site redirects. If this is not
804+
* specified, the `http.followRedirects` configuration setting
805+
* will be consulted.
806+
*/
807+
git_remote_redirect_t follow_redirects;
808+
771809
/**
772810
* Extra headers for this push operation
773811
*/
@@ -807,6 +845,13 @@ typedef struct {
807845
/** HTTP Proxy settings */
808846
git_proxy_options proxy_opts;
809847

848+
/**
849+
* Whether to allow off-site redirects. If this is not
850+
* specified, the `http.followRedirects` configuration setting
851+
* will be consulted.
852+
*/
853+
git_remote_redirect_t follow_redirects;
854+
810855
/** Extra HTTP headers to use in this connection */
811856
git_strarray custom_headers;
812857
} git_remote_connect_options;

src/remote.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,7 @@ static int ls_to_vector(git_vector *out, git_remote *remote)
11711171
(out)->callbacks = (in)->callbacks; \
11721172
(out)->proxy_opts = (in)->proxy_opts; \
11731173
(out)->custom_headers = (in)->custom_headers; \
1174+
(out)->follow_redirects = (in)->follow_redirects; \
11741175
}
11751176

11761177
GIT_INLINE(int) connect_opts_from_fetch_opts(

src/transports/http.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ typedef struct {
3838
const char *url;
3939
const char *request_type;
4040
const char *response_type;
41-
unsigned chunked : 1;
41+
unsigned int initial : 1,
42+
chunked : 1;
4243
} http_service;
4344

4445
typedef struct {
@@ -70,24 +71,28 @@ static const http_service upload_pack_ls_service = {
7071
GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack",
7172
NULL,
7273
"application/x-git-upload-pack-advertisement",
74+
1,
7375
0
7476
};
7577
static const http_service upload_pack_service = {
7678
GIT_HTTP_METHOD_POST, "/git-upload-pack",
7779
"application/x-git-upload-pack-request",
7880
"application/x-git-upload-pack-result",
81+
0,
7982
0
8083
};
8184
static const http_service receive_pack_ls_service = {
8285
GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack",
8386
NULL,
8487
"application/x-git-receive-pack-advertisement",
88+
1,
8589
0
8690
};
8791
static const http_service receive_pack_service = {
8892
GIT_HTTP_METHOD_POST, "/git-receive-pack",
8993
"application/x-git-receive-pack-request",
9094
"application/x-git-receive-pack-result",
95+
0,
9196
1
9297
};
9398

@@ -215,6 +220,19 @@ GIT_INLINE(int) handle_proxy_auth(
215220
connect_opts->proxy_opts.payload);
216221
}
217222

223+
static bool allow_redirect(http_stream *stream)
224+
{
225+
http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
226+
227+
switch (transport->owner->connect_opts.follow_redirects) {
228+
case GIT_REMOTE_REDIRECT_INITIAL:
229+
return (stream->service->initial == 1);
230+
case GIT_REMOTE_REDIRECT_ALL:
231+
return true;
232+
default:
233+
return false;
234+
}
235+
}
218236

219237
static int handle_response(
220238
bool *complete,
@@ -233,7 +251,7 @@ static int handle_response(
233251
return -1;
234252
}
235253

236-
if (git_net_url_apply_redirect(&transport->server.url, response->location, false, stream->service->url) < 0) {
254+
if (git_net_url_apply_redirect(&transport->server.url, response->location, allow_redirect(stream), stream->service->url) < 0) {
237255
return -1;
238256
}
239257

src/transports/winhttp.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1189,8 +1189,10 @@ static int winhttp_stream_read(
11891189
winhttp_stream_close(s);
11901190

11911191
if (!git__prefixcmp_icase(location8, prefix_https)) {
1192+
bool follow = (t->owner->connect_opts.follow_redirects != GIT_REMOTE_REDIRECT_NONE);
1193+
11921194
/* Upgrade to secure connection; disconnect and start over */
1193-
if (git_net_url_apply_redirect(&t->server.url, location8, false, s->service_url) < 0) {
1195+
if (git_net_url_apply_redirect(&t->server.url, location8, follow, s->service_url) < 0) {
11941196
git__free(location8);
11951197
return -1;
11961198
}

tests/online/clone.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ static char *_remote_proxy_user = NULL;
3232
static char *_remote_proxy_pass = NULL;
3333
static char *_remote_proxy_selfsigned = NULL;
3434
static char *_remote_expectcontinue = NULL;
35+
static char *_remote_redirect_initial = NULL;
36+
static char *_remote_redirect_subsequent = NULL;
3537

3638
static int _orig_proxies_need_reset = 0;
3739
static char *_orig_http_proxy = NULL;
@@ -78,6 +80,8 @@ void test_online_clone__initialize(void)
7880
_remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS");
7981
_remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED");
8082
_remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE");
83+
_remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL");
84+
_remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT");
8185

8286
if (_remote_expectcontinue)
8387
git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
@@ -92,6 +96,8 @@ void test_online_clone__cleanup(void)
9296
g_repo = NULL;
9397
}
9498
cl_fixture_cleanup("./foo");
99+
cl_fixture_cleanup("./initial");
100+
cl_fixture_cleanup("./subsequent");
95101

96102
git__free(_remote_url);
97103
git__free(_remote_user);
@@ -107,6 +113,8 @@ void test_online_clone__cleanup(void)
107113
git__free(_remote_proxy_pass);
108114
git__free(_remote_proxy_selfsigned);
109115
git__free(_remote_expectcontinue);
116+
git__free(_remote_redirect_initial);
117+
git__free(_remote_redirect_subsequent);
110118

111119
if (_orig_proxies_need_reset) {
112120
cl_setenv("HTTP_PROXY", _orig_http_proxy);
@@ -938,3 +946,59 @@ void test_online_clone__path_whitespace(void)
938946
cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options));
939947
cl_assert(git_fs_path_exists("./foo/master.txt"));
940948
}
949+
950+
void test_online_clone__redirect_default_succeeds_for_initial(void)
951+
{
952+
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
953+
954+
if (!_remote_redirect_initial || !_remote_redirect_subsequent)
955+
cl_skip();
956+
957+
cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options));
958+
}
959+
960+
void test_online_clone__redirect_default_fails_for_subsequent(void)
961+
{
962+
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
963+
964+
if (!_remote_redirect_initial || !_remote_redirect_subsequent)
965+
cl_skip();
966+
967+
cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options));
968+
}
969+
970+
void test_online_clone__redirect_none(void)
971+
{
972+
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
973+
974+
if (!_remote_redirect_initial)
975+
cl_skip();
976+
977+
options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_NONE;
978+
979+
cl_git_fail(git_clone(&g_repo, _remote_redirect_initial, "./fail", &options));
980+
}
981+
982+
void test_online_clone__redirect_initial_succeeds_for_initial(void)
983+
{
984+
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
985+
986+
if (!_remote_redirect_initial || !_remote_redirect_subsequent)
987+
cl_skip();
988+
989+
options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL;
990+
991+
cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options));
992+
}
993+
994+
void test_online_clone__redirect_initial_fails_for_subsequent(void)
995+
{
996+
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
997+
998+
if (!_remote_redirect_initial || !_remote_redirect_subsequent)
999+
cl_skip();
1000+
1001+
options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL;
1002+
1003+
cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options));
1004+
}

0 commit comments

Comments
 (0)