Unverified Commit 2bfd8ddc by Edward Thomson Committed by GitHub

Merge pull request #6175 from libgit2/ethomson/follow_redirects_initial

remote: support `http.followRedirects` (`false` and `initial`) and follow initial redirects by default
parents 6723edc7 f4fec251
......@@ -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
......
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.
......@@ -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.
......
......@@ -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);
......
......@@ -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)
......
......@@ -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. */
......
......@@ -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;
}
......@@ -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
......@@ -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) {
......
......@@ -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
......
......@@ -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;
}
......
......@@ -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
......@@ -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;
......
......@@ -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) {
......
......@@ -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;
......
......@@ -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);
......
......@@ -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;
......
......@@ -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())
......
......@@ -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;
......
......@@ -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");
}
......@@ -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));
}
......@@ -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"));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment