clone.c 29.1 KB
Newer Older
1 2 3
#include "clar_libgit2.h"

#include "git2/clone.h"
4
#include "git2/cred_helpers.h"
5
#include "remote.h"
6
#include "futils.h"
7
#include "refs.h"
8

9 10
#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
11 12 13
#define BB_REPO_URL "https://libgit3@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git"
14
#define GOOGLESOURCE_REPO_URL "https://chromium.googlesource.com/external/github.com/sergi/go-diff"
15

16 17
#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository"

18
static git_repository *g_repo;
19
static git_clone_options g_options;
20

21 22 23
static char *_remote_url = NULL;
static char *_remote_user = NULL;
static char *_remote_pass = NULL;
24
static char *_remote_sslnoverify = NULL;
25 26 27 28
static char *_remote_ssh_pubkey = NULL;
static char *_remote_ssh_privkey = NULL;
static char *_remote_ssh_passphrase = NULL;
static char *_remote_ssh_fingerprint = NULL;
29
static char *_remote_proxy_scheme = NULL;
30
static char *_remote_proxy_host = NULL;
31 32
static char *_remote_proxy_user = NULL;
static char *_remote_proxy_pass = NULL;
33
static char *_remote_proxy_selfsigned = NULL;
34
static char *_remote_expectcontinue = NULL;
35 36
static char *_remote_redirect_initial = NULL;
static char *_remote_redirect_subsequent = NULL;
37

38 39 40
static int _orig_proxies_need_reset = 0;
static char *_orig_http_proxy = NULL;
static char *_orig_https_proxy = NULL;
41
static char *_orig_no_proxy = NULL;
42

43 44 45 46 47 48 49 50 51 52 53 54
static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload)
{
	GIT_UNUSED(cert);
	GIT_UNUSED(host);
	GIT_UNUSED(payload);

	if (_remote_sslnoverify != NULL)
		valid = 1;

	return valid ? 0 : GIT_ECERTIFICATE;
}

55
void test_online_clone__initialize(void)
56
{
57
	git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT;
58
	git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT;
59

60
	g_repo = NULL;
61 62 63

	memset(&g_options, 0, sizeof(git_clone_options));
	g_options.version = GIT_CLONE_OPTIONS_VERSION;
64 65
	g_options.checkout_opts = dummy_opts;
	g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
66
	g_options.fetch_opts = dummy_fetch;
67
	g_options.fetch_opts.callbacks.certificate_check = ssl_cert;
68 69 70 71

	_remote_url = cl_getenv("GITTEST_REMOTE_URL");
	_remote_user = cl_getenv("GITTEST_REMOTE_USER");
	_remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
72
	_remote_sslnoverify = cl_getenv("GITTEST_REMOTE_SSL_NOVERIFY");
73 74 75 76
	_remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
	_remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY");
	_remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
	_remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT");
77
	_remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME");
78
	_remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST");
79 80
	_remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER");
	_remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS");
81
	_remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED");
82
	_remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE");
83 84
	_remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL");
	_remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT");
85 86 87

	if (_remote_expectcontinue)
		git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
88 89

	_orig_proxies_need_reset = 0;
90 91
}

92
void test_online_clone__cleanup(void)
93
{
94
	if (g_repo) {
95
		git_repository_free(g_repo);
96 97
		g_repo = NULL;
	}
98
	cl_fixture_cleanup("./foo");
99 100
	cl_fixture_cleanup("./initial");
	cl_fixture_cleanup("./subsequent");
101 102 103 104

	git__free(_remote_url);
	git__free(_remote_user);
	git__free(_remote_pass);
105
	git__free(_remote_sslnoverify);
106 107 108 109
	git__free(_remote_ssh_pubkey);
	git__free(_remote_ssh_privkey);
	git__free(_remote_ssh_passphrase);
	git__free(_remote_ssh_fingerprint);
110
	git__free(_remote_proxy_scheme);
111
	git__free(_remote_proxy_host);
112 113
	git__free(_remote_proxy_user);
	git__free(_remote_proxy_pass);
114
	git__free(_remote_proxy_selfsigned);
115
	git__free(_remote_expectcontinue);
116 117
	git__free(_remote_redirect_initial);
	git__free(_remote_redirect_subsequent);
118 119 120 121

	if (_orig_proxies_need_reset) {
		cl_setenv("HTTP_PROXY", _orig_http_proxy);
		cl_setenv("HTTPS_PROXY", _orig_https_proxy);
122
		cl_setenv("NO_PROXY", _orig_no_proxy);
123 124 125

		git__free(_orig_http_proxy);
		git__free(_orig_https_proxy);
126
		git__free(_orig_no_proxy);
127
	}
128 129

	git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL);
