| /* |
| * 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; |
| } |