/* * 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 "git2/sys/transport.h" #include "stream.h" #include "socket_stream.h" #define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) 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; git_stream *io; const char *cmd; char *url; unsigned sent_command : 1; } git_proto_stream; typedef struct { git_smart_subtransport parent; git_transport *owner; git_proto_stream *current_stream; } git_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; if (repo[1] == '~') ++repo; 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_proto_stream *s) { int error; git_buf request = GIT_BUF_INIT; error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; error = git_stream_write(s->io, request.ptr, request.size, 0); if (error >= 0) s->sent_command = 1; cleanup: git_buf_free(&request); return error; } static int git_proto_stream_read( git_smart_subtransport_stream *stream, char *buffer, size_t buf_size, size_t *bytes_read) { int error; git_proto_stream *s = (git_proto_stream *)stream; gitno_buffer buf; *bytes_read = 0; if (!s->sent_command && (error = send_command(s)) < 0) return error; gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size); if ((error = gitno_recv(&buf)) < 0) return error; *bytes_read = buf.offset; return 0; } static int git_proto_stream_write( git_smart_subtransport_stream *stream, const char *buffer, size_t len) { int error; git_proto_stream *s = (git_proto_stream *)stream; if (!s->sent_command && (error = send_command(s)) < 0) return error; return git_stream_write(s->io, buffer, len, 0); } static void git_proto_stream_free(git_smart_subtransport_stream *stream) { git_proto_stream *s; git_subtransport *t; if (!stream) return; s = (git_proto_stream *)stream; t = OWNING_SUBTRANSPORT(s); t->current_stream = NULL; git_stream_close(s->io); git_stream_free(s->io); git__free(s->url); git__free(s); } static int git_proto_stream_alloc( git_subtransport *t, const char *url, const char *cmd, const char *host, const char *port, git_smart_subtransport_stream **stream) { git_proto_stream *s; if (!stream) return -1; s = git__calloc(1, sizeof(git_proto_stream)); GITERR_CHECK_ALLOC(s); s->parent.subtransport = &t->parent; s->parent.read = git_proto_stream_read; s->parent.write = git_proto_stream_write; s->parent.free = git_proto_stream_free; s->cmd = cmd; s->url = git__strdup(url); if (!s->url) { git__free(s); return -1; } if ((git_socket_stream_new(&s->io, host, port)) < 0) return -1; GITERR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); *stream = &s->parent; return 0; } static int _git_uploadpack_ls( git_subtransport *t, const char *url, git_smart_subtransport_stream **stream) { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *stream_url = url; git_proto_stream *s; int error; *stream = NULL; 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) return error; error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); git__free(host); git__free(port); git__free(path); git__free(user); git__free(pass); if (error < 0) { git_proto_stream_free(*stream); return error; } s = (git_proto_stream *) *stream; if ((error = git_stream_connect(s->io)) < 0) { git_proto_stream_free(*stream); return error; } t->current_stream = s; return 0; } static int _git_uploadpack( git_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( git_subtransport *t, const char *url, git_smart_subtransport_stream **stream) { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *stream_url = url; git_proto_stream *s; int error; *stream = NULL; 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) return error; error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream); git__free(host); git__free(port); git__free(path); git__free(user); git__free(pass); if (error < 0) { git_proto_stream_free(*stream); return error; } s = (git_proto_stream *) *stream; if ((error = git_stream_connect(s->io)) < 0) return error; t->current_stream = s; return 0; } static int _git_receivepack( git_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) { git_subtransport *t = (git_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) { git_subtransport *t = (git_subtransport *) subtransport; assert(!t->current_stream); GIT_UNUSED(t); return 0; } static void _git_free(git_smart_subtransport *subtransport) { git_subtransport *t = (git_subtransport *) subtransport; assert(!t->current_stream); git__free(t); } int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param) { git_subtransport *t; GIT_UNUSED(param); if (!out) return -1; t = git__calloc(1, sizeof(git_subtransport)); 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; }