Disconnect path string to preserve after redirect

The subtransport path was relying on pointing to data owned by
the remote which meant that after a redirect, the updated path
was getting lost for future requests.  This updates the http
transport to strdup the path and maintain its own lifetime.

This also pulls responsibility for parsing the URL back into the
http transport and isolates the functions that parse and free that
connection data so that they can be reused between the initial
parsing and the redirect parsing.
This commit is contained in:
Russell Belfer 2013-09-24 14:07:08 -07:00
parent c91444055a
commit eb0ff13071
3 changed files with 109 additions and 79 deletions

View File

@ -581,17 +581,13 @@ int gitno_extract_url_parts(
const char *url, const char *url,
const char *default_port) const char *default_port)
{ {
char *colon, *dblslash, *slash, *at, *end; char *colon, *slash, *at, *end;
const char *start; const char *start;
/* /*
*
* ==> [user[:pass]@]hostname.tld[:port]/resource * ==> [user[:pass]@]hostname.tld[:port]/resource
*/ */
dblslash = strstr(url, "://");
if (dblslash) url = dblslash+3;
colon = strchr(url, ':'); colon = strchr(url, ':');
slash = strchr(url, '/'); slash = strchr(url, '/');
at = strchr(url, '@'); at = strchr(url, '@');

View File

@ -59,7 +59,7 @@ typedef struct {
git_smart_subtransport parent; git_smart_subtransport parent;
transport_smart *owner; transport_smart *owner;
gitno_socket socket; gitno_socket socket;
const char *path; char *path;
char *host; char *host;
char *port; char *port;
char *user_from_url; char *user_from_url;
@ -125,15 +125,9 @@ static int gen_request(
size_t content_length) size_t content_length)
{ {
http_subtransport *t = OWNING_SUBTRANSPORT(s); http_subtransport *t = OWNING_SUBTRANSPORT(s);
const char *path = t->path ? t->path : "/";
if (!t->path) git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
t->path = "/";
/* If we were redirected, make sure to respect that here */
if (s->redirect_url)
git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url);
else
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
git_buf_printf(buf, "Host: %s\r\n", t->host); git_buf_printf(buf, "Host: %s\r\n", t->host);
@ -255,6 +249,93 @@ static int on_header_value(http_parser *parser, const char *str, size_t len)
return 0; return 0;
} }
static void free_connection_data(http_subtransport *t)
{
if (t->host) {
git__free(t->host);
t->host = NULL;
}
if (t->port) {
git__free(t->port);
t->port = NULL;
}
if (t->user_from_url) {
git__free(t->user_from_url);
t->user_from_url = NULL;
}
if (t->pass_from_url) {
git__free(t->pass_from_url);
t->pass_from_url = NULL;
}
if (t->path) {
git__free(t->path);
t->path = NULL;
}
}
static int set_connection_data_from_url(
http_subtransport *t, const char *url, const char *service_suffix)
{
int error = 0;
const char *default_port = NULL;
char *original_host = NULL;
if (!git__prefixcmp(url, prefix_http)) {
url = url + strlen(prefix_http);
default_port = "80";
}
if (!git__prefixcmp(url, prefix_https)) {
url += strlen(prefix_https);
default_port = "443";
t->use_ssl = 1;
}
if (!default_port) {
giterr_set(GITERR_NET, "Unrecognized URL prefix");
return -1;
}
/* preserve original host name for checking */
original_host = t->host;
t->host = NULL;
free_connection_data(t);
error = gitno_extract_url_parts(
&t->host, &t->port, &t->user_from_url, &t->pass_from_url,
url, default_port);
if (!error) {
const char *path = strchr(url, '/');
size_t pathlen = strlen(path);
size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
if (suffixlen &&
!memcmp(path + pathlen - suffixlen, service_suffix, suffixlen))
t->path = git__strndup(path, pathlen - suffixlen);
else
t->path = git__strdup(path);
/* Allow '/'-led urls, or a change of protocol */
if (original_host != NULL) {
if (strcmp(original_host, t->host) && t->location[0] != '/') {
giterr_set(GITERR_NET, "Only same-host redirects are supported");
error = -1;
}
git__free(original_host);
}
}
return error;
}
static int on_headers_complete(http_parser *parser) static int on_headers_complete(http_parser *parser)
{ {
parser_context *ctx = (parser_context *) parser->data; parser_context *ctx = (parser_context *) parser->data;
@ -303,28 +384,13 @@ static int on_headers_complete(http_parser *parser)
parser->status_code == 307) && parser->status_code == 307) &&
t->location) { t->location) {
char *host=NULL, *port=NULL, *user=NULL, *pass=NULL;
if (s->redirect_count >= 7) { if (s->redirect_count >= 7) {
giterr_set(GITERR_NET, "Too many redirects"); giterr_set(GITERR_NET, "Too many redirects");
return t->parse_error = PARSE_ERROR_GENERIC; return t->parse_error = PARSE_ERROR_GENERIC;
} }
if (gitno_extract_url_parts(&host, &port, &user, &pass, t->location, "") < 0) { if (set_connection_data_from_url(t, t->location, s->service_url) < 0)
giterr_set(GITERR_NET, "Redirect to unparseable url '%s'", t->location);
return t->parse_error = PARSE_ERROR_GENERIC; return t->parse_error = PARSE_ERROR_GENERIC;
}
git__free(port);
git__free(user);
git__free(pass);
/* Allow '/'-led urls, or a change of protocol */
if (strcmp(t->host, host) && t->location[0] != '/') {
git__free(host);
giterr_set(GITERR_NET, "Only same-host redirects are supported");
return t->parse_error = PARSE_ERROR_GENERIC;
}
git__free(host);
/* Set the redirect URL on the stream. This is a transfer of /* Set the redirect URL on the stream. This is a transfer of
* ownership of the memory. */ * ownership of the memory. */
@ -835,39 +901,20 @@ static int http_action(
git_smart_service_t action) git_smart_service_t action)
{ {
http_subtransport *t = (http_subtransport *)subtransport; http_subtransport *t = (http_subtransport *)subtransport;
const char *default_port = NULL;
int ret; int ret;
if (!stream) if (!stream)
return -1; return -1;
if (!t->host || !t->port || !t->path) { if (!t->host || !t->port || !t->path) {
if (!git__prefixcmp(url, prefix_http)) { if ((ret = set_connection_data_from_url(t, url, NULL)) < 0)
url = url + strlen(prefix_http);
default_port = "80";
}
if (!git__prefixcmp(url, prefix_https)) {
url += strlen(prefix_https);
default_port = "443";
t->use_ssl = 1;
}
if (!default_port)
return -1;
if ((ret = gitno_extract_url_parts(&t->host, &t->port,
&t->user_from_url, &t->pass_from_url, url, default_port)) < 0)
return ret; return ret;
t->path = strchr(url, '/');
} }
if (http_connect(t) < 0) if (http_connect(t) < 0)
return -1; return -1;
switch (action) switch (action) {
{
case GIT_SERVICE_UPLOADPACK_LS: case GIT_SERVICE_UPLOADPACK_LS:
return http_uploadpack_ls(t, stream); return http_uploadpack_ls(t, stream);
@ -906,25 +953,7 @@ static int http_close(git_smart_subtransport *subtransport)
t->url_cred = NULL; t->url_cred = NULL;
} }
if (t->host) { free_connection_data(t);
git__free(t->host);
t->host = NULL;
}
if (t->port) {
git__free(t->port);
t->port = NULL;
}
if (t->user_from_url) {
git__free(t->user_from_url);
t->user_from_url = NULL;
}
if (t->pass_from_url) {
git__free(t->pass_from_url);
t->pass_from_url = NULL;
}
return 0; return 0;
} }

View File

@ -64,6 +64,11 @@ void test_online_fetch__default_http(void)
do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
} }
void test_online_fetch__default_https(void)
{
do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
}
void test_online_fetch__no_tags_git(void) void test_online_fetch__no_tags_git(void)
{ {
do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);