summaryrefslogtreecommitdiff
path: root/t/t5550-http-fetch-dumb.sh
blob: b5758f1c9ca1c2b827495be0b67bb31f1495f965 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
#!/bin/sh

test_description='test dumb fetching over http via static file'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

if test_have_prereq !REFFILES
then
	skip_all='skipping test; dumb HTTP protocol not supported with reftable.'
	test_done
fi

. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd

test_expect_success 'setup repository' '
	git config push.default matching &&
	echo content1 >file &&
	git add file &&
	git commit -m one &&
	echo content2 >file &&
	git add file &&
	git commit -m two
'

test_expect_success 'packfile without repository does not crash' '
	echo "fatal: not a git repository" >expect &&
	test_must_fail nongit git http-fetch --packfile=abc 2>err &&
	test_cmp expect err
'

setup_post_update_server_info_hook () {
	test_hook --setup -C "$1" post-update <<-\EOF &&
	exec git update-server-info
	EOF
	git -C "$1" update-server-info
}

test_expect_success 'create http-accessible bare repository with loose objects' '
	cp -R .git "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
	git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config core.bare true &&
	setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
	git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
	git push public main:main
'

test_expect_success 'clone http repository' '
	git clone $HTTPD_URL/dumb/repo.git clone-tmpl &&
	cp -R clone-tmpl clone &&
	test_cmp file clone/file
'

test_expect_success 'list refs from outside any repository' '
	cat >expect <<-EOF &&
	$(git rev-parse main)	HEAD
	$(git rev-parse main)	refs/heads/main
	EOF
	nongit git ls-remote "$HTTPD_URL/dumb/repo.git" >actual &&
	test_cmp expect actual
'


test_expect_success 'list detached HEAD from outside any repository' '
	git clone --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
		"$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" &&
	git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" \
		update-ref --no-deref HEAD refs/heads/main &&
	git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" update-server-info &&
	cat >expect <<-EOF &&
	$(git rev-parse main)	HEAD
	$(git rev-parse main)	refs/heads/main
	EOF
	nongit git ls-remote "$HTTPD_URL/dumb/repo-detached.git" >actual &&
	test_cmp expect actual
'

test_expect_success 'create password-protected repository' '
	mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" &&
	cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
	       "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git"
'

test_expect_success 'create empty remote repository' '
	git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" &&
	setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/empty.git"
'

test_expect_success 'empty dumb HTTP repository falls back to SHA1' '
	test_when_finished "rm -fr clone-empty" &&
	git clone $HTTPD_URL/dumb/empty.git clone-empty &&
	git -C clone-empty rev-parse --show-object-format >empty-format &&
	test "$(cat empty-format)" = sha1
'

setup_askpass_helper

test_expect_success 'cloning password-protected repository can fail' '
	set_askpass wrong &&
	test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-fail &&
	expect_askpass both wrong
'

test_expect_success 'using credentials from netrc to clone successfully' '
	test_when_finished clear_netrc &&
	set_askpass wrong &&
	set_netrc 127.0.0.1 user@host pass@host &&
	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc &&
	expect_askpass none
'

test_expect_success 'netrc unauthorized credentials (prompt after 401)' '
	test_when_finished clear_netrc &&
	set_askpass wrong &&
	set_netrc 127.0.0.1 user@host pass@wrong &&
	test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-401 &&
	expect_askpass both wrong
'

test_expect_success 'netrc authorized but forbidden credentials (fail on 403)' '
	test_when_finished clear_netrc &&
	set_askpass wrong &&
	set_netrc 127.0.0.1 forbidden-user@host pass@host &&
	test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-403 2>err &&
	expect_askpass none &&
	test_grep "The requested URL returned error: 403" err
'

test_expect_success 'http auth can use user/pass in URL' '
	set_askpass wrong &&
	git clone "$HTTPD_URL_USER_PASS/auth/dumb/repo.git" clone-auth-none &&
	expect_askpass none
'

test_expect_success 'http auth can use just user in URL' '
	set_askpass wrong pass@host &&
	git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass &&
	expect_askpass pass user%40host
'

test_expect_success 'http auth can request both user and pass' '
	set_askpass user@host pass@host &&
	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both &&
	expect_askpass both user%40host