130 131
}

132
void test_online_clone__network_full(void)
133 134 135
{
	git_remote *origin;

136
	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
137
	cl_assert(!git_repository_is_bare(g_repo));
138
	cl_git_pass(git_remote_lookup(&origin, g_repo, "origin"));
nulltoken committed
139

140 141
	cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, origin->download_tags);

nulltoken committed
142
	git_remote_free(origin);
143 144
}

145
void test_online_clone__network_bare(void)
146 147 148
{
	git_remote *origin;

149
	g_options.bare = true;
150

151
	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
152
	cl_assert(git_repository_is_bare(g_repo));
153
	cl_git_pass(git_remote_lookup(&origin, g_repo, "origin"));
nulltoken committed
154 155

	git_remote_free(origin);
156 157
}

158
void test_online_clone__empty_repository(void)
159 160 161
{
	git_reference *head;

162
	cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options));
163 164

	cl_assert_equal_i(true, git_repository_is_empty(g_repo));
165
	cl_assert_equal_i(true, git_repository_head_unborn(g_repo));
166 167

	cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE));
168
	cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head));
169
	cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
170 171 172

	git_reference_free(head);
}
173

174
static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload)
175 176
{
	bool *was_called = (bool*)payload;
Ben Straub committed
177
	GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
178 179 180
	(*was_called) = true;
}

181
static int fetch_progress(const git_indexer_progress *stats, void *payload)
182 183
{
	bool *was_called = (bool*)payload;
Ben Straub committed
184
	GIT_UNUSED(stats);
185
	(*was_called) = true;
186
	return 0;
187 188
}

189
void test_online_clone__can_checkout_a_cloned_repo(void)
190
{
191
	git_str path = GIT_STR_INIT;
192
	git_reference *head, *remote_head;
193 194
	bool checkout_progress_cb_was_called = false,
		  fetch_progress_cb_was_called = false;
195

196
	g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
197 198
	g_options.checkout_opts.progress_cb = &checkout_progress;
	g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called;
199 200
	g_options.fetch_opts.callbacks.transfer_progress = &fetch_progress;
	g_options.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called;
201

202
	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
203

204
	cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "master.txt"));
205
	cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&path)));
206 207

	cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
208
	cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head));
209
	cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
nulltoken committed
210

211 212 213 214
	cl_git_pass(git_reference_lookup(&remote_head, g_repo, "refs/remotes/origin/HEAD"));
	cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(remote_head));
	cl_assert_equal_s("refs/remotes/origin/master", git_reference_symbolic_target(remote_head));

215 216
	cl_assert_equal_i(true, checkout_progress_cb_was_called);
	cl_assert_equal_i(true, fetch_progress_cb_was_called);
217

218
	git_reference_free(remote_head);
nulltoken committed
219
	git_reference_free(head);
220
	git_str_dispose(&path);
221
}
Ben Straub committed
222

223 224
static int remote_mirror_cb(git_remote **out, git_repository *repo,
			    const char *name, const char *url, void *payload)
225
{
226
	int error;
227 228
	git_remote *remote;

229
	GIT_UNUSED(payload);
230

231
	if ((error = git_remote_create_with_fetchspec(&remote, repo, name, url, "+refs/*:refs/*")) < 0)
232
		return error;
233

234 235
	*out = remote;
	return 0;
236 237
}

