Skip to content

Commit 471daee

Browse files
committed
net: refactor gitno redirect handling
Move the redirect handling into `git_net_url` for consistency.
1 parent 297c61e commit 471daee

File tree

7 files changed

+124
-132
lines changed

7 files changed

+124
-132
lines changed

src/net.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,112 @@ int git_net_url_parse(git_net_url *url, const char *given)
153153
return error;
154154
}
155155

156+
/*
157+
* Some servers strip the query parameters from the Location header
158+
* when sending a redirect. Others leave it in place.
159+
* Check for both, starting with the stripped case first,
160+
* since it appears to be more common.
161+
*/
162+
static void remove_service_suffix(
163+
git_net_url *url,
164+
const char *service_suffix)
165+
{
166+
const char *service_query = strchr(service_suffix, '?');
167+
size_t full_suffix_len = strlen(service_suffix);
168+
size_t suffix_len = service_query ?
169+
(size_t)(service_query - service_suffix) : full_suffix_len;
170+
size_t path_len = strlen(url->path);
171+
ssize_t truncate = -1;
172+
173+
/*
174+
* Check for a redirect without query parameters,
175+
* like "/newloc/info/refs"'
176+
*/
177+
if (suffix_len && path_len >= suffix_len) {
178+
size_t suffix_offset = path_len - suffix_len;
179+
180+
if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
181+
(!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
182+
truncate = suffix_offset;
183+
}
184+
}
185+
186+
/*
187+
* If we haven't already found where to truncate to remove the
188+
* suffix, check for a redirect with query parameters, like
189+
* "/newloc/info/refs?service=git-upload-pack"
190+
*/
191+
if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0)
192+
truncate = path_len - full_suffix_len;
193+
194+
/* Ensure we leave a minimum of '/' as the path */
195+
if (truncate == 0)
196+
truncate++;
197+
198+
if (truncate > 0) {
199+
url->path[truncate] = '\0';
200+
201+
git__free(url->query);
202+
url->query = NULL;
203+
}
204+
}
205+
206+
int git_net_url_apply_redirect(
207+
git_net_url *url,
208+
const char *redirect_location,
209+
const char *service_suffix)
210+
{
211+
git_net_url tmp = GIT_NET_URL_INIT;
212+
int error = 0;
213+
214+
assert(url && redirect_location);
215+
216+
if (redirect_location[0] == '/') {
217+
git__free(url->path);
218+
219+
if ((url->path = git__strdup(redirect_location)) == NULL) {
220+
error = -1;
221+
goto done;
222+
}
223+
} else {
224+
git_net_url *original = url;
225+
226+
if ((error = git_net_url_parse(&tmp, redirect_location)) < 0)
227+
goto done;
228+
229+
/* Validate that this is a legal redirection */
230+
231+
if (original->scheme &&
232+
strcmp(original->scheme, tmp.scheme) != 0 &&
233+
strcmp(tmp.scheme, "https") != 0) {
234+
git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
235+
original->scheme, tmp.scheme);
236+
237+
error = -1;
238+
goto done;
239+
}
240+
241+
if (original->host &&
242+
git__strcasecmp(original->host, tmp.host) != 0) {
243+
git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
244+
original->host, tmp.host);
245+
246+
error = -1;
247+
goto done;
248+
}
249+
250+
git_net_url_swap(url, &tmp);
251+
}
252+
253+
/* Remove the service suffix if it was given to us */
254+
if (service_suffix)
255+
remove_service_suffix(url, service_suffix);
256+
257+
done:
258+
git_net_url_dispose(&tmp);
259+
return error;
260+
}
261+
156262
bool git_net_url_valid(git_net_url *url)
157263
{
158264
return (url->host && url->port && url->path);

src/net.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ extern bool git_net_url_valid(git_net_url *url);
3030
/** Returns nonzero if the URL is on the default port. */
3131
extern int git_net_url_is_default_port(git_net_url *url);
3232

33+
/* Applies a redirect to the URL with a git-aware service suffix. */
34+
extern int git_net_url_apply_redirect(
35+
git_net_url *url,
36+
const char *redirect_location,
37+
const char *service_suffix);
38+
3339
/** Swaps the contents of one URL for another. */
3440
extern void git_net_url_swap(git_net_url *a, git_net_url *b);
3541

src/netops.c

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -121,101 +121,3 @@ int gitno__match_host(const char *pattern, const char *host)
121121

122122
return -1;
123123
}
124-
125-
int gitno_connection_data_handle_redirect(
126-
git_net_url *url,
127-
const char *redirect_str,
128-
const char *service_suffix)
129-
{
130-
git_net_url tmp = GIT_NET_URL_INIT;
131-
int error = 0;
132-
133-
assert(url && redirect_str);
134-
135-
if (redirect_str[0] == '/') {
136-
git__free(url->path);
137-
138-
if ((url->path = git__strdup(redirect_str)) == NULL) {
139-
error = -1;
140-
goto done;
141-
}
142-
} else {
143-
git_net_url *original = url;
144-
145-
if ((error = git_net_url_parse(&tmp, redirect_str)) < 0)
146-
goto done;
147-
148-
/* Validate that this is a legal redirection */
149-
150-
if (original->scheme &&
151-
strcmp(original->scheme, tmp.scheme) != 0 &&
152-
strcmp(tmp.scheme, "https") != 0) {
153-
git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
154-
original->scheme, tmp.scheme);
155-
156-
error = -1;
157-
goto done;
158-
}
159-
160-
if (original->host &&
161-
git__strcasecmp(original->host, tmp.host) != 0) {
162-
git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
163-
original->host, tmp.host);
164-
165-
error = -1;
166-
goto done;
167-
}
168-
169-
git_net_url_swap(url, &tmp);
170-
}
171-
172-
/* Remove the service suffix if it was given to us */
173-
if (service_suffix) {
174-
/*
175-
* Some servers strip the query parameters from the Location header
176-
* when sending a redirect. Others leave it in place.
177-
* Check for both, starting with the stripped case first,
178-
* since it appears to be more common.
179-
*/
180-
const char *service_query = strchr(service_suffix, '?');
181-
size_t full_suffix_len = strlen(service_suffix);
182-
size_t suffix_len = service_query ?
183-
(size_t)(service_query - service_suffix) : full_suffix_len;
184-
size_t path_len = strlen(url->path);
185-
ssize_t truncate = -1;
186-
187-
/* Check for a redirect without query parameters, like "/newloc/info/refs" */
188-
if (suffix_len && path_len >= suffix_len) {
189-
size_t suffix_offset = path_len - suffix_len;
190-
191-
if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
192-
(!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
193-
truncate = suffix_offset;
194-
}
195-
}
196-
197-
/*
198-
* If we haven't already found where to truncate to remove the suffix,
199-
* check for a redirect with query parameters,
200-
* like "/newloc/info/refs?service=git-upload-pack"
201-
*/
202-
if (truncate == -1 && git__suffixcmp(url->path, service_suffix) == 0) {
203-
truncate = path_len - full_suffix_len;
204-
}
205-
206-
if (truncate >= 0) {
207-
/* Ensure we leave a minimum of '/' as the path */
208-
if (truncate == 0)
209-
truncate++;
210-
url->path[truncate] = '\0';
211-
212-
git__free(url->query);
213-
url->query = NULL;
214-
}
215-
}
216-
217-
done:
218-
git_net_url_dispose(&tmp);
219-
return error;
220-
}
221-