'

test_expect_success 'http auth respects credential helper config' '
	test_config_global credential.helper "!f() {
		cat >/dev/null
		echo username=user@host
		echo password=pass@host
	}; f" &&
	set_askpass wrong &&
	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-helper &&
	expect_askpass none
'

test_expect_success 'http auth can get username from config' '
	test_config_global "credential.$HTTPD_URL.username" user@host &&
	set_askpass wrong pass@host &&
	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user &&
	expect_askpass pass user%40host
'

test_expect_success 'configured username does not override URL' '
	test_config_global "credential.$HTTPD_URL.username" wrong &&
	set_askpass wrong pass@host &&
	git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 &&
	expect_askpass pass user%40host
'

test_expect_success 'set up repo with http submodules' '
	git init super &&
	set_askpass user@host pass@host &&
	(
		cd super &&
		git submodule add "$HTTPD_URL/auth/dumb/repo.git" sub &&
		git commit -m "add submodule"
	)
'

test_expect_success 'cmdline credential config passes to submodule via clone' '
	set_askpass wrong pass@host &&
	test_must_fail git clone --recursive super super-clone &&
	rm -rf super-clone &&

	set_askpass wrong pass@host &&
	git -c "credential.$HTTPD_URL.username=user@host" \
		clone --recursive super super-clone &&
	expect_askpass pass user%40host
'

test_expect_success 'cmdline credential config passes submodule via fetch' '
	set_askpass wrong pass@host &&
	test_must_fail git -C super-clone fetch --recurse-submodules &&

	set_askpass wrong pass@host &&
	git -C super-clone \
	    -c "credential.$HTTPD_URL.username=user@host" \
	    fetch --recurse-submodules &&
	expect_askpass pass user%40host
'

test_expect_success 'cmdline credential config passes submodule update' '
	# advance the submodule HEAD so that a fetch is required
	git commit --allow-empty -m foo &&
	git push "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git" HEAD &&
	sha1=$(git rev-parse HEAD) &&
	git -C super-clone update-index --cacheinfo 160000,$sha1,sub &&

	set_askpass wrong pass@host &&
	test_must_fail git -C super-clone submodule update &&

	set_askpass wrong pass@host &&
	git -C super-clone \
	    -c "credential.$HTTPD_URL.username=user@host" \
	    submodule update &&
	expect_askpass pass user%40host
'

test_expect_success 'fetch changes via http' '
	echo content >>file &&
	git commit -a -m two &&
	git push public &&
	(cd clone && git pull) &&
	test_cmp file clone/file
'

test_expect_success 'fetch changes via manual http-fetch' '
	cp -R clone-tmpl clone2 &&

	HEAD=$(git rev-parse --verify HEAD) &&
	(cd clone2 &&
	 git http-fetch -a -w heads/main-new $HEAD $(git config remote.origin.url) &&
	 git checkout main-new &&
	 test $HEAD = $(git rev-parse --verify HEAD)) &&
	test_cmp file clone2/file
'

test_expect_success 'manual http-fetch without -a works just as well' '
	cp -R clone-tmpl clone3 &&

	HEAD=$(git rev-parse --verify HEAD) &&
	(cd clone3 &&
	 git http-fetch -w heads/main-new $HEAD $(git config remote.origin.url) &&
	 git checkout main-new &&
	 test $HEAD = $(git rev-parse --verify HEAD)) &&
	test_cmp file clone3/file
'

test_expect_success 'http remote detects correct HEAD' '
	git push public main:other &&
	(cd clone &&
	 git remote set-head origin -d &&
	 git remote set-head origin -a &&
	 git symbolic-ref refs/remotes/origin/HEAD > output &&
	 echo refs/remotes/origin/main > expect &&
	 test_cmp expect output
	)
'

test_expect_success 'fetch packed objects' '
	cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
	(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
	 git --bare repack -a -d
	) &&
	git clone $HTTPD_URL/dumb/repo_pack.git
'