238 239
void test_online_clone__clone_mirror(void)
{
240
	git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
241 242 243 244
	git_reference *head;

	bool fetch_progress_cb_was_called = false;

245 246
	opts.fetch_opts.callbacks.transfer_progress = &fetch_progress;
	opts.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called;
247

248 249
	opts.bare = true;
	opts.remote_cb = remote_mirror_cb;
250

251
	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo.git", &opts));
252 253

	cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
254
	cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head));
255 256 257 258 259
	cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));

	cl_assert_equal_i(true, fetch_progress_cb_was_called);

	git_reference_free(head);
260 261 262
	git_repository_free(g_repo);
	g_repo = NULL;

263 264 265
	cl_fixture_cleanup("./foo.git");
}

Ben Straub committed
266 267 268 269 270 271 272 273
static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload)
{
	int *callcount = (int*)payload;
	GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b);
	*callcount = *callcount + 1;
	return 0;
}

274
void test_online_clone__custom_remote_callbacks(void)
Ben Straub committed
275 276 277
{
	int callcount = 0;

278 279
	g_options.fetch_opts.callbacks.update_tips = update_tips;
	g_options.fetch_opts.callbacks.payload = &callcount;
Ben Straub committed
280 281 282 283 284

	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
	cl_assert(callcount > 0);
}

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
void test_online_clone__custom_headers(void)
{
	char *empty_header = "";
	char *unnamed_header = "this is a header about nothing";
	char *newlines = "X-Custom: almost OK\n";
	char *conflict = "Accept: defined-by-git";
	char *ok = "X-Custom: this should be ok";

	g_options.fetch_opts.custom_headers.count = 1;

	g_options.fetch_opts.custom_headers.strings = &empty_header;
	cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));

	g_options.fetch_opts.custom_headers.strings = &unnamed_header;
	cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));

	g_options.fetch_opts.custom_headers.strings = &newlines;
	cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));

	g_options.fetch_opts.custom_headers.strings = &conflict;
	cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));

	/* Finally, we got it right! */
	g_options.fetch_opts.custom_headers.strings = &ok;
	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
}

312
static int cred_failure_cb(
313
	git_credential **cred,
314 315 316 317 318
	const char *url,
	const char *username_from_url,
	unsigned int allowed_types,
	void *data)
{
Russell Belfer committed
319 320
	GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url);
	GIT_UNUSED(allowed_types); GIT_UNUSED(data);
Ben Straub committed
321
	return -172;
322 323
}

Ben Straub committed
324
void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void)
325
{
326 327 328
	git__free(_remote_url);
	git__free(_remote_user);

329 330
	_remote_url = git__strdup("https://github.com/libgit2/non-existent");
	_remote_user = git__strdup("libgit2test");
331

332
	g_options.fetch_opts.callbacks.credentials = cred_failure_cb;
333

334
	cl_git_fail_with(-172, git_clone(&g_repo, _remote_url, "./foo", &g_options));
335 336
}

337
static int cred_count_calls_cb(git_credential **cred, const char *url, const char *user,
338 339 340 341 342 343
			       unsigned int allowed_types, void *data)
{
	size_t *counter = (size_t *) data;

	GIT_UNUSED(url); GIT_UNUSED(user); GIT_UNUSED(allowed_types);

344 345
	if (allowed_types == GIT_CREDENTIAL_USERNAME)
		return git_credential_username_new(cred, "foo");
346

347 348 349 350 351
	(*counter)++;

	if (*counter == 3)
		return GIT_EUSER;

352
	return git_credential_userpass_plaintext_new(cred, "foo", "bar");
353 354 355 356 357 358
}

void test_online_clone__cred_callback_called_again_on_auth_failure(void)
{
	size_t counter = 0;

359 360 361
	git__free(_remote_url);
	git__free(_remote_user);

362
	_remote_url = git__strdup("https://gitlab.com/libgit2/non-existent");
363
	_remote_user = git__strdup("libgit2test");
364

365 366
	g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb;
	g_options.fetch_opts.callbacks.payload = &counter;
367

368
	cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options));
369 370 371
	cl_assert_equal_i(3, counter);
}

