Raw
1 #!/bin/sh
2
3 test_description='test dumb fetching over http via static file'
4 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
5 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
6
7 . ./test-lib.sh
8
9 if test_have_prereq !REFFILES
10 then
11 skip_all='skipping test; dumb HTTP protocol not supported with reftable.'
12 test_done
13 fi
14
15 . "$TEST_DIRECTORY"/lib-httpd.sh
16 start_httpd
17
18 test_expect_success 'setup repository' '
19 git config push.default matching &&
20 echo content1 >file &&
21 git add file &&
22 git commit -m one &&
23 echo content2 >file &&
24 git add file &&
25 git commit -m two
26 '
27
28 test_expect_success 'packfile without repository does not crash' '
29 echo "fatal: not a git repository" >expect &&
30 test_must_fail nongit git http-fetch --packfile=abc 2>err &&
31 test_cmp expect err
32 '
33
34 setup_post_update_server_info_hook () {
35 test_hook --setup -C "$1" post-update <<-\EOF &&
36 exec git update-server-info
37 EOF
38 git -C "$1" update-server-info
39 }
40
41 test_expect_success 'create http-accessible bare repository with loose objects' '
42 cp -R .git "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
43 git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config core.bare true &&
44 setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
45 git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
46 git push public main:main
47 '
48
49 test_expect_success 'clone http repository' '
50 git clone $HTTPD_URL/dumb/repo.git clone-tmpl &&
51 cp -R clone-tmpl clone &&
52 test_cmp file clone/file
53 '
54
55 test_expect_success 'list refs from outside any repository' '
56 cat >expect <<-EOF &&
57 $(git rev-parse main) HEAD
58 $(git rev-parse main) refs/heads/main
59 EOF
60 nongit git ls-remote "$HTTPD_URL/dumb/repo.git" >actual &&
61 test_cmp expect actual
62 '
63
64
65 test_expect_success 'list detached HEAD from outside any repository' '
66 git clone --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
67 "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" &&
68 git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" \
69 update-ref --no-deref HEAD refs/heads/main &&
70 git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" update-server-info &&
71 cat >expect <<-EOF &&
72 $(git rev-parse main) HEAD
73 $(git rev-parse main) refs/heads/main
74 EOF
75 nongit git ls-remote "$HTTPD_URL/dumb/repo-detached.git" >actual &&
76 test_cmp expect actual
77 '
78
79 test_expect_success 'create password-protected repository' '
80 mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" &&
81 cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
82 "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git"
83 '
84
85 test_expect_success 'create empty remote repository' '
86 git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" &&
87 setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/empty.git"
88 '
89
90 test_expect_success 'empty dumb HTTP repository falls back to SHA1' '
91 test_when_finished "rm -fr clone-empty" &&
92 git clone $HTTPD_URL/dumb/empty.git clone-empty &&
93 git -C clone-empty rev-parse --show-object-format >empty-format &&
94 test "$(cat empty-format)" = sha1
95 '
96
97 setup_askpass_helper
98
99 test_expect_success 'cloning password-protected repository can fail' '
100 set_askpass wrong &&
101 test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-fail &&
102 expect_askpass both wrong
103 '
104
105 test_expect_success 'using credentials from netrc to clone successfully' '
106 test_when_finished clear_netrc &&
107 set_askpass wrong &&
108 set_netrc 127.0.0.1 user@host pass@host &&
109 git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc &&
110 expect_askpass none
111 '
112
113 test_expect_success 'netrc unauthorized credentials (prompt after 401)' '
114 test_when_finished clear_netrc &&
115 set_askpass wrong &&
116 set_netrc 127.0.0.1 user@host pass@wrong &&
117 test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-401 &&
118 expect_askpass both wrong
119 '
120
121 test_expect_success 'netrc authorized but forbidden credentials (fail on 403)' '
122 test_when_finished clear_netrc &&
123 set_askpass wrong &&
124 set_netrc 127.0.0.1 forbidden-user@host pass@host &&
125 test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-403 2>err &&
126 expect_askpass none &&
127 test_grep "The requested URL returned error: 403" err
128 '
129
130 test_expect_success 'http auth can use user/pass in URL' '
131 set_askpass wrong &&
132 git clone "$HTTPD_URL_USER_PASS/auth/dumb/repo.git" clone-auth-none &&
133 expect_askpass none
134 '
135
136 test_expect_success 'http auth can use just user in URL' '
137 set_askpass wrong pass@host &&
138 git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass &&
139 expect_askpass pass user%40host
140 '
141
142 test_expect_success 'http auth can request both user and pass' '
143 set_askpass user@host pass@host &&
144 git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both &&
145 expect_askpass both user%40host
146 '
147
148 test_expect_success 'http auth respects credential helper config' '
149 test_config_global credential.helper "!f() {
150 cat >/dev/null
151 echo username=user@host
152 echo password=pass@host
153 }; f" &&
154 set_askpass wrong &&
155 git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-helper &&
156 expect_askpass none
157 '
158
159 test_expect_success 'http auth can get username from config' '
160 test_config_global "credential.$HTTPD_URL.username" user@host &&
161 set_askpass wrong pass@host &&
162 git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user &&
163 expect_askpass pass user%40host
164 '
165
166 test_expect_success 'configured username does not override URL' '
167 test_config_global "credential.$HTTPD_URL.username" wrong &&
168 set_askpass wrong pass@host &&
169 git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 &&
170 expect_askpass pass user%40host
171 '
172
173 test_expect_success 'set up repo with http submodules' '
174 git init super &&
175 set_askpass user@host pass@host &&
176 (
177 cd super &&
178 git submodule add "$HTTPD_URL/auth/dumb/repo.git" sub &&
179 git commit -m "add submodule"
180 )
181 '
182
183 test_expect_success 'cmdline credential config passes to submodule via clone' '
184 set_askpass wrong pass@host &&
185 test_must_fail git clone --recursive super super-clone &&
186 rm -rf super-clone &&
187
188 set_askpass wrong pass@host &&
189 git -c "credential.$HTTPD_URL.username=user@host" \
190 clone --recursive super super-clone &&
191 expect_askpass pass user%40host
192 '
193
194 test_expect_success 'cmdline credential config passes submodule via fetch' '
195 set_askpass wrong pass@host &&
196 test_must_fail git -C super-clone fetch --recurse-submodules &&
197
198 set_askpass wrong pass@host &&
199 git -C super-clone \
200 -c "credential.$HTTPD_URL.username=user@host" \
201 fetch --recurse-submodules &&
202 expect_askpass pass user%40host
203 '
204
205 test_expect_success 'cmdline credential config passes submodule update' '
206 # advance the submodule HEAD so that a fetch is required
207 git commit --allow-empty -m foo &&
208 git push "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git" HEAD &&
209 sha1=$(git rev-parse HEAD) &&
210 git -C super-clone update-index --cacheinfo 160000,$sha1,sub &&
211
212 set_askpass wrong pass@host &&
213 test_must_fail git -C super-clone submodule update &&
214
215 set_askpass wrong pass@host &&
216 git -C super-clone \
217 -c "credential.$HTTPD_URL.username=user@host" \
218 submodule update &&
219 expect_askpass pass user%40host
220 '
221
222 test_expect_success 'fetch changes via http' '
223 echo content >>file &&
224 git commit -a -m two &&
225 git push public &&
226 (cd clone && git pull) &&
227 test_cmp file clone/file
228 '
229
230 test_expect_success 'fetch changes via manual http-fetch' '
231 cp -R clone-tmpl clone2 &&
232
233 HEAD=$(git rev-parse --verify HEAD) &&
234 (cd clone2 &&
235 git http-fetch -a -w heads/main-new $HEAD $(git config remote.origin.url) &&
236 git checkout main-new &&
237 test $HEAD = $(git rev-parse --verify HEAD)) &&
238 test_cmp file clone2/file
239 '
240
241 test_expect_success 'manual http-fetch without -a works just as well' '
242 cp -R clone-tmpl clone3 &&
243
244 HEAD=$(git rev-parse --verify HEAD) &&
245 (cd clone3 &&
246 git http-fetch -w heads/main-new $HEAD $(git config remote.origin.url) &&
247 git checkout main-new &&
248 test $HEAD = $(git rev-parse --verify HEAD)) &&
249 test_cmp file clone3/file
250 '
251
252 test_expect_success 'http remote detects correct HEAD' '
253 git push public main:other &&
254 (cd clone &&
255 git remote set-head origin -d &&
256 git remote set-head origin -a &&
257 git symbolic-ref refs/remotes/origin/HEAD > output &&
258 echo refs/remotes/origin/main > expect &&
259 test_cmp expect output
260 )
261 '
262
263 test_expect_success 'fetch packed objects' '
264 cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
265 (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
266 git --bare repack -a -d
267 ) &&
268 git clone $HTTPD_URL/dumb/repo_pack.git
269 '
270
271 test_expect_success 'http-fetch --packfile' '
272 # Arbitrary hash. Use rev-parse so that we get one of the correct
273 # length.
274 ARBITRARY=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&
275
276 git init packfileclient &&
277 p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && ls objects/pack/pack-*.pack) &&
278 git -C packfileclient http-fetch --packfile=$ARBITRARY \
279 --index-pack-arg=index-pack --index-pack-arg=--stdin \
280 --index-pack-arg=--keep \
281 "$HTTPD_URL"/dumb/repo_pack.git/$p >out &&
282
283 test_grep -E "^keep.[0-9a-f]{16,}$" out &&
284 cut -c6- out >packhash &&
285
286 # Ensure that the expected files are generated
287 test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).pack" &&
288 test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).idx" &&
289 test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).keep" &&
290
291 # Ensure that it has the HEAD of repo_pack, at least
292 HASH=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&
293 git -C packfileclient cat-file -e "$HASH"
294 '
295
296 test_expect_success 'http-fetch --packfile resumes a partial download' '
297 git init packfileclient-resume &&
298 p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
299 ls objects/pack/pack-*.pack) &&
300 tmpfile="packfileclient-resume/.git/objects/pack/pack-$ARBITRARY.pack.temp" &&
301 test_copy_bytes 64 <"$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" >"$tmpfile" &&
302 GIT_TRACE_CURL="$TRASH_DIRECTORY/resume.trace" \
303 git -C packfileclient-resume http-fetch --packfile="$ARBITRARY" \
304 --index-pack-arg=index-pack --index-pack-arg=--stdin \
305 --index-pack-arg=--keep \
306 "$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
307 test_grep "Range: bytes=64-" resume.trace &&
308 test_path_is_missing "$tmpfile" &&
309 git -C packfileclient-resume cat-file -e "$HASH"
310 '
311
312 test_expect_success 'http-fetch --packfile permits unlink while indexing' '
313 git init packfileclient-unlink &&
314 p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
315 ls objects/pack/pack-*.pack) &&
316 tmpfile="packfileclient-unlink/.git/objects/pack/pack-$ARBITRARY.pack.temp" &&
317 write_script git-unlink-index-pack <<-\EOF &&
318 test -f "$GIT_TEST_PACK_TEMP" || exit 1
319 rm "$GIT_TEST_PACK_TEMP" || exit 1
320 exec git index-pack "$@"
321 EOF
322 test_when_finished "rm -f git-unlink-index-pack" &&
323 PATH="$TRASH_DIRECTORY:$PATH" \
324 GIT_TEST_PACK_TEMP="$TRASH_DIRECTORY/$tmpfile" \
325 git -C packfileclient-unlink http-fetch --packfile="$ARBITRARY" \
326 --index-pack-arg=unlink-index-pack \
327 --index-pack-arg=--stdin --index-pack-arg=--keep \
328 "$HTTPD_URL/dumb/repo_pack.git/$p" >out &&
329 test_path_is_missing "$tmpfile" &&
330 git -C packfileclient-unlink cat-file -e "$HASH"
331 '
332
333 test_expect_success PIPE 'concurrent http-fetch --packfile accepts a complete partial' '
334 git init packfileclient-concurrent &&
335 p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
336 ls objects/pack/pack-*.pack) &&
337 packhash=$(basename "$p" .pack) &&
338 packhash=${packhash#pack-} &&
339 tmpfile="packfileclient-concurrent/.git/objects/pack/pack-$packhash.pack.temp" &&
340 test_copy_bytes 64 <"$HTTPD_DOCUMENT_ROOT_PATH/repo_pack.git/$p" >"$tmpfile" &&
341 mkfifo first-ready first-continue &&
342 exec 8<>first-ready &&
343 exec 9<>first-continue &&
344 write_script git-wait-index-pack <<-\EOF &&
345 echo ready >"$GIT_TEST_WAIT_READY" &&
346 read continue <"$GIT_TEST_WAIT_CONTINUE" &&
347 exec git index-pack "$@"
348 EOF
349 {
350 (
351 if ! PATH="$TRASH_DIRECTORY:$PATH" \
352 GIT_TEST_WAIT_READY="$TRASH_DIRECTORY/first-ready" \
353 GIT_TEST_WAIT_CONTINUE="$TRASH_DIRECTORY/first-continue" \
354 GIT_TRACE_CURL="$TRASH_DIRECTORY/first.trace" \
355 git -C packfileclient-concurrent http-fetch --packfile="$packhash" \
356 --index-pack-arg=wait-index-pack \
357 --index-pack-arg=--stdin --index-pack-arg=--keep \
358 "$HTTPD_URL/dumb/repo_pack.git/$p" >first.out
359 then
360 echo failed >"$TRASH_DIRECTORY/first-ready" &&
361 exit 1
362 fi
363 ) &
364 first_pid=$!
365 } &&
366 test_when_finished "
367 echo continue >&9
368 kill $first_pid 2>/dev/null || :
369 wait $first_pid 2>/dev/null || :
370 exec 8>&-
371 exec 9>&-
372 rm -f first-ready first-continue git-wait-index-pack
373 " &&
374 read ready <&8 &&
375 test "$ready" = ready &&
376 GIT_TRACE_CURL="$TRASH_DIRECTORY/second.trace" \
377 git -C packfileclient-concurrent http-fetch --packfile="$packhash" \
378 --index-pack-arg=index-pack \
379 --index-pack-arg=--stdin --index-pack-arg=--keep \
380 "$HTTPD_URL/dumb/repo_pack.git/$p" >second.out &&
381 echo continue >&9 &&
382 wait "$first_pid" &&
383 printf "pack\t%s\n" "$packhash" >expect &&
384 test_cmp expect first.out &&
385 printf "keep\t%s\n" "$packhash" >expect &&
386 test_cmp expect second.out &&
387 test_grep "Range: bytes=64-" first.trace &&
388 test_grep "Range: bytes=[0-9]*-" second.trace &&
389 test_grep "HTTP/[0-9.]* 416" second.trace &&
390 test_path_is_missing "$tmpfile" &&
391 git -C packfileclient-concurrent cat-file -e "$HASH"
392 '
393
394 test_expect_success PERL,PIPE 'concurrent http-fetch --packfile cannot corrupt an overlapping download' '
395 git init packfileclient-overlap &&
396 blob=$(test-tool genrandom pack-overlap 2m |
397 git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git \
398 hash-object -w --stdin) &&
399 packhash=$(printf "%s\n" "$blob" |
400 git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git \
401 pack-objects "$TRASH_DIRECTORY/overlap-pack") &&
402 pack="$TRASH_DIRECTORY/overlap-pack-$packhash.pack" &&
403 tmpfile="packfileclient-overlap/.git/objects/pack/pack-$packhash.pack.temp" &&
404 mkfifo server-ready first-ready &&
405 exec 7<>server-ready &&
406 exec 8<>first-ready &&
407 write_script slow-pack-server "$PERL_PATH" <<-\EOF &&
408 use strict;
409 use warnings;
410 use IO::Socket::INET;
411
412 my ($packfile, $server_ready, $first_ready) = @ARGV;
413 open(my $in, "<:raw", $packfile) or die "open $packfile: $!";
414 my $pack = do { local $/; <$in> };
415 close($in) or die "close $packfile: $!";
416 my $server = IO::Socket::INET->new(LocalAddr => "127.0.0.1",
417 LocalPort => 0, Proto => "tcp", Listen => 2, ReuseAddr => 1)
418 or die "listen: $!";
419
420 sub signal_ready {
421 my ($file, $value) = @_;
422 open(my $out, ">", $file) or die "open $file: $!";
423 print $out "$value\n" or die "write $file: $!";
424 close($out) or die "close $file: $!";
425 }
426
427 sub write_all {
428 my ($out, $data) = @_;
429 my $offset = 0;
430 while ($offset < length($data)) {
431 my $written = syswrite($out, $data,
432 length($data) - $offset, $offset);
433 defined($written) && $written or die "write response: $!";
434 $offset += $written;
435 }
436 }
437
438 sub start_response {
439 my $out = $server->accept() or die "accept: $!";
440 <$out> or die "read request: $!";
441 my $start = 0;
442 while (<$out>) {
443 last if /^\r?\n$/;
444 $start = $1 if /^Range: bytes=(\d+)-/i;
445 }
446 $start < length($pack) or die "invalid range $start";
447 my $length = length($pack) - $start;
448 my $middle = int($length / 2);
449 my $status = $start ? "206 Partial Content" : "200 OK";
450 my $headers = "HTTP/1.1 $status\r\n" .
451 "Content-Length: $length\r\n" .
452 ($start ? "Content-Range: bytes $start-" .
453 (length($pack) - 1) . "/" . length($pack) . "\r\n" : "") .
454 "Connection: close\r\n\r\n";
455 write_all($out, $headers);
456 write_all($out, substr($pack, $start, $middle));
457 return ($out, $start + $middle);
458 }
459
460 signal_ready($server_ready, $server->sockport());
461 my ($first, $first_pos) = start_response();
462 signal_ready($first_ready, "ready");
463 my ($second, $second_pos) = start_response();
464 write_all($first, substr($pack, $first_pos));
465 write_all($second, substr($pack, $second_pos));
466 close($first) or die "close first response: $!";
467 close($second) or die "close second response: $!";
468 EOF
469 {
470 (
471 if ! "$TRASH_DIRECTORY/slow-pack-server" "$pack" \
472 "$TRASH_DIRECTORY/server-ready" \
473 "$TRASH_DIRECTORY/first-ready"
474 then
475 echo failed >"$TRASH_DIRECTORY/server-ready" &&
476 echo failed >"$TRASH_DIRECTORY/first-ready" &&
477 exit 1
478 fi
479 ) >server.log 2>&1 &
480 server_pid=$!
481 } &&
482 test_when_finished "
483 kill $server_pid 2>/dev/null || :
484 wait $server_pid 2>/dev/null || :
485 exec 7>&-
486 exec 8>&-
487 rm -f server-ready first-ready slow-pack-server
488 " &&
489 read port <&7 &&
490 url="http://127.0.0.1:$port/pack" &&
491 {
492 (
493 if ! GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-first.trace" \
494 GIT_TRACE_CURL_NO_DATA=1 \
495 git -C packfileclient-overlap http-fetch --packfile="$packhash" \
496 --index-pack-arg=index-pack \
497 --index-pack-arg=--stdin --index-pack-arg=--keep \
498 "$url" >first.out
499 then
500 echo failed >"$TRASH_DIRECTORY/first-ready" &&
501 exit 1
502 fi
503 ) &
504 first_pid=$!
505 } &&
506 test_when_finished "
507 kill $first_pid 2>/dev/null || :
508 wait $first_pid 2>/dev/null || :
509 " &&
510 read ready <&8 &&
511 test "$ready" = ready &&
512 test_path_is_file "$tmpfile" &&
513 test -s "$tmpfile" &&
514 {
515 GIT_TRACE_CURL="$TRASH_DIRECTORY/overlap-second.trace" \
516 GIT_TRACE_CURL_NO_DATA=1 \
517 git -C packfileclient-overlap http-fetch --packfile="$packhash" \
518 --index-pack-arg=index-pack \
519 --index-pack-arg=--stdin --index-pack-arg=--keep \
520 "$url" >second.out &
521 second_pid=$!
522 } &&
523 test_when_finished "
524 kill $second_pid 2>/dev/null || :
525 wait $second_pid 2>/dev/null || :
526 " &&
527 wait "$server_pid" &&
528 wait "$first_pid" &&
529 wait "$second_pid" &&
530 test_grep "HTTP/[0-9.]* 200" overlap-first.trace &&
531 test_grep "Range: bytes=[1-9][0-9]*-" overlap-second.trace &&
532 test_grep "HTTP/[0-9.]* 206" overlap-second.trace &&
533 printf "keep\t%s\npack\t%s\n" "$packhash" "$packhash" | sort >expect &&
534 sort first.out second.out >actual &&
535 test_cmp expect actual &&
536 test_path_is_missing "$tmpfile" &&
537 git -C packfileclient-overlap cat-file -e "$blob"
538 '
539
540 test_expect_success 'fetch notices corrupt pack' '
541 cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
542 (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
543 p=$(ls objects/pack/pack-*.pack) &&
544 chmod u+w $p &&
545 printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
546 ) &&
547 mkdir repo_bad1.git &&
548 (cd repo_bad1.git &&
549 git --bare init &&
550 test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git &&
551 test 0 = $(ls objects/pack/pack-*.pack | wc -l)
552 )
553 '
554
555 test_expect_success 'http-fetch --packfile with corrupt pack' '
556 rm -rf packfileclient &&
557 git init packfileclient &&
558 p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && ls objects/pack/pack-*.pack) &&
559 test_must_fail git -C packfileclient http-fetch --packfile \
560 "$HTTPD_URL"/dumb/repo_bad1.git/$p
561 '
562
563 test_expect_success 'fetch notices corrupt idx' '
564 cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
565 (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
566 p=$(ls objects/pack/pack-*.idx) &&
567 chmod u+w $p &&
568 printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
569 ) &&
570 mkdir repo_bad2.git &&
571 (cd repo_bad2.git &&
572 git --bare init &&
573 test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git &&
574 test 0 = $(ls objects/pack | wc -l)
575 )
576 '
577
578 # usage: count_fetches <nr> <extension> <trace_file>
579 count_fetches () {
580 # ignore grep exit code; it may return non-zero if we are expecting no
581 # matches
582 grep "GET .*objects/pack/pack-[a-z0-9]*.$2" "$3" >trace.count
583 test_line_count = "$1" trace.count
584 }
585
586 test_expect_success 'fetch can handle previously-fetched .idx files' '
587 git checkout --orphan branch1 &&
588 echo base >file &&
589 git add file &&
590 git commit -m base &&
591 git --bare init "$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git &&
592 git push "$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git branch1 &&
593 git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git repack -d &&
594 git checkout -b branch2 branch1 &&
595 echo b2 >>file &&
596 git commit -a -m b2 &&
597 git push "$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git branch2 &&
598 git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH"/repo_packed_branches.git repack -d &&
599 git --bare init clone_packed_branches.git &&
600 GIT_TRACE_CURL=$PWD/one.trace git --git-dir=clone_packed_branches.git \
601 fetch "$HTTPD_URL"/dumb/repo_packed_branches.git branch1:branch1 &&
602 count_fetches 2 idx one.trace &&
603 count_fetches 1 pack one.trace &&
604 GIT_TRACE_CURL=$PWD/two.trace git --git-dir=clone_packed_branches.git \
605 fetch "$HTTPD_URL"/dumb/repo_packed_branches.git branch2:branch2 &&
606 count_fetches 1 idx two.trace &&
607 count_fetches 1 pack two.trace
608 '
609
610 test_expect_success 'did not use upload-pack service' '
611 test_grep ! "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log"
612 '
613
614 test_expect_success ICONV 'git client shows text/plain errors' '
615 test_must_fail git clone "$HTTPD_URL/error/text" 2>stderr &&
616 test_grep "this is the error message" stderr
617 '
618
619 test_expect_success ICONV 'git client does not show html errors' '
620 test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr &&
621 test_grep ! "this is the error message" stderr
622 '
623
624 test_expect_success ICONV 'git client shows text/plain with a charset' '
625 test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr &&
626 test_grep "this is the error message" stderr
627 '
628
629 test_expect_success ICONV 'http error messages are reencoded' '
630 test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr &&
631 test_grep "this is the error message" stderr
632 '
633
634 test_expect_success ICONV 'reencoding is robust to whitespace oddities' '
635 test_must_fail git clone "$HTTPD_URL/error/odd-spacing" 2>stderr &&
636 test_grep "this is the error message" stderr
637 '
638
639 check_language () {
640 case "$2" in
641 '')
642 >expect
643 ;;
644 ?*)
645 echo "=> Send header: Accept-Language: $1" >expect
646 ;;
647 esac &&
648 GIT_TRACE_CURL=true \
649 LANGUAGE=$2 \
650 git ls-remote "$HTTPD_URL/dumb/repo.git" >output 2>&1 &&
651 tr -d '\015' <output |
652 sort -u |
653 sed -ne '/^=> Send header: Accept-Language:/ p' >actual &&
654 test_cmp expect actual
655 }
656
657 test_expect_success 'git client sends Accept-Language based on LANGUAGE' '
658 check_language "ko-KR, *;q=0.9" ko_KR.UTF-8'
659
660 test_expect_success 'git client sends Accept-Language correctly with unordinary LANGUAGE' '
661 check_language "ko-KR, *;q=0.9" "ko_KR:" &&
662 check_language "ko-KR, en-US;q=0.9, *;q=0.8" "ko_KR::en_US" &&
663 check_language "ko-KR, *;q=0.9" ":::ko_KR" &&
664 check_language "ko-KR, en-US;q=0.9, *;q=0.8" "ko_KR!!:en_US" &&
665 check_language "ko-KR, ja-JP;q=0.9, *;q=0.8" "ko_KR en_US:ja_JP"'
666
667 test_expect_success 'git client sends Accept-Language with many preferred languages' '
668 check_language "ko-KR, en-US;q=0.9, fr-CA;q=0.8, de;q=0.7, sr;q=0.6, \
669 ja;q=0.5, zh;q=0.4, sv;q=0.3, pt;q=0.2, *;q=0.1" \
670 ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt &&
671 check_language "ko-KR, en-US;q=0.99, fr-CA;q=0.98, de;q=0.97, sr;q=0.96, \
672 ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \
673 ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt:nb
674 '
675
676 test_expect_success 'git client send an empty Accept-Language' '
677 GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr &&
678 test_grep ! "^=> Send header: Accept-Language:" stderr
679 '
680
681 test_expect_success 'remote-http complains cleanly about malformed urls' '
682 test_must_fail git remote-http http::/example.com/repo.git 2>stderr &&
683 test_grep "url has no scheme" stderr
684 '
685
686 # NEEDSWORK: Writing commands to git-remote-curl can race against the latter
687 # erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has
688 # learned to handle early remote helper failures more cleanly.
689 test_expect_success 'remote-http complains cleanly about empty scheme' '
690 test_must_fail ok=sigpipe git ls-remote \
691 http::${HTTPD_URL#http}/dumb/repo.git 2>stderr &&
692 test_grep "url has no scheme" stderr
693 '
694
695 test_expect_success 'redirects can be forbidden/allowed' '
696 test_must_fail git -c http.followRedirects=false \
697 clone $HTTPD_URL/dumb-redir/repo.git dumb-redir &&
698 git -c http.followRedirects=true \
699 clone $HTTPD_URL/dumb-redir/repo.git dumb-redir 2>stderr
700 '
701
702 test_expect_success 'redirects are reported to stderr' '
703 # just look for a snippet of the redirected-to URL
704 test_grep /dumb/ stderr
705 '
706
707 test_expect_success 'non-initial redirects can be forbidden' '
708 test_must_fail git -c http.followRedirects=initial \
709 clone $HTTPD_URL/redir-objects/repo.git redir-objects &&
710 git -c http.followRedirects=true \
711 clone $HTTPD_URL/redir-objects/repo.git redir-objects
712 '
713
714 test_expect_success 'http.followRedirects defaults to "initial"' '
715 test_must_fail git clone $HTTPD_URL/redir-objects/repo.git default
716 '
717
718 # The goal is for a clone of the "evil" repository, which has no objects
719 # itself, to cause the client to fetch objects from the "victim" repository.
720 test_expect_success 'set up evil alternates scheme' '
721 victim=$HTTPD_DOCUMENT_ROOT_PATH/victim.git &&
722 git init --bare "$victim" &&
723 git -C "$victim" --work-tree=. commit --allow-empty -m secret &&
724 git -C "$victim" repack -ad &&
725 git -C "$victim" update-server-info &&
726 sha1=$(git -C "$victim" rev-parse HEAD) &&
727
728 evil=$HTTPD_DOCUMENT_ROOT_PATH/evil.git &&
729 git init --template= --bare "$evil" &&
730 mkdir "$evil/info" &&
731 # do this by hand to avoid object existence check
732 printf "%s\\t%s\\n" $sha1 refs/heads/main >"$evil/info/refs"
733 '
734
735 # Here we'll just redirect via HTTP. In a real-world attack these would be on
736 # different servers, but we should reject it either way.
737 test_expect_success 'http-alternates is a non-initial redirect' '
738 echo "$HTTPD_URL/dumb/victim.git/objects" \
739 >"$evil/objects/info/http-alternates" &&
740 test_must_fail git -c http.followRedirects=initial \
741 clone $HTTPD_URL/dumb/evil.git evil-initial &&
742 git -c http.followRedirects=true \
743 clone $HTTPD_URL/dumb/evil.git evil-initial
744 '
745
746 # Curl supports a lot of protocols that we'd prefer not to allow
747 # http-alternates to use, but it's hard to test whether curl has
748 # accessed, say, the SMTP protocol, because we are not running an SMTP server.
749 # But we can check that it does not allow access to file://, which would
750 # otherwise allow this clone to complete.
751 test_expect_success 'http-alternates cannot point at funny protocols' '
752 echo "file://$victim/objects" >"$evil/objects/info/http-alternates" &&
753 test_must_fail git -c http.followRedirects=true \
754 clone "$HTTPD_URL/dumb/evil.git" evil-file
755 '
756
757 test_expect_success 'http-alternates triggers not-from-user protocol check' '
758 echo "$HTTPD_URL/dumb/victim.git/objects" \
759 >"$evil/objects/info/http-alternates" &&
760 test_config_global http.followRedirects true &&
761 test_must_fail git -c protocol.http.allow=user \
762 clone $HTTPD_URL/dumb/evil.git evil-user &&
763 git -c protocol.http.allow=always \
764 clone $HTTPD_URL/dumb/evil.git evil-user
765 '
766
767 test_expect_success 'can redirect through non-"info/refs?service=git-upload-pack" URL' '
768 git clone "$HTTPD_URL/redir-to/dumb/repo.git"
769 '
770
771 test_expect_success 'print HTTP error when any intermediate redirect throws error' '
772 test_must_fail git clone "$HTTPD_URL/redir-to/502" 2> stderr &&
773 test_grep "unable to access.*/redir-to/502" stderr
774 '
775
776 test_expect_success 'fetching via http alternates works' '
777 parent=$HTTPD_DOCUMENT_ROOT_PATH/alt-parent.git &&
778 git init --bare "$parent" &&
779 git -C "$parent" --work-tree=. commit --allow-empty -m foo &&
780 git -C "$parent" update-server-info &&
781 commit=$(git -C "$parent" rev-parse HEAD) &&
782
783 child=$HTTPD_DOCUMENT_ROOT_PATH/alt-child.git &&
784 git init --bare "$child" &&
785 echo "../../alt-parent.git/objects" >"$child/objects/info/alternates" &&
786 git -C "$child" update-ref HEAD $commit &&
787 git -C "$child" update-server-info &&
788
789 git -c http.followredirects=true clone "$HTTPD_URL/dumb/alt-child.git"
790 '
791
792 test_expect_success 'dumb http can fetch index v1' '
793 server=$HTTPD_DOCUMENT_ROOT_PATH/idx-v1.git &&
794 git init --bare "$server" &&
795 git -C "$server" --work-tree=. commit --allow-empty -m foo &&
796 git -C "$server" -c pack.indexVersion=1 gc &&
797
798 git clone "$HTTPD_URL/dumb/idx-v1.git" &&
799 git -C idx-v1 fsck
800 '
801
802 test_expect_success 'absolute-path alternate when url has no path' '
803 src=$HTTPD_DOCUMENT_ROOT_PATH/repo.git &&
804 alt=absolute-alt.git &&
805 git clone --bare --shared "$src" "$alt" &&
806
807 # Our repo has an alternate pointing to the absolute filesystem path,
808 # but that will not make any sense to an http client. So we will
809 # manually give it the equivalent path that the http server will
810 # understand.
811 echo "/dumb/repo.git/objects" >"$alt/objects/info/http-alternates" &&
812
813 # Now make our alt repository available at the root of the http
814 # server without any path (i.e., just http://localhost:1234).
815 git -C "$alt" update-server-info &&
816 mv absolute-alt.git/* "$HTTPD_DOCUMENT_ROOT_PATH" &&
817
818 git -c http.followRedirects=true clone "$HTTPD_URL" alt-clone.git 2>err &&
819 test_grep "adding alternate object store: $HTTPD_URL/dumb/repo.git" err
820 '
821
822 test_done