diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2022-01-17 21:05:17 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-17 21:05:17 -0500 |
commit | 2bfd8ddc9c015d3e430648927f1242f60ba95632 (patch) | |
tree | 7283971b01605f67e30014cc0502ed4237fb0206 | |
parent | 6723edc7bca3a7cab2d98711b14bc3d0261e323c (diff) | |
parent | f4fec25182eb04ceec05d41e47670b1370ca7420 (diff) | |
download | libgit2-2bfd8ddc9c015d3e430648927f1242f60ba95632.tar.gz |
Merge pull request #6175 from libgit2/ethomson/follow_redirects_initial
remote: support `http.followRedirects` (`false` and `initial`) and follow initial redirects by default
-rwxr-xr-x | ci/test.sh | 17 | ||||
-rw-r--r-- | docs/api-stability.md | 63 | ||||
-rw-r--r-- | include/git2/remote.h | 210 | ||||
-rw-r--r-- | include/git2/sys/transport.h | 47 | ||||
-rw-r--r-- | src/fetch.c | 15 | ||||
-rw-r--r-- | src/fetch.h | 2 | ||||
-rw-r--r-- | src/net.c | 6 | ||||
-rw-r--r-- | src/net.h | 1 | ||||
-rw-r--r-- | src/proxy.c | 5 | ||||
-rw-r--r-- | src/proxy.h | 2 | ||||
-rw-r--r-- | src/push.c | 51 | ||||
-rw-r--r-- | src/push.h | 20 | ||||
-rw-r--r-- | src/remote.c | 429 | ||||
-rw-r--r-- | src/remote.h | 18 | ||||
-rw-r--r-- | src/transports/http.c | 50 | ||||
-rw-r--r-- | src/transports/local.c | 160 | ||||
-rw-r--r-- | src/transports/smart.c | 163 | ||||
-rw-r--r-- | src/transports/smart.h | 20 | ||||
-rw-r--r-- | src/transports/smart_protocol.c | 19 | ||||
-rw-r--r-- | src/transports/ssh.c | 18 | ||||
-rw-r--r-- | src/transports/winhttp.c | 36 | ||||
-rw-r--r-- | tests/network/url/redirect.c | 62 | ||||
-rw-r--r-- | tests/online/clone.c | 64 | ||||
-rw-r--r-- | tests/online/fetch.c | 66 |
24 files changed, 976 insertions, 568 deletions
diff --git a/ci/test.sh b/ci/test.sh index 4d6c41f76..0dc43aa87 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -83,7 +83,7 @@ echo "########################################################################## if [ -z "$SKIP_GITDAEMON_TESTS" ]; then echo "Starting git daemon..." GITDAEMON_DIR=`mktemp -d ${TMPDIR}/gitdaemon.XXXXXXXX` - git init --bare "${GITDAEMON_DIR}/test.git" + git init --bare "${GITDAEMON_DIR}/test.git" >/dev/null git daemon --listen=localhost --export-all --enable=receive-pack --base-path="${GITDAEMON_DIR}" "${GITDAEMON_DIR}" 2>/dev/null & GITDAEMON_PID=$! disown $GITDAEMON_PID @@ -101,8 +101,8 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then java -jar poxyproxy.jar --address 127.0.0.1 --port 8090 --credentials foo:bar --auth-type ntlm --quiet & fi -if [ -z "$SKIP_NTLM_TESTS" ]; then - curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.4.0/poxygit-0.4.0.jar >poxygit.jar +if [ -z "$SKIP_NTLM_TESTS" -o -z "$SKIP_ONLINE_TESTS" ]; then + curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.5.1/poxygit-0.5.1.jar >poxygit.jar echo "" echo "Starting HTTP server..." @@ -112,10 +112,11 @@ if [ -z "$SKIP_NTLM_TESTS" ]; then fi if [ -z "$SKIP_SSH_TESTS" ]; then + echo "" echo "Starting ssh daemon..." HOME=`mktemp -d ${TMPDIR}/home.XXXXXXXX` SSHD_DIR=`mktemp -d ${TMPDIR}/sshd.XXXXXXXX` - git init --bare "${SSHD_DIR}/test.git" + git init --bare "${SSHD_DIR}/test.git" >/dev/null cat >"${SSHD_DIR}/sshd_config" <<-EOF Port 2222 ListenAddress 0.0.0.0 @@ -188,9 +189,11 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then echo "## Running (online) tests" echo "##############################################################################" - export GITTEST_FLAKY_RETRY=5 + export GITTEST_REMOTE_REDIRECT_INITIAL="http://localhost:9000/initial-redirect/libgit2/TestGitRepository" + export GITTEST_REMOTE_REDIRECT_SUBSEQUENT="http://localhost:9000/subsequent-redirect/libgit2/TestGitRepository" run_test online - unset GITTEST_FLAKY_RETRY + unset GITTEST_REMOTE_REDIRECT_INITIAL + unset GITTEST_REMOTE_REDIRECT_SUBSEQUENT # Run the online tests that immutably change global state separately # to avoid polluting the test environment. @@ -231,9 +234,7 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then export GITTEST_REMOTE_PROXY_HOST="localhost:8090" export GITTEST_REMOTE_PROXY_USER="foo" export GITTEST_REMOTE_PROXY_PASS="bar" - export GITTEST_FLAKY_RETRY=5 run_test proxy - unset GITTEST_FLAKY_RETRY unset GITTEST_REMOTE_PROXY_HOST unset GITTEST_REMOTE_PROXY_USER unset GITTEST_REMOTE_PROXY_PASS diff --git a/docs/api-stability.md b/docs/api-stability.md new file mode 100644 index 000000000..a02e864d1 --- /dev/null +++ b/docs/api-stability.md @@ -0,0 +1,63 @@ +The maintainers of the libgit2 project believe that having a stable API +to program against is important for our users and the ecosystem - whether +you're building against the libgit2 C APIs directly, creating a wrapper to +a managed language, or programming against one of those managed wrappers +like LibGit2Sharp or Rugged. + +Our API stability considerations are: + +* Our standard API is considered stable through a major release. + + * We define our "standard API" to be anything included in the "git2.h" + header - in other words, anything defined in a header in the `git2` + directory. + + * APIs will maintain their signature and will not be removed within a + major release, but new APIs may be added. + + * Any APIs may be marked as deprecated within a major release, but will + not be removed until the next major release (at the earliest). You + may define `GIT_DEPRECATE_HARD` to produce compiler warnings if you + target these deprecated APIs. + + * We consider API compatibility to be against the C APIs. That means + that we may use macros to keep API compatibility - for example, if we + rename a structure from `git_widget_options` to `git_foobar_options` + then we would `#define git_widget_options git_foobar_options` to retain + API compatibility. Note that this does _not_ provide ABI compatibility. + +* Our systems API is only considered stable through a _minor_ release. + + * We define our "systems API" to be anything included in the `git2/sys` + directory. These are not "standard" APIs but are mechanisms to extend + libgit2 by adding new extensions - for example, a custom HTTPS transport, + TLS engine, or merge strategy. + + * Additionally, the cmake options and the resulting constants that it + produces to be "systems API". + + * Generally these mechanism are well defined and will not need significant + changes, but are considered a part of the library itself and may need + + * Systems API changes will be noted specially within a release's changelog. + +* Our ABI is only considered stable through a _minor_ release. + + * Our ABI consists of actual symbol names in the library, the function + signatures, and the actual layout of structures. These are only + stable within minor releases, they are not stable within major releases + (yet). + + * Since many FFIs use ABIs directly (for example, .NET P/Invoke or Rust), + this instability is unfortunate. + + * In a future major release, we will begin providing ABI stability + throughout the major release cycle. + + * ABI changes will be noted specially within a release's changelog. + +* Point releases are _generally_ only for bugfixes, and generally do _not_ + include new features. This means that point releases generally do _not_ + include new APIs. Point releases will never break API, systems API or + ABI compatibility. + diff --git a/include/git2/remote.h b/include/git2/remote.h index 088f528a6..162b076ea 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -42,6 +42,30 @@ GIT_EXTERN(int) git_remote_create( const char *url); /** + * Remote redirection settings; whether redirects to another host + * are permitted. By default, git will follow a redirect on the + * initial request (`/info/refs`), but not subsequent requests. + */ +typedef enum { + /** + * Do not follow any off-site redirects at any stage of + * the fetch or push. + */ + GIT_REMOTE_REDIRECT_NONE = (1 << 0), + + /** + * Allow off-site redirects only upon the initial request. + * This is the default. + */ + GIT_REMOTE_REDIRECT_INITIAL = (1 << 1), + + /** + * Allow redirects at any stage in the fetch or push. + */ + GIT_REMOTE_REDIRECT_ALL = (1 << 2) +} git_remote_redirect_t; + +/** * Remote creation options flags */ typedef enum { @@ -345,23 +369,6 @@ GIT_EXTERN(size_t) git_remote_refspec_count(const git_remote *remote); GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, size_t n); /** - * Open a connection to a remote - * - * The transport is selected based on the URL. The direction argument - * is due to a limitation of the git protocol (over TCP or SSH) which - * starts up a specific binary which can only do the one or the other. - * - * @param remote the remote to connect to - * @param direction GIT_DIRECTION_FETCH if you want to fetch or - * GIT_DIRECTION_PUSH if you want to push - * @param callbacks the callbacks to use for this connection - * @param proxy_opts proxy settings - * @param custom_headers extra HTTP headers to use in this connection - * @return 0 or an error code - */ -GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy_opts, const git_strarray *custom_headers); - -/** * Get the remote repository's reference advertisement list * * Get the list of references with which the server responds to a new @@ -735,6 +742,13 @@ typedef struct { git_proxy_options proxy_opts; /** + * Whether to allow off-site redirects. If this is not + * specified, the `http.followRedirects` configuration setting + * will be consulted. + */ + git_remote_redirect_t follow_redirects; + + /** * Extra headers for this fetch operation */ git_strarray custom_headers; @@ -786,6 +800,13 @@ typedef struct { git_proxy_options proxy_opts; /** + * Whether to allow off-site redirects. If this is not + * specified, the `http.followRedirects` configuration setting + * will be consulted. + */ + git_remote_redirect_t follow_redirects; + + /** * Extra headers for this push operation */ git_strarray custom_headers; @@ -809,7 +830,100 @@ GIT_EXTERN(int) git_push_options_init( unsigned int version); /** - * Download and index the packfile + * Remote creation options structure + * + * Initialize with `GIT_REMOTE_CREATE_OPTIONS_INIT`. Alternatively, you can + * use `git_remote_create_options_init`. + * + */ +typedef struct { + unsigned int version; + + /** Callbacks to use for this connection */ + git_remote_callbacks callbacks; + + /** HTTP Proxy settings */ + git_proxy_options proxy_opts; + + /** + * Whether to allow off-site redirects. If this is not + * specified, the `http.followRedirects` configuration setting + * will be consulted. + */ + git_remote_redirect_t follow_redirects; + + /** Extra HTTP headers to use in this connection */ + git_strarray custom_headers; +} git_remote_connect_options; + +#define GIT_REMOTE_CONNECT_OPTIONS_VERSION 1 +#define GIT_REMOTE_CONNECT_OPTIONS_INIT { \ + GIT_REMOTE_CONNECT_OPTIONS_VERSION, \ + GIT_REMOTE_CALLBACKS_INIT, \ + GIT_PROXY_OPTIONS_INIT } + +/** + * Initialize git_remote_connect_options structure. + * + * Initializes a `git_remote_connect_options` with default values. + * Equivalent to creating an instance with + * `GIT_REMOTE_CONNECT_OPTIONS_INIT`. + * + * @param opts The `git_remote_connect_options` struct to initialize. + * @param version The struct version; pass `GIT_REMOTE_CONNECT_OPTIONS_VERSION`. + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_remote_connect_options_init( + git_remote_connect_options *opts, + unsigned int version); + +/** + * Open a connection to a remote. + * + * The transport is selected based on the URL; the direction argument + * is due to a limitation of the git protocol which starts up a + * specific binary which can only do the one or the other. + * + * @param remote the remote to connect to + * @param direction GIT_DIRECTION_FETCH if you want to fetch or + * GIT_DIRECTION_PUSH if you want to push + * @param callbacks the callbacks to use for this connection + * @param proxy_opts proxy settings + * @param custom_headers extra HTTP headers to use in this connection + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_connect( + git_remote *remote, + git_direction direction, + const git_remote_callbacks *callbacks, + const git_proxy_options *proxy_opts, + const git_strarray *custom_headers); + +/** + * Open a connection to a remote with extended options. + * + * The transport is selected based on the URL; the direction argument + * is due to a limitation of the git protocol which starts up a + * specific binary which can only do the one or the other. + * + * The given options structure will form the defaults for connection + * options and callback setup. Callers may override these defaults + * by specifying `git_fetch_options` or `git_push_options` in + * subsequent calls. + * + * @param remote the remote to connect to + * @param direction GIT_DIRECTION_FETCH if you want to fetch or + * GIT_DIRECTION_PUSH if you want to push + * @param opts the remote connection options + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_connect_ext( + git_remote *remote, + git_direction direction, + const git_remote_connect_options *opts); + +/** + * Download and index the packfile. * * Connect to the remote if it hasn't been done yet, negotiate with * the remote git which objects are missing, download and index the @@ -818,19 +932,31 @@ GIT_EXTERN(int) git_push_options_init( * The .idx file will be created and both it and the packfile with be * renamed to their final name. * + * If options are specified and this remote is already connected then + * the existing remote connection options will be discarded and the + * remote will now use the new options. + * * @param remote the remote * @param refspecs the refspecs to use for this negotiation and * download. Use NULL or an empty array to use the base refspecs - * @param opts the options to use for this fetch + * @param opts the options to use for this fetch or NULL * @return 0 or an error code */ - GIT_EXTERN(int) git_remote_download(git_remote *remote, const git_strarray *refspecs, const git_fetch_options *opts); + GIT_EXTERN(int) git_remote_download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts); /** * Create a packfile and send it to the server * * Connect to the remote if it hasn't been done yet, negotiate with - * the remote git which objects are missing, create a packfile with the missing objects and send it. + * the remote git which objects are missing, create a packfile with + * the missing objects and send it. + * + * If options are specified and this remote is already connected then + * the existing remote connection options will be discarded and the + * remote will now use the new options. * * @param remote the remote * @param refspecs the refspecs to use for this negotiation and @@ -838,17 +964,23 @@ GIT_EXTERN(int) git_push_options_init( * @param opts the options to use for this push * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_upload(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts); +GIT_EXTERN(int) git_remote_upload( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts); /** - * Update the tips to the new state + * Update the tips to the new state. + * + * If callbacks are not specified then the callbacks specified to + * `git_remote_connect` will be used (if it was called). * * @param remote the remote to update * @param reflog_message The message to insert into the reflogs. If * NULL and fetching, the default is "fetch <name>", where <name> is * the name of the remote (or its url, for in-memory remotes). This * parameter is ignored when pushing. - * @param callbacks pointer to the callback structure to use + * @param callbacks pointer to the callback structure to use or NULL * @param update_fetchhead whether to write to FETCH_HEAD. Pass 1 to behave like git. * @param download_tags what the behaviour for downloading tags is for this fetch. This is * ignored for push. This must be the same value passed to `git_remote_download()`. @@ -862,15 +994,19 @@ GIT_EXTERN(int) git_remote_update_tips( const char *reflog_message); /** - * Download new data and update tips + * Download new data and update tips. * * Convenience function to connect to a remote, download the data, * disconnect and update the remote-tracking branches. * + * If options are specified and this remote is already connected then + * the existing remote connection options will be discarded and the + * remote will now use the new options. + * * @param remote the remote to fetch from * @param refspecs the refspecs to use for this fetch. Pass NULL or an * empty array to use the base refspecs. - * @param opts options to use for this fetch + * @param opts options to use for this fetch or NULL * @param reflog_message The message to insert into the reflogs. If NULL, the * default is "fetch" * @return 0 or an error code @@ -882,27 +1018,35 @@ GIT_EXTERN(int) git_remote_fetch( const char *reflog_message); /** - * Prune tracking refs that are no longer present on remote + * Prune tracking refs that are no longer present on remote. + * + * If callbacks are not specified then the callbacks specified to + * `git_remote_connect` will be used (if it was called). * * @param remote the remote to prune * @param callbacks callbacks to use for this prune * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks); +GIT_EXTERN(int) git_remote_prune( + git_remote *remote, + const git_remote_callbacks *callbacks); /** - * Perform a push + * Perform a push. * - * Peform all the steps from a push. + * If options are specified and this remote is already connected then + * the existing remote connection options will be discarded and the + * remote will now use the new options. * * @param remote the remote to push to * @param refspecs the refspecs to use for pushing. If NULL or an empty * array, the configured refspecs will be used * @param opts options to use for this push */ -GIT_EXTERN(int) git_remote_push(git_remote *remote, - const git_strarray *refspecs, - const git_push_options *opts); +GIT_EXTERN(int) git_remote_push( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts); /** * Get the statistics structure that is filled in by the fetch operation. diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index 76003ee84..89e687649 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -24,31 +24,9 @@ GIT_BEGIN_DECL -/** - * Flags to pass to transport - * - * Currently unused. - */ -typedef enum { - GIT_TRANSPORTFLAGS_NONE = 0 -} git_transport_flags_t; - struct git_transport { unsigned int version; /**< The struct version */ - /** Set progress and error callbacks */ - int GIT_CALLBACK(set_callbacks)( - git_transport *transport, - git_transport_message_cb progress_cb, - git_transport_message_cb error_cb, - git_transport_certificate_check_cb certificate_check_cb, - void *payload); - - /** Set custom headers for HTTP requests */ - int GIT_CALLBACK(set_custom_headers)( - git_transport *transport, - const git_strarray *custom_headers); - /** * Connect the transport to the remote repository, using the given * direction. @@ -56,11 +34,17 @@ struct git_transport { int GIT_CALLBACK(connect)( git_transport *transport, const char *url, - git_credential_acquire_cb cred_acquire_cb, - void *cred_acquire_payload, - const git_proxy_options *proxy_opts, int direction, - int flags); + const git_remote_connect_options *connect_opts); + + /** + * Resets the connect options for the given transport. This + * is useful for updating settings or callbacks for an already + * connected transport. + */ + int GIT_CALLBACK(set_connect_opts)( + git_transport *transport, + const git_remote_connect_options *connect_opts); /** * Get the list of available references in the remote repository. @@ -75,7 +59,9 @@ struct git_transport { git_transport *transport); /** Executes the push whose context is in the git_push object. */ - int GIT_CALLBACK(push)(git_transport *transport, git_push *push, const git_remote_callbacks *callbacks); + int GIT_CALLBACK(push)( + git_transport *transport, + git_push *push); /** * Negotiate a fetch with the remote repository. @@ -99,16 +85,11 @@ struct git_transport { int GIT_CALLBACK(download_pack)( git_transport *transport, git_repository *repo, - git_indexer_progress *stats, - git_indexer_progress_cb progress_cb, - void *progress_payload); + git_indexer_progress *stats); /** Checks to see if the transport is connected */ int GIT_CALLBACK(is_connected)(git_transport *transport); - /** Reads the flags value previously passed into connect() */ - int GIT_CALLBACK(read_flags)(git_transport *transport, int *flags); - /** Cancels any outstanding transport operation */ void GIT_CALLBACK(cancel)(git_transport *transport); diff --git a/src/fetch.c b/src/fetch.c index dedbb54fa..117c8f26f 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -87,10 +87,10 @@ static int filter_wants(git_remote *remote, const git_fetch_options *opts) goto cleanup; } - if (git_repository_odb__weakptr(&odb, remote->repo) < 0) + if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) goto cleanup; - if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) + if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0) goto cleanup; for (i = 0; i < heads_len; i++) { @@ -134,21 +134,14 @@ int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts) remote->refs.length); } -int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks) +int git_fetch_download_pack(git_remote *remote) { git_transport *t = remote->transport; - git_indexer_progress_cb progress = NULL; - void *payload = NULL; if (!remote->need_pack) return 0; - if (callbacks) { - progress = callbacks->transfer_progress; - payload = callbacks->payload; - } - - return t->download_pack(t, remote->repo, &remote->stats, progress, payload); + return t->download_pack(t, remote->repo, &remote->stats); } int git_fetch_options_init(git_fetch_options *opts, unsigned int version) diff --git a/src/fetch.h b/src/fetch.h index 1c75af9c3..10b6731f0 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -15,7 +15,7 @@ int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts); -int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks); +int git_fetch_download_pack(git_remote *remote); int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); @@ -315,6 +315,7 @@ static void remove_service_suffix( int git_net_url_apply_redirect( git_net_url *url, const char *redirect_location, + bool allow_offsite, const char *service_suffix) { git_net_url tmp = GIT_NET_URL_INIT; @@ -339,8 +340,8 @@ int git_net_url_apply_redirect( /* Validate that this is a legal redirection */ if (original->scheme && - strcmp(original->scheme, tmp.scheme) != 0 && - strcmp(tmp.scheme, "https") != 0) { + strcmp(original->scheme, tmp.scheme) != 0 && + strcmp(tmp.scheme, "https") != 0) { git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", original->scheme, tmp.scheme); @@ -349,6 +350,7 @@ int git_net_url_apply_redirect( } if (original->host && + !allow_offsite && git__strcasecmp(original->host, tmp.host) != 0) { git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", original->host, tmp.host); @@ -46,6 +46,7 @@ extern bool git_net_url_is_ipv6(git_net_url *url); extern int git_net_url_apply_redirect( git_net_url *url, const char *redirect_location, + bool allow_offsite, const char *service_suffix); /** Swaps the contents of one URL for another. */ diff --git a/src/proxy.c b/src/proxy.c index 78a4c5535..ef91ad6ea 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -39,8 +39,11 @@ int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) return 0; } -void git_proxy_options_clear(git_proxy_options *opts) +void git_proxy_options_dispose(git_proxy_options *opts) { + if (!opts) + return; + git__free((char *) opts->url); opts->url = NULL; } diff --git a/src/proxy.h b/src/proxy.h index f8b5c4b50..7c0ab598d 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -12,6 +12,6 @@ #include "git2/proxy.h" extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); -extern void git_proxy_options_clear(git_proxy_options *opts); +extern void git_proxy_options_dispose(git_proxy_options *opts); #endif diff --git a/src/push.c b/src/push.c index 3bf7ba5d9..da8aebadd 100644 --- a/src/push.c +++ b/src/push.c @@ -29,19 +29,26 @@ static int push_status_ref_cmp(const void *a, const void *b) return strcmp(push_status_a->ref, push_status_b->ref); } -int git_push_new(git_push **out, git_remote *remote) +int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts) { git_push *p; *out = NULL; + GIT_ERROR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); + p = git__calloc(1, sizeof(*p)); GIT_ERROR_CHECK_ALLOC(p); p->repo = remote->repo; p->remote = remote; p->report_status = 1; - p->pb_parallelism = 1; + p->pb_parallelism = opts ? opts->pb_parallelism : 1; + + if (opts) { + GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + memcpy(&p->callbacks, &opts->callbacks, sizeof(git_remote_callbacks)); + } if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) { git__free(p); @@ -65,20 +72,6 @@ int git_push_new(git_push **out, git_remote *remote) return 0; } -int git_push_set_options(git_push *push, const git_push_options *opts) -{ - if (!push || !opts) - return -1; - - GIT_ERROR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); - - push->pb_parallelism = opts->pb_parallelism; - push->connection.custom_headers = &opts->custom_headers; - push->connection.proxy = &opts->proxy_opts; - - return 0; -} - static void free_refspec(push_spec *spec) { if (spec == NULL) @@ -291,7 +284,7 @@ static int queue_objects(git_push *push) if (git_oid_equal(&spec->loid, &spec->roid)) continue; /* up-to-date */ - if (git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid) < 0) + if ((error = git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid)) < 0) goto on_error; if (type == GIT_OBJECT_TAG) { @@ -301,19 +294,19 @@ static int queue_objects(git_push *push) goto on_error; if (git_object_type(target) == GIT_OBJECT_COMMIT) { - if (git_revwalk_push(rw, git_object_id(target)) < 0) { + if ((error = git_revwalk_push(rw, git_object_id(target))) < 0) { git_object_free(target); goto on_error; } } else { - if (git_packbuilder_insert( - push->pb, git_object_id(target), NULL) < 0) { + if ((error = git_packbuilder_insert( + push->pb, git_object_id(target), NULL)) < 0) { git_object_free(target); goto on_error; } } git_object_free(target); - } else if (git_revwalk_push(rw, &spec->loid) < 0) + } else if ((error = git_revwalk_push(rw, &spec->loid)) < 0) goto on_error; if (!spec->refspec.force) { @@ -411,10 +404,11 @@ static int calculate_work(git_push *push) return 0; } -static int do_push(git_push *push, const git_remote_callbacks *callbacks) +static int do_push(git_push *push) { int error = 0; git_transport *transport = push->remote->transport; + git_remote_callbacks *callbacks = &push->callbacks; if (!transport->push) { git_error_set(GIT_ERROR_NET, "remote transport doesn't support push"); @@ -446,7 +440,7 @@ static int do_push(git_push *push, const git_remote_callbacks *callbacks) goto on_error; if ((error = queue_objects(push)) < 0 || - (error = transport->push(transport, push, callbacks)) < 0) + (error = transport->push(transport, push)) < 0) goto on_error; on_error: @@ -472,16 +466,17 @@ static int filter_refs(git_remote *remote) return 0; } -int git_push_finish(git_push *push, const git_remote_callbacks *callbacks) +int git_push_finish(git_push *push) { int error; - if (!git_remote_connected(push->remote) && - (error = git_remote__connect(push->remote, GIT_DIRECTION_PUSH, callbacks, &push->connection)) < 0) - return error; + if (!git_remote_connected(push->remote)) { + git_error_set(GIT_ERROR_NET, "remote is disconnected"); + return -1; + } if ((error = filter_refs(push->remote)) < 0 || - (error = do_push(push, callbacks)) < 0) + (error = do_push(push)) < 0) return error; if (!push->unpack_ok) { diff --git a/src/push.h b/src/push.h index 867563940..fc72e845e 100644 --- a/src/push.h +++ b/src/push.h @@ -41,7 +41,7 @@ struct git_push { /* options */ unsigned pb_parallelism; - git_remote_connection_opts connection; + git_remote_callbacks callbacks; }; /** @@ -56,22 +56,11 @@ void git_push_status_free(push_status *status); * * @param out New push object * @param remote Remote instance + * @param opts Push options or NULL * * @return 0 or an error code */ -int git_push_new(git_push **out, git_remote *remote); - -/** - * Set options on a push object - * - * @param push The push object - * @param opts The options to set on the push object - * - * @return 0 or an error code - */ -int git_push_set_options( - git_push *push, - const git_push_options *opts); +int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts); /** * Add a refspec to be pushed @@ -104,11 +93,10 @@ int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks); * order to find out which updates were accepted or rejected. * * @param push The push object - * @param callbacks the callbacks to use for this connection * * @return 0 or an error code */ -int git_push_finish(git_push *push, const git_remote_callbacks *callbacks); +int git_push_finish(git_push *push); /** * Invoke callback `cb' on each status entry 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; } diff --git a/src/remote.h b/src/remote.h index 77eefdf40..3cf0fd953 100644 --- a/src/remote.h +++ b/src/remote.h @@ -37,15 +37,6 @@ struct git_remote { int passed_refspecs; }; -typedef struct git_remote_connection_opts { - const git_strarray *custom_headers; - const git_proxy_options *proxy; -} git_remote_connection_opts; - -#define GIT_REMOTE_CONNECTION_OPTIONS_INIT { NULL, NULL } - -int git_remote__connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_remote_connection_opts *conn); - int git_remote__urlfordirection(git_str *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks); int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url); @@ -54,4 +45,13 @@ git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *re int git_remote__default_branch(git_str *out, git_remote *remote); +int git_remote_connect_options_dup( + git_remote_connect_options *dst, + const git_remote_connect_options *src); +int git_remote_connect_options_normalize( + git_remote_connect_options *dst, + git_repository *repo, + const git_remote_connect_options *src); +void git_remote_connect_options_dispose(git_remote_connect_options *opts); + #endif diff --git a/src/transports/http.c b/src/transports/http.c index adcb1ac43..7db5582ca 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -38,7 +38,8 @@ typedef struct { const char *url; const char *request_type; const char *response_type; - unsigned chunked : 1; + unsigned int initial : 1, + chunked : 1; } http_service; typedef struct { @@ -70,24 +71,28 @@ static const http_service upload_pack_ls_service = { GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack", NULL, "application/x-git-upload-pack-advertisement", + 1, 0 }; static const http_service upload_pack_service = { GIT_HTTP_METHOD_POST, "/git-upload-pack", "application/x-git-upload-pack-request", "application/x-git-upload-pack-result", + 0, 0 }; static const http_service receive_pack_ls_service = { GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack", NULL, "application/x-git-receive-pack-advertisement", + 1, 0 }; static const http_service receive_pack_service = { GIT_HTTP_METHOD_POST, "/git-receive-pack", "application/x-git-receive-pack-request", "application/x-git-receive-pack-result", + 0, 1 }; @@ -174,6 +179,7 @@ GIT_INLINE(int) handle_remote_auth( git_http_response *response) { http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; if (response->server_auth_credtypes == 0) { git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); @@ -187,8 +193,8 @@ GIT_INLINE(int) handle_remote_auth( transport->owner->url, response->server_auth_schemetypes, response->server_auth_credtypes, - transport->owner->cred_acquire_cb, - transport->owner->cred_acquire_payload); + connect_opts->callbacks.credentials, + connect_opts->callbacks.payload); } GIT_INLINE(int) handle_proxy_auth( @@ -196,6 +202,7 @@ GIT_INLINE(int) handle_proxy_auth( git_http_response *response) { http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; if (response->proxy_auth_credtypes == 0) { git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); @@ -206,13 +213,26 @@ GIT_INLINE(int) handle_proxy_auth( return handle_auth( &transport->proxy, SERVER_TYPE_PROXY, - transport->owner->proxy.url, + connect_opts->proxy_opts.url, response->server_auth_schemetypes, response->proxy_auth_credtypes, - transport->owner->proxy.credentials, - transport->owner->proxy.payload); + connect_opts->proxy_opts.credentials, + connect_opts->proxy_opts.payload); } +static bool allow_redirect(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + + switch (transport->owner->connect_opts.follow_redirects) { + case GIT_REMOTE_REDIRECT_INITIAL: + return (stream->service->initial == 1); + case GIT_REMOTE_REDIRECT_ALL: + return true; + default: + return false; + } +} static int handle_response( bool *complete, @@ -231,7 +251,7 @@ static int handle_response( return -1; } - if (git_net_url_apply_redirect(&transport->server.url, response->location, stream->service->url) < 0) { + if (git_net_url_apply_redirect(&transport->server.url, response->location, allow_redirect(stream), stream->service->url) < 0) { return -1; } @@ -286,6 +306,7 @@ static int lookup_proxy( bool *out_use, http_subtransport *transport) { + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; const char *proxy; git_remote *remote; char *config = NULL; @@ -294,9 +315,9 @@ static int lookup_proxy( *out_use = false; git_net_url_dispose(&transport->proxy.url); - switch (transport->owner->proxy.type) { + switch (connect_opts->proxy_opts.type) { case GIT_PROXY_SPECIFIED: - proxy = transport->owner->proxy.url; + proxy = connect_opts->proxy_opts.url; break; case GIT_PROXY_AUTO: @@ -345,7 +366,7 @@ static int generate_request( request->credentials = transport->server.cred; request->proxy = use_proxy ? &transport->proxy.url : NULL; request->proxy_credentials = transport->proxy.cred; - request->custom_headers = &transport->owner->custom_headers; + request->custom_headers = &transport->owner->connect_opts.custom_headers; if (stream->service->method == GIT_HTTP_METHOD_POST) { request->chunked = stream->service->chunked; @@ -633,6 +654,7 @@ static int http_action( git_smart_service_t action) { http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; http_stream *stream; const http_service *service; int error; @@ -664,10 +686,10 @@ static int http_action( if (!transport->http_client) { git_http_client_options opts = {0}; - opts.server_certificate_check_cb = transport->owner->certificate_check_cb; - opts.server_certificate_check_payload = transport->owner->message_cb_payload; - opts.proxy_certificate_check_cb = transport->owner->proxy.certificate_check; - opts.proxy_certificate_check_payload = transport->owner->proxy.payload; + opts.server_certificate_check_cb = connect_opts->callbacks.certificate_check; + opts.server_certificate_check_payload = connect_opts->callbacks.payload; + opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check; + opts.proxy_certificate_check_payload = connect_opts->proxy_opts.payload; if (git_http_client_new(&transport->http_client, &opts) < 0) return -1; diff --git a/src/transports/local.c b/src/transports/local.c index 0983914b1..86524edf1 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -34,12 +34,9 @@ typedef struct { git_remote *owner; char *url; int direction; - int flags; git_atomic32 cancelled; git_repository *repo; - git_transport_message_cb progress_cb; - git_transport_message_cb error_cb; - void *message_cb_payload; + git_remote_connect_options connect_opts; git_vector refs; unsigned connected : 1, have_refs : 1; @@ -200,30 +197,26 @@ on_error: static int local_connect( git_transport *transport, const char *url, - git_credential_acquire_cb cred_acquire_cb, - void *cred_acquire_payload, - const git_proxy_options *proxy, - int direction, int flags) + int direction, + const git_remote_connect_options *connect_opts) { git_repository *repo; int error; - transport_local *t = (transport_local *) transport; + transport_local *t = (transport_local *)transport; const char *path; git_str buf = GIT_STR_INIT; - GIT_UNUSED(cred_acquire_cb); - GIT_UNUSED(cred_acquire_payload); - GIT_UNUSED(proxy); - if (t->connected) return 0; + if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) + return -1; + free_heads(&t->refs); t->url = git__strdup(url); GIT_ERROR_CHECK_ALLOC(t->url); t->direction = direction; - t->flags = flags; /* 'url' may be a url or path; convert to a path */ if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) { @@ -249,6 +242,20 @@ static int local_connect( return 0; } +static int local_set_connect_opts( + git_transport *transport, + const git_remote_connect_options *connect_opts) +{ + transport_local *t = (transport_local *)transport; + + if (!t->connected) { + git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); + return -1; + } + + return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts); +} + static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) { transport_local *t = (transport_local *)transport; @@ -337,10 +344,10 @@ static int transfer_to_push_transfer(const git_indexer_progress *stats, void *pa static int local_push( git_transport *transport, - git_push *push, - const git_remote_callbacks *cbs) + git_push *push) { transport_local *t = (transport_local *)transport; + git_remote_callbacks *cbs = &t->connect_opts.callbacks; git_repository *remote_repo = NULL; push_spec *spec; char *url = NULL; @@ -349,8 +356,6 @@ static int local_push( int error; size_t j; - GIT_UNUSED(cbs); - /* 'push->remote->url' may be a url or path; convert to a path */ if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) { git_str_dispose(&buf); @@ -440,12 +445,11 @@ static int local_push( } if (push->specs.length) { - int flags = t->flags; url = git__strdup(t->url); if (!url || t->parent.close(&t->parent) < 0 || t->parent.connect(&t->parent, url, - NULL, NULL, NULL, GIT_DIRECTION_PUSH, flags)) + GIT_DIRECTION_PUSH, NULL)) goto on_error; } @@ -482,7 +486,7 @@ static int local_counting(int stage, unsigned int current, unsigned int total, v transport_local *t = payload; int error; - if (!t->progress_cb) + if (!t->connect_opts.callbacks.sideband_progress) return 0; if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) { @@ -500,9 +504,19 @@ static int local_counting(int stage, unsigned int current, unsigned int total, v if (git_str_oom(&progress_info)) return -1; - error = t->progress_cb(git_str_cstr(&progress_info), (int)git_str_len(&progress_info), t->message_cb_payload); - git_str_dispose(&progress_info); + if (progress_info.size > INT_MAX) { + git_error_set(GIT_ERROR_NET, "remote sent overly large progress data"); + git_str_dispose(&progress_info); + return -1; + } + + + error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload); + git_str_dispose(&progress_info); return error; } @@ -532,9 +546,7 @@ static int foreach_reference_cb(git_reference *reference, void *payload) static int local_download_pack( git_transport *transport, git_repository *repo, - git_indexer_progress *stats, - git_indexer_progress_cb progress_cb, - void *progress_payload) + git_indexer_progress *stats) { transport_local *t = (transport_local*)transport; git_revwalk *walk = NULL; @@ -545,9 +557,11 @@ static int local_download_pack( git_odb_writepack *writepack = NULL; git_odb *odb = NULL; git_str progress_info = GIT_STR_INIT; + foreach_data data = {0}; if ((error = git_revwalk_new(&walk, t->repo)) < 0) goto cleanup; + git_revwalk_sorting(walk, GIT_SORT_TIME); if ((error = git_packbuilder_new(&pack, t->repo)) < 0) @@ -583,44 +597,56 @@ static int local_download_pack( if ((error = git_packbuilder_insert_walk(pack, walk))) goto cleanup; - if ((error = git_str_printf(&progress_info, counting_objects_fmt, git_packbuilder_object_count(pack))) < 0) - goto cleanup; - - if (t->progress_cb && - (error = t->progress_cb(git_str_cstr(&progress_info), (int)git_str_len(&progress_info), t->message_cb_payload)) < 0) - goto cleanup; + if (t->connect_opts.callbacks.sideband_progress) { + if ((error = git_str_printf( + &progress_info, + counting_objects_fmt, + git_packbuilder_object_count(pack))) < 0 || + (error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + } /* Walk the objects, building a packfile */ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) goto cleanup; /* One last one with the newline */ - git_str_clear(&progress_info); - git_str_printf(&progress_info, counting_objects_fmt, git_packbuilder_object_count(pack)); - if ((error = git_str_putc(&progress_info, '\n')) < 0) - goto cleanup; - - if (t->progress_cb && - (error = t->progress_cb(git_str_cstr(&progress_info), (int)git_str_len(&progress_info), t->message_cb_payload)) < 0) - goto cleanup; + if (t->connect_opts.callbacks.sideband_progress) { + git_str_clear(&progress_info); + + if ((error = git_str_printf( + &progress_info, + counting_objects_fmt, + git_packbuilder_object_count(pack))) < 0 || + (error = git_str_putc(&progress_info, '\n')) < 0 || + (error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + } - if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0) + if ((error = git_odb_write_pack( + &writepack, + odb, + t->connect_opts.callbacks.transfer_progress, + t->connect_opts.callbacks.payload)) < 0) goto cleanup; /* Write the data to the ODB */ - { - foreach_data data = {0}; - data.stats = stats; - data.progress_cb = progress_cb; - data.progress_payload = progress_payload; - data.writepack = writepack; + data.stats = stats; + data.progress_cb = t->connect_opts.callbacks.transfer_progress; + data.progress_payload = t->connect_opts.callbacks.payload; + data.writepack = writepack; - /* autodetect */ - git_packbuilder_set_threads(pack, 0); + /* autodetect */ + git_packbuilder_set_threads(pack, 0); - if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) - goto cleanup; - } + if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) + goto cleanup; error = writepack->commit(writepack, stats); @@ -632,24 +658,6 @@ cleanup: return error; } -static int local_set_callbacks( - git_transport *transport, - git_transport_message_cb progress_cb, - git_transport_message_cb error_cb, - git_transport_certificate_check_cb certificate_check_cb, - void *message_cb_payload) -{ - transport_local *t = (transport_local *)transport; - - GIT_UNUSED(certificate_check_cb); - - t->progress_cb = progress_cb; - t->error_cb = error_cb; - t->message_cb_payload = message_cb_payload; - - return 0; -} - static int local_is_connected(git_transport *transport) { transport_local *t = (transport_local *)transport; @@ -657,15 +665,6 @@ static int local_is_connected(git_transport *transport) return t->connected; } -static int local_read_flags(git_transport *transport, int *flags) -{ - transport_local *t = (transport_local *)transport; - - *flags = t->flags; - - return 0; -} - static void local_cancel(git_transport *transport) { transport_local *t = (transport_local *)transport; @@ -720,8 +719,8 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param) GIT_ERROR_CHECK_ALLOC(t); t->parent.version = GIT_TRANSPORT_VERSION; - t->parent.set_callbacks = local_set_callbacks; t->parent.connect = local_connect; + t->parent.set_connect_opts = local_set_connect_opts; t->parent.negotiate_fetch = local_negotiate_fetch; t->parent.download_pack = local_download_pack; t->parent.push = local_push; @@ -729,7 +728,6 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param) t->parent.free = local_free; t->parent.ls = local_ls; t->parent.is_connected = local_is_connected; - t->parent.read_flags = local_read_flags; t->parent.cancel = local_cancel; if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) { diff --git a/src/transports/smart.c b/src/transports/smart.c index fe024de2f..e76c18fc3 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -56,101 +56,6 @@ GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransp return 0; } -static int git_smart__set_callbacks( - git_transport *transport, - git_transport_message_cb progress_cb, - git_transport_message_cb error_cb, - git_transport_certificate_check_cb certificate_check_cb, - void *message_cb_payload) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - t->progress_cb = progress_cb; - t->error_cb = error_cb; - t->certificate_check_cb = certificate_check_cb; - t->message_cb_payload = message_cb_payload; - - return 0; -} - -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 */ - c = strchr(http_header, '\r'); - if (c) - return true; - c = strchr(http_header, '\n'); - if (c) - return true; - - /* Require a header name followed by : */ - name_len = http_header_name_length(http_header); - if (name_len < 1) - return true; - - return false; -} - -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) -{ - 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 git_smart__set_custom_headers( - git_transport *transport, - const git_strarray *custom_headers) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - size_t i; - - if (t->custom_headers.count) - git_strarray_dispose(&t->custom_headers); - - if (!custom_headers) - return 0; - - 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 git_strarray_copy(&t->custom_headers, custom_headers); -} - int git_smart__update_heads(transport_smart *t, git_vector *symrefs) { size_t i; @@ -206,11 +111,8 @@ static void free_symrefs(git_vector *symrefs) static int git_smart__connect( git_transport *transport, const char *url, - git_credential_acquire_cb cred_acquire_cb, - void *cred_acquire_payload, - const git_proxy_options *proxy, int direction, - int flags) + const git_remote_connect_options *connect_opts) { transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); git_smart_subtransport_stream *stream; @@ -223,24 +125,19 @@ static int git_smart__connect( if (git_smart__reset_stream(t, true) < 0) return -1; + if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) + return -1; + t->url = git__strdup(url); GIT_ERROR_CHECK_ALLOC(t->url); - git_proxy_options_clear(&t->proxy); - - if (git_proxy_options_dup(&t->proxy, proxy) < 0) - return -1; - t->direction = direction; - t->flags = flags; - t->cred_acquire_cb = cred_acquire_cb; - t->cred_acquire_payload = cred_acquire_payload; - if (GIT_DIRECTION_FETCH == t->direction) + if (GIT_DIRECTION_FETCH == t->direction) { service = GIT_SERVICE_UPLOADPACK_LS; - else if (GIT_DIRECTION_PUSH == t->direction) + } else if (GIT_DIRECTION_PUSH == t->direction) { service = GIT_SERVICE_RECEIVEPACK_LS; - else { + } else { git_error_set(GIT_ERROR_NET, "invalid direction"); return -1; } @@ -315,6 +212,20 @@ cleanup: return error; } +static int git_smart__set_connect_opts( + git_transport *transport, + const git_remote_connect_options *opts) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + if (!t->connected) { + git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); + return -1; + } + + return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts); +} + static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport) { transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); @@ -401,15 +312,6 @@ static int git_smart__is_connected(git_transport *transport) return t->connected; } -static int git_smart__read_flags(git_transport *transport, int *flags) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - *flags = t->flags; - - return 0; -} - static int git_smart__close(git_transport *transport) { transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); @@ -465,9 +367,8 @@ static void git_smart__free(git_transport *transport) git_pkt_free(p); git_vector_free(refs); - git__free((char *)t->proxy.url); - git_strarray_dispose(&t->custom_headers); + git_remote_connect_options_dispose(&t->connect_opts); git__free(t); } @@ -482,34 +383,30 @@ static int ref_name_cmp(const void *a, const void *b) int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) { transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_remote_connect_options *connect_opts = &t->connect_opts; GIT_ASSERT_ARG(transport); GIT_ASSERT_ARG(cert); GIT_ASSERT_ARG(hostname); - if (!t->certificate_check_cb) + if (!connect_opts->callbacks.certificate_check) return GIT_PASSTHROUGH; - return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload); + return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload); } int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods) { transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_remote_connect_options *connect_opts = &t->connect_opts; GIT_ASSERT_ARG(out); GIT_ASSERT_ARG(transport); - if (!t->cred_acquire_cb) + if (!connect_opts->callbacks.credentials) return GIT_PASSTHROUGH; - return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload); -} - -int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - return git_proxy_options_dup(out, &t->proxy); + return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload); } int git_transport_smart(git_transport **out, git_remote *owner, void *param) @@ -524,9 +421,8 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) GIT_ERROR_CHECK_ALLOC(t); t->parent.version = GIT_TRANSPORT_VERSION; - t->parent.set_callbacks = git_smart__set_callbacks; - t->parent.set_custom_headers = git_smart__set_custom_headers; t->parent.connect = git_smart__connect; + t->parent.set_connect_opts = git_smart__set_connect_opts; t->parent.close = git_smart__close; t->parent.free = git_smart__free; t->parent.negotiate_fetch = git_smart__negotiate_fetch; @@ -534,7 +430,6 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) 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; diff --git a/src/transports/smart.h b/src/transports/smart.h index c003052c8..8860a1ebd 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -137,16 +137,8 @@ typedef struct { git_transport parent; git_remote *owner; char *url; - git_credential_acquire_cb cred_acquire_cb; - void *cred_acquire_payload; - git_proxy_options proxy; + git_remote_connect_options connect_opts; int direction; - int flags; - git_transport_message_cb progress_cb; - git_transport_message_cb error_cb; - git_transport_certificate_check_cb certificate_check_cb; - void *message_cb_payload; - git_strarray custom_headers; git_smart_subtransport *wrapped; git_smart_subtransport_stream *current_stream; transport_smart_caps caps; @@ -157,8 +149,8 @@ typedef struct { packetsize_cb packetsize_cb; void *packetsize_payload; unsigned rpc : 1, - have_refs : 1, - connected : 1; + have_refs : 1, + connected : 1; gitno_buffer buffer; char buffer_data[65536]; } transport_smart; @@ -166,7 +158,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, git_vector *symrefs); -int git_smart__push(git_transport *transport, git_push *push, const git_remote_callbacks *cbs); +int git_smart__push(git_transport *transport, git_push *push); int git_smart__negotiate_fetch( git_transport *transport, @@ -177,9 +169,7 @@ int git_smart__negotiate_fetch( int git_smart__download_pack( git_transport *transport, git_repository *repo, - git_indexer_progress *stats, - git_indexer_progress_cb progress_cb, - void *progress_payload); + git_indexer_progress *stats); /* smart.c */ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index a9a623cc7..f1f111715 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -512,9 +512,7 @@ static int network_packetsize(size_t received, void *payload) int git_smart__download_pack( git_transport *transport, git_repository *repo, - git_indexer_progress *stats, - git_indexer_progress_cb progress_cb, - void *progress_payload) + git_indexer_progress *stats) { transport_smart *t = (transport_smart *)transport; gitno_buffer *buf = &t->buffer; @@ -523,6 +521,10 @@ int git_smart__download_pack( int error = 0; struct network_packetsize_payload npp = {0}; + // TODO + git_indexer_progress_cb progress_cb = t->connect_opts.callbacks.transfer_progress; + void *progress_payload = t->connect_opts.callbacks.payload; + memset(stats, 0, sizeof(git_indexer_progress)); if (progress_cb) { @@ -568,7 +570,7 @@ int git_smart__download_pack( git_error_clear(); error = GIT_EUSER; } else if (pkt->type == GIT_PKT_PROGRESS) { - if (t->progress_cb) { + if (t->connect_opts.callbacks.sideband_progress) { git_pkt_progress *p = (git_pkt_progress *) pkt; if (p->len > INT_MAX) { @@ -577,7 +579,7 @@ int git_smart__download_pack( goto done; } - error = t->progress_cb(p->data, (int)p->len, t->message_cb_payload); + error = t->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, t->connect_opts.callbacks.payload); } } else if (pkt->type == GIT_PKT_DATA) { git_pkt_data *p = (git_pkt_data *) pkt; @@ -811,7 +813,7 @@ static int parse_report(transport_smart *transport, git_push *push) error = -1; break; case GIT_PKT_PROGRESS: - if (transport->progress_cb) { + if (transport->connect_opts.callbacks.sideband_progress) { git_pkt_progress *p = (git_pkt_progress *) pkt; if (p->len > INT_MAX) { @@ -820,7 +822,7 @@ static int parse_report(transport_smart *transport, git_push *push) goto done; } - error = transport->progress_cb(p->data, (int)p->len, transport->message_cb_payload); + error = transport->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, transport->connect_opts.callbacks.payload); } break; default: @@ -987,9 +989,10 @@ static int stream_thunk(void *buf, size_t size, void *data) return error; } -int git_smart__push(git_transport *transport, git_push *push, const git_remote_callbacks *cbs) +int git_smart__push(git_transport *transport, git_push *push) { transport_smart *t = (transport_smart *)transport; + git_remote_callbacks *cbs = &t->connect_opts.callbacks; struct push_packbuilder_payload packbuilder_payload = {0}; git_str pktline = GIT_STR_INIT; int error = 0, need_pack = 0; diff --git a/src/transports/ssh.c b/src/transports/ssh.c index e3bb5eef6..f37bf70bb 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -441,11 +441,15 @@ static int request_creds(git_credential **out, ssh_subtransport *t, const char * int error, no_callback = 0; git_credential *cred = NULL; - if (!t->owner->cred_acquire_cb) { + if (!t->owner->connect_opts.callbacks.credentials) { no_callback = 1; } else { - error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods, - t->owner->cred_acquire_payload); + error = t->owner->connect_opts.callbacks.credentials( + &cred, + t->owner->url, + user, + auth_methods, + t->owner->connect_opts.callbacks.payload); if (error == GIT_PASSTHROUGH) { no_callback = 1; @@ -558,7 +562,7 @@ post_extract: if ((error = _git_ssh_session_create(&session, s->io)) < 0) goto done; - if (t->owner->certificate_check_cb != NULL) { + if (t->owner->connect_opts.callbacks.certificate_check != NULL) { git_cert_hostkey cert = {{ 0 }}, *cert_ptr; const char *key; size_t cert_len; @@ -632,7 +636,11 @@ post_extract: cert_ptr = &cert; - error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload); + error = t->owner->connect_opts.callbacks.certificate_check( + (git_cert *)cert_ptr, + 0, + urldata.host, + t->owner->connect_opts.callbacks.payload); if (error < 0 && error != GIT_PASSTHROUGH) { if (!git_error_last()) diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 60f536fdb..8ec5b37c5 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -293,14 +293,14 @@ static int certificate_check(winhttp_stream *s, int valid) git_cert_x509 cert; /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ - if (t->owner->certificate_check_cb == NULL && !valid) { + if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { if (!git_error_last()) git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); return GIT_ECERTIFICATE; } - if (t->owner->certificate_check_cb == NULL || git__strcmp(t->server.url.scheme, "https") != 0) + if (t->owner->connect_opts.callbacks.certificate_check == NULL || git__strcmp(t->server.url.scheme, "https") != 0) return 0; if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) { @@ -312,7 +312,7 @@ static int certificate_check(winhttp_stream *s, int valid) cert.parent.cert_type = GIT_CERT_X509; cert.data = cert_ctx->pbCertEncoded; cert.len = cert_ctx->cbCertEncoded; - error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->server.url.host, t->owner->message_cb_payload); + error = t->owner->connect_opts.callbacks.certificate_check((git_cert *) &cert, valid, t->server.url.host, t->owner->connect_opts.callbacks.payload); CertFreeCertificateContext(cert_ctx); if (error == GIT_PASSTHROUGH) @@ -426,7 +426,7 @@ static int winhttp_stream_connect(winhttp_stream *s) goto on_error; } - proxy_opts = &t->owner->proxy; + proxy_opts = &t->owner->connect_opts.proxy_opts; if (proxy_opts->type == GIT_PROXY_AUTO) { /* Set proxy if necessary */ if (git_remote__http_proxy(&proxy_url, t->owner->owner, &t->server.url) < 0) @@ -560,10 +560,10 @@ static int winhttp_stream_connect(winhttp_stream *s) } } - for (i = 0; i < t->owner->custom_headers.count; i++) { - if (t->owner->custom_headers.strings[i]) { + for (i = 0; i < t->owner->connect_opts.custom_headers.count; i++) { + if (t->owner->connect_opts.custom_headers.strings[i]) { git_str_clear(&buf); - git_str_puts(&buf, t->owner->custom_headers.strings[i]); + git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]); if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters"); goto on_error; @@ -577,14 +577,6 @@ static int winhttp_stream_connect(winhttp_stream *s) } } - /* If requested, disable certificate validation */ - if (strcmp(t->server.url.scheme, "https") == 0) { - int flags; - - if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) - goto on_error; - } - if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0) goto on_error; @@ -1197,8 +1189,10 @@ replay: winhttp_stream_close(s); if (!git__prefixcmp_icase(location8, prefix_https)) { + bool follow = (t->owner->connect_opts.follow_redirects != GIT_REMOTE_REDIRECT_NONE); + /* Upgrade to secure connection; disconnect and start over */ - if (git_net_url_apply_redirect(&t->server.url, location8, s->service_url) < 0) { + if (git_net_url_apply_redirect(&t->server.url, location8, follow, s->service_url) < 0) { git__free(location8); return -1; } @@ -1218,8 +1212,8 @@ replay: int error = acquire_credentials(s->request, &t->server, t->owner->url, - t->owner->cred_acquire_cb, - t->owner->cred_acquire_payload); + t->owner->connect_opts.callbacks.credentials, + t->owner->connect_opts.callbacks.payload); if (error < 0) { return error; @@ -1231,9 +1225,9 @@ replay: } else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) { int error = acquire_credentials(s->request, &t->proxy, - t->owner->proxy.url, - t->owner->proxy.credentials, - t->owner->proxy.payload); + t->owner->connect_opts.proxy_opts.url, + t->owner->connect_opts.proxy_opts.credentials, + t->owner->connect_opts.proxy_opts.payload); if (error < 0) { return error; diff --git a/tests/network/url/redirect.c b/tests/network/url/redirect.c index 2c0b614d9..a94db7daf 100644 --- a/tests/network/url/redirect.c +++ b/tests/network/url/redirect.c @@ -17,9 +17,9 @@ void test_network_url_redirect__cleanup(void) void test_network_url_redirect__redirect_http(void) { cl_git_pass(git_net_url_parse(&conndata, - "http://example.com/foo/bar/baz")); + "http://example.com/foo/bar/baz")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "http://example.com/foo/bar/baz", "bar/baz")); + "http://example.com/foo/bar/baz", false, "bar/baz")); cl_assert_equal_s(conndata.scheme, "http"); cl_assert_equal_s(conndata.host, "example.com"); cl_assert_equal_s(conndata.port, "80"); @@ -31,9 +31,9 @@ void test_network_url_redirect__redirect_http(void) void test_network_url_redirect__redirect_ssl(void) { cl_git_pass(git_net_url_parse(&conndata, - "https://example.com/foo/bar/baz")); + "https://example.com/foo/bar/baz")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://example.com/foo/bar/baz", "bar/baz")); + "https://example.com/foo/bar/baz", false, "bar/baz")); cl_assert_equal_s(conndata.scheme, "https"); cl_assert_equal_s(conndata.host, "example.com"); cl_assert_equal_s(conndata.port, "443"); @@ -45,9 +45,9 @@ void test_network_url_redirect__redirect_ssl(void) void test_network_url_redirect__redirect_leaves_root_path(void) { cl_git_pass(git_net_url_parse(&conndata, - "https://example.com/foo/bar/baz")); + "https://example.com/foo/bar/baz")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://example.com/foo/bar/baz", "/foo/bar/baz")); + "https://example.com/foo/bar/baz", false, "/foo/bar/baz")); cl_assert_equal_s(conndata.scheme, "https"); cl_assert_equal_s(conndata.host, "example.com"); cl_assert_equal_s(conndata.port, "443"); @@ -59,9 +59,9 @@ void test_network_url_redirect__redirect_leaves_root_path(void) void test_network_url_redirect__redirect_encoded_username_password(void) { cl_git_pass(git_net_url_parse(&conndata, - "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz")); + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz")); + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", false, "bar/baz")); cl_assert_equal_s(conndata.scheme, "https"); cl_assert_equal_s(conndata.host, "example.com"); cl_assert_equal_s(conndata.port, "443"); @@ -70,27 +70,42 @@ void test_network_url_redirect__redirect_encoded_username_password(void) cl_assert_equal_s(conndata.password, "pass@word%zyx%v"); } +void test_network_url_redirect__redirect_cross_host_allowed(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://bar.com/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://foo.com/bar/baz", true, NULL)); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/bar/baz"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + void test_network_url_redirect__redirect_cross_host_denied(void) { - cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz")); + cl_git_pass(git_net_url_parse(&conndata, + "https://bar.com/bar/baz")); cl_git_fail_with(git_net_url_apply_redirect(&conndata, - "https://foo.com/bar/baz", NULL), - -1); + "https://foo.com/bar/baz", false, NULL), -1); } void test_network_url_redirect__redirect_http_downgrade_denied(void) { - cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz")); + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/baz")); cl_git_fail_with(git_net_url_apply_redirect(&conndata, - "http://foo.com/bar/baz", NULL), - -1); + "http://foo.com/bar/baz", true, NULL), -1); } void test_network_url_redirect__redirect_relative(void) { - cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff")); + cl_git_pass(git_net_url_parse(&conndata, + "http://foo.com/bar/baz/biff")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "/zap/baz/biff?bam", NULL)); + "/zap/baz/biff?bam", true, NULL)); cl_assert_equal_s(conndata.scheme, "http"); cl_assert_equal_s(conndata.host, "foo.com"); cl_assert_equal_s(conndata.port, "80"); @@ -101,9 +116,10 @@ void test_network_url_redirect__redirect_relative(void) void test_network_url_redirect__redirect_relative_ssl(void) { - cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff")); + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/baz/biff")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "/zap/baz/biff?bam", NULL)); + "/zap/baz/biff?bam", true, NULL)); cl_assert_equal_s(conndata.scheme, "https"); cl_assert_equal_s(conndata.host, "foo.com"); cl_assert_equal_s(conndata.port, "443"); @@ -114,16 +130,18 @@ void test_network_url_redirect__redirect_relative_ssl(void) void test_network_url_redirect__service_query_no_query_params_in_location(void) { - cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack")); + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/info/refs?service=git-upload-pack")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "/baz/info/refs", "/info/refs?service=git-upload-pack")); + "/baz/info/refs", true, "/info/refs?service=git-upload-pack")); cl_assert_equal_s(conndata.path, "/baz"); } void test_network_url_redirect__service_query_with_query_params_in_location(void) { - cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack")); + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/info/refs?service=git-upload-pack")); cl_git_pass(git_net_url_apply_redirect(&conndata, - "/baz/info/refs?service=git-upload-pack", "/info/refs?service=git-upload-pack")); + "/baz/info/refs?service=git-upload-pack", true, "/info/refs?service=git-upload-pack")); cl_assert_equal_s(conndata.path, "/baz"); } diff --git a/tests/online/clone.c b/tests/online/clone.c index 8186dda16..9f2580bb3 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -32,6 +32,8 @@ static char *_remote_proxy_user = NULL; static char *_remote_proxy_pass = NULL; static char *_remote_proxy_selfsigned = NULL; static char *_remote_expectcontinue = NULL; +static char *_remote_redirect_initial = NULL; +static char *_remote_redirect_subsequent = NULL; static int _orig_proxies_need_reset = 0; static char *_orig_http_proxy = NULL; @@ -78,6 +80,8 @@ void test_online_clone__initialize(void) _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); _remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED"); _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); + _remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL"); + _remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT"); if (_remote_expectcontinue) git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); @@ -92,6 +96,8 @@ void test_online_clone__cleanup(void) g_repo = NULL; } cl_fixture_cleanup("./foo"); + cl_fixture_cleanup("./initial"); + cl_fixture_cleanup("./subsequent"); git__free(_remote_url); git__free(_remote_user); @@ -107,6 +113,8 @@ void test_online_clone__cleanup(void) git__free(_remote_proxy_pass); git__free(_remote_proxy_selfsigned); git__free(_remote_expectcontinue); + git__free(_remote_redirect_initial); + git__free(_remote_redirect_subsequent); if (_orig_proxies_need_reset) { cl_setenv("HTTP_PROXY", _orig_http_proxy); @@ -938,3 +946,59 @@ void test_online_clone__path_whitespace(void) cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options)); cl_assert(git_fs_path_exists("./foo/master.txt")); } + +void test_online_clone__redirect_default_succeeds_for_initial(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options)); +} + +void test_online_clone__redirect_default_fails_for_subsequent(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options)); +} + +void test_online_clone__redirect_none(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial) + cl_skip(); + + options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_NONE; + + cl_git_fail(git_clone(&g_repo, _remote_redirect_initial, "./fail", &options)); +} + +void test_online_clone__redirect_initial_succeeds_for_initial(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL; + + cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options)); +} + +void test_online_clone__redirect_initial_fails_for_subsequent(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL; + + cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options)); +} diff --git a/tests/online/fetch.c b/tests/online/fetch.c index 914291015..e09d338ce 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -7,15 +7,19 @@ static char *_remote_proxy_scheme = NULL; static char *_remote_proxy_host = NULL; static char *_remote_proxy_user = NULL; static char *_remote_proxy_pass = NULL; +static char *_remote_redirect_initial = NULL; +static char *_remote_redirect_subsequent = NULL; void test_online_fetch__initialize(void) { cl_git_pass(git_repository_init(&_repo, "./fetch", 0)); - _remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME"); - _remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST"); - _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); - _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); + _remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME"); + _remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST"); + _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); + _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); + _remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL"); + _remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT"); } void test_online_fetch__cleanup(void) @@ -24,11 +28,14 @@ void test_online_fetch__cleanup(void) _repo = NULL; cl_fixture_cleanup("./fetch"); - - git__free(_remote_proxy_scheme); - git__free(_remote_proxy_host); - git__free(_remote_proxy_user); - git__free(_remote_proxy_pass); + cl_fixture_cleanup("./redirected"); + + git__free(_remote_proxy_scheme); + git__free(_remote_proxy_host); + git__free(_remote_proxy_user); + git__free(_remote_proxy_pass); + git__free(_remote_redirect_initial); + git__free(_remote_redirect_subsequent); } static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) @@ -247,3 +254,44 @@ void test_online_fetch__proxy(void) git_remote_free(remote); git_str_dispose(&url); } + +static int do_redirected_fetch(const char *url, const char *name, const char *config) +{ + git_repository *repo; + git_remote *remote; + int error; + + cl_git_pass(git_repository_init(&repo, "./redirected", 0)); + cl_fixture_cleanup(name); + + if (config) + cl_repo_set_string(repo, "http.followRedirects", config); + + cl_git_pass(git_remote_create(&remote, repo, name, url)); + error = git_remote_fetch(remote, NULL, NULL, NULL); + + git_remote_free(remote); + git_repository_free(repo); + + cl_fixture_cleanup("./redirected"); + + return error; +} + +void test_online_fetch__redirect_config(void) +{ + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + /* config defaults */ + cl_git_pass(do_redirected_fetch(_remote_redirect_initial, "initial", NULL)); + cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", NULL)); + + /* redirect=initial */ + cl_git_pass(do_redirected_fetch(_remote_redirect_initial, "initial", "initial")); + cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "initial")); + + /* redirect=false */ + cl_git_fail(do_redirected_fetch(_remote_redirect_initial, "initial", "false")); + cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "false")); +} |