test_expect_success 'http-fetch --packfile' '
	# Arbitrary hash. Use rev-parse so that we get one of the correct
	# length.
	ARBITRARY=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&

	git init packfileclient &&
	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && ls objects/pack/pack-*.pack) &&
	git -C packfileclient http-fetch --packfile=$ARBITRARY \
		--index-pack-arg=index-pack --index-pack-arg=--stdin \
		--index-pack-arg=--keep \
		"$HTTPD_URL"/dumb/repo_pack.git/$p >out &&

	test_grep -E "^keep.[0-9a-f]{16,}$" out &&
	cut -c6- out >packhash &&

	# Ensure that the expected files are generated
	test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).pack" &&
	test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).idx" &&
	test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).keep" &&

	# Ensure that it has the HEAD of repo_pack, at least
	HASH=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&
	git -C packfileclient cat-file -e "$HASH"
'

test_expect_success 'http-fetch --packfile accepts an already complete partial' '
	git init packfileclient-complete &&
	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
		ls objects/pack/pack-*.pack) &&
	packhash=$(basename "$p" .pack) &&
	packhash=${packhash#pack-} &&
	tmpfile="packfileclient-complete/.git/objects/pack/pack-$packhash.pack.temp" &&
	cp "$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" "$tmpfile" &&
	chmod u+w "$tmpfile" &&
	GIT_TRACE_CURL="$TRASH_DIRECTORY/complete.trace" \
	git -C packfileclient-complete http-fetch --packfile="$packhash" \
		--index-pack-arg=index-pack \
		--index-pack-arg=--stdin --index-pack-arg=--keep \
		"$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
	test_grep "416 Requested Range Not Satisfiable" complete.trace &&
	test_path_is_missing "$tmpfile" &&
	git -C packfileclient-complete cat-file -e "$HASH"
'

test_expect_success 'http-fetch --packfile resumes a partial download' '
	git init packfileclient-resume &&
	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
		ls objects/pack/pack-*.pack) &&
	tmpfile="packfileclient-resume/.git/objects/pack/pack-$ARBITRARY.pack.temp" &&
	test_copy_bytes 64 <"$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" >"$tmpfile" &&
	GIT_TRACE_CURL="$TRASH_DIRECTORY/resume.trace" \
	git -C packfileclient-resume http-fetch --packfile="$ARBITRARY" \
		--index-pack-arg=index-pack --index-pack-arg=--stdin \
		--index-pack-arg=--keep \
		"$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
	test_grep "Range: bytes=64-" resume.trace &&
	test_path_is_missing "$tmpfile" &&
	git -C packfileclient-resume cat-file -e "$HASH"
'

test_expect_success 'http-fetch --packfile permits unlink while indexing' '
	git init packfileclient-unlink &&
	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
		ls objects/pack/pack-*.pack) &&
	tmpfile="packfileclient-unlink/.git/objects/pack/pack-$ARBITRARY.pack.temp" &&
	write_script git-unlink-index-pack <<-\EOF &&
	test -f "$GIT_TEST_PACK_TEMP" || exit 1
	rm "$GIT_TEST_PACK_TEMP" || exit 1
	exec git index-pack "$@"
	EOF
	test_when_finished "rm -f git-unlink-index-pack" &&
	PATH="$TRASH_DIRECTORY:$PATH" \
	GIT_TEST_PACK_TEMP="$TRASH_DIRECTORY/$tmpfile" \
	git -C packfileclient-unlink http-fetch --packfile="$ARBITRARY" \
		--index-pack-arg=unlink-index-pack \
		--index-pack-arg=--stdin --index-pack-arg=--keep \
		"$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
	test_path_is_missing "$tmpfile" &&
	git -C packfileclient-unlink cat-file -e "$HASH"
'