src/netops.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,4 @@ int gitno_recv(gitno_buffer *buf);
6565
void gitno_consume(gitno_buffer *buf, const char *ptr);
6666
void gitno_consume_n(gitno_buffer *buf, size_t cons);
6767

68-
/*
69-
* This replaces all the pointers in `data` with freshly-allocated strings,
70-
* that the caller is responsible for freeing.
71-
* `gitno_connection_data_free_ptrs` is good for this.
72-
*/
73-
74-
int gitno_connection_data_handle_redirect(
75-
git_net_url *data,
76-
const char *url,
77-
const char *service_suffix);
78-
7968
#endif

src/transports/http.c

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -609,17 +609,9 @@ static int on_headers_complete(http_parser *parser)
609609
parser->status_code == 308) &&
610610
t->location) {
611611

612-
if (gitno_connection_data_handle_redirect(&t->server.url, t->location, s->service_url) < 0)
612+
if (git_net_url_apply_redirect(&t->server.url, t->location, s->service_url) < 0)
613613
return t->parse_error = PARSE_ERROR_GENERIC;
614614

615-
/* Set the redirect URL on the stream. This is a transfer of
616-
* ownership of the memory. */
617-
if (s->redirect_url)
618-
git__free(s->redirect_url);
619-
620-
s->redirect_url = t->location;
621-
t->location = NULL;
622-
623615
t->connected = 0;
624616
t->parse_error = PARSE_ERROR_REPLAY;
625617
return 0;
@@ -1422,9 +1414,6 @@ static void http_stream_free(git_smart_subtransport_stream *stream)
14221414
if (s->chunk_buffer)
14231415
git__free(s->chunk_buffer);
14241416

1425-
if (s->redirect_url)
1426-
git__free(s->redirect_url);
1427-
14281417
git__free(s);
14291418
}
14301419

