Skip to content

Commit 6c213e8

Browse files
max630gitster
authored andcommitted
http-backend: respect CONTENT_LENGTH for receive-pack
Push passes to another commands, as described in https://public-inbox.org/git/[email protected]/ As it gets complicated to correctly track the data length, instead transfer the data through parent process and cut the pipe as the specified length is reached. Do it only when CONTENT_LENGTH is set, otherwise pass the input directly to the forked commands. Add tests for cases: * CONTENT_LENGTH is set, script's stdin has more data, with all combinations of variations: fetch or push, plain or compressed body, correct or truncated input. * CONTENT_LENGTH is specified to a value which does not fit into ssize_t. Helped-by: Junio C Hamano <[email protected]> Signed-off-by: Max Kirillov <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c79edf7 commit 6c213e8

File tree

4 files changed

+223
-2
lines changed

4 files changed

+223
-2
lines changed

help.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ int cmd_version(int argc, const char **argv, const char *prefix)
609609
else
610610
printf("no commit associated with this build\n");
611611
printf("sizeof-long: %d\n", (int)sizeof(long));
612+
printf("sizeof-size_t: %d\n", (int)sizeof(size_t));
612613
/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
613614
}
614615
return 0;

http-backend.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,8 @@ static void inflate_request(const char *prog_name, int out, int buffer_input, ss
373373
unsigned char in_buf[8192];
374374
unsigned char out_buf[8192];
375375
unsigned long cnt = 0;
376+
int req_len_defined = req_len >= 0;
377+
size_t req_remaining_len = req_len;
376378

377379
memset(&stream, 0, sizeof(stream));
378380
git_inflate_init_gzip_only(&stream);
@@ -387,8 +389,15 @@ static void inflate_request(const char *prog_name, int out, int buffer_input, ss
387389
n = read_request(0, &full_request, req_len);
388390
stream.next_in = full_request;
389391
} else {
390-
n = xread(0, in_buf, sizeof(in_buf));
392+
ssize_t buffer_len;
393+
if (req_len_defined && req_remaining_len <= sizeof(in_buf))
394+
buffer_len = req_remaining_len;
395+
else
396+
buffer_len = sizeof(in_buf);
397+
n = xread(0, in_buf, buffer_len);
391398
stream.next_in = in_buf;
399+
if (req_len_defined && n > 0)
400+
req_remaining_len -= n;
392401
}
393402

394403
if (n <= 0)
@@ -431,6 +440,23 @@ static void copy_request(const char *prog_name, int out, ssize_t req_len)
431440
free(buf);
432441
}
433442