372
static int cred_default(
373
	git_credential **cred,
374 375 376 377 378 379 380 381 382
	const char *url,
	const char *user_from_url,
	unsigned int allowed_types,
	void *payload)
{
	GIT_UNUSED(url);
	GIT_UNUSED(user_from_url);
	GIT_UNUSED(payload);

383
	if (!(allowed_types & GIT_CREDENTIAL_DEFAULT))
384 385
		return 0;

386
	return git_credential_default_new(cred);
387 388
}

389
void test_online_clone__credentials(void)
Ben Straub committed
390
{
391 392 393
	/* Remote URL environment variable must be set.
	 * User and password are optional.
	 */
394
	git_credential_userpass_payload user_pass = {
395 396
		_remote_user,
		_remote_pass
Ben Straub committed
397 398
	};

399 400
	if (!_remote_url)
		clar__skip();
Ben Straub committed
401

402
	if (cl_is_env_set("GITTEST_REMOTE_DEFAULT")) {
403
		g_options.fetch_opts.callbacks.credentials = cred_default;
404
	} else {
405
		g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
406
		g_options.fetch_opts.callbacks.payload = &user_pass;
407
	}
Ben Straub committed
408

409
	cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
410 411 412 413
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
}

414 415 416
void test_online_clone__credentials_via_custom_headers(void)
{
	const char *creds = "libgit3:libgit3";
417
	git_str auth = GIT_STR_INIT;
418

419 420
	cl_git_pass(git_str_puts(&auth, "Authorization: Basic "));
	cl_git_pass(git_str_encode_base64(&auth, creds, strlen(creds)));
421 422 423 424 425
	g_options.fetch_opts.custom_headers.count = 1;
	g_options.fetch_opts.custom_headers.strings = &auth.ptr;

	cl_git_pass(git_clone(&g_repo, "https://bitbucket.org/libgit2/testgitrepository.git", "./foo", &g_options));

426
	git_str_dispose(&auth);
427 428
}

429 430
void test_online_clone__bitbucket_style(void)
{
431
	git_credential_userpass_payload user_pass = {
432
		"libgit3", "libgit3"
433 434
	};

435
	g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
436
	g_options.fetch_opts.callbacks.payload = &user_pass;
437 438 439 440

	cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options));
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
441 442 443 444
}

void test_online_clone__bitbucket_uses_creds_in_url(void)
{
445
	git_credential_userpass_payload user_pass = {
446 447
		"libgit2", "wrong"
	};
448

449
	g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
450 451 452 453
	g_options.fetch_opts.callbacks.payload = &user_pass;

	/*
	 * Correct user and pass are in the URL; the (incorrect) creds in
454
	 * the `git_credential_userpass_payload` should be ignored.
455
	 */
456 457 458
	cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options));
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
459
}
460

461 462
void test_online_clone__bitbucket_falls_back_to_specified_creds(void)
{
463
	git_credential_userpass_payload user_pass = {
464 465 466
		"libgit2", "libgit2"
	};

467
	g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
468 469 470 471 472 473 474 475 476 477
	g_options.fetch_opts.callbacks.payload = &user_pass;

	/*
	 * TODO: as of March 2018, bitbucket sporadically fails with
	 * 403s instead of replying with a 401 - but only sometimes.
	 */
	cl_skip();

	/*
	 * Incorrect user and pass are in the URL; the (correct) creds in
478
	 * the `git_credential_userpass_payload` should be used as a fallback.
479
	 */
480
	cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options));
481 482 483 484 485 486 487
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
}

void test_online_clone__googlesource(void)
{
	cl_git_pass(git_clone(&g_repo, GOOGLESOURCE_REPO_URL, "./foo", &g_options));
488 489
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
Ben Straub committed
490
}
491

492
static int cancel_at_half(const git_indexer_progress *stats, void *payload)
493 494 495 496
{
	GIT_UNUSED(payload);

	if (stats->received_objects > (stats->total_objects/2))
497
		return 4321;
498 499 500 501 502
	return 0;
}

