Skip to content

Commit 0f1dc53

Browse files
bmwillgitster
authored andcommitted
remote-curl: implement stateless-connect command
Teach remote-curl the 'stateless-connect' command which is used to establish a stateless connection with servers which support protocol version 2. This allows remote-curl to act as a proxy, allowing the git client to communicate natively with a remote end, simply using remote-curl as a pass through to convert requests to http. Signed-off-by: Brandon Williams <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 237ffed commit 0f1dc53

File tree

2 files changed

+251
-1
lines changed

2 files changed

+251
-1
lines changed

remote-curl.c

Lines changed: 206 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,12 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
188188
heads->version = discover_version(&reader);
189189
switch (heads->version) {
190190
case protocol_v2:
191-
die("support for protocol v2 not implemented yet");
191+
/*
192+
* Do nothing. This isn't a list of refs but rather a
193+
* capability advertisement. Client would have run
194+
* 'stateless-connect' so we'll dump this capability listing
195+
* and let them request the refs themselves.
196+
*/
192197
break;
193198
case protocol_v1:
194199
case protocol_v0:
@@ -1085,6 +1090,202 @@ static void parse_push(struct strbuf *buf)
10851090
free(specs);
10861091
}
10871092

1093+
/*
1094+
* Used to represent the state of a connection to an HTTP server when
1095+
* communicating using git's wire-protocol version 2.
1096+
*/
1097+
struct proxy_state {
1098+
char *service_name;
1099+
char *service_url;
1100+
struct curl_slist *headers;
1101+
struct strbuf request_buffer;
1102+
int in;
1103+
int out;
1104+
struct packet_reader reader;
1105+
size_t pos;
1106+
int seen_flush;
1107+
};
1108+
1109+
static void proxy_state_init(struct proxy_state *p, const char *service_name,
1110+
enum protocol_version version)
1111+
{
1112+
struct strbuf buf = STRBUF_INIT;
1113+
1114+
memset(p, 0, sizeof(*p));
1115+
p->service_name = xstrdup(service_name);
1116+
1117+
p->in = 0;
1118+
p->out = 1;
1119+
strbuf_init(&p->request_buffer, 0);
1120+
1121+
strbuf_addf(&buf, "%s%s", url.buf, p->service_name);
1122+
p->service_url = strbuf_detach(&buf, NULL);
1123+
1124+
p->headers = http_copy_default_headers();
1125+
1126+
strbuf_addf(&buf, "Content-Type: application/x-%s-request", p->service_name);
1127+
p->headers = curl_slist_append(p->headers, buf.buf);
1128+
strbuf_reset(&buf);
1129+
1130+
strbuf_addf(&buf, "Accept: application/x-%s-result", p->service_name);
1131+
p->headers = curl_slist_append(p->headers, buf.buf);
1132+
strbuf_reset(&buf);
1133+
1134+
p->headers = curl_slist_append(p->headers, "Transfer-Encoding: chunked");
1135+
1136+
/* Add the Git-Protocol header */
1137+
if (get_protocol_http_header(version, &buf))
1138+
p->headers = curl_slist_append(p->headers, buf.buf);
1139+
1140+
packet_reader_init(&p->reader, p->in, NULL, 0,
1141+
PACKET_READ_GENTLE_ON_EOF);
1142+
1143+
strbuf_release(&buf);
1144+
}
1145+
1146+
static void proxy_state_clear(struct proxy_state *p)
1147+
{
1148+
free(p->service_name);
1149+
free(p->service_url);
1150+
curl_slist_free_all(p->headers);
1151+
strbuf_release(&p->request_buffer);
1152+
}
1153+
1154+
/*
1155+
* CURLOPT_READFUNCTION callback function.
1156+
* Attempts to copy over a single packet-line at a time into the
1157+
* curl provided buffer.
1158+
*/
1159+
static size_t proxy_in(char *buffer, size_t eltsize,
1160+
size_t nmemb, void *userdata)
1161+
{
1162+
size_t max;
1163+
struct proxy_state *p = userdata;
1164+
size_t avail = p->request_buffer.len - p->pos;
1165+
1166+
1167+
if (eltsize != 1)
1168+
BUG("curl read callback called with size = %"PRIuMAX" != 1",
1169+
(uintmax_t)eltsize);
1170+
max = nmemb;
1171+
1172+
if (!avail) {
1173+
if (p->seen_flush) {
1174+
p->seen_flush = 0;
1175+
return 0;
1176+
}
1177+
1178+
strbuf_reset(&p->request_buffer);
1179+
switch (packet_reader_read(&p->reader)) {
1180+
case PACKET_READ_EOF:
1181+
die("unexpected EOF when reading from parent process");
1182+
case PACKET_READ_NORMAL:
1183+
packet_buf_write_len(&p->request_buffer, p->reader.line,
1184+
p->reader.pktlen);
1185+
break;
1186+
case PACKET_READ_DELIM:
1187+
packet_buf_delim(&p->request_buffer);
1188+
break;
1189+
case PACKET_READ_FLUSH:
1190+
packet_buf_flush(&p->request_buffer);
1191+
p->seen_flush = 1;
1192+
break;
1193+
}
1194+
p->pos = 0;
1195+
avail = p->request_buffer.len;
1196+
}
1197+
1198+
if (max < avail)
1199+
avail = max;
1200+
memcpy(buffer, p->request_buffer.buf + p->pos, avail);
1201+
p->pos += avail;
1202+
return avail;
1203+
}
1204+
1205+
static size_t proxy_out(char *buffer, size_t eltsize,
1206+
size_t nmemb, void *userdata)
1207+
{
1208+
size_t size;
1209+
struct proxy_state *p = userdata;
1210+
1211+
if (eltsize != 1)
1212+
BUG("curl read callback called with size = %"PRIuMAX" != 1",
1213+
(uintmax_t)eltsize);
1214+
size = nmemb;
1215+
1216+
write_or_die(p->out, buffer, size);
1217+
return size;
1218+
}
1219+
1220+
/* Issues a request to the HTTP server configured in `p` */
1221+
static int proxy_request(struct proxy_state *p)
1222+
{
1223+
struct active_request_slot *slot;
1224+
1225+
slot = get_active_slot();
1226+
1227+
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
1228+
curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
1229+
curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
1230+
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, p->headers);
1231+
1232+
/* Setup function to read request from client */
1233+
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, proxy_in);
1234+
curl_easy_setopt(slot->curl, CURLOPT_READDATA, p);
1235+
1236+
/* Setup function to write server response to client */
1237+
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, proxy_out);
1238+
curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, p);
1239+
1240+
if (run_slot(slot, NULL) != HTTP_OK)
1241+
return -1;
1242+
1243+
return 0;
1244+
}
1245+
1246+
static int stateless_connect(const char *service_name)
1247+
{
1248+
struct discovery *discover;
1249+
struct proxy_state p;
1250+
1251+
/*
1252+
* Run the info/refs request and see if the server supports protocol
1253+
* v2. If and only if the server supports v2 can we successfully
1254+
* establish a stateless connection, otherwise we need to tell the
1255+
* client to fallback to using other transport helper functions to
1256+
* complete their request.
1257+
*/
1258+
discover = discover_refs(service_name, 0);
1259+
if (discover->version != protocol_v2) {
1260+
printf("fallback\n");
1261+
fflush(stdout);
1262+
return -1;
1263+
} else {
1264+
/* Stateless Connection established */
1265+
printf("\n");
1266+
fflush(stdout);
1267+
}
1268+
1269+
proxy_state_init(&p, service_name, discover->version);
1270+
1271+
/*
1272+
* Dump the capability listing that we got from the server earlier
1273+
* during the info/refs request.
1274+
*/
1275+
write_or_die(p.out, discover->buf, discover->len);
1276+
1277+
/* Peek the next packet line. Until we see EOF keep sending POSTs */
1278+
while (packet_reader_peek(&p.reader) != PACKET_READ_EOF) {
1279+
if (proxy_request(&p)) {
1280+
/* We would have an err here */
1281+
break;
1282+
}
1283+
}
1284+
1285+
proxy_state_clear(&p);
1286+
return 0;
1287+
}
1288+
10881289
int cmd_main(int argc, const char **argv)
10891290
{
10901291
struct strbuf buf = STRBUF_INIT;
@@ -1153,12 +1354,16 @@ int cmd_main(int argc, const char **argv)
11531354
fflush(stdout);
11541355

11551356
} else if (!strcmp(buf.buf, "capabilities")) {
1357+
printf("stateless-connect\n");
11561358
printf("fetch\n");
11571359
printf("option\n");
11581360
printf("push\n");
11591361
printf("check-connectivity\n");
11601362
printf("\n");
11611363
fflush(stdout);
1364+
} else if (skip_prefix(buf.buf, "stateless-connect ", &arg)) {
1365+
if (!stateless_connect(arg))
1366+
break;
11621367
} else {
11631368
error("remote-curl: unknown command '%s' from git", buf.buf);
11641369
return 1;

t/t5702-protocol-v2.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,49 @@ test_expect_success 'ref advertisment is filtered during fetch using protocol v2
201201
! grep "refs/tags/three" log
202202
'
203203

204+
# Test protocol v2 with 'http://' transport
205+
#
206+
. "$TEST_DIRECTORY"/lib-httpd.sh
207+
start_httpd
208+
209+
test_expect_success 'create repo to be served by http:// transport' '
210+
git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
211+
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
212+
test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one
213+
'
214+
215+
test_expect_success 'clone with http:// using protocol v2' '
216+
test_when_finished "rm -f log" &&
217+
218+
GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
219+
clone "$HTTPD_URL/smart/http_parent" http_child &&
220+
221+
git -C http_child log -1 --format=%s >actual &&
222+
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
223+
test_cmp expect actual &&
224+
225+
# Client requested to use protocol v2
226+
grep "Git-Protocol: version=2" log &&
227+
# Server responded using protocol v2
228+
grep "git< version 2" log
229+
'
230+
231+
test_expect_success 'fetch with http:// using protocol v2' '
232+
test_when_finished "rm -f log" &&
233+
234+
test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two &&
235+
236+
GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \
237+
fetch &&
238+
239+
git -C http_child log -1 --format=%s origin/master >actual &&
240+
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
241+
test_cmp expect actual &&
242+
243+
# Server responded using protocol v2
244+
grep "git< version 2" log
245+
'
246+
247+
stop_httpd
248+
204249
test_done

0 commit comments

Comments
 (0)