443+
static void pipe_fixed_length(const char *prog_name, int out, size_t req_len)
444+
{
445+
unsigned char buf[8192];
446+
size_t remaining_len = req_len;
447+
448+
while (remaining_len > 0) {
449+
size_t chunk_length = remaining_len > sizeof(buf) ? sizeof(buf) : remaining_len;
450+
ssize_t n = xread(0, buf, chunk_length);
451+
if (n < 0)
452+
die_errno("Reading request failed");
453+
write_to_child(out, buf, n, prog_name);
454+
remaining_len -= n;
455+
}
456+
457+
close(out);
458+
}
459+
434460
static void run_service(const char **argv, int buffer_input)
435461
{
436462
const char *encoding = getenv("HTTP_CONTENT_ENCODING");
@@ -457,7 +483,7 @@ static void run_service(const char **argv, int buffer_input)
457483
"GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
458484

459485
cld.argv = argv;
460-
if (buffer_input || gzipped_request)
486+
if (buffer_input || gzipped_request || req_len >= 0)
461487
cld.in = -1;
462488
cld.git_cmd = 1;
463489
if (start_command(&cld))
@@ -468,6 +494,8 @@ static void run_service(const char **argv, int buffer_input)
468494
inflate_request(argv[0], cld.in, buffer_input, req_len);
469495
else if (buffer_input)
470496
copy_request(argv[0], cld.in, req_len);
497+
else if (req_len >= 0)
498+
pipe_fixed_length(argv[0], cld.in, req_len);
471499
else
472500
close(0);
473501

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/bin/sh
2+
3+
test_description='test git-http-backend respects CONTENT_LENGTH'
4+
. ./test-lib.sh
5+
6+
test_lazy_prereq GZIP 'gzip --version'
7+
8+
verify_http_result() {
9+
# some fatal errors still produce status 200
10+
# so check if there is the error message
11+
if grep 'fatal:' act.err
12+
then
13+
return 1
14+
fi
15+
16+
if ! grep "Status" act.out >act
17+
then
18+
printf "Status: 200 OK\r\n" >act
19+
fi
20+
printf "Status: $1\r\n" >exp &&
21+
test_cmp exp act
22+
}
23+
24+
test_http_env() {
25+
handler_type="$1"
26+
request_body="$2"
27+
shift
28+
env \
29+
CONTENT_TYPE="application/x-git-$handler_type-pack-request" \
30+
QUERY_STRING="/repo.git/git-$handler_type-pack" \
31+
PATH_TRANSLATED="$PWD/.git/git-$handler_type-pack" \
32+
GIT_HTTP_EXPORT_ALL=TRUE \
33+
REQUEST_METHOD=POST \
34+
"$TEST_DIRECTORY"/t5562/invoke-with-content-length.pl \
35+
"$request_body" git http-backend >act.out 2>act.err
36+
}
37+
38+
ssize_b100dots() {
39+
# hardcoded ((size_t) SSIZE_MAX) + 1
40+
case "$(build_option sizeof-size_t)" in
41+
8) echo 9223372036854775808;;
42+
4) echo 2147483648;;
43+
*) die "Unexpected ssize_t size: $(build_option sizeof-size_t)";;
44+
esac
45+
}
46+
47+
test_expect_success 'setup' '
48+
export HTTP_CONTENT_ENCODING="identity" &&
49+
git config http.receivepack true &&
50+
test_commit c0 &&
51+
test_commit c1 &&
52+
hash_head=$(git rev-parse HEAD) &&
53+
hash_prev=$(git rev-parse HEAD~1) &&
54+
printf "want %s" "$hash_head" | packetize >fetch_body &&
55+
printf 0000 >>fetch_body &&
56+
printf "have %s" "$hash_prev" | packetize >>fetch_body &&
57+
printf done | packetize >>fetch_body &&
58+
test_copy_bytes 10 <fetch_body >fetch_body.trunc &&
59+
hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) &&
60+
printf "%s %s refs/heads/newbranch\\0report-status\\n" "$_z40" "$hash_next" | packetize >push_body &&
61+
printf 0000 >>push_body &&
62+
echo "$hash_next" | git pack-objects --stdout >>push_body &&
63+
test_copy_bytes 10 <push_body >push_body.trunc &&
64+
: >empty_body
65+
'
66+
67+
test_expect_success GZIP 'setup, compression related' '
68+
gzip -c fetch_body >fetch_body.gz &&
69+
test_copy_bytes 10 <fetch_body.gz >fetch_body.gz.trunc &&
70+
gzip -c push_body >push_body.gz &&
71+
test_copy_bytes 10 <push_body.gz >push_body.gz.trunc
72+
'
73+
74+
test_expect_success 'fetch plain' '
75+
test_http_env upload fetch_body &&
76+
verify_http_result "200 OK"
77+
'
78+
79+
test_expect_success 'fetch plain truncated' '
80+
test_http_env upload fetch_body.trunc &&
81+
! verify_http_result "200 OK"
82+
'
83+
84+
test_expect_success 'fetch plain empty' '
85+
test_http_env upload empty_body &&
86+
! verify_http_result "200 OK"
87+
'
88+
89+
test_expect_success GZIP 'fetch gzipped' '
90+
test_env HTTP_CONTENT_ENCODING="gzip" test_http_env upload fetch_body.gz &&
91+
verify_http_result "200 OK"
92+
'
93+
94+
test_expect_success GZIP 'fetch gzipped truncated' '
95+
test_env HTTP_CONTENT_ENCODING="gzip" test_http_env upload fetch_body.gz.trunc &&
96+
! verify_http_result "200 OK"
97+
'
98+
99+
test_expect_success GZIP 'fetch gzipped empty' '
100+
test_env HTTP_CONTENT_ENCODING="gzip" test_http_env upload empty_body &&
101+
! verify_http_result "200 OK"
102+
'
103+
104+
test_expect_success GZIP 'push plain' '
105+
test_when_finished "git branch -D newbranch" &&
106+
test_http_env receive push_body &&
107+
verify_http_result "200 OK" &&
108+
git rev-parse newbranch >act.head &&
109+
echo "$hash_next" >exp.head &&
110+
test_cmp act.head exp.head
111+
'
112+
113+
test_expect_success 'push plain truncated' '
114+
test_http_env receive push_body.trunc &&
115+
! verify_http_result "200 OK"
116+
'
117+
118+
test_expect_success 'push plain empty' '
119+
test_http_env receive empty_body &&
120+
! verify_http_result "200 OK"
121+
'
122+
123+
test_expect_success GZIP 'push gzipped' '
124+
test_when_finished "git branch -D newbranch" &&
125+
test_env HTTP_CONTENT_ENCODING="gzip" test_http_env receive push_body.gz &&
126+
verify_http_result "200 OK" &&
127+
git rev-parse newbranch >act.head &&
128+
echo "$hash_next" >exp.head &&
129+
test_cmp act.head exp.head
130+
'
131+
132+
test_expect_success GZIP 'push gzipped truncated' '
133+
test_env HTTP_CONTENT_ENCODING="gzip" test_http_env receive push_body.gz.trunc &&
134+
! verify_http_result "200 OK"
135+
'
136+
137+
test_expect_success GZIP 'push gzipped empty' '
138+
test_env HTTP_CONTENT_ENCODING="gzip" test_http_env receive empty_body &&
139+
! verify_http_result "200 OK"
140+
'
141+
142+
test_expect_success 'CONTENT_LENGTH overflow ssite_t' '
143+
NOT_FIT_IN_SSIZE=$(ssize_b100dots) &&
144+
env \
145+
CONTENT_TYPE=application/x-git-upload-pack-request \
146+
QUERY_STRING=/repo.git/git-upload-pack \
147+
PATH_TRANSLATED="$PWD"/.git/git-upload-pack \
148+
GIT_HTTP_EXPORT_ALL=TRUE \
149+
REQUEST_METHOD=POST \
150+
CONTENT_LENGTH="$NOT_FIT_IN_SSIZE" \
151+
git http-backend </dev/zero >/dev/null 2>err &&
152+
grep "fatal:.*CONTENT_LENGTH" err
153+
'
154+
155+
test_done