void test_online_clone__can_cancel(void)
{
503
	g_options.fetch_opts.callbacks.transfer_progress = cancel_at_half;
504

505 506
	cl_git_fail_with(4321,
		git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
507
}
508

509
static int cred_cb(git_credential **cred, const char *url, const char *user_from_url,
510 511 512 513
		   unsigned int allowed_types, void *payload)
{
	GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload);

514 515
	if (allowed_types & GIT_CREDENTIAL_USERNAME)
		return git_credential_username_new(cred, _remote_user);
516

517 518
	if (allowed_types & GIT_CREDENTIAL_SSH_KEY)
		return git_credential_ssh_key_new(cred,
519 520
			_remote_user, _remote_ssh_pubkey,
			_remote_ssh_privkey, _remote_ssh_passphrase);
521

522
	git_error_set(GIT_ERROR_NET, "unexpected cred type");
523 524
	return -1;
}
525

526
static int check_ssh_auth_methods(git_credential **cred, const char *url, const char *username_from_url,
527 528
				  unsigned int allowed_types, void *data)
{
529
	int *with_user = (int *) data;
530
	GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data);
531

532
	if (!*with_user)
533
		cl_assert_equal_i(GIT_CREDENTIAL_USERNAME, allowed_types);
534
	else
535
		cl_assert(!(allowed_types & GIT_CREDENTIAL_USERNAME));
536

537 538
	return GIT_EUSER;
}
539

540 541
void test_online_clone__ssh_auth_methods(void)
{
542 543
	int with_user;

544 545 546
#ifndef GIT_SSH
	clar__skip();
#endif
547 548
	g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods;
	g_options.fetch_opts.callbacks.payload = &with_user;
549
	g_options.fetch_opts.callbacks.certificate_check = NULL;
550

551
	with_user = 0;
552 553
	cl_git_fail_with(GIT_EUSER,
		git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options));
554 555 556 557 558 559

	with_user = 1;
	cl_git_fail_with(GIT_EUSER,
		git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
}

560 561 562 563 564 565 566 567 568
static int custom_remote_ssh_with_paths(
	git_remote **out,
	git_repository *repo,
	const char *name,
	const char *url,
	void *payload)
{
	int error;

569
	GIT_UNUSED(payload);
570

571
	if ((error = git_remote_create(out, repo, name, url)) < 0)
572
		return error;
573

574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
	return 0;
}

void test_online_clone__ssh_with_paths(void)
{
	char *bad_paths[] = {
		"/bin/yes",
		"/bin/false",
	};
	char *good_paths[] = {
		"/usr/bin/git-upload-pack",
		"/usr/bin/git-receive-pack",
	};
	git_strarray arr = {
		bad_paths,
		2,
	};

592 593 594
#ifndef GIT_SSH
	clar__skip();
#endif
595
	if (!_remote_url || !_remote_user || strncmp(_remote_url, "ssh://", 5) != 0)
596 597 598
		clar__skip();

	g_options.remote_cb = custom_remote_ssh_with_paths;
599
	g_options.fetch_opts.callbacks.transport = git_transport_ssh_with_paths;
600
	g_options.fetch_opts.callbacks.credentials = cred_cb;
601
	g_options.fetch_opts.callbacks.payload = &arr;
602
	g_options.fetch_opts.callbacks.certificate_check = NULL;
603

604
	cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options));
605

606
	arr.strings = good_paths;
607
	cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
608
}
609

610
static int cred_foo_bar(git_credential **cred, const char *url, const char *username_from_url,
611 612 613 614 615
				  unsigned int allowed_types, void *data)

{
	GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(allowed_types); GIT_UNUSED(data);

616
	return git_credential_userpass_plaintext_new(cred, "foo", "bar");
617 618 619 620
}

void test_online_clone__ssh_cannot_change_username(void)
{
621 622 623
#ifndef GIT_SSH
	clar__skip();
#endif
624
	g_options.fetch_opts.callbacks.credentials = cred_foo_bar;
625 626

	cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
627
}
628

629
static int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
630 631 632 633 634 635 636
{
	git_cert_hostkey *key;
	git_oid expected = {{0}}, actual = {{0}};

	GIT_UNUSED(valid);
	GIT_UNUSED(payload);

637
	cl_assert(_remote_ssh_fingerprint);
638

639
	cl_git_pass(git_oid_fromstrp(&expected, _remote_ssh_fingerprint));
640 641
	cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type);
	key = (git_cert_hostkey *) cert;
642