test_expect_success PERL,PIPE 'concurrent http-fetch --packfile cannot corrupt an overlapping download' '
	git init packfileclient-overlap &&
	blob=$(test-tool genrandom pack-overlap 2m |
		git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git \
			hash-object -w --stdin) &&
	packhash=$(printf "%s\n" "$blob" |
		git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git \
			pack-objects "$TRASH_DIRECTORY/overlap-pack") &&
	pack="$TRASH_DIRECTORY/overlap-pack-$packhash.pack" &&
	tmpfile="packfileclient-overlap/.git/objects/pack/pack-$packhash.pack.temp" &&
	mkfifo server-ready first-ready &&
	exec 7<>server-ready &&
	exec 8<>first-ready &&
	write_script slow-pack-server "$PERL_PATH" <<-\EOF &&
	use strict;
	use warnings;
	use IO::Socket::INET;

	my ($packfile, $server_ready, $first_ready) = @ARGV;
	my $completed = 0;
	END {
		if (!$completed) {
			signal_ready($server_ready, "failed");
			signal_ready($first_ready, "failed");
		}
	}

	$SIG{ALRM} = sub { die "timed out serving concurrent pack requests\n" };
	alarm 60;

	open(my $in, "<:raw", $packfile) or die "open $packfile: $!";
	my $pack = do { local $/; <$in> };
	close($in) or die "close $packfile: $!";
	my $server = IO::Socket::INET->new(LocalAddr => "127.0.0.1",
		LocalPort => 0, Proto => "tcp", Listen => 2, ReuseAddr => 1)
		or die "listen: $!";

	sub signal_ready {
		my ($file, $value) = @_;
		open(my $out, ">", $file) or die "open $file: $!";
		print $out "$value\n" or die "write $file: $!";
		close($out) or die "close $file: $!";
	}

	sub write_all {
		my ($out, $data) = @_;
		my $offset = 0;
		while ($offset < length($data)) {
			my $written = syswrite($out, $data,
				length($data) - $offset, $offset);
			defined($written) && $written or die "write response: $!";
			$offset += $written;
		}
	}

	sub start_response {
		my $out = $server->accept() or die "accept: $!";
		<$out> or die "read request: $!";
		my $start = 0;
		while (<$out>) {
			last if /^\r?\n$/;
			$start = $1 if /^Range: bytes=(\d+)-/i;
		}
		$start < length($pack) or die "invalid range $start";
		my $length = length($pack) - $start;
		my $middle = int($length / 2);
		my $status = $start ? "206 Partial Content" : "200 OK";
		my $headers = "HTTP/1.1 $status\r\n" .
			"Content-Length: $length\r\n" .
			($start ? "Content-Range: bytes $start-" .
				(length($pack) - 1) . "/" . length($pack) . "\r\n" : "") .
			"Connection: close\r\n\r\n";
		write_all($out, $headers);
		write_all($out, substr($pack, $start, $middle));
		return ($out, $start + $middle);
	}

	signal_ready($server_ready, $server->sockport());
	my ($first, $first_pos) = start_response();
	signal_ready($first_ready, "ready");
	my ($second, $second_pos) = start_response();
	write_all($first, substr($pack, $first_pos));
	write_all($second, substr($pack, $second_pos));
	close($first) or die "close first response: $!";
	close($second) or die "close second response: $!";
	$completed = 1;
	alarm 0;
	EOF
	{
		"$TRASH_DIRECTORY/slow-pack-server" "$pack" \
			"$TRASH_DIRECTORY/server-ready" \
			"$TRASH_DIRECTORY/first-ready" >server.log 2>&1 &
		server_pid=$!
	} &&
	test_when_finished "
		kill $server_pid 2>/dev/null || :
		wait $server_pid 2>/dev/null || :
		exec 7>&-
		exec 8>&-
		rm -f server-ready first-ready slow-pack-server
	" &&
	read port <&7 &&
	url="http://127.0.0.1:$port/pack" &&
	{
		(
			if ! GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-first.trace" \
			GIT_TRACE_CURL_NO_DATA=1 \
			git -C packfileclient-overlap http-fetch --packfile="$packhash" \
				--index-pack-arg=index-pack \
				--index-pack-arg=--stdin --index-pack-arg=--keep \
				"$url" >first.out
			then
				echo failed >"$TRASH_DIRECTORY/first-ready" &&
				exit 1
			fi
		) &
		first_pid=$!
	} &&
	test_when_finished "
		kill $first_pid 2>/dev/null || :
		wait $first_pid 2>/dev/null || :
	" &&
	read ready <&8 &&
	test "$ready" = ready &&
	test_path_is_file "$tmpfile" &&
	{
		GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-second.trace" \
		GIT_TRACE_CURL_NO_DATA=1 \
		git -C packfileclient-overlap http-fetch --packfile="$packhash" \
			--index-pack-arg=index-pack \
			--index-pack-arg=--stdin --index-pack-arg=--keep \
			"$url" >second.out &
		second_pid=$!
	} &&
	test_when_finished "
		kill $second_pid 2>/dev/null || :
		wait $second_pid 2>/dev/null || :
	" &&
	wait "$second_pid" &&
	wait "$first_pid" &&
	wait "$server_pid" &&
	printf "keep\t%s\npack\t%s\n" "$packhash" "$packhash" | sort >expect &&
	sort first.out second.out >actual &&
	test_cmp expect actual &&
	test_path_is_missing "$tmpfile" &&
	git -C packfileclient-overlap cat-file -e "$blob"