t/t5562/invoke-with-content-length.pl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/perl
2+
use 5.008;
3+
use strict;
4+
use warnings;
5+
6+
my $body_filename = $ARGV[0];
7+
my @command = @ARGV[1 .. $#ARGV];
8+
9+
# read data
10+
my $body_size = -s $body_filename;
11+
$ENV{"CONTENT_LENGTH"} = $body_size;
12+
open(my $body_fh, "<", $body_filename) or die "Cannot open $body_filename: $!";
13+
my $body_data;
14+
defined read($body_fh, $body_data, $body_size) or die "Cannot read $body_filename: $!";
15+
close($body_fh);
16+
17+
my $exited = 0;
18+
$SIG{"CHLD"} = sub {
19+
$exited = 1;
20+
};
21+
22+
# write data
23+
my $pid = open(my $out, "|-", @command);
24+
{
25+
# disable buffering at $out
26+
my $old_selected = select;
27+
select $out;
28+
$| = 1;
29+
select $old_selected;
30+
}
31+
print $out $body_data or die "Cannot write data: $!";
32+
33+
sleep 60; # is interrupted by SIGCHLD
34+
if (!$exited) {
35+
close($out);
36+
die "Command did not exit after reading whole body";
37+
}

0 commit comments

Comments
 (0)