643 644 645 646 647
	/*
	 * We need to figure out how long our input was to check for
	 * the type. Here we abuse the fact that both hashes fit into
	 * our git_oid type.
	 */
648
	if (strlen(_remote_ssh_fingerprint) == 32 && key->type & GIT_CERT_SSH_MD5) {
649
		memcpy(&actual.id, key->hash_md5, 16);
650
	} else 	if (strlen(_remote_ssh_fingerprint) == 40 && key->type & GIT_CERT_SSH_SHA1) {
651 652 653 654
		memcpy(&actual, key->hash_sha1, 20);
	} else {
		cl_fail("Cannot find a usable SSH hash");
	}
655

656
	cl_assert(!memcmp(&expected, &actual, 20));
657

658 659
	cl_assert_equal_s("localhost", host);

660 661 662 663 664
	return GIT_EUSER;
}

void test_online_clone__ssh_cert(void)
{
665
	g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check;
666

667
	if (!_remote_ssh_fingerprint)
668 669
		cl_skip();

670
	cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options));
671 672
}

673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
static char *read_key_file(const char *path)
{
	FILE *f;
	char *buf;
	long key_length;

	if (!path || !*path)
		return NULL;

	cl_assert((f = fopen(path, "r")) != NULL);
	cl_assert(fseek(f, 0, SEEK_END) != -1);
	cl_assert((key_length = ftell(f)) != -1);
	cl_assert(fseek(f, 0, SEEK_SET) != -1);
	cl_assert((buf = malloc(key_length)) != NULL);
	cl_assert(fread(buf, key_length, 1, f) == 1);
	fclose(f);

	return buf;
}

693
static int ssh_memory_cred_cb(git_credential **cred, const char *url, const char *user_from_url,
694 695 696 697
		   unsigned int allowed_types, void *payload)
{
	GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload);

698 699
	if (allowed_types & GIT_CREDENTIAL_USERNAME)
		return git_credential_username_new(cred, _remote_user);
700

701
	if (allowed_types & GIT_CREDENTIAL_SSH_KEY)
702
	{
703 704
		char *pubkey = read_key_file(_remote_ssh_pubkey);
		char *privkey = read_key_file(_remote_ssh_privkey);
705

706
		int ret = git_credential_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase);
707 708 709 710 711 712 713 714

		if (privkey)
			free(privkey);
		if (pubkey)
			free(pubkey);
		return ret;
	}

715
	git_error_set(GIT_ERROR_NET, "unexpected cred type");
716 717 718 719 720 721 722 723
	return -1;
}

void test_online_clone__ssh_memory_auth(void)
{
#ifndef GIT_SSH_MEMORY_CREDENTIALS
	clar__skip();
#endif
724
	if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0)
725 726 727 728
		clar__skip();

	g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb;

729
	cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
730 731
}

732
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
733
{
734
	GIT_UNUSED(cert);
735
	GIT_UNUSED(valid);
736
	GIT_UNUSED(host);
737 738
	GIT_UNUSED(payload);

739
	return GIT_ECERTIFICATE;
740 741 742 743
}

void test_online_clone__certificate_invalid(void)
{
744
	g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check;
745

746
	cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options),
747
		GIT_ECERTIFICATE);
748

749
#ifdef GIT_SSH
750 751
	cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options),
		GIT_ECERTIFICATE);
752
#endif
753 754
}

755
static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
756
{
757
	GIT_UNUSED(cert);
758
	GIT_UNUSED(valid);
759 760
	GIT_UNUSED(payload);

761 762
	cl_assert_equal_s("github.com", host);

763
	return 0;
764 765 766 767
}

void test_online_clone__certificate_valid(void)
{
768
	g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
769

770
	cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
771
}
772 773 774

void test_online_clone__start_with_http(void)
{
775
	g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
776 777 778

	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
779 780

static int called_proxy_creds;
781
static int proxy_cred_cb(git_credential **out, const char *url, const char *username, unsigned int allowed, void *payload)
782
{
783
	GIT_UNUSED(url);
784
	GIT_UNUSED(username);
785 786
	GIT_UNUSED(allowed);
	GIT_UNUSED(payload);
787 788

	called_proxy_creds = 1;
789
	return git_credential_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass);
790 791
}

