clone.c 29.3 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://libgit2-test@bitbucket.org/libgit2-test/testgitrepository.git"
#define BB_REPO_URL_WITH_PASS "https://libgit2-test:YT77Ppm2nq8w4TYjGS8U@bitbucket.org/libgit2-test/testgitrepository.git"
#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2-test:wrong@bitbucket.org/libgit2-test/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
void test_online_clone__credentials_via_custom_headers(void)
{
416
	const char *creds = "libgit2-test:YT77Ppm2nq8w4TYjGS8U";
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
	g_options.fetch_opts.custom_headers.count = 1;
	g_options.fetch_opts.custom_headers.strings = &auth.ptr;

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

426
	git_str_dispose(&auth);
427 428
}

429 430
void test_online_clone__bitbucket_style(void)
{
431
	git_credential_userpass_payload user_pass = {
432
		"libgit2-test", "YT77Ppm2nq8w4TYjGS8U"
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
		"libgit2-test", "wrong"
447
	};
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
		"libgit2-test", "libgit2"
465 466
	};

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
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
}

void test_online_clone__googlesource(void)
{
487 488 489
#ifdef __APPLE__
	cl_skip();
#else
490
	cl_git_pass(git_clone(&g_repo, GOOGLESOURCE_REPO_URL, "./foo", &g_options));
491 492
	git_repository_free(g_repo); g_repo = NULL;
	cl_fixture_cleanup("./foo");
493
#endif
Ben Straub committed
494
}
495

496
static int cancel_at_half(const git_indexer_progress *stats, void *payload)
497 498 499 500
{
	GIT_UNUSED(payload);

	if (stats->received_objects > (stats->total_objects/2))
501
		return 4321;
502 503 504 505 506
	return 0;
}

void test_online_clone__can_cancel(void)
{
507
	g_options.fetch_opts.callbacks.transfer_progress = cancel_at_half;
508

509 510
	cl_git_fail_with(4321,
		git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
511
}
512

513
static int cred_cb(git_credential **cred, const char *url, const char *user_from_url,
514 515 516 517
		   unsigned int allowed_types, void *payload)
{
	GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload);

518 519
	if (allowed_types & GIT_CREDENTIAL_USERNAME)
		return git_credential_username_new(cred, _remote_user);
520

521 522
	if (allowed_types & GIT_CREDENTIAL_SSH_KEY)
		return git_credential_ssh_key_new(cred,
523 524
			_remote_user, _remote_ssh_pubkey,
			_remote_ssh_privkey, _remote_ssh_passphrase);
525

526
	git_error_set(GIT_ERROR_NET, "unexpected cred type");
527 528
	return -1;
}
529

530
static int check_ssh_auth_methods(git_credential **cred, const char *url, const char *username_from_url,
531 532
				  unsigned int allowed_types, void *data)
{
533
	int *with_user = (int *) data;
534
	GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data);
535

536
	if (!*with_user)
537
		cl_assert_equal_i(GIT_CREDENTIAL_USERNAME, allowed_types);
538
	else
539
		cl_assert(!(allowed_types & GIT_CREDENTIAL_USERNAME));
540

541 542
	return GIT_EUSER;
}
543

544 545
void test_online_clone__ssh_auth_methods(void)
{
546 547
	int with_user;

548 549 550
#ifndef GIT_SSH
	clar__skip();
#endif
551 552
	g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods;
	g_options.fetch_opts.callbacks.payload = &with_user;
553
	g_options.fetch_opts.callbacks.certificate_check = NULL;
554

555
	with_user = 0;
556 557
	cl_git_fail_with(GIT_EUSER,
		git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options));
558 559 560 561 562 563

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

564 565 566 567 568 569 570 571 572
static int custom_remote_ssh_with_paths(
	git_remote **out,
	git_repository *repo,
	const char *name,
	const char *url,
	void *payload)
{
	int error;

573
	GIT_UNUSED(payload);
574

575
	if ((error = git_remote_create(out, repo, name, url)) < 0)
576
		return error;
577

578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
	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,
	};

596 597 598
#ifndef GIT_SSH
	clar__skip();
#endif
599
	if (!_remote_url || !_remote_user || strncmp(_remote_url, "ssh://", 5) != 0)
600 601 602
		clar__skip();

	g_options.remote_cb = custom_remote_ssh_with_paths;
603
	g_options.fetch_opts.callbacks.transport = git_transport_ssh_with_paths;
604
	g_options.fetch_opts.callbacks.credentials = cred_cb;
605
	g_options.fetch_opts.callbacks.payload = &arr;
606
	g_options.fetch_opts.callbacks.certificate_check = NULL;
607

608
	cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options));
609

610
	arr.strings = good_paths;
611
	cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
612
}
613

614
static int cred_foo_bar(git_credential **cred, const char *url, const char *username_from_url,
615 616 617 618 619
				  unsigned int allowed_types, void *data)

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

620
	return git_credential_userpass_plaintext_new(cred, "foo", "bar");
621 622 623 624
}

void test_online_clone__ssh_cannot_change_username(void)
{
625 626 627
#ifndef GIT_SSH
	clar__skip();
#endif
628
	g_options.fetch_opts.callbacks.credentials = cred_foo_bar;
629 630

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

633
static int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
634 635
{
	git_cert_hostkey *key;
636
	git_oid expected = GIT_OID_SHA1_ZERO, actual = GIT_OID_SHA1_ZERO;
637 638 639 640

	GIT_UNUSED(valid);
	GIT_UNUSED(payload);

641
	cl_assert(_remote_ssh_fingerprint);
642

643
	cl_git_pass(git_oid__fromstrp(&expected, _remote_ssh_fingerprint, GIT_OID_SHA1));
644 645
	cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type);
	key = (git_cert_hostkey *) cert;
646

647 648 649 650 651
	/*
	 * 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.
	 */
652
	if (strlen(_remote_ssh_fingerprint) == 32 && key->type & GIT_CERT_SSH_MD5) {
653
		memcpy(&actual.id, key->hash_md5, 16);
654
	} else 	if (strlen(_remote_ssh_fingerprint) == 40 && key->type & GIT_CERT_SSH_SHA1) {
655 656 657 658
		memcpy(&actual, key->hash_sha1, 20);
	} else {
		cl_fail("Cannot find a usable SSH hash");
	}
659

660
	cl_assert(!memcmp(&expected, &actual, 20));
661

662 663
	cl_assert_equal_s("localhost", host);

664 665 666 667 668
	return GIT_EUSER;
}

void test_online_clone__ssh_cert(void)
{
669
	g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check;
670

671
	if (!_remote_ssh_fingerprint)
672 673
		cl_skip();

674
	cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options));
675 676
}

677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
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;
}

697
static int ssh_memory_cred_cb(git_credential **cred, const char *url, const char *user_from_url,
698 699 700 701
		   unsigned int allowed_types, void *payload)
{
	GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload);

702 703
	if (allowed_types & GIT_CREDENTIAL_USERNAME)
		return git_credential_username_new(cred, _remote_user);
704

705
	if (allowed_types & GIT_CREDENTIAL_SSH_KEY)
706
	{
707 708
		char *pubkey = read_key_file(_remote_ssh_pubkey);
		char *privkey = read_key_file(_remote_ssh_privkey);
709

710
		int ret = git_credential_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase);
711 712 713 714 715 716 717 718

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

719
	git_error_set(GIT_ERROR_NET, "unexpected cred type");
720 721 722 723 724 725 726 727
	return -1;
}

void test_online_clone__ssh_memory_auth(void)
{
#ifndef GIT_SSH_MEMORY_CREDENTIALS
	clar__skip();
#endif
728
	if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0)
729 730 731 732
		clar__skip();

	g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb;

733
	cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
734 735
}