'

test_expect_success 'fetch notices corrupt pack' '
	cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
	(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
	 p=$(ls objects/pack/pack-*.pack) &&
	 chmod u+w $p &&
	 printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
	) &&
	mkdir repo_bad1.git &&
	(cd repo_bad1.git &&
	 git --bare init &&
	 test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git &&
	 test 0 = $(ls objects/pack/pack-*.pack | wc -l)
	)
'

test_expect_success 'http-fetch --packfile with corrupt pack' '
	rm -rf packfileclient &&
	git init packfileclient &&
	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && ls objects/pack/pack-*.pack) &&
	test_must_fail git -C packfileclient http-fetch --packfile \
		"$HTTPD_URL"/dumb/repo_bad1.git/$p
'

test_expect_success 'fetch notices corrupt idx' '
	cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
	(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
	 p=$(ls objects/pack/pack-*.idx) &&
	 chmod u+w $p &&
	 printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
	) &&
	mkdir repo_bad2.git &&
	(cd repo_bad2.git &&
	 git --bare init &&
	 test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git &&
	 test 0 = $(ls objects/pack | wc -l)
	)
'

# usage: count_fetches <nr> <extension> <trace_file>
count_fetches () {
	# ignore grep exit code; it may return non-zero if we are expecting no
	# matches
	grep "GET .*objects/pack/pack-[a-z0-9]*.$2" "$3" >trace.count
	test_line_count = "$1" trace.count
}

test_expect_success 'fetch can handle previously-fetched .idx files' '
	git checkout --orphan branch1 &&
	echo base >file &&
	git add file &&
	git commit -m base &&
	git --bare init "$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git &&
	git push "$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git branch1 &&
	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git repack -d &&
	git checkout -b branch2 branch1 &&
	echo b2 >>file &&
	git commit -a -m b2 &&
	git push "$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git branch2 &&
	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git repack -d &&
	git --bare init clone_packed_branches.git &&
	GIT_TRACE_CURL=$PWD/one.trace git --git-dir=clone_packed_branches.git \
		fetch "$HTTPD_URL"/dumb/repo_packed_branches.git branch1:branch1 &&
	count_fetches 2 idx one.trace &&
	count_fetches 1 pack one.trace &&
	GIT_TRACE_CURL=$PWD/two.trace git --git-dir=clone_packed_branches.git \
		fetch "$HTTPD_URL"/dumb/repo_packed_branches.git branch2:branch2 &&
	count_fetches 1 idx two.trace &&
	count_fetches 1 pack two.trace
'

test_expect_success 'did not use upload-pack service' '
	test_grep ! "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log"
'

test_expect_success ICONV 'git client shows text/plain errors' '
	test_must_fail git clone "$HTTPD_URL/error/text" 2>stderr &&
	test_grep "this is the error message" stderr
'

test_expect_success ICONV 'git client does not show html errors' '
	test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr &&
	test_grep ! "this is the error message" stderr
'

test_expect_success ICONV 'git client shows text/plain with a charset' '
	test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr &&
	test_grep "this is the error message" stderr
'

test_expect_success ICONV 'http error messages are reencoded' '
	test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr &&
	test_grep "this is the error message" stderr
'

test_expect_success ICONV 'reencoding is robust to whitespace oddities' '
	test_must_fail git clone "$HTTPD_URL/error/odd-spacing" 2>stderr &&
	test_grep "this is the error message" stderr
'