792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payload)
{
	char *colon;
	size_t host_len;

	GIT_UNUSED(cert);
	GIT_UNUSED(valid);
	GIT_UNUSED(payload);

	cl_assert(_remote_proxy_host);

	if ((colon = strchr(_remote_proxy_host, ':')) != NULL)
		host_len = (colon - _remote_proxy_host);
	else
		host_len = strlen(_remote_proxy_host);

	if (_remote_proxy_selfsigned != NULL &&
	    strlen(host) == host_len &&
	    strncmp(_remote_proxy_host, host, host_len) == 0)
		valid = 1;

	return valid ? 0 : GIT_ECERTIFICATE;
}

816 817
void test_online_clone__proxy_credentials_request(void)
{
818
	git_str url = GIT_STR_INIT;
819

820
	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
821 822
		cl_skip();

823
	cl_git_pass(git_str_printf(&url, "%s://%s/",
824 825
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_host));
826

827
	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
828
	g_options.fetch_opts.proxy_opts.url = url.ptr;
829
	g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb;
830
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
831 832 833
	called_proxy_creds = 0;
	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
	cl_assert(called_proxy_creds);
834

835
	git_str_dispose(&url);
836 837 838 839
}

void test_online_clone__proxy_credentials_in_url(void)
{
840
	git_str url = GIT_STR_INIT;
841

842
	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
843 844
		cl_skip();

845
	cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/",
846 847
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
848

849
	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
850
	g_options.fetch_opts.proxy_opts.url = url.ptr;
851
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
852 853 854
	called_proxy_creds = 0;
	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
	cl_assert(called_proxy_creds == 0);
855

856
	git_str_dispose(&url);
857
}
858 859 860

void test_online_clone__proxy_credentials_in_environment(void)
{
861
	git_str url = GIT_STR_INIT;
862

863
	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
864 865 866 867
		cl_skip();

	_orig_http_proxy = cl_getenv("HTTP_PROXY");
	_orig_https_proxy = cl_getenv("HTTPS_PROXY");
868
	_orig_no_proxy = cl_getenv("NO_PROXY");
869 870 871
	_orig_proxies_need_reset = 1;

	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
872
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
873

874
	cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/",
875 876
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
877 878 879

	cl_setenv("HTTP_PROXY", url.ptr);
	cl_setenv("HTTPS_PROXY", url.ptr);
880
	cl_setenv("NO_PROXY", NULL);
881 882 883

	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));

884
	git_str_dispose(&url);
885
}
886

887 888
void test_online_clone__proxy_credentials_in_url_https(void)
{
889
	git_str url = GIT_STR_INIT;
890 891 892 893

	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
		cl_skip();

894
	cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/",
895 896 897 898 899 900 901 902 903 904 905
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));

	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
	g_options.fetch_opts.proxy_opts.url = url.ptr;
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
	g_options.fetch_opts.callbacks.certificate_check = ssl_cert;
	called_proxy_creds = 0;
	cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
	cl_assert(called_proxy_creds == 0);

906
	git_str_dispose(&url);
907 908
}

909 910 911 912 913 914
void test_online_clone__proxy_auto_not_detected(void)
{
	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;

	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
915 916 917

void test_online_clone__proxy_cred_callback_after_failed_url_creds(void)
{
918
	git_str url = GIT_STR_INIT;
919 920 921 922

	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
		cl_skip();

923
	cl_git_pass(git_str_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/",
924 925 926 927 928 929 930 931 932 933 934
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_host));

	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
	g_options.fetch_opts.proxy_opts.url = url.ptr;
	g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb;
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
	called_proxy_creds = 0;
	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
	cl_assert(called_proxy_creds);

935
	git_str_dispose(&url);
936
}
937

938 939 940
void test_online_clone__azurerepos(void)
{
	cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/test", "./foo", &g_options));
941
	cl_assert(git_fs_path_exists("./foo/master.txt"));
942 943
}

944
void test_online_clone__path_whitespace(void)
945
{
946
	cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options));
947
	cl_assert(git_fs_path_exists("./foo/master.txt"));
948
}
949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004

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));
}