Commit c6ab183e by Edward Thomson

net: rename gitno_connection_data to git_net_url

"Connection data" is an imprecise and largely incorrect name; these
structures are actually parsed URLs.  Provide a parser that takes a URL
string and produces a URL structure (if it is valid).

Separate the HTTP redirect handling logic from URL parsing, keeping a
`gitno_connection_data_handle_redirect` whose only job is redirect
handling logic and does not parse URLs itself.
parent f4584a1e
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "net.h"
#include "netops.h"
#include <ctype.h>
#include "git2/errors.h"
#include "posix.h"
#include "buffer.h"
#include "http_parser.h"
#include "global.h"
#define DEFAULT_PORT_HTTP "80"
#define DEFAULT_PORT_HTTPS "443"
#define DEFAULT_PORT_GIT "9418"
#define DEFAULT_PORT_SSH "22"
static const char *default_port_for_scheme(const char *scheme)
{
if (strcmp(scheme, "http") == 0)
return DEFAULT_PORT_HTTP;
else if (strcmp(scheme, "https") == 0)
return DEFAULT_PORT_HTTPS;
else if (strcmp(scheme, "git") == 0)
return DEFAULT_PORT_GIT;
else if (strcmp(scheme, "ssh") == 0)
return DEFAULT_PORT_SSH;
return NULL;
}
int git_net_url_parse(git_net_url *url, const char *given)
{
struct http_parser_url u = {0};
bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo;
git_buf scheme = GIT_BUF_INIT,
host = GIT_BUF_INIT,
port = GIT_BUF_INIT,
path = GIT_BUF_INIT,
username = GIT_BUF_INIT,
password = GIT_BUF_INIT,
query = GIT_BUF_INIT;
int error = GIT_EINVALIDSPEC;
if (http_parser_parse_url(given, strlen(given), false, &u)) {
git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
goto done;
}
has_scheme = !!(u.field_set & (1 << UF_SCHEMA));
has_host = !!(u.field_set & (1 << UF_HOST));
has_port = !!(u.field_set & (1 << UF_PORT));
has_path = !!(u.field_set & (1 << UF_PATH));
has_query = !!(u.field_set & (1 << UF_QUERY));
has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
if (has_scheme) {
const char *url_scheme = given + u.field_data[UF_SCHEMA].off;
size_t url_scheme_len = u.field_data[UF_SCHEMA].len;
git_buf_put(&scheme, url_scheme, url_scheme_len);
git__strntolower(scheme.ptr, scheme.size);
} else {
git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
goto done;
}
if (has_host) {
const char *url_host = given + u.field_data[UF_HOST].off;
size_t url_host_len = u.field_data[UF_HOST].len;
git_buf_decode_percent(&host, url_host, url_host_len);
}
if (has_port) {
const char *url_port = given + u.field_data[UF_PORT].off;
size_t url_port_len = u.field_data[UF_PORT].len;
git_buf_put(&port, url_port, url_port_len);
} else {
const char *default_port = default_port_for_scheme(scheme.ptr);
if (default_port == NULL) {
git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given);
goto done;
}
git_buf_puts(&port, default_port);
}
if (has_path) {
const char *url_path = given + u.field_data[UF_PATH].off;
size_t url_path_len = u.field_data[UF_PATH].len;
git_buf_decode_percent(&path, url_path, url_path_len);
} else {
git_buf_puts(&path, "/");
}
if (has_query) {
const char *url_query = given + u.field_data[UF_QUERY].off;
size_t url_query_len = u.field_data[UF_QUERY].len;
git_buf_decode_percent(&query, url_query, url_query_len);
}
if (has_userinfo) {
const char *url_userinfo = given + u.field_data[UF_USERINFO].off;
size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
if (colon) {
const char *url_username = url_userinfo;
size_t url_username_len = colon - url_userinfo;
const char *url_password = colon + 1;
size_t url_password_len = url_userinfo_len - (url_username_len + 1);
git_buf_decode_percent(&username, url_username, url_username_len);
git_buf_decode_percent(&password, url_password, url_password_len);
} else {
git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
}
}
if (git_buf_oom(&scheme) ||
git_buf_oom(&host) ||
git_buf_oom(&port) ||
git_buf_oom(&path) ||
git_buf_oom(&query) ||
git_buf_oom(&username) ||
git_buf_oom(&password))
return -1;
url->scheme = git_buf_detach(&scheme);
url->host = git_buf_detach(&host);
url->port = git_buf_detach(&port);
url->path = git_buf_detach(&path);
url->query = git_buf_detach(&query);
url->username = git_buf_detach(&username);
url->password = git_buf_detach(&password);
error = 0;
done:
git_buf_dispose(&scheme);
git_buf_dispose(&host);
git_buf_dispose(&port);
git_buf_dispose(&path);
git_buf_dispose(&query);
git_buf_dispose(&username);
git_buf_dispose(&password);
return error;
}
int git_net_url_is_default_port(git_net_url *url)
{
return (strcmp(url->port, default_port_for_scheme(url->scheme)) == 0);
}
void git_net_url_swap(git_net_url *a, git_net_url *b)
{
git_net_url tmp = GIT_NET_URL_INIT;
memcpy(&tmp, a, sizeof(git_net_url));
memcpy(a, b, sizeof(git_net_url));
memcpy(b, &tmp, sizeof(git_net_url));
}
void git_net_url_dispose(git_net_url *url)
{
if (url->username)
git__memzero(url->username, strlen(url->username));
if (url->password)
git__memzero(url->password, strlen(url->password));
git__free(url->scheme); url->scheme = NULL;
git__free(url->host); url->host = NULL;
git__free(url->port); url->port = NULL;
git__free(url->path); url->path = NULL;
git__free(url->username); url->username = NULL;
git__free(url->password); url->password = NULL;
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_net_h__
#define INCLUDE_net_h__
#include "common.h"
typedef struct git_net_url {
char *scheme;
char *host;
char *port;
char *path;
char *query;
char *username;
char *password;
} git_net_url;
#define GIT_NET_URL_INIT { NULL }
/** Parses a string containing a URL into a structure. */
int git_net_url_parse(git_net_url *url, const char *str);
/** Returns nonzero if the URL is on the default port. */
int git_net_url_is_default_port(git_net_url *url);
/** Swaps the contents of one URL for another. */
void git_net_url_swap(git_net_url *a, git_net_url *b);
/** Disposes the contents of the structure. */
void git_net_url_dispose(git_net_url *url);
#endif
......@@ -119,192 +119,79 @@ int gitno__match_host(const char *pattern, const char *host)
return -1;
}
static const char *default_port_http = "80";
static const char *default_port_https = "443";
const char *gitno__default_port(
gitno_connection_data *data)
{
return data->use_ssl ? default_port_https : default_port_http;
}
static const char *prefix_http = "http://";
static const char *prefix_https = "https://";
int gitno_connection_data_from_url(
gitno_connection_data *data,
const char *url,
int gitno_connection_data_handle_redirect(
git_net_url *url,
const char *redirect_str,
const char *service_suffix)
{
int error = -1;
const char *default_port = NULL, *path_search_start = NULL;
char *original_host = NULL;
/* service_suffix is optional */
assert(data && url);
git_net_url tmp = GIT_NET_URL_INIT;
int error = 0;
/* Save these for comparison later */
original_host = data->host;
data->host = NULL;
gitno_connection_data_free_ptrs(data);
assert(url && redirect_str);
if (!git__prefixcmp(url, prefix_http)) {
path_search_start = url + strlen(prefix_http);
default_port = default_port_http;
if (redirect_str[0] == '/') {
git__free(url->path);
if (data->use_ssl) {
git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed");
goto cleanup;
if ((url->path = git__strdup(redirect_str)) == NULL) {
error = -1;
goto done;
}
} else if (!git__prefixcmp(url, prefix_https)) {
path_search_start = url + strlen(prefix_https);
default_port = default_port_https;
data->use_ssl = true;
} else if (url[0] == '/')
default_port = gitno__default_port(data);
if (!default_port) {
git_error_set(GIT_ERROR_NET, "unrecognized URL prefix");
goto cleanup;
}
} else {
git_net_url *original = url;
error = gitno_extract_url_parts(
&data->host, &data->port, &data->path, &data->user, &data->pass,
url, default_port);
if ((error = git_net_url_parse(&tmp, redirect_str)) < 0)
goto done;
if (url[0] == '/') {
/* Relative redirect; reuse original host name and port */
path_search_start = url;
git__free(data->host);
data->host = original_host;
original_host = NULL;
}
/* Validate that this is a legal redirection */
if (!error) {
const char *path = strchr(path_search_start, '/');
size_t pathlen = strlen(path);
size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
if (suffixlen &&
!memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
git__free(data->path);
data->path = git__strndup(path, pathlen - suffixlen);
} else {
git__free(data->path);
data->path = git__strdup(path);
}
if (original->scheme &&
strcmp(original->scheme, tmp.scheme) != 0 &&
strcmp(tmp.scheme, "https") != 0) {
git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
original->scheme, tmp.scheme);
/* Check for errors in the resulting data */
if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
git_error_set(GIT_ERROR_NET, "cross host redirect not allowed");
error = -1;
goto done;
}
}
cleanup:
if (original_host) git__free(original_host);
return error;
}
if (original->host &&
git__strcasecmp(original->host, tmp.host) != 0) {
git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
original->host, tmp.host);
void gitno_connection_data_free_ptrs(gitno_connection_data *d)
{
git__free(d->host); d->host = NULL;
git__free(d->port); d->port = NULL;
git__free(d->path); d->path = NULL;
git__free(d->user); d->user = NULL;
git__free(d->pass); d->pass = NULL;
}
int gitno_extract_url_parts(
char **host_out,
char **port_out,
char **path_out,
char **username_out,
char **password_out,
const char *url,
const char *default_port)
{
struct http_parser_url u = {0};
bool has_host, has_port, has_path, has_userinfo;
git_buf host = GIT_BUF_INIT,
port = GIT_BUF_INIT,
path = GIT_BUF_INIT,
username = GIT_BUF_INIT,
password = GIT_BUF_INIT;
int error = 0;
error = -1;
goto done;
}
if (http_parser_parse_url(url, strlen(url), false, &u)) {
git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url);
error = GIT_EINVALIDSPEC;
goto done;
git_net_url_swap(url, &tmp);
}
has_host = !!(u.field_set & (1 << UF_HOST));
has_port = !!(u.field_set & (1 << UF_PORT));
has_path = !!(u.field_set & (1 << UF_PATH));
has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
/* Remove the service suffix if it was given to us */
if (service_suffix) {
const char *service_query = strchr(service_suffix, '?');
size_t suffix_len = service_query ?
(size_t)(service_query - service_suffix) : strlen(service_suffix);
size_t path_len = strlen(url->path);
if (has_host) {
const char *url_host = url + u.field_data[UF_HOST].off;
size_t url_host_len = u.field_data[UF_HOST].len;
git_buf_decode_percent(&host, url_host, url_host_len);
}
if (suffix_len && path_len >= suffix_len) {
size_t suffix_offset = path_len - suffix_len;
if (has_port) {
const char *url_port = url + u.field_data[UF_PORT].off;
size_t url_port_len = u.field_data[UF_PORT].len;
git_buf_put(&port, url_port, url_port_len);
} else {
git_buf_puts(&port, default_port);
}
if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
(!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
/* Ensure we leave a minimum of '/' as the path */
if (suffix_offset == 0)
suffix_offset++;
if (has_path && path_out) {
const char *url_path = url + u.field_data[UF_PATH].off;
size_t url_path_len = u.field_data[UF_PATH].len;
git_buf_decode_percent(&path, url_path, url_path_len);
} else if (path_out) {
git_error_set(GIT_ERROR_NET, "invalid url, missing path");
error = GIT_EINVALIDSPEC;
goto done;
}
url->path[suffix_offset] = '\0';
if (has_userinfo) {
const char *url_userinfo = url + u.field_data[UF_USERINFO].off;
size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
if (colon) {
const char *url_username = url_userinfo;
size_t url_username_len = colon - url_userinfo;
const char *url_password = colon + 1;
size_t url_password_len = url_userinfo_len - (url_username_len + 1);
git_buf_decode_percent(&username, url_username, url_username_len);
git_buf_decode_percent(&password, url_password, url_password_len);
} else {
git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
git__free(url->query);
url->query = NULL;
}
}
}
if (git_buf_oom(&host) ||
git_buf_oom(&port) ||
git_buf_oom(&path) ||
git_buf_oom(&username) ||
git_buf_oom(&password))
return -1;
*host_out = git_buf_detach(&host);
*port_out = git_buf_detach(&port);
if (path_out)
*path_out = git_buf_detach(&path);
*username_out = git_buf_detach(&username);
*password_out = git_buf_detach(&password);
done:
git_buf_dispose(&host);
git_buf_dispose(&port);
git_buf_dispose(&path);
git_buf_dispose(&username);
git_buf_dispose(&password);
git_net_url_dispose(&tmp);
return error;
}
......@@ -11,6 +11,7 @@
#include "posix.h"
#include "stream.h"
#include "net.h"
#ifdef GIT_OPENSSL
# include <openssl/ssl.h>
......@@ -64,38 +65,15 @@ int gitno_recv(gitno_buffer *buf);
void gitno_consume(gitno_buffer *buf, const char *ptr);
void gitno_consume_n(gitno_buffer *buf, size_t cons);
typedef struct gitno_connection_data {
char *host;
char *port;
char *path;
char *user;
char *pass;
bool use_ssl;
} gitno_connection_data;
/*
* This replaces all the pointers in `data` with freshly-allocated strings,
* that the caller is responsible for freeing.
* `gitno_connection_data_free_ptrs` is good for this.
*/
int gitno_connection_data_from_url(
gitno_connection_data *data,
int gitno_connection_data_handle_redirect(
git_net_url *data,
const char *url,
const char *service_suffix);
/* This frees all the pointers IN the struct, but not the struct itself. */
void gitno_connection_data_free_ptrs(gitno_connection_data *data);
int gitno_extract_url_parts(
char **host,
char **port,
char **path,
char **username,
char **password,
const char *url,
const char *default_port);
const char *gitno__default_port(gitno_connection_data *data);
#endif
......@@ -56,18 +56,18 @@ static git_http_auth_context basic_context = {
};
int git_http_auth_basic(
git_http_auth_context **out, const gitno_connection_data *connection_data)
git_http_auth_context **out, const git_net_url *url)
{
GIT_UNUSED(connection_data);
GIT_UNUSED(url);
*out = &basic_context;
return 0;
}
int git_http_auth_dummy(
git_http_auth_context **out, const gitno_connection_data *connection_data)
git_http_auth_context **out, const git_net_url *url)
{
GIT_UNUSED(connection_data);
GIT_UNUSED(url);
*out = NULL;
return 0;
......
......@@ -50,15 +50,15 @@ typedef struct {
/** Function to initialize an authentication context */
int (*init_context)(
git_http_auth_context **out,
const gitno_connection_data *connection_data);
const git_net_url *url);
} git_http_auth_scheme;
int git_http_auth_dummy(
git_http_auth_context **out,
const gitno_connection_data *connection_data);
const git_net_url *url);
int git_http_auth_basic(
git_http_auth_context **out,
const gitno_connection_data *connection_data);
const git_net_url *url);
#endif
......@@ -194,7 +194,7 @@ static void negotiate_context_free(git_http_auth_context *c)
static int negotiate_init_context(
http_auth_negotiate_context *ctx,
const gitno_connection_data *connection_data)
const git_net_url *url)
{
OM_uint32 status_major, status_minor;
gss_OID item, *oid;
......@@ -235,7 +235,7 @@ static int negotiate_init_context(
}
git_buf_puts(&ctx->target, "HTTP@");
git_buf_puts(&ctx->target, connection_data->host);
git_buf_puts(&ctx->target, url->host);
if (git_buf_oom(&ctx->target))
return -1;
......@@ -248,7 +248,7 @@ static int negotiate_init_context(
int git_http_auth_negotiate(
git_http_auth_context **out,
const gitno_connection_data *connection_data)
const git_net_url *url)
{
http_auth_negotiate_context *ctx;
......@@ -257,7 +257,7 @@ int git_http_auth_negotiate(
ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
GIT_ERROR_CHECK_ALLOC(ctx);
if (negotiate_init_context(ctx, connection_data) < 0) {
if (negotiate_init_context(ctx, url) < 0) {
git__free(ctx);
return -1;
}
......
......@@ -16,7 +16,7 @@
extern int git_http_auth_negotiate(
git_http_auth_context **out,
const gitno_connection_data *connection_data);
const git_net_url *url);
#else
......
......@@ -192,8 +192,9 @@ static int _git_uploadpack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
git_net_url urldata = GIT_NET_URL_INIT;
const char *stream_url = url;
const char *host, *port;
git_proto_stream *s;
int error;
......@@ -202,17 +203,15 @@ static int _git_uploadpack_ls(
if (!git__prefixcmp(url, prefix_git))
stream_url += strlen(prefix_git);
if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0)
if ((error = git_net_url_parse(&urldata, url)) < 0)
return error;
error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
host = urldata.host;
port = urldata.port ? urldata.port : GIT_DEFAULT_PORT;
git__free(host);
git__free(port);
git__free(path);
git__free(user);
git__free(pass);
error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
git_net_url_dispose(&urldata);
if (error < 0) {
git_proto_stream_free(*stream);
......@@ -251,7 +250,7 @@ static int _git_receivepack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
git_net_url urldata = GIT_NET_URL_INIT;
const char *stream_url = url;
git_proto_stream *s;
int error;
......@@ -260,16 +259,12 @@ static int _git_receivepack_ls(
if (!git__prefixcmp(url, prefix_git))
stream_url += strlen(prefix_git);
if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0)
if ((error = git_net_url_parse(&urldata, url)) < 0)
return error;
error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream);
error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream);
git__free(host);
git__free(port);
git__free(path);
git__free(user);
git__free(pass);
git_net_url_dispose(&urldata);
if (error < 0) {
git_proto_stream_free(*stream);
......
......@@ -12,6 +12,7 @@
#include "git2.h"
#include "http_parser.h"
#include "buffer.h"
#include "net.h"
#include "netops.h"
#include "global.h"
#include "remote.h"
......@@ -71,7 +72,7 @@ typedef struct {
} http_stream;
typedef struct {
gitno_connection_data url;
git_net_url url;
git_stream *stream;
git_cred *cred;
......@@ -191,10 +192,10 @@ static int apply_credentials(
git_http_auth_context *context;
/* Apply the credentials given to us in the URL */
if (!cred && server->url.user && server->url.pass) {
if (!cred && server->url.username && server->url.password) {
if (!server->url_cred &&
git_cred_userpass_plaintext_new(&server->url_cred,
server->url.user, server->url.pass) < 0)
server->url.username, server->url.password) < 0)
return -1;
cred = server->url_cred;
......@@ -226,7 +227,7 @@ static int gen_request(
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED)
git_buf_printf(buf, "%s %s://%s:%s%s%s HTTP/1.1\r\n",
s->verb,
t->server.url.use_ssl ? "https" : "http",
t->server.url.scheme,
t->server.url.host,
t->server.url.port,
path, s->service_url);
......@@ -238,9 +239,10 @@ static int gen_request(
git_http__user_agent(buf);
git_buf_puts(buf, "\r\n");
git_buf_printf(buf, "Host: %s", t->server.url.host);
if (strcmp(t->server.url.port, gitno__default_port(&t->server.url)) != 0) {
if (!git_net_url_is_default_port(&t->server.url))
git_buf_printf(buf, ":%s", t->server.url.port);
}
git_buf_puts(buf, "\r\n");
if (s->chunked || content_length > 0) {
......@@ -484,7 +486,7 @@ static int on_headers_complete(http_parser *parser)
SERVER_TYPE_PROXY,
t->proxy_opts.credentials,
t->proxy_opts.payload,
t->proxy.url.user,
t->proxy.url.username,
proxy_auth_types);
/* Check for an authentication failure. */
......@@ -495,7 +497,7 @@ static int on_headers_complete(http_parser *parser)
SERVER_TYPE_REMOTE,
t->owner->cred_acquire_cb,
t->owner->cred_acquire_payload,
t->server.url.user,
t->server.url.username,
server_auth_types);
/* Check for a redirect.
......@@ -507,7 +509,7 @@ static int on_headers_complete(http_parser *parser)
parser->status_code == 308) &&
t->location) {
if (gitno_connection_data_from_url(&t->server.url, t->location, s->service_url) < 0)
if (gitno_connection_data_handle_redirect(&t->server.url, t->location, s->service_url) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Set the redirect URL on the stream. This is a transfer of
......@@ -676,7 +678,7 @@ static int load_proxy_config(http_subtransport *t)
git_proxy_init_options(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION);
if ((error = git_remote__get_http_proxy(t->owner->owner,
!!t->server.url.use_ssl, &t->proxy_url)) < 0)
!strcmp(t->server.url.scheme, "https"), &t->proxy_url)) < 0)
return error;
if (!t->proxy_url)
......@@ -698,10 +700,11 @@ static int load_proxy_config(http_subtransport *t)
return -1;
}
if ((error = gitno_connection_data_from_url(&t->proxy.url, t->proxy_opts.url, NULL)) < 0)
git_net_url_dispose(&t->proxy.url);
if ((error = git_net_url_parse(&t->proxy.url, t->proxy_opts.url)) < 0)
return error;
if (t->proxy.url.use_ssl) {
if (!strcmp(t->proxy.url.scheme, "https")) {
git_error_set(GIT_ERROR_NET, "SSL connections to proxy are not supported");
return -1;
}
......@@ -711,7 +714,7 @@ static int load_proxy_config(http_subtransport *t)
static int check_certificate(
git_stream *stream,
gitno_connection_data *url,
git_net_url *url,
int is_valid,
git_transport_certificate_check_cb cert_cb,
void *cert_cb_payload)
......@@ -740,7 +743,7 @@ static int check_certificate(
static int stream_connect(
git_stream *stream,
gitno_connection_data *url,
git_net_url *url,
git_transport_certificate_check_cb cert_cb,
void *cb_payload)
{
......@@ -812,7 +815,7 @@ static int proxy_headers_complete(http_parser *parser)
SERVER_TYPE_PROXY,
t->proxy_opts.credentials,
t->proxy_opts.payload,
t->proxy.url.user,
t->proxy.url.username,
proxy_auth_types);
if (parser->status_code != 200) {
......@@ -927,7 +930,7 @@ done:
static int http_connect(http_subtransport *t)
{
gitno_connection_data *url;
git_net_url *url;
git_stream *proxy_stream = NULL, *stream = NULL;
git_transport_certificate_check_cb cert_cb;
void *cb_payload;
......@@ -965,7 +968,7 @@ static int http_connect(http_subtransport *t)
cb_payload = t->owner->message_cb_payload;
}
if (url->use_ssl)
if (strcmp(url->scheme, "https") == 0)
error = git_tls_stream_new(&stream, url->host, url->port);
else
error = git_socket_stream_new(&stream, url->host, url->port);
......@@ -982,7 +985,7 @@ static int http_connect(http_subtransport *t)
* an HTTPS connection, then we need to build a CONNECT tunnel.
*/
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED &&
t->server.url.use_ssl) {
strcmp(t->server.url.scheme, "https") == 0) {
proxy_stream = stream;
stream = NULL;
......@@ -1378,7 +1381,7 @@ static int http_action(
* that would be insecure in plaintext (eg, HTTP Basic).
*/
if ((!t->server.url.host || !t->server.url.port || !t->server.url.path) &&
(ret = gitno_connection_data_from_url(&t->server.url, url, NULL)) < 0)
(ret = git_net_url_parse(&t->server.url, url)) < 0)
return ret;
assert(t->server.url.host && t->server.url.port && t->server.url.path);
......@@ -1445,11 +1448,8 @@ static int http_close(git_smart_subtransport *subtransport)
free_auth_contexts(&t->server.auth_contexts);
free_auth_contexts(&t->proxy.auth_contexts);
gitno_connection_data_free_ptrs(&t->server.url);
memset(&t->server.url, 0x0, sizeof(gitno_connection_data));
gitno_connection_data_free_ptrs(&t->proxy.url);
memset(&t->proxy.url, 0x0, sizeof(gitno_connection_data));
git_net_url_dispose(&t->server.url);
git_net_url_dispose(&t->proxy.url);
git__free(t->proxy_url);
t->proxy_url = NULL;
......
......@@ -14,6 +14,7 @@
#include "global.h"
#include "git2.h"
#include "buffer.h"
#include "net.h"
#include "netops.h"
#include "smart.h"
#include "cred.h"
......@@ -258,8 +259,7 @@ static int ssh_stream_alloc(
}
static int git_ssh_extract_url_parts(
char **host,
char **username,
git_net_url *urldata,
const char *url)
{
char *colon, *at;
......@@ -271,11 +271,11 @@ static int git_ssh_extract_url_parts(
at = strchr(url, '@');
if (at) {
start = at + 1;
*username = git__substrdup(url, at - url);
GIT_ERROR_CHECK_ALLOC(*username);
urldata->username = git__substrdup(url, at - url);
GIT_ERROR_CHECK_ALLOC(urldata->username);
} else {
start = url;
*username = NULL;
urldata->username = NULL;
}
if (colon == NULL || (colon < start)) {
......@@ -283,8 +283,8 @@ static int git_ssh_extract_url_parts(
return -1;
}
*host = git__substrdup(start, colon - start);
GIT_ERROR_CHECK_ALLOC(*host);
urldata->host = git__substrdup(start, colon - start);
GIT_ERROR_CHECK_ALLOC(urldata->host);
return 0;
}
......@@ -506,14 +506,15 @@ static int _git_ssh_session_create(
return 0;
}
#define SSH_DEFAULT_PORT "22"
static int _git_ssh_setup_conn(
ssh_subtransport *t,
const char *url,
const char *cmd,
git_smart_subtransport_stream **stream)
{
char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
const char *default_port="22";
git_net_url urldata = GIT_NET_URL_INIT;
int auth_methods, error = 0;
size_t i;
ssh_stream *s;
......@@ -535,19 +536,22 @@ static int _git_ssh_setup_conn(
const char *p = ssh_prefixes[i];
if (!git__prefixcmp(url, p)) {
if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0)
if ((error = git_net_url_parse(&urldata, url)) < 0)
goto done;
goto post_extract;
}
}
if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0)
goto done;
port = git__strdup(default_port);
GIT_ERROR_CHECK_ALLOC(port);
if (urldata.port == NULL)
urldata.port = git__strdup(SSH_DEFAULT_PORT);
GIT_ERROR_CHECK_ALLOC(urldata.port);
post_extract:
if ((error = git_socket_stream_new(&s->io, host, port)) < 0 ||
if ((error = git_socket_stream_new(&s->io, urldata.host, urldata.port)) < 0 ||
(error = git_stream_connect(s->io)) < 0)
goto done;
......@@ -583,7 +587,7 @@ post_extract:
cert_ptr = &cert;
error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload);
error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload);
if (error < 0 && error != GIT_PASSTHROUGH) {
if (!git_error_last())
......@@ -594,21 +598,21 @@ post_extract:
}
/* we need the username to ask for auth methods */
if (!user) {
if (!urldata.username) {
if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
goto done;
user = git__strdup(((git_cred_username *) cred)->username);
urldata.username = git__strdup(((git_cred_username *) cred)->username);
cred->free(cred);
cred = NULL;
if (!user)
if (!urldata.username)
goto done;
} else if (user && pass) {
if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0)
} else if (urldata.username && urldata.password) {
if ((error = git_cred_userpass_plaintext_new(&cred, urldata.username, urldata.password)) < 0)
goto done;
}
if ((error = list_auth_methods(&auth_methods, session, user)) < 0)
if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0)
goto done;
error = GIT_EAUTH;
......@@ -622,10 +626,10 @@ post_extract:
cred = NULL;
}
if ((error = request_creds(&cred, t, user, auth_methods)) < 0)
if ((error = request_creds(&cred, t, urldata.username, auth_methods)) < 0)
goto done;
if (strcmp(user, git_cred__username(cred))) {
if (strcmp(urldata.username, git_cred__username(cred))) {
git_error_set(GIT_ERROR_SSH, "username does not match previous request");
error = -1;
goto done;
......@@ -662,11 +666,7 @@ done:
if (cred)
cred->free(cred);
git__free(host);
git__free(port);
git__free(path);
git__free(user);
git__free(pass);
git_net_url_dispose(&urldata);
return error;
}
......
......@@ -103,8 +103,8 @@ typedef struct {
typedef struct {
git_smart_subtransport parent;
transport_smart *owner;
gitno_connection_data connection_data;
gitno_connection_data proxy_connection_data;
git_net_url url;
git_net_url proxy_url;
git_cred *cred;
git_cred *url_cred;
git_cred *proxy_cred;
......@@ -280,7 +280,7 @@ static int certificate_check(winhttp_stream *s, int valid)
return GIT_ECERTIFICATE;
}
if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl)
if (t->owner->certificate_check_cb == NULL || git__strcmp(t->url.scheme, "https") != 0)
return 0;
if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
......@@ -292,7 +292,7 @@ static int certificate_check(winhttp_stream *s, int valid)
cert.parent.cert_type = GIT_CERT_X509;
cert.data = cert_ctx->pbCertEncoded;
cert.len = cert_ctx->cbCertEncoded;
error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->message_cb_payload);
error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->url.host, t->owner->message_cb_payload);
CertFreeCertificateContext(cert_ctx);
if (error == GIT_PASSTHROUGH)
......@@ -329,9 +329,6 @@ static void winhttp_stream_close(winhttp_stream *s)
s->sent_request = 0;
}
#define SCHEME_HTTP "http://"
#define SCHEME_HTTPS "https://"
static int winhttp_stream_connect(winhttp_stream *s)
{
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
......@@ -348,7 +345,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
const git_proxy_options *proxy_opts;
/* Prepare URL */
git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url);
git_buf_printf(&buf, "%s%s", t->url.path, s->service_url);
if (git_buf_oom(&buf))
return -1;
......@@ -367,7 +364,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
NULL,
WINHTTP_NO_REFERER,
types,
t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0);
git__strcmp(t->url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0);
if (!s->request) {
git_error_set(GIT_ERROR_OS, "failed to open request");
......@@ -382,7 +379,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
proxy_opts = &t->owner->proxy;
if (proxy_opts->type == GIT_PROXY_AUTO) {
/* Set proxy if necessary */
if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0)
if (git_remote__get_http_proxy(t->owner->owner, (strcmp(t->url.scheme, "https") == 0), &proxy_url) < 0)
goto on_error;
}
else if (proxy_opts->type == GIT_PROXY_SPECIFIED) {
......@@ -395,38 +392,33 @@ static int winhttp_stream_connect(winhttp_stream *s)
WINHTTP_PROXY_INFO proxy_info;
wchar_t *proxy_wide;
if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) {
t->proxy_connection_data.use_ssl = false;
} else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) {
t->proxy_connection_data.use_ssl = true;
} else {
git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url);
return -1;
}
git_net_url_dispose(&t->proxy_url);
gitno_connection_data_free_ptrs(&t->proxy_connection_data);
if ((error = git_net_url_parse(&t->proxy_url, proxy_url)) < 0)
goto on_error;
if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL,
&t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0)
if (strcmp(t->proxy_url.scheme, "http") != 0 && strcmp(t->proxy_url.scheme, "https") != 0) {
git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url);
error = -1;
goto on_error;
}
if (t->proxy_connection_data.user && t->proxy_connection_data.pass) {
if (t->proxy_url.username && t->proxy_url.password) {
if (t->proxy_cred) {
t->proxy_cred->free(t->proxy_cred);
}
if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0)
if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_url.username, t->proxy_url.password)) < 0)
goto on_error;
}
if (t->proxy_connection_data.use_ssl)
git_buf_PUTS(&processed_url, SCHEME_HTTPS);
else
git_buf_PUTS(&processed_url, SCHEME_HTTP);
git_buf_puts(&processed_url, t->proxy_url.scheme);
git_buf_PUTS(&processed_url, "://");
git_buf_puts(&processed_url, t->proxy_url.host);
git_buf_puts(&processed_url, t->proxy_connection_data.host);
if (t->proxy_connection_data.port)
git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port);
if (!git_net_url_is_default_port(&t->proxy_url))
git_buf_printf(&processed_url, ":%s", t->proxy_url.port);
if (git_buf_oom(&processed_url)) {
error = -1;
......@@ -543,7 +535,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
}
/* If requested, disable certificate validation */
if (t->connection_data.use_ssl) {
if (strcmp(t->url.scheme, "https") == 0) {
int flags;
if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
......@@ -562,9 +554,9 @@ static int winhttp_stream_connect(winhttp_stream *s)
/* If no other credentials have been applied and the URL has username and
* password, use those */
if (!t->cred && t->connection_data.user && t->connection_data.pass) {
if (!t->cred && t->url.username && t->url.password) {
if (!t->url_cred &&
git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0)
git_cred_userpass_plaintext_new(&t->url_cred, t->url.username, t->url.password) < 0)
goto on_error;
if (apply_userpass_credential(s->request, GIT_WINHTTP_AUTH_BASIC, t->url_cred) < 0)
goto on_error;
......@@ -741,12 +733,12 @@ static int winhttp_connect(
t->connection = NULL;
/* Prepare port */
if (git__strntol32(&port, t->connection_data.port,
strlen(t->connection_data.port), NULL, 10) < 0)
if (git__strntol32(&port, t->url.port,
strlen(t->url.port), NULL, 10) < 0)
return -1;
/* Prepare host */
if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) {
if (git__utf8_to_16_alloc(&wide_host, t->url.host) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
return -1;
}
......@@ -1062,7 +1054,7 @@ replay:
if (!git__prefixcmp_icase(location8, prefix_https)) {
/* Upgrade to secure connection; disconnect and start over */
if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) {
if (gitno_connection_data_handle_redirect(&t->url, location8, s->service_url) < 0) {
git__free(location8);
return -1;
}
......@@ -1112,7 +1104,7 @@ replay:
/* Start with the user-supplied credential callback, if present */
if (t->owner->cred_acquire_cb) {
cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url,
t->connection_data.user, allowed_types, t->owner->cred_acquire_payload);
t->url.username, allowed_types, t->owner->cred_acquire_payload);
/* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */
if (cred_error == GIT_PASSTHROUGH)
......@@ -1124,7 +1116,7 @@ replay:
/* Invoke the fallback credentials acquisition callback if necessary */
if (cred_error > 0) {
cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url,
t->connection_data.user, allowed_types, NULL);
t->url.username, allowed_types, NULL);
if (cred_error < 0)
return cred_error;
......@@ -1496,7 +1488,7 @@ static int winhttp_action(
int ret = -1;
if (!t->connection)
if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 ||
if ((ret = git_net_url_parse(&t->url, url)) < 0 ||
(ret = winhttp_connect(t)) < 0)
return ret;
......@@ -1538,10 +1530,8 @@ static int winhttp_close(git_smart_subtransport *subtransport)
{
winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
gitno_connection_data_free_ptrs(&t->connection_data);
memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
gitno_connection_data_free_ptrs(&t->proxy_connection_data);
memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data));
git_net_url_dispose(&t->url);
git_net_url_dispose(&t->proxy_url);
if (t->cred) {
t->cred->free(t->cred);
......
#include "clar_libgit2.h"
#include "net.h"
#include "netops.h"
static git_net_url conndata;
void test_network_redirect__initialize(void)
{
memset(&conndata, 0, sizeof(conndata));
}
void test_network_redirect__cleanup(void)
{
git_net_url_dispose(&conndata);
}
void test_network_redirect__redirect_http(void)
{
cl_git_pass(git_net_url_parse(&conndata,
"http://example.com/foo/bar/baz"));
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
"http://example.com/foo/bar/baz", "bar/baz"));
cl_assert_equal_s(conndata.scheme, "http");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "80");
cl_assert_equal_s(conndata.path, "/foo/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
}
void test_network_redirect__redirect_ssl(void)
{
cl_git_pass(git_net_url_parse(&conndata,
"https://example.com/foo/bar/baz"));
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
"https://example.com/foo/bar/baz", "bar/baz"));
cl_assert_equal_s(conndata.scheme, "https");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "443");
cl_assert_equal_s(conndata.path, "/foo/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
}
void test_network_redirect__redirect_leaves_root_path(void)
{
cl_git_pass(git_net_url_parse(&conndata,
"https://example.com/foo/bar/baz"));
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
"https://example.com/foo/bar/baz", "/foo/bar/baz"));
cl_assert_equal_s(conndata.scheme, "https");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "443");
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
}
void test_network_redirect__redirect_encoded_username_password(void)
{
cl_git_pass(git_net_url_parse(&conndata,
"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz"));
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz"));
cl_assert_equal_s(conndata.scheme, "https");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "443");
cl_assert_equal_s(conndata.path, "/foo/");
cl_assert_equal_s(conndata.username, "user/name");
cl_assert_equal_s(conndata.password, "pass@word%zyx%v");
}
void test_network_redirect__redirect_cross_host_denied(void)
{
cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz"));
cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata,
"https://foo.com/bar/baz", NULL),
-1);
}
void test_network_redirect__redirect_http_downgrade_denied(void)
{
cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz"));
cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata,
"http://foo.com/bar/baz", NULL),
-1);
}
void test_network_redirect__redirect_relative(void)
{
cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff"));
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
"/zap/baz/biff?bam", NULL));
cl_assert_equal_s(conndata.scheme, "http");
cl_assert_equal_s(conndata.host, "foo.com");
cl_assert_equal_s(conndata.port, "80");
cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
}
void test_network_redirect__redirect_relative_ssl(void)
{
cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff"));
cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
"/zap/baz/biff?bam", NULL));
cl_assert_equal_s(conndata.scheme, "https");
cl_assert_equal_s(conndata.host, "foo.com");
cl_assert_equal_s(conndata.port, "443");
cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment