summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Morgan <brad@dmgctrl.com>2013-05-03 10:37:33 -0400
committerBrad Morgan <brad@dmgctrl.com>2013-05-03 10:37:33 -0400
commit297758dce3bd012b17da379996dca4413d9d651c (patch)
treed697021ff0a8e5f16010706c8b70f587db563a3d
parenta50086d174658914d4d6462afbc83b02825b1f5b (diff)
downloadlibgit2-297758dce3bd012b17da379996dca4413d9d651c.tar.gz
Added ssh transport file
-rw-r--r--include/git2/transport.h11
-rw-r--r--src/transport.c6
-rw-r--r--src/transports/ssh.c356
3 files changed, 371 insertions, 2 deletions
diff --git a/include/git2/transport.h b/include/git2/transport.h
index 5e9968363..9b9ecc5fc 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -319,6 +319,17 @@ GIT_EXTERN(int) git_smart_subtransport_git(
git_smart_subtransport **out,
git_transport* owner);
+/**
+ * Create an instance of the ssh subtransport.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_ssh(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
/*
*** End interface for subtransports for the smart transport ***
*/
diff --git a/src/transport.c b/src/transport.c
index adb6d5355..7b7a5aa85 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -23,14 +23,16 @@ static transport_definition dummy_transport_definition = { NULL, 1, git_transpor
static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };
static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };
+static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 };
static transport_definition transports[] = {
{"git://", 1, git_transport_smart, &git_subtransport_definition},
{"http://", 1, git_transport_smart, &http_subtransport_definition},
{"https://", 1, git_transport_smart, &http_subtransport_definition},
{"file://", 1, git_transport_local, NULL},
- {"git+ssh://", 1, git_transport_dummy, NULL},
- {"ssh+git://", 1, git_transport_dummy, NULL},
+ {"git+ssh://", 1, git_transport_smart, &git_smart_subtransport_ssh},
+ {"ssh+git://", 1, git_transport_smart, &git_smart_subtransport_ssh},
+ {"git@", 1, git_transport_smart, &git_smart_subtransport_ssh},
{NULL, 0, 0}
};
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
new file mode 100644
index 000000000..a7f21f554
--- /dev/null
+++ b/src/transports/ssh.c
@@ -0,0 +1,356 @@
+/*
+ * 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 "git2.h"
+#include "buffer.h"
+#include "netops.h"
+
+#include <libssh2.h>
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_git_ssh[] = "git+ssh://";
+static const char prefix_ssh_git[] = "ssh+git://";
+static const char prefix_git[] = "git@";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} ssh_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ git_transport *owner;
+ git_stream *current_stream;
+} ssh_subtransport;
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *delim, *repo;
+ char host[] = "host=";
+ size_t len;
+
+ delim = strchr(url, '/');
+ if (delim == NULL) {
+ giterr_set(GITERR_NET, "Malformed URL");
+ return -1;
+ }
+
+ repo = delim;
+
+ delim = strchr(url, ':');
+ if (delim == NULL)
+ delim = strchr(url, '/');
+
+ len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%04x%s %s%c%s",
+ (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host);
+ git_buf_put(request, url, delim - url);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(git_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ /* It looks like negative values are errors here, and positive values
+ * are the number of bytes sent. */
+ error = gitno_send(&s->socket, request.ptr, request.size, 0);
+
+ if (error >= 0)
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error;
+}
+
+static int git_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ git_stream *s = (git_stream *)stream;
+ gitno_buffer buf;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
+
+ if (gitno_recv(&buf) < 0)
+ return -1;
+
+ *bytes_read = buf.offset;
+
+ return 0;
+}
+
+static int git_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ git_stream *s = (git_stream *)stream;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ return gitno_send(&s->socket, buffer, len, 0);
+}
+
+static void git_stream_free(git_smart_subtransport_stream *stream)
+{
+ git_stream *s = (git_stream *)stream;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
+
+ GIT_UNUSED(ret);
+
+ t->current_stream = NULL;
+
+ if (s->socket.socket) {
+ ret = gitno_close(&s->socket);
+ assert(!ret);
+ }
+
+ git__free(s->url);
+ git__free(s);
+}
+
+static int git_stream_alloc(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ git_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(sizeof(git_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_stream_read;
+ s->parent.write = git_stream_write;
+ s->parent.free = git_stream_free;
+
+ s->cmd = cmd;
+ s->url = git__strdup(url);
+
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int _git_uploadpack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host, *port, *user=NULL, *pass=NULL;
+ git_stream *s;
+
+ *stream = NULL;
+
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
+
+ if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
+ return -1;
+
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
+
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+ return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
+}
+
+static int _git_uploadpack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int _git_receivepack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host, *port, *user=NULL, *pass=NULL;
+ git_stream *s;
+
+ *stream = NULL;
+
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
+
+ if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)
+ return -1;
+
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
+
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+ return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
+}
+
+static int _git_receivepack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return _git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return _git_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return _git_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return _git_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _git_close(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _git_free(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
+{
+ ssh_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(ssh_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = owner;
+ t->parent.action = _git_action;
+ t->parent.close = _git_close;
+ t->parent.free = _git_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}