diff options
Diffstat (limited to 'src/transports')
-rw-r--r-- | src/transports/git.c | 89 | ||||
-rw-r--r-- | src/transports/http.c | 383 | ||||
-rw-r--r-- | src/transports/local.c | 11 | ||||
-rw-r--r-- | src/transports/smart.c | 209 | ||||
-rw-r--r-- | src/transports/smart.h | 29 | ||||
-rw-r--r-- | src/transports/smart_pkt.c | 114 | ||||
-rw-r--r-- | src/transports/smart_protocol.c | 244 | ||||
-rw-r--r-- | src/transports/winhttp.c | 583 |
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; |