summaryrefslogtreecommitdiff
path: root/src/transports
diff options
context:
space:
mode:
Diffstat (limited to 'src/transports')
-rw-r--r--src/transports/git.c89
-rw-r--r--src/transports/http.c383
-rw-r--r--src/transports/local.c11
-rw-r--r--src/transports/smart.c209
-rw-r--r--src/transports/smart.h29
-rw-r--r--src/transports/smart_pkt.c114
-rw-r--r--src/transports/smart_protocol.c244
-rw-r--r--src/transports/winhttp.c583
8 files changed, 1358 insertions, 304 deletions
diff --git a/src/transports/git.c b/src/transports/git.c
index a895c1389..c931dd82b 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -13,6 +13,7 @@
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;
@@ -173,7 +174,7 @@ static int git_stream_alloc(
return 0;
}
-static int git_git_uploadpack_ls(
+static int _git_uploadpack_ls(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
@@ -211,7 +212,7 @@ on_error:
return -1;
}
-static int git_git_uploadpack(
+static int _git_uploadpack(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
@@ -227,29 +228,100 @@ static int git_git_uploadpack(
return -1;
}
+static int _git_receivepack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host, *port;
+ 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_host_and_port(&host, &port, 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);
+ return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
+}
+
+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 *smart_transport,
+ git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
- git_subtransport *t = (git_subtransport *) smart_transport;
+ git_subtransport *t = (git_subtransport *) subtransport;
switch (action) {
case GIT_SERVICE_UPLOADPACK_LS:
- return git_git_uploadpack_ls(t, url, stream);
+ return _git_uploadpack_ls(t, url, stream);
case GIT_SERVICE_UPLOADPACK:
- return git_git_uploadpack(t, url, stream);
+ 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 void _git_free(git_smart_subtransport *smart_transport)
+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 *) smart_transport;
+ git_subtransport *t = (git_subtransport *) subtransport;
assert(!t->current_stream);
@@ -268,6 +340,7 @@ int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owne
t->owner = owner;
t->parent.action = _git_action;
+ t->parent.close = _git_close;
t->parent.free = _git_free;
*out = (git_smart_subtransport *) t;
diff --git a/src/transports/http.c b/src/transports/http.c
index ba4d8746f..b8fc4fe79 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -17,6 +17,9 @@ static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
static const char *get_verb = "GET";
static const char *post_verb = "POST";
static const char *basic_authtype = "Basic";
@@ -26,6 +29,9 @@ static const char *basic_authtype = "Basic";
#define PARSE_ERROR_GENERIC -1
#define PARSE_ERROR_REPLAY -2
+#define CHUNK_SIZE 4096
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
enum last_cb {
NONE,
FIELD,
@@ -41,7 +47,11 @@ typedef struct {
const char *service;
const char *service_url;
const char *verb;
- unsigned sent_request : 1;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1;
} http_stream;
typedef struct {
@@ -106,33 +116,33 @@ on_error:
static int gen_request(
git_buf *buf,
- const char *path,
- const char *host,
- git_cred *cred,
- http_authmechanism_t auth_mechanism,
- const char *op,
- const char *service,
- const char *service_url,
- ssize_t content_length)
+ http_stream *s,
+ size_t content_length)
{
- if (!path)
- path = "/";
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!t->path)
+ t->path = "/";
- git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url);
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
- git_buf_printf(buf, "Host: %s\r\n", host);
- if (content_length > 0) {
- git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);
- git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);
- git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
- } else {
+ git_buf_printf(buf, "Host: %s\r\n", t->host);
+
+ if (s->chunked || content_length > 0) {
+ git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
+ git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);
+
+ if (s->chunked)
+ git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
+ else
+ git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
+ } else
git_buf_puts(buf, "Accept: */*\r\n");
- }
/* Apply credentials to the request */
- if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
- auth_mechanism == GIT_HTTP_AUTH_BASIC &&
- apply_basic_credential(buf, cred) < 0)
+ if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
+ apply_basic_credential(buf, t->cred) < 0)
return -1;
git_buf_puts(buf, "\r\n");
@@ -168,7 +178,7 @@ static int on_header_ready(http_subtransport *t)
git_buf *value = &t->parse_header_value;
char *dup;
- if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) {
+ if (!t->content_type && !strcasecmp("Content-Type", git_buf_cstr(name))) {
t->content_type = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->content_type);
}
@@ -355,6 +365,34 @@ static void clear_parser_state(http_subtransport *t)
git_vector_free(&t->www_authenticate);
}
+static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", (unsigned)len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) {
+ git_buf_free(&buf);
+ return -1;
+ }
+
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (len > 0 && gitno_send(socket, buffer, len, 0) < 0)
+ return -1;
+
+ /* Chunk footer */
+ if (gitno_send(socket, "\r\n", 2, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
static int http_stream_read(
git_smart_subtransport_stream *stream,
char *buffer,
@@ -363,8 +401,8 @@ static int http_stream_read(
{
http_stream *s = (http_stream *)stream;
http_subtransport *t = OWNING_SUBTRANSPORT(s);
- git_buf request = GIT_BUF_INIT;
parser_context ctx;
+ size_t bytes_parsed;
replay:
*bytes_read = 0;
@@ -372,11 +410,11 @@ replay:
assert(t->connected);
if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
clear_parser_state(t);
- if (gen_request(&request, t->path, t->host,
- t->cred, t->auth_mechanism, s->verb,
- s->service, s->service_url, 0) < 0) {
+ if (gen_request(&request, s, 0) < 0) {
giterr_set(GITERR_NET, "Failed to generate request");
return -1;
}
@@ -387,86 +425,182 @@ replay:
}
git_buf_free(&request);
+
s->sent_request = 1;
}
- t->parse_buffer.offset = 0;
+ if (!s->received_response) {
+ if (s->chunked) {
+ assert(s->verb == post_verb);
- if (t->parse_finished)
- return 0;
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
- if (gitno_recv(&t->parse_buffer) < 0)
- return -1;
+ s->chunk_buffer_len = 0;
- /* This call to http_parser_execute will result in invocations of the on_*
- * family of callbacks. The most interesting of these is
- * on_body_fill_buffer, which is called when data is ready to be copied
- * into the target buffer. We need to marshal the buffer, buf_size, and
- * bytes_read parameters to this callback. */
- ctx.t = t;
- ctx.s = s;
- ctx.buffer = buffer;
- ctx.buf_size = buf_size;
- ctx.bytes_read = bytes_read;
-
- /* Set the context, call the parser, then unset the context. */
- t->parser.data = &ctx;
-
- http_parser_execute(&t->parser,
- &t->settings,
- t->parse_buffer.data,
- t->parse_buffer.offset);
-
- t->parser.data = NULL;
-
- /* If there was a handled authentication failure, then parse_error
- * will have signaled us that we should replay the request. */
- if (PARSE_ERROR_REPLAY == t->parse_error) {
- s->sent_request = 0;
- goto replay;
+ /* Write the final chunk. */
+ if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)
+ return -1;
+ }
+
+ s->received_response = 1;
}
- if (t->parse_error < 0)
- return -1;
+ while (!*bytes_read && !t->parse_finished) {
+ t->parse_buffer.offset = 0;
+
+ if (gitno_recv(&t->parse_buffer) < 0)
+ return -1;
+
+ /* This call to http_parser_execute will result in invocations of the
+ * on_* family of callbacks. The most interesting of these is
+ * on_body_fill_buffer, which is called when data is ready to be copied
+ * into the target buffer. We need to marshal the buffer, buf_size, and
+ * bytes_read parameters to this callback. */
+ ctx.t = t;
+ ctx.s = s;
+ ctx.buffer = buffer;
+ ctx.buf_size = buf_size;
+ ctx.bytes_read = bytes_read;
+
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
+
+ bytes_parsed = http_parser_execute(&t->parser,
+ &t->settings,
+ t->parse_buffer.data,
+ t->parse_buffer.offset);
+
+ t->parser.data = NULL;
+
+ /* If there was a handled authentication failure, then parse_error
+ * will have signaled us that we should replay the request. */
+ if (PARSE_ERROR_REPLAY == t->parse_error) {
+ s->sent_request = 0;
+ goto replay;
+ }
+
+ if (t->parse_error < 0)
+ return -1;
+
+ if (bytes_parsed != t->parse_buffer.offset) {
+ giterr_set(GITERR_NET,
+ "HTTP parser error: %s",
+ http_errno_description((enum http_errno)t->parser.http_errno));
+ return -1;
+ }
+ }
return 0;
}
-static int http_stream_write(
+static int http_stream_write_chunked(
git_smart_subtransport_stream *stream,
const char *buffer,
size_t len)
{
http_stream *s = (http_stream *)stream;
http_subtransport *t = OWNING_SUBTRANSPORT(s);
- git_buf request = GIT_BUF_INIT;
assert(t->connected);
- /* Since we have to write the Content-Length header up front, we're
- * basically limited to a single call to write() per request. */
- assert(!s->sent_request);
-
+ /* Send the request, if necessary */
if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
clear_parser_state(t);
- if (gen_request(&request, t->path, t->host,
- t->cred, t->auth_mechanism, s->verb,
- s->service, s->service_url, len) < 0) {
+ if (gen_request(&request, s, 0) < 0) {
giterr_set(GITERR_NET, "Failed to generate request");
return -1;
}
- if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
- goto on_error;
-
- if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
- goto on_error;
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
git_buf_free(&request);
+
s->sent_request = 1;
}
+ if (len > CHUNK_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(&t->socket, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = MIN(CHUNK_SIZE - s->chunk_buffer_len, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = (char *)git__malloc(CHUNK_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CHUNK_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = len;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int http_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
+
+ assert(t->connected);
+
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, len) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
+
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
+ goto on_error;
+
+ if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
+ goto on_error;
+
+ git_buf_free(&request);
+ s->sent_request = 1;
+
return 0;
on_error:
@@ -478,6 +612,9 @@ static void http_stream_free(git_smart_subtransport_stream *stream)
{
http_stream *s = (http_stream *)stream;
+ if (s->chunk_buffer)
+ git__free(s->chunk_buffer);
+
git__free(s);
}
@@ -494,7 +631,7 @@ static int http_stream_alloc(http_subtransport *t,
s->parent.subtransport = &t->parent;
s->parent.read = http_stream_read;
- s->parent.write = http_stream_write;
+ s->parent.write = http_stream_write_single;
s->parent.free = http_stream_free;
*stream = (git_smart_subtransport_stream *)s;
@@ -537,14 +674,54 @@ static int http_uploadpack(
return 0;
}
+static int http_receivepack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int http_receivepack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ /* Use Transfer-Encoding: chunked for this request */
+ s->chunked = 1;
+ s->parent.write = http_stream_write_chunked;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
static int http_action(
git_smart_subtransport_stream **stream,
- git_smart_subtransport *smart_transport,
+ git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
- http_subtransport *t = (http_subtransport *)smart_transport;
- const char *default_port;
+ http_subtransport *t = (http_subtransport *)subtransport;
+ const char *default_port = NULL;
int flags = 0, ret;
if (!stream)
@@ -562,6 +739,9 @@ static int http_action(
t->use_ssl = 1;
}
+ if (!default_port)
+ return -1;
+
if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
url, default_port)) < 0)
return ret;
@@ -569,7 +749,13 @@ static int http_action(
t->path = strchr(url, '/');
}
- if (!t->connected || !http_should_keep_alive(&t->parser)) {
+ if (!t->connected ||
+ !http_should_keep_alive(&t->parser) ||
+ !http_body_is_final(&t->parser)) {
+
+ if (t->socket.socket)
+ gitno_close(&t->socket);
+
if (t->use_ssl) {
int transport_flags;
@@ -588,9 +774,6 @@ static int http_action(
t->connected = 1;
}
- t->parse_finished = 0;
- t->parse_error = 0;
-
switch (action)
{
case GIT_SERVICE_UPLOADPACK_LS:
@@ -598,28 +781,53 @@ static int http_action(
case GIT_SERVICE_UPLOADPACK:
return http_uploadpack(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return http_receivepack_ls(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return http_receivepack(t, stream);
}
*stream = NULL;
return -1;
}
-static void http_free(git_smart_subtransport *smart_transport)
+static int http_close(git_smart_subtransport *subtransport)
{
- http_subtransport *t = (http_subtransport *) smart_transport;
+ http_subtransport *t = (http_subtransport *) subtransport;
clear_parser_state(t);
- if (t->socket.socket)
+ if (t->socket.socket) {
gitno_close(&t->socket);
+ memset(&t->socket, 0x0, sizeof(gitno_socket));
+ }
if (t->cred) {
t->cred->free(t->cred);
t->cred = NULL;
}
- git__free(t->host);
- git__free(t->port);
+ if (t->host) {
+ git__free(t->host);
+ t->host = NULL;
+ }
+
+ if (t->port) {
+ git__free(t->port);
+ t->port = NULL;
+ }
+
+ return 0;
+}
+
+static void http_free(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
+ http_close(subtransport);
+
git__free(t);
}
@@ -635,6 +843,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
t->owner = (transport_smart *)owner;
t->parent.action = http_action;
+ t->parent.close = http_close;
t->parent.free = http_free;
t->settings.on_header_field = on_header_field;
diff --git a/src/transports/local.c b/src/transports/local.c
index 51544416d..db9a08a57 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -26,6 +26,7 @@
typedef struct {
git_transport parent;
+ git_remote *owner;
char *url;
int direction;
int flags;
@@ -339,13 +340,11 @@ cleanup:
return error;
}
-static int local_is_connected(git_transport *transport, int *connected)
+static int local_is_connected(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
- *connected = t->connected;
-
- return 0;
+ return t->connected;
}
static int local_read_flags(git_transport *transport, int *flags)
@@ -398,7 +397,7 @@ static void local_free(git_transport *transport)
* Public API *
**************/
-int git_transport_local(git_transport **out, void *param)
+int git_transport_local(git_transport **out, git_remote *owner, void *param)
{
transport_local *t;
@@ -419,6 +418,8 @@ int git_transport_local(git_transport **out, void *param)
t->parent.read_flags = local_read_flags;
t->parent.cancel = local_cancel;
+ t->owner = owner;
+
*out = (git_transport *) t;
return 0;
diff --git a/src/transports/smart.c b/src/transports/smart.c
index e8dbbef5c..00f8832e0 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -29,12 +29,18 @@ static int git_smart__recv_cb(gitno_buffer *buf)
return (int)(buf->offset - old_len);
}
-GIT_INLINE(void) git_smart__reset_stream(transport_smart *t)
+GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
{
if (t->current_stream) {
t->current_stream->free(t->current_stream);
t->current_stream = NULL;
}
+
+ if (close_subtransport &&
+ t->wrapped->close(t->wrapped) < 0)
+ return -1;
+
+ return 0;
}
static int git_smart__set_callbacks(
@@ -63,8 +69,11 @@ static int git_smart__connect(
git_smart_subtransport_stream *stream;
int error;
git_pkt *pkt;
+ git_pkt_ref *first;
+ git_smart_service_t service;
- git_smart__reset_stream(t);
+ if (git_smart__reset_stream(t, true) < 0)
+ return -1;
t->url = git__strdup(url);
GITERR_CHECK_ALLOC(t->url);
@@ -73,55 +82,64 @@ static int git_smart__connect(
t->flags = flags;
t->cred_acquire_cb = cred_acquire_cb;
- if (GIT_DIRECTION_FETCH == direction)
- {
- if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0)
- return error;
-
- /* Save off the current stream (i.e. socket) that we are working with */
- t->current_stream = stream;
-
- gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
-
- /* 2 flushes for RPC; 1 for stateful */
- if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
- return error;
-
- /* Strip the comment packet for RPC */
- if (t->rpc) {
- pkt = (git_pkt *)git_vector_get(&t->refs, 0);
-
- if (!pkt || GIT_PKT_COMMENT != pkt->type) {
- giterr_set(GITERR_NET, "Invalid response");
- return -1;
- } else {
- /* Remove the comment pkt from the list */
- git_vector_remove(&t->refs, 0);
- git__free(pkt);
- }
- }
+ if (GIT_DIRECTION_FETCH == t->direction)
+ service = GIT_SERVICE_UPLOADPACK_LS;
+ else if (GIT_DIRECTION_PUSH == t->direction)
+ service = GIT_SERVICE_RECEIVEPACK_LS;
+ else {
+ giterr_set(GITERR_NET, "Invalid direction");
+ return -1;
+ }
- /* We now have loaded the refs. */
- t->have_refs = 1;
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
+ return error;
- if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0)
- return -1;
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
- if (t->rpc)
- git_smart__reset_stream(t);
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
- /* We're now logically connected. */
- t->connected = 1;
+ /* 2 flushes for RPC; 1 for stateful */
+ if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
+ return error;
+
+ /* Strip the comment packet for RPC */
+ if (t->rpc) {
+ pkt = (git_pkt *)git_vector_get(&t->refs, 0);
- return 0;
+ if (!pkt || GIT_PKT_COMMENT != pkt->type) {
+ giterr_set(GITERR_NET, "Invalid response");
+ return -1;
+ } else {
+ /* Remove the comment pkt from the list */
+ git_vector_remove(&t->refs, 0);
+ git__free(pkt);
+ }
}
- else
- {
- giterr_set(GITERR_NET, "Push not implemented");
+
+ /* We now have loaded the refs. */
+ t->have_refs = 1;
+
+ first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
+
+ /* Detect capabilities */
+ if (git_smart__detect_caps(first, &t->caps) < 0)
return -1;
+
+ /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
+ if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
+ git_oid_iszero(&first->head.oid)) {
+ git_vector_clear(&t->refs);
+ git_pkt_free((git_pkt *)first);
}
- return -1;
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ /* We're now logically connected. */
+ t->connected = 1;
+
+ return 0;
}
static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
@@ -156,29 +174,55 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len
git_smart_subtransport_stream *stream;
int error;
- if (t->rpc)
- git_smart__reset_stream(t);
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_FETCH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for fetch");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == stream);
- if (GIT_DIRECTION_FETCH == t->direction) {
- if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
- return error;
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
- /* If this is a stateful implementation, the stream we get back should be the same */
- assert(t->rpc || t->current_stream == stream);
+ if ((error = stream->write(stream, (const char *)data, len)) < 0)
+ return error;
- /* Save off the current stream (i.e. socket) that we are working with */
- t->current_stream = stream;
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
- if ((error = stream->write(stream, (const char *)data, len)) < 0)
- return error;
+ return 0;
+}
- gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
+{
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
- return 0;
+ if (GIT_DIRECTION_PUSH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for push");
+ return -1;
}
- giterr_set(GITERR_NET, "Push not implemented");
- return -1;
+ if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == *stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = *stream;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
}
static void git_smart__cancel(git_transport *transport)
@@ -188,13 +232,11 @@ static void git_smart__cancel(git_transport *transport)
git_atomic_set(&t->cancelled, 1);
}
-static int git_smart__is_connected(git_transport *transport, int *connected)
+static int git_smart__is_connected(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
- *connected = t->connected;
-
- return 0;
+ return t->connected;
}
static int git_smart__read_flags(git_transport *transport, int *flags)
@@ -209,21 +251,37 @@ static int git_smart__read_flags(git_transport *transport, int *flags)
static int git_smart__close(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
-
- git_smart__reset_stream(t);
+ git_vector *refs = &t->refs;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+ int ret;
+
+ ret = git_smart__reset_stream(t, true);
+
+ git_vector_foreach(refs, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(refs);
+
+ git_vector_foreach(common, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(common);
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
t->connected = 0;
- return 0;
+ return ret;
}
static void git_smart__free(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
- git_vector *refs = &t->refs;
- git_vector *common = &t->common;
- unsigned int i;
- git_pkt *p;
/* Make sure that the current stream is closed, if we have one. */
git_smart__close(transport);
@@ -231,21 +289,10 @@ static void git_smart__free(git_transport *transport)
/* Free the subtransport */
t->wrapped->free(t->wrapped);
- git_vector_foreach(refs, i, p) {
- git_pkt_free(p);
- }
- git_vector_free(refs);
-
- git_vector_foreach(common, i, p) {
- git_pkt_free(p);
- }
- git_vector_free(common);
-
- git__free(t->url);
git__free(t);
}
-int git_transport_smart(git_transport **out, void *param)
+int git_transport_smart(git_transport **out, git_remote *owner, void *param)
{
transport_smart *t;
git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
@@ -262,11 +309,13 @@ int git_transport_smart(git_transport **out, void *param)
t->parent.free = git_smart__free;
t->parent.negotiate_fetch = git_smart__negotiate_fetch;
t->parent.download_pack = git_smart__download_pack;
+ t->parent.push = git_smart__push;
t->parent.ls = git_smart__ls;
t->parent.is_connected = git_smart__is_connected;
t->parent.read_flags = git_smart__read_flags;
t->parent.cancel = git_smart__cancel;
+ t->owner = owner;
t->rpc = definition->rpc;
if (git_vector_init(&t->refs, 16, NULL) < 0) {
diff --git a/src/transports/smart.h b/src/transports/smart.h
index b37c4ba96..ea2784bb1 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -8,6 +8,7 @@
#include "vector.h"
#include "netops.h"
#include "buffer.h"
+#include "push.h"
#define GIT_SIDE_BAND_DATA 1
#define GIT_SIDE_BAND_PROGRESS 2
@@ -18,6 +19,8 @@
#define GIT_CAP_SIDE_BAND "side-band"
#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
#define GIT_CAP_INCLUDE_TAG "include-tag"
+#define GIT_CAP_DELETE_REFS "delete-refs"
+#define GIT_CAP_REPORT_STATUS "report-status"
enum git_pkt_type {
GIT_PKT_CMD,
@@ -31,6 +34,9 @@ enum git_pkt_type {
GIT_PKT_ERR,
GIT_PKT_DATA,
GIT_PKT_PROGRESS,
+ GIT_PKT_OK,
+ GIT_PKT_NG,
+ GIT_PKT_UNPACK,
};
/* Used for multi-ack */
@@ -85,19 +91,38 @@ typedef struct {
char error[GIT_FLEX_ARRAY];
} git_pkt_err;
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+} git_pkt_ok;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+ char *msg;
+} git_pkt_ng;
+
+typedef struct {
+ enum git_pkt_type type;
+ int unpack_ok;
+} git_pkt_unpack;
+
typedef struct transport_smart_caps {
int common:1,
ofs_delta:1,
multi_ack: 1,
side_band:1,
side_band_64k:1,
- include_tag:1;
+ include_tag:1,
+ delete_refs:1,
+ report_status:1;
} transport_smart_caps;
typedef void (*packetsize_cb)(size_t received, void *payload);
typedef struct {
git_transport parent;
+ git_remote *owner;
char *url;
git_cred_acquire_cb cred_acquire_cb;
int direction;
@@ -123,6 +148,7 @@ typedef struct {
/* smart_protocol.c */
int git_smart__store_refs(transport_smart *t, int flushes);
int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps);
+int git_smart__push(git_transport *transport, git_push *push);
int git_smart__negotiate_fetch(
git_transport *transport,
@@ -139,6 +165,7 @@ int git_smart__download_pack(
/* smart.c */
int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
/* smart_pkt.c */
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c
index 26fc0e4aa..df9863728 100644
--- a/src/transports/smart_pkt.c
+++ b/src/transports/smart_pkt.c
@@ -30,7 +30,7 @@ static int flush_pkt(git_pkt **out)
{
git_pkt *pkt;
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = (git_pkt *) git__malloc(sizeof(git_pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_FLUSH;
@@ -46,7 +46,7 @@ static int ack_pkt(git_pkt **out, const char *line, size_t len)
GIT_UNUSED(line);
GIT_UNUSED(len);
- pkt = git__calloc(1, sizeof(git_pkt_ack));
+ pkt = (git_pkt_ack *) git__calloc(1, sizeof(git_pkt_ack));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ACK;
@@ -73,7 +73,7 @@ static int nak_pkt(git_pkt **out)
{
git_pkt *pkt;
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = (git_pkt *) git__malloc(sizeof(git_pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_NAK;
@@ -86,7 +86,7 @@ static int pack_pkt(git_pkt **out)
{
git_pkt *pkt;
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = (git_pkt *) git__malloc(sizeof(git_pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_PACK;
@@ -99,7 +99,7 @@ static int comment_pkt(git_pkt **out, const char *line, size_t len)
{
git_pkt_comment *pkt;
- pkt = git__malloc(sizeof(git_pkt_comment) + len + 1);
+ pkt = (git_pkt_comment *) git__malloc(sizeof(git_pkt_comment) + len + 1);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_COMMENT;
@@ -118,7 +118,7 @@ static int err_pkt(git_pkt **out, const char *line, size_t len)
/* Remove "ERR " from the line */
line += 4;
len -= 4;
- pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+ pkt = (git_pkt_err *) git__malloc(sizeof(git_pkt_err) + len + 1);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ERR;
@@ -136,7 +136,7 @@ static int data_pkt(git_pkt **out, const char *line, size_t len)
line++;
len--;
- pkt = git__malloc(sizeof(git_pkt_data) + len);
+ pkt = (git_pkt_data *) git__malloc(sizeof(git_pkt_data) + len);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_DATA;
@@ -154,7 +154,7 @@ static int progress_pkt(git_pkt **out, const char *line, size_t len)
line++;
len--;
- pkt = git__malloc(sizeof(git_pkt_progress) + len);
+ pkt = (git_pkt_progress *) git__malloc(sizeof(git_pkt_progress) + len);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_PROGRESS;
@@ -174,7 +174,7 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len)
int error;
git_pkt_ref *pkt;
- pkt = git__malloc(sizeof(git_pkt_ref));
+ pkt = (git_pkt_ref *) git__malloc(sizeof(git_pkt_ref));
GITERR_CHECK_ALLOC(pkt);
memset(pkt, 0x0, sizeof(git_pkt_ref));
@@ -196,7 +196,7 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len)
if (line[len - 1] == '\n')
--len;
- pkt->head.name = git__malloc(len + 1);
+ pkt->head.name = (char *) git__malloc(len + 1);
GITERR_CHECK_ALLOC(pkt->head.name);
memcpy(pkt->head.name, line, len);
@@ -214,6 +214,83 @@ error_out:
return error;
}
+static int ok_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ok *pkt;
+ const char *ptr;
+
+ pkt = (git_pkt_ok *) git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_OK;
+
+ line += 3; /* skip "ok " */
+ ptr = strchr(line, '\n');
+ len = ptr - line;
+
+ pkt->ref = (char *) git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int ng_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ng *pkt;
+ const char *ptr;
+
+ pkt = (git_pkt_ng *) git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_NG;
+
+ line += 3; /* skip "ng " */
+ ptr = strchr(line, ' ');
+ len = ptr - line;
+
+ pkt->ref = (char *) git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ line = ptr + 1;
+ ptr = strchr(line, '\n');
+ len = ptr - line;
+
+ pkt->msg = (char *) git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->msg);
+
+ memcpy(pkt->msg, line, len);
+ pkt->msg[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int unpack_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_unpack *pkt;
+
+ GIT_UNUSED(len);
+
+ pkt = (git_pkt_unpack *) git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_UNPACK;
+ if (!git__prefixcmp(line, "unpack ok"))
+ pkt->unpack_ok = 1;
+ else
+ pkt->unpack_ok = 0;
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
static int32_t parse_len(const char *line)
{
char num[PKT_LEN_SIZE + 1];
@@ -311,6 +388,12 @@ int git_pkt_parse_line(
ret = err_pkt(head, line, len);
else if (*line == '#')
ret = comment_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ok"))
+ ret = ok_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ng"))
+ ret = ng_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "unpack"))
+ ret = unpack_pkt(head, line, len);
else
ret = ref_pkt(head, line, len);
@@ -326,6 +409,17 @@ void git_pkt_free(git_pkt *pkt)
git__free(p->head.name);
}
+ if (pkt->type == GIT_PKT_OK) {
+ git_pkt_ok *p = (git_pkt_ok *) pkt;
+ git__free(p->ref);
+ }
+
+ if (pkt->type == GIT_PKT_NG) {
+ git_pkt_ng *p = (git_pkt_ng *) pkt;
+ git__free(p->ref);
+ git__free(p->msg);
+ }
+
git__free(pkt);
}
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 99d34e23b..7a604aaff 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -4,9 +4,14 @@
* 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 "smart.h"
#include "refs.h"
#include "repository.h"
+#include "push.h"
+#include "pack-objects.h"
+#include "remote.h"
#define NETWORK_XFER_THRESHOLD (100*1024)
@@ -18,6 +23,11 @@ int git_smart__store_refs(transport_smart *t, int flushes)
const char *line_end;
git_pkt *pkt;
+ /* Clear existing refs in case git_remote_connect() is called again
+ * after git_remote_disconnect().
+ */
+ git_vector_clear(refs);
+
do {
if (buf->offset > 0)
error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
@@ -71,37 +81,43 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
if (*ptr == ' ')
ptr++;
- if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
+ if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
caps->common = caps->ofs_delta = 1;
ptr += strlen(GIT_CAP_OFS_DELTA);
continue;
}
- if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
caps->common = caps->multi_ack = 1;
ptr += strlen(GIT_CAP_MULTI_ACK);
continue;
}
- if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+ if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
caps->common = caps->include_tag = 1;
ptr += strlen(GIT_CAP_INCLUDE_TAG);
continue;
}
/* Keep side-band check after side-band-64k */
- if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
caps->common = caps->side_band_64k = 1;
ptr += strlen(GIT_CAP_SIDE_BAND_64K);
continue;
}
- if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
caps->common = caps->side_band = 1;
ptr += strlen(GIT_CAP_SIDE_BAND);
continue;
}
+ if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) {
+ caps->common = caps->delete_refs = 1;
+ ptr += strlen(GIT_CAP_DELETE_REFS);
+ continue;
+ }
+
/* We don't know this capability, so skip it */
ptr = strchr(ptr, ' ');
}
@@ -471,7 +487,8 @@ on_success:
error = 0;
on_error:
- writepack->free(writepack);
+ if (writepack)
+ writepack->free(writepack);
/* Trailing execution of progress_cb, if necessary */
if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes)
@@ -479,3 +496,218 @@ on_error:
return error;
}
+
+static int gen_pktline(git_buf *buf, git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i, j, len;
+ char hex[41]; hex[40] = '\0';
+
+ git_vector_foreach(&push->specs, i, spec) {
+ len = 2*GIT_OID_HEXSZ + 7;
+
+ if (i == 0) {
+ len +=1; /* '\0' */
+ if (push->report_status)
+ len += strlen(GIT_CAP_REPORT_STATUS);
+ }
+
+ if (spec->lref) {
+ len += spec->rref ? strlen(spec->rref) : strlen(spec->lref);
+
+ if (git_oid_iszero(&spec->roid)) {
+
+ /*
+ * Create remote reference
+ */
+ git_oid_fmt(hex, &spec->loid);
+ git_buf_printf(buf, "%04x%s %s %s", len,
+ GIT_OID_HEX_ZERO, hex,
+ spec->rref ? spec->rref : spec->lref);
+
+ } else {
+
+ /*
+ * Update remote reference
+ */
+ git_oid_fmt(hex, &spec->roid);
+ git_buf_printf(buf, "%04x%s ", len, hex);
+
+ git_oid_fmt(hex, &spec->loid);
+ git_buf_printf(buf, "%s %s", hex,
+ spec->rref ? spec->rref : spec->lref);
+ }
+ } else {
+ /*
+ * Delete remote reference
+ */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->rref, head->name)) {
+ len += strlen(spec->rref);
+
+ git_oid_fmt(hex, &head->oid);
+ git_buf_printf(buf, "%04x%s %s %s", len,
+ hex, GIT_OID_HEX_ZERO, head->name);
+
+ break;
+ }
+ }
+ }
+
+ if (i == 0) {
+ git_buf_putc(buf, '\0');
+ if (push->report_status)
+ git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+ }
+
+ git_buf_putc(buf, '\n');
+ }
+ git_buf_puts(buf, "0000");
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int parse_report(gitno_buffer *buf, git_push *push)
+{
+ git_pkt *pkt;
+ const char *line_end;
+ int error, recvd;
+
+ for (;;) {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data,
+ &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return -1;
+
+ if (recvd == 0) {
+ giterr_set(GITERR_NET, "Early EOF");
+ return -1;
+ }
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+
+ if (pkt->type == GIT_PKT_OK) {
+ push_status *status = (push_status *) git__malloc(sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+ status->msg = NULL;
+ git_pkt_free(pkt);
+ if (git_vector_insert(&push->status, status) < 0) {
+ git__free(status);
+ return -1;
+ }
+ continue;
+ }
+
+ if (pkt->type == GIT_PKT_NG) {
+ push_status *status = (push_status *) git__malloc(sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+ git_pkt_free(pkt);
+ if (git_vector_insert(&push->status, status) < 0) {
+ git__free(status);
+ return -1;
+ }
+ continue;
+ }
+
+ if (pkt->type == GIT_PKT_UNPACK) {
+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+ git_pkt_free(pkt);
+ continue;
+ }
+
+ if (pkt->type == GIT_PKT_FLUSH) {
+ git_pkt_free(pkt);
+ return 0;
+ }
+
+ git_pkt_free(pkt);
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+}
+
+static int stream_thunk(void *buf, size_t size, void *data)
+{
+ git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data;
+
+ return s->write(s, (const char *)buf, size);
+}
+
+int git_smart__push(git_transport *transport, git_push *push)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *s;
+ git_buf pktline = GIT_BUF_INIT;
+ char *url = NULL;
+ int error = -1;
+
+#ifdef PUSH_DEBUG
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i;
+ char hex[41]; hex[40] = '\0';
+
+ git_vector_foreach(&push->remote->refs, i, head) {
+ git_oid_fmt(hex, &head->oid);
+ fprintf(stderr, "%s (%s)\n", hex, head->name);
+ }
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_oid_fmt(hex, &spec->roid);
+ fprintf(stderr, "%s (%s) -> ", hex, spec->lref);
+ git_oid_fmt(hex, &spec->loid);
+ fprintf(stderr, "%s (%s)\n", hex, spec->rref ?
+ spec->rref : spec->lref);
+ }
+}
+#endif
+
+ if (git_smart__get_push_stream(t, &s) < 0 ||
+ gen_pktline(&pktline, push) < 0 ||
+ s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0 ||
+ git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0)
+ goto on_error;
+
+ /* If we sent nothing or the server doesn't support report-status, then
+ * we consider the pack to have been unpacked successfully */
+ if (!push->specs.length || !push->report_status)
+ push->unpack_ok = 1;
+ else if (parse_report(&t->buffer, push) < 0)
+ goto on_error;
+
+ /* If we updated at least one ref, then we need to re-acquire the list of
+ * refs so the caller can call git_remote_update_tips afterward. TODO: Use
+ * the data from the push report to do this without another network call */
+ if (push->specs.length) {
+ git_cred_acquire_cb cred_cb = t->cred_acquire_cb;
+ int flags = t->flags;
+
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url, cred_cb, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git__free(url);
+ git_buf_free(&pktline);
+
+ return error;
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index f3abe8598..fa1bbb7e3 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -13,25 +13,37 @@
#include "posix.h"
#include "netops.h"
#include "smart.h"
+#include "remote.h"
+#include "repository.h"
#include <winhttp.h>
#pragma comment(lib, "winhttp")
+/* For UuidCreate */
+#pragma comment(lib, "rpcrt4")
+
#define WIDEN2(s) L ## s
#define WIDEN(s) WIDEN2(s)
#define MAX_CONTENT_TYPE_LEN 100
#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
+#define CACHED_POST_BODY_BUF_SIZE 4096
+#define UUID_LENGTH_CCH 32
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
static const char *prefix_http = "http://";
static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
static const wchar_t *get_verb = L"GET";
static const wchar_t *post_verb = L"POST";
-static const wchar_t *basic_authtype = L"Basic";
static const wchar_t *pragma_nocache = L"Pragma: no-cache";
+static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_UNKNOWN_CA;
@@ -48,8 +60,13 @@ typedef struct {
const char *service_url;
const wchar_t *verb;
HINTERNET request;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ HANDLE post_body;
+ DWORD post_body_len;
unsigned sent_request : 1,
- received_response : 1;
+ received_response : 1,
+ chunked : 1;
} winhttp_stream;
typedef struct {
@@ -126,9 +143,11 @@ static int winhttp_stream_connect(winhttp_stream *s)
{
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
git_buf buf = GIT_BUF_INIT;
+ char *proxy_url = NULL;
wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
wchar_t *types[] = { L"*/*", NULL };
BOOL peerdist = FALSE;
+ int error = -1;
/* Prepare URL */
git_buf_printf(&buf, "%s%s", t->path, s->service_url);
@@ -153,6 +172,36 @@ static int winhttp_stream_connect(winhttp_stream *s)
goto on_error;
}
+ /* Set proxy if necessary */
+ if (git_remote__get_http_proxy(t->owner->owner, t->use_ssl, &proxy_url) < 0)
+ goto on_error;
+
+ if (proxy_url) {
+ WINHTTP_PROXY_INFO proxy_info;
+ size_t wide_len;
+
+ git__utf8_to_16(url, GIT_WIN_PATH, proxy_url);
+
+ wide_len = wcslen(url);
+
+ /* Strip any trailing forward slash on the proxy URL;
+ * WinHTTP doesn't like it if one is present */
+ if (L'/' == url[wide_len - 1])
+ url[wide_len - 1] = L'\0';
+
+ proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxy_info.lpszProxy = url;
+ proxy_info.lpszProxyBypass = NULL;
+
+ if (!WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PROXY,
+ &proxy_info,
+ sizeof(WINHTTP_PROXY_INFO))) {
+ giterr_set(GITERR_OS, "Failed to set proxy");
+ goto on_error;
+ }
+ }
+
/* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
* adds itself. This option may not be supported by the underlying
* platform, so we do not error-check it */
@@ -205,11 +254,12 @@ static int winhttp_stream_connect(winhttp_stream *s)
/* We've done everything up to calling WinHttpSendRequest. */
- return 0;
+ error = 0;
on_error:
+ git__free(proxy_url);
git_buf_free(&buf);
- return -1;
+ return error;
}
static int parse_unauthorized_response(
@@ -217,57 +267,65 @@ static int parse_unauthorized_response(
int *allowed_types,
int *auth_mechanism)
{
- DWORD index, buf_size, last_error;
- int error = 0;
- wchar_t *buf = NULL;
+ DWORD supported, first, target;
*allowed_types = 0;
+ *auth_mechanism = 0;
- for (index = 0; ; index++) {
- /* Make a first call to ask for the size of the buffer to allocate
- * to hold the WWW-Authenticate header */
- if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
- WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER,
- &buf_size, &index))
- {
- last_error = GetLastError();
-
- if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) {
- /* End of enumeration */
- break;
- } else if (ERROR_INSUFFICIENT_BUFFER == last_error) {
- git__free(buf);
- buf = (wchar_t *)git__malloc(buf_size);
-
- if (!buf) {
- error = -1;
- break;
- }
- } else {
- giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
- error = -1;
- break;
- }
- }
+ /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
+ * We can assume this was already done, since we know we are unauthorized.
+ */
+ if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
+ giterr_set(GITERR_OS, "Failed to parse supported auth schemes");
+ return -1;
+ }
- /* Actually receive the data into our now-allocated buffer */
- if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
- WINHTTP_HEADER_NAME_BY_INDEX, buf,
- &buf_size, &index)) {
- giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
- error = -1;
- break;
- }
+ if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
- if (!wcsncmp(buf, basic_authtype, 5) &&
- (buf[5] == L'\0' || buf[5] == L' ')) {
- *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
- *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
- }
+ return 0;
+}
+
+static int write_chunk(HINTERNET request, const char *buffer, size_t len)
+{
+ DWORD bytes_written;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (!WinHttpWriteData(request,
+ git_buf_cstr(&buf), git_buf_len(&buf),
+ &bytes_written)) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_OS, "Failed to write chunk header");
+ return -1;
}
- git__free(buf);
- return error;
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (!WinHttpWriteData(request,
+ buffer, len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk");
+ return -1;
+ }
+
+ /* Chunk footer */
+ if (!WinHttpWriteData(request,
+ "\r\n", 2,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk footer");
+ return -1;
+ }
+
+ return 0;
}
static int winhttp_stream_read(
@@ -285,22 +343,84 @@ replay:
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
- if (!s->sent_request &&
- !WinHttpSendRequest(s->request,
- WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- WINHTTP_NO_REQUEST_DATA, 0,
- 0, 0)) {
- giterr_set(GITERR_OS, "Failed to send request");
- return -1;
- }
-
- s->sent_request = 1;
-
if (!s->received_response) {
- DWORD status_code, status_code_length, content_type_length;
+ DWORD status_code, status_code_length, content_type_length, bytes_written;
char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
+ if (!s->sent_request) {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ s->post_body_len, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+ }
+
+ if (s->chunked) {
+ assert(s->verb == post_verb);
+
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (!WinHttpWriteData(s->request,
+ "0\r\n\r\n", 5,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write final chunk");
+ return -1;
+ }
+ }
+ else if (s->post_body) {
+ char *buffer;
+ DWORD len = s->post_body_len, bytes_read;
+
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,
+ 0, 0, FILE_BEGIN) &&
+ NO_ERROR != GetLastError()) {
+ giterr_set(GITERR_OS, "Failed to reset file pointer");
+ return -1;
+ }
+
+ buffer = (char *)git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ while (len > 0) {
+ DWORD bytes_written;
+
+ if (!ReadFile(s->post_body, buffer,
+ MIN(CACHED_POST_BODY_BUF_SIZE, len),
+ &bytes_read, NULL) ||
+ !bytes_read) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to read from temp file");
+ return -1;
+ }
+
+ if (!WinHttpWriteData(s->request, buffer,
+ bytes_read, &bytes_written)) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ len -= bytes_read;
+ assert(bytes_read == bytes_written);
+ }
+
+ git__free(buffer);
+
+ /* Eagerly close the temp file */
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
if (!WinHttpReceiveResponse(s->request, 0)) {
giterr_set(GITERR_OS, "Failed to receive response");
return -1;
@@ -376,7 +496,7 @@ replay:
if (!WinHttpReadData(s->request,
(LPVOID)buffer,
- (DWORD)buf_size,
+ buf_size,
&dw_bytes_read))
{
giterr_set(GITERR_OS, "Failed to read data");
@@ -388,7 +508,7 @@ replay:
return 0;
}
-static int winhttp_stream_write(
+static int winhttp_stream_write_single(
git_smart_subtransport_stream *stream,
const char *buffer,
size_t len)
@@ -400,10 +520,13 @@ static int winhttp_stream_write(
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
- /* Since we have to write the Content-Length header up front, we're
- * basically limited to a single call to write() per request. */
- if (!s->sent_request &&
- !WinHttpSendRequest(s->request,
+ /* This implementation of write permits only a single call. */
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ if (!WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
(DWORD)len, 0)) {
@@ -417,12 +540,190 @@ static int winhttp_stream_write(
(LPCVOID)buffer,
(DWORD)len,
&bytes_written)) {
- giterr_set(GITERR_OS, "Failed to send request");
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch)
+{
+ UUID uuid;
+ RPC_STATUS status = UuidCreate(&uuid);
+ int result;
+
+ if (RPC_S_OK != status &&
+ RPC_S_UUID_LOCAL_ONLY != status &&
+ RPC_S_UUID_NO_ADDRESS != status) {
+ giterr_set(GITERR_NET, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ if (buffer_len_cch < (UUID_LENGTH_CCH + 1)) {
+ giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name");
+ return -1;
+ }
+
+ result = wsprintfW(buffer, L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
+ uuid.Data1, uuid.Data2, uuid.Data3,
+ uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
+ uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);
+
+ if (result != UUID_LENGTH_CCH) {
+ giterr_set(GITERR_OS, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
+{
+ int len;
+
+ if (!GetTempPathW(buffer_len_cch, buffer)) {
+ giterr_set(GITERR_OS, "Failed to get temp path");
+ return -1;
+ }
+
+ len = wcslen(buffer);
+
+ /* 1 prefix character for the backslash, 1 postfix for
+ * the null terminator */
+ if (buffer_len_cch - len < 1 + UUID_LENGTH_CCH + 1) {
+ giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name");
+ return -1;
+ }
+
+ if (buffer[len - 1] != '\\')
+ buffer[len++] = '\\';
+
+ if (put_uuid_string(&buffer[len], UUID_LENGTH_CCH + 1) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int winhttp_stream_write_buffered(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD bytes_written;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* Buffer the payload, using a temporary file so we delegate
+ * memory management of the data to the operating system. */
+ if (!s->post_body) {
+ wchar_t temp_path[MAX_PATH + 1];
+
+ if (get_temp_file(temp_path, MAX_PATH + 1) < 0)
+ return -1;
+
+ s->post_body = CreateFileW(temp_path,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_DELETE, NULL,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,
+ NULL);
+
+ if (INVALID_HANDLE_VALUE == s->post_body) {
+ s->post_body = NULL;
+ giterr_set(GITERR_OS, "Failed to create temporary file");
+ return -1;
+ }
+ }
+
+ if (!WriteFile(s->post_body, buffer, len, &bytes_written, NULL)) {
+ giterr_set(GITERR_OS, "Failed to write to temporary file");
return -1;
}
assert((DWORD)len == bytes_written);
+ s->post_body_len += bytes_written;
+
+ return 0;
+}
+
+static int winhttp_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->sent_request) {
+ /* Send Transfer-Encoding: chunked header */
+ if (!WinHttpAddRequestHeaders(s->request,
+ transfer_encoding, (ULONG) -1L,
+ WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ return -1;
+ }
+
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+ }
+
+ if (len > CACHED_POST_BODY_BUF_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(s->request, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = MIN(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = (char *)git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Is there any remaining data from the source? */
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = len;
+ }
+ }
+ }
+
return 0;
}
@@ -430,6 +731,16 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream)
{
winhttp_stream *s = (winhttp_stream *)stream;
+ if (s->chunk_buffer) {
+ git__free(s->chunk_buffer);
+ s->chunk_buffer = NULL;
+ }
+
+ if (s->post_body) {
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
if (s->request) {
WinHttpCloseHandle(s->request);
s->request = NULL;
@@ -438,7 +749,7 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream)
git__free(s);
}
-static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream)
+static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
{
winhttp_stream *s;
@@ -450,10 +761,11 @@ static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_
s->parent.subtransport = &t->parent;
s->parent.read = winhttp_stream_read;
- s->parent.write = winhttp_stream_write;
+ s->parent.write = winhttp_stream_write_single;
s->parent.free = winhttp_stream_free;
- *stream = (git_smart_subtransport_stream *)s;
+ *stream = s;
+
return 0;
}
@@ -520,20 +832,8 @@ static int winhttp_connect(
static int winhttp_uploadpack_ls(
winhttp_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
+ winhttp_stream *s)
{
- winhttp_stream *s;
-
- if (!t->connection &&
- winhttp_connect(t, url) < 0)
- return -1;
-
- if (winhttp_stream_alloc(t, stream) < 0)
- return -1;
-
- s = (winhttp_stream *)*stream;
-
s->service = upload_pack_service;
s->service_url = upload_pack_ls_service_url;
s->verb = get_verb;
@@ -543,22 +843,41 @@ static int winhttp_uploadpack_ls(
static int winhttp_uploadpack(
winhttp_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
+ winhttp_stream *s)
{
- winhttp_stream *s;
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
- if (!t->connection &&
- winhttp_connect(t, url) < 0)
- return -1;
+ return 0;
+}
- if (winhttp_stream_alloc(t, stream) < 0)
- return -1;
+static int winhttp_receivepack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
- s = (winhttp_stream *)*stream;
+ return 0;
+}
- s->service = upload_pack_service;
- s->service_url = upload_pack_service_url;
+static int winhttp_receivepack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ /* WinHTTP only supports Transfer-Encoding: chunked
+ * on Windows Vista (NT 6.0) and higher. */
+ s->chunked = LOBYTE(LOWORD(GetVersion())) >= 6;
+
+ if (s->chunked)
+ s->parent.write = winhttp_stream_write_chunked;
+ else
+ s->parent.write = winhttp_stream_write_buffered;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
s->verb = post_verb;
return 0;
@@ -566,11 +885,20 @@ static int winhttp_uploadpack(
static int winhttp_action(
git_smart_subtransport_stream **stream,
- git_smart_subtransport *smart_transport,
+ git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
- winhttp_subtransport *t = (winhttp_subtransport *)smart_transport;
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ winhttp_stream *s;
+ int ret = -1;
+
+ if (!t->connection &&
+ winhttp_connect(t, url) < 0)
+ return -1;
+
+ if (winhttp_stream_alloc(t, &s) < 0)
+ return -1;
if (!stream)
return -1;
@@ -578,22 +906,45 @@ static int winhttp_action(
switch (action)
{
case GIT_SERVICE_UPLOADPACK_LS:
- return winhttp_uploadpack_ls(t, url, stream);
+ ret = winhttp_uploadpack_ls(t, s);
+ break;
case GIT_SERVICE_UPLOADPACK:
- return winhttp_uploadpack(t, url, stream);
+ ret = winhttp_uploadpack(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ ret = winhttp_receivepack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK:
+ ret = winhttp_receivepack(t, s);
+ break;
+
+ default:
+ assert(0);
}
- *stream = NULL;
- return -1;
+ if (!ret)
+ *stream = &s->parent;
+
+ return ret;
}
-static void winhttp_free(git_smart_subtransport *smart_transport)
+static int winhttp_close(git_smart_subtransport *subtransport)
{
- winhttp_subtransport *t = (winhttp_subtransport *) smart_transport;
-
- git__free(t->host);
- git__free(t->port);
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ int ret = 0;
+
+ if (t->host) {
+ git__free(t->host);
+ t->host = NULL;
+ }
+
+ if (t->port) {
+ git__free(t->port);
+ t->port = NULL;
+ }
if (t->cred) {
t->cred->free(t->cred);
@@ -601,15 +952,32 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
}
if (t->connection) {
- WinHttpCloseHandle(t->connection);
+ if (!WinHttpCloseHandle(t->connection)) {
+ giterr_set(GITERR_OS, "Unable to close connection");
+ ret = -1;
+ }
+
t->connection = NULL;
}
if (t->session) {
- WinHttpCloseHandle(t->session);
+ if (!WinHttpCloseHandle(t->session)) {
+ giterr_set(GITERR_OS, "Unable to close session");
+ ret = -1;
+ }
+
t->session = NULL;
}
+ return ret;
+}
+
+static void winhttp_free(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+
+ winhttp_close(subtransport);
+
git__free(t);
}
@@ -625,6 +993,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
t->owner = (transport_smart *)owner;
t->parent.action = winhttp_action;
+ t->parent.close = winhttp_close;
t->parent.free = winhttp_free;
*out = (git_smart_subtransport *) t;