check_language () {
	case "$2" in
	'')
		>expect
		;;
	?*)
		echo "=> Send header: Accept-Language: $1" >expect
		;;
	esac &&
	GIT_TRACE_CURL=true \
	LANGUAGE=$2 \
	git ls-remote "$HTTPD_URL/dumb/repo.git" >output 2>&1 &&
	tr -d '\015' <output |
	sort -u |
	sed -ne '/^=> Send header: Accept-Language:/ p' >actual &&
	test_cmp expect actual
}

test_expect_success 'git client sends Accept-Language based on LANGUAGE' '
	check_language "ko-KR, *;q=0.9" ko_KR.UTF-8'

test_expect_success 'git client sends Accept-Language correctly with unordinary LANGUAGE' '
	check_language "ko-KR, *;q=0.9" "ko_KR:" &&
	check_language "ko-KR, en-US;q=0.9, *;q=0.8" "ko_KR::en_US" &&
	check_language "ko-KR, *;q=0.9" ":::ko_KR" &&
	check_language "ko-KR, en-US;q=0.9, *;q=0.8" "ko_KR!!:en_US" &&
	check_language "ko-KR, ja-JP;q=0.9, *;q=0.8" "ko_KR en_US:ja_JP"'

test_expect_success 'git client sends Accept-Language with many preferred languages' '
	check_language "ko-KR, en-US;q=0.9, fr-CA;q=0.8, de;q=0.7, sr;q=0.6, \
ja;q=0.5, zh;q=0.4, sv;q=0.3, pt;q=0.2, *;q=0.1" \
		ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt &&
	check_language "ko-KR, en-US;q=0.99, fr-CA;q=0.98, de;q=0.97, sr;q=0.96, \
ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \
		ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt:nb
'

test_expect_success 'git client send an empty Accept-Language' '
	GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr &&
	test_grep ! "^=> Send header: Accept-Language:" stderr
'

test_expect_success 'remote-http complains cleanly about malformed urls' '
	test_must_fail git remote-http http::/example.com/repo.git 2>stderr &&
	test_grep "url has no scheme" stderr
'

