Commit b7809b84692b4df7f11d603cc5da0860609e0555

Edward Thomson 2016-03-08T13:38:55

Merge pull request #3555 from cbargren/ssh-git-protocols Support for ssh+git and git+ssh protocols

diff --git a/deps/http-parser/http_parser.c b/deps/http-parser/http_parser.c
index 2035302..27bdd20 100644
--- a/deps/http-parser/http_parser.c
+++ b/deps/http-parser/http_parser.c
@@ -99,7 +99,7 @@ do {                                                                 \
     FOR##_mark = NULL;                                               \
   }                                                                  \
 } while (0)
-  
+
 /* Run the data callback FOR and consume the current byte */
 #define CALLBACK_DATA(FOR)                                           \
     CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
@@ -444,6 +444,9 @@ parse_url_char(enum state s, const char ch)
         return s_req_path;
       }
 
+      /* The schema must start with an alpha character. After that, it may
+       * consist of digits, '+', '-' or '.', followed by a ':'.
+       */
       if (IS_ALPHA(ch)) {
         return s_req_schema;
       }
@@ -451,7 +454,7 @@ parse_url_char(enum state s, const char ch)
       break;
 
     case s_req_schema:
-      if (IS_ALPHA(ch)) {
+      if (IS_ALPHANUM(ch) || ch == '+' || ch == '-' || ch == '.') {
         return s;
       }
 
diff --git a/src/transport.c b/src/transport.c
index 5c65c7c..327052f 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -35,6 +35,8 @@ static transport_definition transports[] = {
 	{ "file://",  git_transport_local, NULL },
 #ifdef GIT_SSH
 	{ "ssh://",   git_transport_smart, &ssh_subtransport_definition },
+	{ "ssh+git://",   git_transport_smart, &ssh_subtransport_definition },
+	{ "git+ssh://",   git_transport_smart, &ssh_subtransport_definition },
 #endif
 	{ NULL, 0, 0 }
 };
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index 35739ab..cfd5736 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -21,7 +21,8 @@
 
 #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
 
-static const char prefix_ssh[] = "ssh://";
+static const char *ssh_prefixes[] = { "ssh://", "ssh+git://", "git+ssh://" };
+
 static const char cmd_uploadpack[] = "git-upload-pack";
 static const char cmd_receivepack[] = "git-receive-pack";
 
@@ -63,17 +64,24 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
 {
 	char *repo;
 	int len;
+	size_t i;
 
-	if (!git__prefixcmp(url, prefix_ssh)) {
-		url = url + strlen(prefix_ssh);
-		repo = strchr(url, '/');
-		if (repo && repo[1] == '~')
-			++repo;
-	} else {
-		repo = strchr(url, ':');
-		if (repo) repo++;
+	for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) {
+		const char *p = ssh_prefixes[i];
+
+		if (!git__prefixcmp(url, p)) {
+			url = url + strlen(p);
+			repo = strchr(url, '/');
+			if (repo && repo[1] == '~')
+				++repo;
+
+			goto done;
+		}
 	}
+	repo = strchr(url, ':');
+	if (repo) repo++;
 
+done:
 	if (!repo) {
 		giterr_set(GITERR_NET, "Malformed git protocol URL");
 		return -1;
@@ -500,6 +508,7 @@ static int _git_ssh_setup_conn(
 	char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
 	const char *default_port="22";
 	int auth_methods, error = 0;
+	size_t i;
 	ssh_stream *s;
 	git_cred *cred = NULL;
 	LIBSSH2_SESSION* session=NULL;
@@ -515,16 +524,22 @@ static int _git_ssh_setup_conn(
 	s->session = NULL;
 	s->channel = NULL;
 
-	if (!git__prefixcmp(url, prefix_ssh)) {
-		if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0)
-			goto done;
-	} else {
-		if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
-			goto done;
-		port = git__strdup(default_port);
-		GITERR_CHECK_ALLOC(port);
+	for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) {
+		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)
+				goto done;
+
+			goto post_extract;
+		}
 	}
+	if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
+		goto done;
+	port = git__strdup(default_port);
+	GITERR_CHECK_ALLOC(port);
 
+post_extract:
 	if ((error = git_socket_stream_new(&s->io, host, port)) < 0 ||
 	    (error = git_stream_connect(s->io)) < 0)
 		goto done;
diff --git a/tests/transport/register.c b/tests/transport/register.c
index ea917d5..67a2efd 100644
--- a/tests/transport/register.c
+++ b/tests/transport/register.c
@@ -44,8 +44,13 @@ void test_transport_register__custom_transport_ssh(void)
 
 #ifndef GIT_SSH
 	cl_git_fail_with(git_transport_new(&transport, NULL, "ssh://somehost:somepath"), -1);
+	cl_git_fail_with(git_transport_new(&transport, NULL, "ssh+git://somehost:somepath"), -1);
+	cl_git_fail_with(git_transport_new(&transport, NULL, "git+ssh://somehost:somepath"), -1);
 	cl_git_fail_with(git_transport_new(&transport, NULL, "git@somehost:somepath"), -1);
 #else
+	cl_git_pass(git_transport_new(&transport, NULL, "ssh://somehost:somepath"));
+	cl_git_pass(git_transport_new(&transport, NULL, "ssh+git://somehost:somepath"));
+	cl_git_pass(git_transport_new(&transport, NULL, "git+ssh://somehost:somepath"));
 	cl_git_pass(git_transport_new(&transport, NULL, "git@somehost:somepath"));
 	transport->free(transport);
 #endif
@@ -60,8 +65,13 @@ void test_transport_register__custom_transport_ssh(void)
 
 #ifndef GIT_SSH
 	cl_git_fail_with(git_transport_new(&transport, NULL, "ssh://somehost:somepath"), -1);
+	cl_git_fail_with(git_transport_new(&transport, NULL, "ssh+git://somehost:somepath"), -1);
+	cl_git_fail_with(git_transport_new(&transport, NULL, "git+ssh://somehost:somepath"), -1);
 	cl_git_fail_with(git_transport_new(&transport, NULL, "git@somehost:somepath"), -1);
 #else
+	cl_git_pass(git_transport_new(&transport, NULL, "ssh://somehost:somepath"));
+	cl_git_pass(git_transport_new(&transport, NULL, "ssh+git://somehost:somepath"));
+	cl_git_pass(git_transport_new(&transport, NULL, "git+ssh://somehost:somepath"));
 	cl_git_pass(git_transport_new(&transport, NULL, "git@somehost:somepath"));
 	transport->free(transport);
 #endif