Commit e2bda60a by Edward Thomson

url: introduce git_net_url_parse_scp

Provide a mechanism for parsing scp-style paths (eg
`git@github.com:libgit2/libgit2` into the url form
`ssh://git@github.com/libgit2/libgit2`.)
parent 3db53eb1
......@@ -192,6 +192,195 @@ done:
return error;
}
static int scp_invalid(const char *message)
{
git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message);
return GIT_EINVALIDSPEC;
}
static bool is_ipv6(const char *str)
{
const char *c;
size_t colons = 0;
if (*str++ != '[')
return false;
for (c = str; *c; c++) {
if (*c == ':')
colons++;
if (*c == ']')
return (colons > 1);
if (*c != ':' &&
(*c < '0' || *c > '9') &&
(*c < 'a' || *c > 'f') &&
(*c < 'A' || *c > 'F'))
return false;
}
return false;
}
static bool has_at(const char *str)
{
const char *c;
for (c = str; *c; c++) {
if (*c == '@')
return true;
if (*c == ':')
break;
}
return false;
}
int git_net_url_parse_scp(git_net_url *url, const char *given)
{
const char *default_port = default_port_for_scheme("ssh");
const char *c, *user, *host, *port, *path = NULL;
size_t user_len = 0, host_len = 0, port_len = 0;
unsigned short bracket = 0;
enum {
NONE,
USER,
HOST_START, HOST, HOST_END,
IPV6, IPV6_END,
PORT_START, PORT, PORT_END,
PATH_START
} state = NONE;
memset(url, 0, sizeof(git_net_url));
for (c = given; *c && !path; c++) {
switch (state) {
case NONE:
switch (*c) {
case '@':
return scp_invalid("unexpected '@'");
case ':':
return scp_invalid("unexpected ':'");
case '[':
if (is_ipv6(c)) {
state = IPV6;
host = c;
} else if (bracket++ > 1) {
return scp_invalid("unexpected '['");
}
break;
default:
if (has_at(c)) {
state = USER;
user = c;
} else {
state = HOST;
host = c;
}
break;
}
break;
case USER:
if (*c == '@') {
user_len = (c - user);
state = HOST_START;
}
break;
case HOST_START:
state = (*c == '[') ? IPV6 : HOST;
host = c;
break;
case HOST:
if (*c == ':') {
host_len = (c - host);
state = bracket ? PORT_START : PATH_START;
} else if (*c == ']') {
if (bracket-- == 0)
return scp_invalid("unexpected ']'");
host_len = (c - host);
state = HOST_END;
}
break;
case HOST_END:
if (*c != ':')
return scp_invalid("unexpected character after hostname");
state = PATH_START;
break;
case IPV6:
if (*c == ']')
state = IPV6_END;
break;
case IPV6_END:
if (*c != ':')
return scp_invalid("unexpected character after ipv6 address");
host_len = (c - host);
state = bracket ? PORT_START : PATH_START;
break;
case PORT_START:
port = c;
state = PORT;
break;
case PORT:
if (*c == ']') {
if (bracket-- == 0)
return scp_invalid("unexpected ']'");
port_len = c - port;
state = PORT_END;
}
break;
case PORT_END:
if (*c != ':')
return scp_invalid("unexpected character after ipv6 address");
state = PATH_START;
break;
case PATH_START:
path = c;
break;
default:
GIT_ASSERT("unhandled state");
}
}
if (!path)
return scp_invalid("path is required");
GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh"));
if (user_len)
GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len));
GIT_ASSERT(host_len);
GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len));
if (port_len)
GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len));
else
GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port));
GIT_ASSERT(path);
GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path));
return 0;
}
int git_net_url_joinpath(
git_net_url *out,
git_net_url *one,
......
......@@ -24,9 +24,12 @@ typedef struct git_net_url {
/** Duplicate a URL */
extern int git_net_url_dup(git_net_url *out, git_net_url *in);
/** Parses a string containing a URL into a structure. */
/** Parses a string containing a URL into a structure. */
extern int git_net_url_parse(git_net_url *url, const char *str);
/** Parses a string containing an SCP style path into a URL structure. */
extern int git_net_url_parse_scp(git_net_url *url, const char *str);
/** Appends a path and/or query string to the given URL */
extern int git_net_url_joinpath(
git_net_url *out,
......
......@@ -258,37 +258,6 @@ static int ssh_stream_alloc(
return 0;
}
static int git_ssh_extract_url_parts(
git_net_url *urldata,
const char *url)
{
char *colon, *at;
const char *start;
colon = strchr(url, ':');
at = strchr(url, '@');
if (at) {
start = at + 1;
urldata->username = git__substrdup(url, at - url);
GIT_ERROR_CHECK_ALLOC(urldata->username);
} else {
start = url;
urldata->username = NULL;
}
if (colon == NULL || (colon < start)) {
git_error_set(GIT_ERROR_NET, "malformed URL");
return -1;
}
urldata->host = git__substrdup(start, colon - start);
GIT_ERROR_CHECK_ALLOC(urldata->host);
return 0;
}
static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) {
int rc = LIBSSH2_ERROR_NONE;
......@@ -546,14 +515,9 @@ static int _git_ssh_setup_conn(
goto post_extract;
}
}
if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0)
if ((error = git_net_url_parse_scp(&urldata, url)) < 0)
goto done;
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, urldata.host, urldata.port)) < 0 ||
(error = git_stream_connect(s->io)) < 0)
......
#include "clar_libgit2.h"
#include "net.h"
static git_net_url conndata;
void test_network_url_scp__initialize(void)
{
memset(&conndata, 0, sizeof(conndata));
}
void test_network_url_scp__cleanup(void)
{
git_net_url_dispose(&conndata);
}
/* Hostname */
void test_network_url_scp__hostname_trivial(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__hostname_bracketed(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__hostname_root(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__hostname_user(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "git@example.com:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__hostname_user_bracketed(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__hostname_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com:42]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "42");
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_url_scp__hostname_user_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com:42]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "example.com");
cl_assert_equal_s(conndata.port, "42");
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_url_scp__ipv4_trivial(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "192.168.99.88:/resource/a/b/c"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "192.168.99.88");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource/a/b/c");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__ipv4_bracketed(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88]:/resource/a/b/c"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "192.168.99.88");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource/a/b/c");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__ipv4_user(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "git@192.168.99.88:/resource/a/b/c"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "192.168.99.88");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource/a/b/c");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__ipv4_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88:1111]:/resource/a/b/c"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "192.168.99.88");
cl_assert_equal_s(conndata.port, "1111");
cl_assert_equal_s(conndata.path, "/resource/a/b/c");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_url_scp__ipv4_user_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[git@192.168.99.88:1111]:/resource/a/b/c"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "192.168.99.88");
cl_assert_equal_s(conndata.port, "1111");
cl_assert_equal_s(conndata.path, "/resource/a/b/c");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_url_scp__ipv6_trivial(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:/resource/foo"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource/foo");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__ipv6_user(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:/resource/foo"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource/foo");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__ipv6_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[[fe80::dcad:beff:fe00:0001]:99]:/resource/foo"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
cl_assert_equal_s(conndata.port, "99");
cl_assert_equal_s(conndata.path, "/resource/foo");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_url_scp__ipv6_user_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[git@[fe80::dcad:beff:fe00:0001]:99]:/resource/foo"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
cl_assert_equal_s(conndata.port, "99");
cl_assert_equal_s(conndata.path, "/resource/foo");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_url_scp__hexhost_and_port(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[fe:22]:/resource/foo"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "fe");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "/resource/foo");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__malformed_ipv6_one(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "fe80::dcad:beff:fe00:0001]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "fe80");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, ":dcad:beff:fe00:0001]:/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__malformed_ipv6_two(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:42]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "42]:/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__malformed_ipv6_with_user(void)
{
cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:42]:/resource"));
cl_assert_equal_s(conndata.scheme, "ssh");
cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
cl_assert_equal_s(conndata.port, "22");
cl_assert_equal_s(conndata.path, "42]:/resource");
cl_assert_equal_s(conndata.username, "git");
cl_assert_equal_p(conndata.password, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_url_scp__invalid_addresses(void)
{
/* Path is required */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"example.com"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"example.com:"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[example.com:42]:"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[git@example.com:42]:"));
/* Host is required */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
":"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
":foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"git@:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[]:"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"git@[]:"));
/* User is required if specified */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"@example.com:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"@:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[@localhost:22]:foo"));
/* Port is required in brackets */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[example.com:]:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[git@example.com:]:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[fe:]:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[@localhost]:foo"));
/* Extra brackets are disallowed */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[git@[[fe80::dcad:beff:fe00:0001]]:42]:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[[git@[fe80::dcad:beff:fe00:0001]]:42]:foo"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[[git@[fe80::dcad:beff:fe00:0001]:42]]:foo"));
/* Closing bracket missing */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[fe80::dcad:beff:fe00:0001:/resource"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[[fe80::dcad:beff:fe00:0001]:42:/resource"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[git@[fe80::dcad:beff:fe00:0001]:42:/resource"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
"[git@[fe80::dcad:beff:fe00:0001:42]:/resource"));
/* Invalid character inside address */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
"[fe8o::dcad:beff:fe00:0001]:/resource"));
}
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