diff options
Diffstat (limited to 'src/remote.c')
-rw-r--r-- | src/remote.c | 429 |
1 files changed, 313 insertions, 116 deletions
diff --git a/src/remote.c b/src/remote.c index 4bab9482d..f1010415a 100644 --- a/src/remote.c +++ b/src/remote.c @@ -16,6 +16,7 @@ #include "refspec.h" #include "fetchhead.h" #include "push.h" +#include "proxy.h" #include "git2/config.h" #include "git2/types.h" @@ -756,54 +757,200 @@ int git_remote__urlfordirection( return resolve_url(url_out, url, direction, callbacks); } -static int remote_transport_set_callbacks(git_transport *t, const git_remote_callbacks *cbs) +int git_remote_connect_options_init( + git_remote_connect_options *opts, + unsigned int version) { - if (!t->set_callbacks || !cbs) + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_remote_connect_options, GIT_REMOTE_CONNECT_OPTIONS_INIT); + return 0; +} + +int git_remote_connect_options_dup( + git_remote_connect_options *dst, + const git_remote_connect_options *src) +{ + memcpy(dst, src, sizeof(git_remote_connect_options)); + + if (git_proxy_options_dup(&dst->proxy_opts, &src->proxy_opts) < 0 || + git_strarray_copy(&dst->custom_headers, &src->custom_headers) < 0) + return -1; + + return 0; +} + +void git_remote_connect_options_dispose(git_remote_connect_options *opts) +{ + if (!opts) + return; + + git_strarray_dispose(&opts->custom_headers); + git_proxy_options_dispose(&opts->proxy_opts); +} + +static size_t http_header_name_length(const char *http_header) +{ + const char *colon = strchr(http_header, ':'); + if (!colon) return 0; + return colon - http_header; +} + +static bool is_malformed_http_header(const char *http_header) +{ + const char *c; + size_t name_len; + + /* Disallow \r and \n */ + if ((c = strchr(http_header, '\r')) != NULL) + return true; + if ((c = strchr(http_header, '\n')) != NULL) + return true; + + /* Require a header name followed by : */ + if ((name_len = http_header_name_length(http_header)) < 1) + return true; - return t->set_callbacks(t, cbs->sideband_progress, NULL, - cbs->certificate_check, cbs->payload); + return false; } -static int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers) +static char *forbidden_custom_headers[] = { + "User-Agent", + "Host", + "Accept", + "Content-Type", + "Transfer-Encoding", + "Content-Length", +}; + +static bool is_forbidden_custom_header(const char *custom_header) { - if (!t->set_custom_headers) + unsigned long i; + size_t name_len = http_header_name_length(custom_header); + + /* Disallow headers that we set */ + for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) + if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) + return true; + + return false; +} + +static int validate_custom_headers(const git_strarray *custom_headers) +{ + size_t i; + + if (!custom_headers) return 0; - return t->set_custom_headers(t, custom_headers); + for (i = 0; i < custom_headers->count; i++) { + if (is_malformed_http_header(custom_headers->strings[i])) { + git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); + return -1; + } + + if (is_forbidden_custom_header(custom_headers->strings[i])) { + git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); + return -1; + } + } + + return 0; } -int git_remote__connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_remote_connection_opts *conn) +static int lookup_redirect_config( + git_remote_redirect_t *out, + git_repository *repo) { - git_transport *t; + git_config *config; + const char *value; + int bool_value, error = 0; + + if (!repo) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + return 0; + } + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "http.followRedirects")) < 0) { + if (error == GIT_ENOTFOUND) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + *out = bool_value ? GIT_REMOTE_REDIRECT_ALL : + GIT_REMOTE_REDIRECT_NONE; + } else if (strcasecmp(value, "initial") == 0) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + } else { + git_error_set(GIT_ERROR_CONFIG, "invalid configuration setting '%s' for 'http.followRedirects'", value); + error = -1; + } + +done: + git_config_free(config); + return error; +} + +int git_remote_connect_options_normalize( + git_remote_connect_options *dst, + git_repository *repo, + const git_remote_connect_options *src) +{ + git_remote_connect_options_dispose(dst); + git_remote_connect_options_init(dst, GIT_REMOTE_CONNECT_OPTIONS_VERSION); + + if (src) { + GIT_ERROR_CHECK_VERSION(src, GIT_REMOTE_CONNECT_OPTIONS_VERSION, "git_remote_connect_options"); + GIT_ERROR_CHECK_VERSION(&src->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + GIT_ERROR_CHECK_VERSION(&src->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + + if (validate_custom_headers(&src->custom_headers) < 0 || + git_remote_connect_options_dup(dst, src) < 0) + return -1; + } + + if (dst->follow_redirects == 0) { + if (lookup_redirect_config(&dst->follow_redirects, repo) < 0) + return -1; + } + + return 0; +} + +int git_remote_connect_ext( + git_remote *remote, + git_direction direction, + const git_remote_connect_options *given_opts) +{ + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; git_str url = GIT_STR_INIT; - int flags = GIT_TRANSPORTFLAGS_NONE; + git_transport *t; int error; - void *payload = NULL; - git_credential_acquire_cb credentials = NULL; - git_transport_cb transport = NULL; GIT_ASSERT_ARG(remote); - if (callbacks) { - GIT_ERROR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - credentials = callbacks->credentials; - transport = callbacks->transport; - payload = callbacks->payload; - } + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_remote_connect_options)); - if (conn->proxy) - GIT_ERROR_CHECK_VERSION(conn->proxy, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + GIT_ERROR_CHECK_VERSION(&opts.callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + GIT_ERROR_CHECK_VERSION(&opts.proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); t = remote->transport; - if ((error = git_remote__urlfordirection(&url, remote, direction, callbacks)) < 0) + if ((error = git_remote__urlfordirection(&url, remote, direction, &opts.callbacks)) < 0) goto on_error; /* If we don't have a transport object yet, and the caller specified a * custom transport factory, use that */ - if (!t && transport && - (error = transport(&t, remote, payload)) < 0) + if (!t && opts.callbacks.transport && + (error = opts.callbacks.transport(&t, remote, opts.callbacks.payload)) < 0) goto on_error; /* If we still don't have a transport, then use the global @@ -811,11 +958,7 @@ int git_remote__connect(git_remote *remote, git_direction direction, const git_r if (!t && (error = git_transport_new(&t, remote, url.ptr)) < 0) goto on_error; - if ((error = set_transport_custom_headers(t, conn->custom_headers)) != 0) - goto on_error; - - if ((error = remote_transport_set_callbacks(t, callbacks)) < 0 || - (error = t->connect(t, url.ptr, credentials, payload, conn->proxy, direction, flags)) != 0) + if ((error = t->connect(t, url.ptr, direction, &opts)) != 0) goto on_error; remote->transport = t; @@ -836,14 +979,25 @@ on_error: return error; } -int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy, const git_strarray *custom_headers) +int git_remote_connect( + git_remote *remote, + git_direction direction, + const git_remote_callbacks *callbacks, + const git_proxy_options *proxy, + const git_strarray *custom_headers) { - git_remote_connection_opts conn; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + if (callbacks) + memcpy(&opts.callbacks, callbacks, sizeof(git_remote_callbacks)); + + if (proxy) + memcpy(&opts.proxy_opts, proxy, sizeof(git_proxy_options)); - conn.proxy = proxy; - conn.custom_headers = custom_headers; + if (custom_headers) + memcpy(&opts.custom_headers, custom_headers, sizeof(git_strarray)); - return git_remote__connect(remote, direction, callbacks, &conn); + return git_remote_connect_ext(remote, direction, &opts); } int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote) @@ -1057,38 +1211,50 @@ static int ls_to_vector(git_vector *out, git_remote *remote) return 0; } -int git_remote_download(git_remote *remote, const git_strarray *refspecs, const git_fetch_options *opts) -{ - int error = -1; - size_t i; - git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; - const git_remote_callbacks *cbs = NULL; - const git_strarray *custom_headers = NULL; - const git_proxy_options *proxy = NULL; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; +#define copy_opts(out, in) \ + if (in) { \ + (out)->callbacks = (in)->callbacks; \ + (out)->proxy_opts = (in)->proxy_opts; \ + (out)->custom_headers = (in)->custom_headers; \ + (out)->follow_redirects = (in)->follow_redirects; \ } - if (opts) { - GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - cbs = &opts->callbacks; - custom_headers = &opts->custom_headers; - GIT_ERROR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - proxy = &opts->proxy_opts; +GIT_INLINE(int) connect_opts_from_fetch_opts( + git_remote_connect_options *out, + git_remote *remote, + const git_fetch_options *fetch_opts) +{ + git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; + copy_opts(&tmp, fetch_opts); + return git_remote_connect_options_normalize(out, remote->repo, &tmp); +} + +static int connect_or_reset_options( + git_remote *remote, + int direction, + git_remote_connect_options *opts) +{ + if (!git_remote_connected(remote)) { + return git_remote_connect_ext(remote, direction, opts); + } else { + return remote->transport->set_connect_opts(remote->transport, opts); } +} - if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, proxy, custom_headers)) < 0) - goto on_error; +/* Download from an already connected remote. */ +static int git_remote__download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts) +{ + git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; + size_t i; + int error; if (ls_to_vector(&refs, remote) < 0) return -1; - if ((git_vector_init(&specs, 0, NULL)) < 0) + if ((error = git_vector_init(&specs, 0, NULL)) < 0) goto on_error; remote->passed_refspecs = 0; @@ -1116,7 +1282,7 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const git_vector_free(&specs); if (error < 0) - return error; + goto on_error; if (remote->push) { git_push_free(remote->push); @@ -1124,9 +1290,9 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const } if ((error = git_fetch_negotiate(remote, opts)) < 0) - return error; + goto on_error; - return git_fetch_download_pack(remote, cbs); + error = git_fetch_download_pack(remote); on_error: git_vector_free(&refs); @@ -1135,41 +1301,69 @@ on_error: return error; } +int git_remote_download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) + return error; + + return git_remote__download(remote, refspecs, opts); +} + int git_remote_fetch( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts, - const char *reflog_message) + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts, + const char *reflog_message) { int error, update_fetchhead = 1; git_remote_autotag_option_t tagopt = remote->download_tags; bool prune = false; git_str reflog_msg_buf = GIT_STR_INIT; - const git_remote_callbacks *cbs = NULL; - git_remote_connection_opts conn = GIT_REMOTE_CONNECTION_OPTIONS_INIT; + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) + return error; if (opts) { - GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - cbs = &opts->callbacks; - conn.custom_headers = &opts->custom_headers; update_fetchhead = opts->update_fetchhead; tagopt = opts->download_tags; - GIT_ERROR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - conn.proxy = &opts->proxy_opts; } /* Connect and download everything */ - if ((error = git_remote__connect(remote, GIT_DIRECTION_FETCH, cbs, &conn)) != 0) - return error; - - error = git_remote_download(remote, refspecs, opts); + error = git_remote__download(remote, refspecs, opts); /* We don't need to be connected anymore */ git_remote_disconnect(remote); /* If the download failed, return the error */ if (error != 0) - return error; + goto done; /* Default reflog message */ if (reflog_message) @@ -1180,10 +1374,10 @@ int git_remote_fetch( } /* Create "remote/foo" branches for all remote branches */ - error = git_remote_update_tips(remote, cbs, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); + error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); git_str_dispose(&reflog_msg_buf); if (error < 0) - return error; + goto done; if (opts && opts->prune == GIT_FETCH_PRUNE) prune = true; @@ -1195,8 +1389,10 @@ int git_remote_fetch( prune = remote->prune_refs; if (prune) - error = git_remote_prune(remote, cbs); + error = git_remote_prune(remote, &connect_opts.callbacks); +done: + git_remote_connect_options_dispose(&connect_opts); return error; } @@ -2619,14 +2815,26 @@ done: return error; } -int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts) +GIT_INLINE(int) connect_opts_from_push_opts( + git_remote_connect_options *out, + git_remote *remote, + const git_push_options *push_opts) { - size_t i; - int error; + git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; + copy_opts(&tmp, push_opts); + return git_remote_connect_options_normalize(out, remote->repo, &tmp); +} + +int git_remote_upload( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; git_push *push; git_refspec *spec; - const git_remote_callbacks *cbs = NULL; - git_remote_connection_opts conn = GIT_REMOTE_CONNECTION_OPTIONS_INIT; + size_t i; + int error; GIT_ASSERT_ARG(remote); @@ -2635,14 +2843,10 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi return -1; } - if (opts) { - cbs = &opts->callbacks; - conn.custom_headers = &opts->custom_headers; - conn.proxy = &opts->proxy_opts; - } + if ((error = connect_opts_from_push_opts(&connect_opts, remote, opts)) < 0) + goto cleanup; - if (!git_remote_connected(remote) && - (error = git_remote__connect(remote, GIT_DIRECTION_PUSH, cbs, &conn)) < 0) + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_PUSH, &connect_opts)) < 0) goto cleanup; free_refspecs(&remote->active_refspecs); @@ -2654,14 +2858,11 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi remote->push = NULL; } - if ((error = git_push_new(&remote->push, remote)) < 0) - return error; + if ((error = git_push_new(&remote->push, remote, opts)) < 0) + goto cleanup; push = remote->push; - if (opts && (error = git_push_set_options(push, opts)) < 0) - goto cleanup; - if (refspecs && refspecs->count > 0) { for (i = 0; i < refspecs->count; i++) { if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0) @@ -2676,23 +2877,25 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi } } - if ((error = git_push_finish(push, cbs)) < 0) + if ((error = git_push_finish(push)) < 0) goto cleanup; - if (cbs && cbs->push_update_reference && - (error = git_push_status_foreach(push, cbs->push_update_reference, cbs->payload)) < 0) + if (connect_opts.callbacks.push_update_reference && + (error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload)) < 0) goto cleanup; cleanup: + git_remote_connect_options_dispose(&connect_opts); return error; } -int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts) +int git_remote_push( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts) { + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; int error; - const git_remote_callbacks *cbs = NULL; - const git_strarray *custom_headers = NULL; - const git_proxy_options *proxy = NULL; GIT_ASSERT_ARG(remote); @@ -2701,23 +2904,17 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ return -1; } - if (opts) { - GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - cbs = &opts->callbacks; - custom_headers = &opts->custom_headers; - GIT_ERROR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - proxy = &opts->proxy_opts; - } - - if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0) - return error; + if (connect_opts_from_push_opts(&connect_opts, remote, opts) < 0) + return -1; if ((error = git_remote_upload(remote, refspecs, opts)) < 0) - return error; + goto done; - error = git_remote_update_tips(remote, cbs, 0, 0, NULL); + error = git_remote_update_tips(remote, &connect_opts.callbacks, 0, 0, NULL); +done: git_remote_disconnect(remote); + git_remote_connect_options_dispose(&connect_opts); return error; } |