# NEEDSWORK: Writing commands to git-remote-curl can race against the latter
# erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has
# learned to handle early remote helper failures more cleanly.
test_expect_success 'remote-http complains cleanly about empty scheme' '
	test_must_fail ok=sigpipe git ls-remote \
		http::${HTTPD_URL#http}/dumb/repo.git 2>stderr &&
	test_grep "url has no scheme" stderr
'

test_expect_success 'redirects can be forbidden/allowed' '
	test_must_fail git -c http.followRedirects=false \
		clone $HTTPD_URL/dumb-redir/repo.git dumb-redir &&
	git -c http.followRedirects=true \
		clone $HTTPD_URL/dumb-redir/repo.git dumb-redir 2>stderr
'

test_expect_success 'redirects are reported to stderr' '
	# just look for a snippet of the redirected-to URL
	test_grep /dumb/ stderr
'

test_expect_success 'non-initial redirects can be forbidden' '
	test_must_fail git -c http.followRedirects=initial \
		clone $HTTPD_URL/redir-objects/repo.git redir-objects &&
	git -c http.followRedirects=true \
		clone $HTTPD_URL/redir-objects/repo.git redir-objects
'

test_expect_success 'http.followRedirects defaults to "initial"' '
	test_must_fail git clone $HTTPD_URL/redir-objects/repo.git default
'

# The goal is for a clone of the "evil" repository, which has no objects
# itself, to cause the client to fetch objects from the "victim" repository.
test_expect_success 'set up evil alternates scheme' '
	victim=$HTTPD_DOCUMENT_ROOT_PATH/victim.git &&
	git init --bare "$victim" &&
	git -C "$victim" --work-tree=. commit --allow-empty -m secret &&
	git -C "$victim" repack -ad &&
	git -C "$victim" update-server-info &&
	sha1=$(git -C "$victim" rev-parse HEAD) &&

	evil=$HTTPD_DOCUMENT_ROOT_PATH/evil.git &&
	git init --template= --bare "$evil" &&
	mkdir "$evil/info" &&
	# do this by hand to avoid object existence check
	printf "%s\\t%s\\n" $sha1 refs/heads/main >"$evil/info/refs"
'

# Here we'll just redirect via HTTP. In a real-world attack these would be on
# different servers, but we should reject it either way.
test_expect_success 'http-alternates is a non-initial redirect' '
	echo "$HTTPD_URL/dumb/victim.git/objects" \
		>"$evil/objects/info/http-alternates" &&
	test_must_fail git -c http.followRedirects=initial \
		clone $HTTPD_URL/dumb/evil.git evil-initial &&
	git -c http.followRedirects=true \
		clone $HTTPD_URL/dumb/evil.git evil-initial
'

# Curl supports a lot of protocols that we'd prefer not to allow
# http-alternates to use, but it's hard to test whether curl has
# accessed, say, the SMTP protocol, because we are not running an SMTP server.
# But we can check that it does not allow access to file://, which would
# otherwise allow this clone to complete.
test_expect_success 'http-alternates cannot point at funny protocols' '
	echo "file://$victim/objects" >"$evil/objects/info/http-alternates" &&
	test_must_fail git -c http.followRedirects=true \
		clone "$HTTPD_URL/dumb/evil.git" evil-file
'

test_expect_success 'http-alternates triggers not-from-user protocol check' '
	echo "$HTTPD_URL/dumb/victim.git/objects" \
		>"$evil/objects/info/http-alternates" &&
	test_config_global http.followRedirects true &&
	test_must_fail git -c protocol.http.allow=user \
		clone $HTTPD_URL/dumb/evil.git evil-user &&
	git -c protocol.http.allow=always \
		clone $HTTPD_URL/dumb/evil.git evil-user
'

test_expect_success 'can redirect through non-"info/refs?service=git-upload-pack" URL' '
	git clone "$HTTPD_URL/redir-to/dumb/repo.git"
'

test_expect_success 'print HTTP error when any intermediate redirect throws error' '
	test_must_fail git clone "$HTTPD_URL/redir-to/502" 2> stderr &&
	test_grep "unable to access.*/redir-to/502" stderr
'

test_expect_success 'fetching via http alternates works' '
	parent=$HTTPD_DOCUMENT_ROOT_PATH/alt-parent.git &&
	git init --bare "$parent" &&
	git -C "$parent" --work-tree=. commit --allow-empty -m foo &&
	git -C "$parent" update-server-info &&
	commit=$(git -C "$parent" rev-parse HEAD) &&

	child=$HTTPD_DOCUMENT_ROOT_PATH/alt-child.git &&
	git init --bare "$child" &&
	echo "../../alt-parent.git/objects" >"$child/objects/info/alternates" &&
	git -C "$child" update-ref HEAD $commit &&
	git -C "$child" update-server-info &&

	git -c http.followredirects=true clone "$HTTPD_URL/dumb/alt-child.git"
'

test_expect_success 'dumb http can fetch index v1' '
	server=$HTTPD_DOCUMENT_ROOT_PATH/idx-v1.git &&
	git init --bare "$server" &&
	git -C "$server" --work-tree=. commit --allow-empty -m foo &&
	git -C "$server" -c pack.indexVersion=1 gc &&

	git clone "$HTTPD_URL/dumb/idx-v1.git" &&
	git -C idx-v1 fsck
'

test_expect_success 'absolute-path alternate when url has no path' '
	src=$HTTPD_DOCUMENT_ROOT_PATH/repo.git &&
	alt=absolute-alt.git &&
	git clone --bare --shared "$src" "$alt" &&

	# Our repo has an alternate pointing to the absolute filesystem path,
	# but that will not make any sense to an http client. So we will
	# manually give it the equivalent path that the http server will
	# understand.
	echo "/dumb/repo.git/objects" >"$alt/objects/info/http-alternates" &&

	# Now make our alt repository available at the root of the http
	# server without any path (i.e., just http://localhost:1234).
	git -C "$alt" update-server-info &&
	mv absolute-alt.git/* "$HTTPD_DOCUMENT_ROOT_PATH" &&

	git -c http.followRedirects=true clone "$HTTPD_URL" alt-clone.git 2>err &&
	test_grep "adding alternate object store: $HTTPD_URL/dumb/repo.git" err
'

test_done