src/transports/winhttp.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ static int winhttp_stream_read(
11281128

11291129
if (!git__prefixcmp_icase(location8, prefix_https)) {
11301130
/* Upgrade to secure connection; disconnect and start over */
1131-
if (gitno_connection_data_handle_redirect(&t->server.url, location8, s->service_url) < 0) {
1131+
if (git_net_url_apply_redirect(&t->server.url, location8, s->service_url) < 0) {
11321132
git__free(location8);
11331133
return -1;
11341134
}

tests/network/redirect.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void test_network_redirect__redirect_http(void)
1818
{
1919
cl_git_pass(git_net_url_parse(&conndata,
2020
"http://example.com/foo/bar/baz"));
21-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
21+
cl_git_pass(git_net_url_apply_redirect(&conndata,
2222
"http://example.com/foo/bar/baz", "bar/baz"));
2323
cl_assert_equal_s(conndata.scheme, "http");
2424
cl_assert_equal_s(conndata.host, "example.com");
@@ -32,7 +32,7 @@ void test_network_redirect__redirect_ssl(void)
3232
{
3333
cl_git_pass(git_net_url_parse(&conndata,
3434
"https://example.com/foo/bar/baz"));
35-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
35+
cl_git_pass(git_net_url_apply_redirect(&conndata,
3636
"https://example.com/foo/bar/baz", "bar/baz"));
3737
cl_assert_equal_s(conndata.scheme, "https");
3838
cl_assert_equal_s(conndata.host, "example.com");
@@ -46,7 +46,7 @@ void test_network_redirect__redirect_leaves_root_path(void)
4646
{
4747
cl_git_pass(git_net_url_parse(&conndata,
4848
"https://example.com/foo/bar/baz"));
49-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
49+
cl_git_pass(git_net_url_apply_redirect(&conndata,
5050
"https://example.com/foo/bar/baz", "/foo/bar/baz"));
5151
cl_assert_equal_s(conndata.scheme, "https");
5252
cl_assert_equal_s(conndata.host, "example.com");
@@ -60,7 +60,7 @@ void test_network_redirect__redirect_encoded_username_password(void)
6060
{
6161
cl_git_pass(git_net_url_parse(&conndata,
6262
"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz"));
63-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
63+
cl_git_pass(git_net_url_apply_redirect(&conndata,
6464
"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz"));
6565
cl_assert_equal_s(conndata.scheme, "https");
6666
cl_assert_equal_s(conndata.host, "example.com");
@@ -73,23 +73,23 @@ void test_network_redirect__redirect_encoded_username_password(void)
7373
void test_network_redirect__redirect_cross_host_denied(void)
7474
{
7575
cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz"));
76-
cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata,
76+
cl_git_fail_with(git_net_url_apply_redirect(&conndata,
7777
"https://foo.com/bar/baz", NULL),
7878
-1);
7979
}
8080

8181
void test_network_redirect__redirect_http_downgrade_denied(void)
8282
{
8383
cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz"));
84-
cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata,
84+
cl_git_fail_with(git_net_url_apply_redirect(&conndata,
8585
"http://foo.com/bar/baz", NULL),
8686
-1);
8787
}
8888

8989
void test_network_redirect__redirect_relative(void)
9090
{
9191
cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff"));
92-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
92+
cl_git_pass(git_net_url_apply_redirect(&conndata,
9393
"/zap/baz/biff?bam", NULL));
9494
cl_assert_equal_s(conndata.scheme, "http");
9595
cl_assert_equal_s(conndata.host, "foo.com");
@@ -102,7 +102,7 @@ void test_network_redirect__redirect_relative(void)
102102
void test_network_redirect__redirect_relative_ssl(void)
103103
{
104104
cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff"));
105-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
105+
cl_git_pass(git_net_url_apply_redirect(&conndata,
106106
"/zap/baz/biff?bam", NULL));
107107
cl_assert_equal_s(conndata.scheme, "https");
108108
cl_assert_equal_s(conndata.host, "foo.com");
@@ -115,15 +115,15 @@ void test_network_redirect__redirect_relative_ssl(void)
115115
void test_network_redirect__service_query_no_query_params_in_location(void)
116116
{
117117
cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack"));
118-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
118+
cl_git_pass(git_net_url_apply_redirect(&conndata,
119119
"/baz/info/refs", "/info/refs?service=git-upload-pack"));
120120
cl_assert_equal_s(conndata.path, "/baz");
121121
}
122122

123123
void test_network_redirect__service_query_with_query_params_in_location(void)
124124
{
125125
cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack"));
126-
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
126+
cl_git_pass(git_net_url_apply_redirect(&conndata,
127127
"/baz/info/refs?service=git-upload-pack", "/info/refs?service=git-upload-pack"));
128128
cl_assert_equal_s(conndata.path, "/baz");
129129
}

0 commit comments

Comments
 (0)