736
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
737
{
738
	GIT_UNUSED(cert);
739
	GIT_UNUSED(valid);
740
	GIT_UNUSED(host);
741 742
	GIT_UNUSED(payload);

743
	return GIT_ECERTIFICATE;
744 745 746 747
}

void test_online_clone__certificate_invalid(void)
{
748
	g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check;
749

750
	cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options),
751
		GIT_ECERTIFICATE);
752

753
#ifdef GIT_SSH
754 755
	cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options),
		GIT_ECERTIFICATE);
756
#endif
757 758
}

759
static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
760
{
761
	GIT_UNUSED(cert);
762
	GIT_UNUSED(valid);
763 764
	GIT_UNUSED(payload);

765 766
	cl_assert_equal_s("github.com", host);

767
	return 0;
768 769 770 771
}

void test_online_clone__certificate_valid(void)
{
772
	g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
773

774
	cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
775
}
776 777 778

void test_online_clone__start_with_http(void)
{
779
	g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
780 781 782

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

static int called_proxy_creds;
785
static int proxy_cred_cb(git_credential **out, const char *url, const char *username, unsigned int allowed, void *payload)
786
{
787
	GIT_UNUSED(url);
788
	GIT_UNUSED(username);
789 790
	GIT_UNUSED(allowed);
	GIT_UNUSED(payload);
791 792

	called_proxy_creds = 1;
793
	return git_credential_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass);
794 795
}

796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
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;
}

820 821
void test_online_clone__proxy_credentials_request(void)
{
822
	git_str url = GIT_STR_INIT;
823

824
	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
825 826
		cl_skip();

827
	cl_git_pass(git_str_printf(&url, "%s://%s/",
828 829
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_host));
830

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

839
	git_str_dispose(&url);
840 841 842 843
}

void test_online_clone__proxy_credentials_in_url(void)
{
844
	git_str url = GIT_STR_INIT;
845

846
	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
847 848
		cl_skip();

849
	cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/",
850 851
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
852

853
	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
854
	g_options.fetch_opts.proxy_opts.url = url.ptr;
855
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
856 857 858
	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);
859

860
	git_str_dispose(&url);
861
}
862 863 864

void test_online_clone__proxy_credentials_in_environment(void)
{
865
	git_str url = GIT_STR_INIT;
866

867
	if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
868 869 870 871
		cl_skip();

	_orig_http_proxy = cl_getenv("HTTP_PROXY");
	_orig_https_proxy = cl_getenv("HTTPS_PROXY");
872
	_orig_no_proxy = cl_getenv("NO_PROXY");
873 874 875
	_orig_proxies_need_reset = 1;

	g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
876
	g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
877

878
	cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/",
879 880
		_remote_proxy_scheme ? _remote_proxy_scheme : "http",
		_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
881 882 883

	cl_setenv("HTTP_PROXY", url.ptr);
	cl_setenv("HTTPS_PROXY", url.ptr);
884
	cl_setenv("NO_PROXY", NULL);
885 886 887

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

888
	git_str_dispose(&url);
889
}
890

891 892
void test_online_clone__proxy_credentials_in_url_https(void)
{
893
	git_str url = GIT_STR_INIT;
894 895 896 897

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

898
	cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/",
899 900 901 902 903 904 905 906 907 908 909
		_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);

910
	git_str_dispose(&url);
911 912
}

913 914 915 916 917 918
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));
}
919 920 921

void test_online_clone__proxy_cred_callback_after_failed_url_creds(void)
{
922
	git_str url = GIT_STR_INIT;
923 924 925 926

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

927
	cl_git_pass(git_str_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/",
928 929 930 931 932 933 934 935 936 937 938
		_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);

939
	git_str_dispose(&url);
940
}
941

942 943 944
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));
945
	cl_assert(git_fs_path_exists("./foo/master.txt"));
946 947
}

948
void test_online_clone__path_whitespace(void)
949
{
950
	cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options));
951
	cl_assert(git_fs_path_exists("./foo/master.txt"));
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 1005 1006 1007 1008

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