summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkt-line.c2
-rw-r--r--pkt-line.h1
-rw-r--r--remote-curl.c384
-rwxr-xr-xt/t5702-protocol-v2.sh33
4 files changed, 213 insertions, 207 deletions
diff --git a/pkt-line.c b/pkt-line.c
index d4b71d3e82..60329b301b 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -117,7 +117,7 @@ void packet_buf_delim(struct strbuf *buf)
strbuf_add(buf, "0001", 4);
}
-static void set_packet_header(char *buf, const int size)
+void set_packet_header(char *buf, const int size)
{
static char hexchar[] = "0123456789abcdef";
diff --git a/pkt-line.h b/pkt-line.h
index ad9a4a2cd7..c36cb788ed 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -25,6 +25,7 @@ void packet_delim(int fd);
void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
void packet_buf_flush(struct strbuf *buf);
void packet_buf_delim(struct strbuf *buf);
+void set_packet_header(char *buf, int size);
void packet_write(int fd_out, const char *buf, size_t size);
void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
diff --git a/remote-curl.c b/remote-curl.c
index aad02dcc74..5b44794922 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -505,8 +505,6 @@ static void output_refs(struct ref *refs)
struct rpc_state {
const char *service_name;
- const char **argv;
- struct strbuf *stdin_preamble;
char *service_url;
char *hdr_content_type;
char *hdr_accept;
@@ -518,25 +516,121 @@ struct rpc_state {
int in;
int out;
int any_written;
- struct strbuf result;
unsigned gzip_request : 1;
unsigned initial_buffer : 1;
+
+ /*
+ * Whenever a pkt-line is read into buf, append the 4 characters
+ * denoting its length before appending the payload.
+ */
+ unsigned write_line_lengths : 1;
+
+ /*
+ * Used by rpc_out; initialize to 0. This is true if a flush has been
+ * read, but the corresponding line length (if write_line_lengths is
+ * true) and EOF have not been sent to libcurl. Since each flush marks
+ * the end of a request, each flush must be completely sent before any
+ * further reading occurs.
+ */
+ unsigned flush_read_but_not_sent : 1;
};
+/*
+ * Appends the result of reading from rpc->out to the string represented by
+ * rpc->buf and rpc->len if there is enough space. Returns 1 if there was
+ * enough space, 0 otherwise.
+ *
+ * If rpc->write_line_lengths is true, appends the line length as a 4-byte
+ * hexadecimal string before appending the result described above.
+ *
+ * Writes the total number of bytes appended into appended.
+ */
+static int rpc_read_from_out(struct rpc_state *rpc, int options,
+ size_t *appended,
+ enum packet_read_status *status) {
+ size_t left;
+ char *buf;
+ int pktlen_raw;
+
+ if (rpc->write_line_lengths) {
+ left = rpc->alloc - rpc->len - 4;
+ buf = rpc->buf + rpc->len + 4;
+ } else {
+ left = rpc->alloc - rpc->len;
+ buf = rpc->buf + rpc->len;
+ }
+
+ if (left < LARGE_PACKET_MAX)
+ return 0;
+
+ *status = packet_read_with_status(rpc->out, NULL, NULL, buf,
+ left, &pktlen_raw, options);
+ if (*status != PACKET_READ_EOF) {
+ *appended = pktlen_raw + (rpc->write_line_lengths ? 4 : 0);
+ rpc->len += *appended;
+ }
+
+ if (rpc->write_line_lengths) {
+ switch (*status) {
+ case PACKET_READ_EOF:
+ if (!(options & PACKET_READ_GENTLE_ON_EOF))
+ die("shouldn't have EOF when not gentle on EOF");
+ break;
+ case PACKET_READ_NORMAL:
+ set_packet_header(buf - 4, *appended);
+ break;
+ case PACKET_READ_DELIM:
+ memcpy(buf - 4, "0001", 4);
+ break;
+ case PACKET_READ_FLUSH:
+ memcpy(buf - 4, "0000", 4);
+ break;
+ }
+ }
+
+ return 1;
+}
+
static size_t rpc_out(void *ptr, size_t eltsize,
size_t nmemb, void *buffer_)
{
size_t max = eltsize * nmemb;
struct rpc_state *rpc = buffer_;
size_t avail = rpc->len - rpc->pos;
+ enum packet_read_status status;
if (!avail) {
rpc->initial_buffer = 0;
- avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
- if (!avail)
- return 0;
+ rpc->len = 0;
rpc->pos = 0;
- rpc->len = avail;
+ if (!rpc->flush_read_but_not_sent) {
+ if (!rpc_read_from_out(rpc, 0, &avail, &status))
+ BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
+ if (status == PACKET_READ_FLUSH)
+ rpc->flush_read_but_not_sent = 1;
+ }
+ /*
+ * If flush_read_but_not_sent is true, we have already read one
+ * full request but have not fully sent it + EOF, which is why
+ * we need to refrain from reading.
+ */
+ }
+ if (rpc->flush_read_but_not_sent) {
+ if (!avail) {
+ /*
+ * The line length either does not need to be sent at
+ * all or has already been completely sent. Now we can
+ * return 0, indicating EOF, meaning that the flush has
+ * been fully sent.
+ */
+ rpc->flush_read_but_not_sent = 0;
+ return 0;
+ }
+ /*
+ * If avail is non-zerp, the line length for the flush still
+ * hasn't been fully sent. Proceed with sending the line
+ * length.
+ */
}
if (max < avail)
@@ -664,7 +758,11 @@ static curl_off_t xcurl_off_t(size_t len)
return (curl_off_t)size;
}
-static int post_rpc(struct rpc_state *rpc)
+/*
+ * If flush_received is true, do not attempt to read any more; just use what's
+ * in rpc->buf.
+ */
+static int post_rpc(struct rpc_state *rpc, int flush_received)
{
struct active_request_slot *slot;
struct curl_slist *headers = http_copy_default_headers();
@@ -679,21 +777,19 @@ static int post_rpc(struct rpc_state *rpc)
* allocated buffer space we can use HTTP/1.0 and avoid the
* chunked encoding mess.
*/
- while (1) {
- size_t left = rpc->alloc - rpc->len;
- char *buf = rpc->buf + rpc->len;
- int n;
-
- if (left < LARGE_PACKET_MAX) {
- large_request = 1;
- use_gzip = 0;
- break;
+ if (!flush_received) {
+ while (1) {
+ size_t n;
+ enum packet_read_status status;
+
+ if (!rpc_read_from_out(rpc, 0, &n, &status)) {
+ large_request = 1;
+ use_gzip = 0;
+ break;
+ }
+ if (status == PACKET_READ_FLUSH)
+ break;
}
-
- n = packet_read(rpc->out, NULL, NULL, buf, left, 0);
- if (!n)
- break;
- rpc->len += n;
}
if (large_request) {
@@ -829,22 +925,22 @@ retry:
return err;
}
-static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
+static int rpc_service(struct rpc_state *rpc, struct discovery *heads,
+ const char **client_argv, const struct strbuf *preamble,
+ struct strbuf *rpc_result)
{
const char *svc = rpc->service_name;
struct strbuf buf = STRBUF_INIT;
- struct strbuf *preamble = rpc->stdin_preamble;
struct child_process client = CHILD_PROCESS_INIT;
int err = 0;
client.in = -1;
client.out = -1;
client.git_cmd = 1;
- client.argv = rpc->argv;
+ client.argv = client_argv;
if (start_command(&client))
exit(1);
- if (preamble)
- write_or_die(client.in, preamble->buf, preamble->len);
+ write_or_die(client.in, preamble->buf, preamble->len);
if (heads)
write_or_die(client.in, heads->buf, heads->len);
@@ -852,7 +948,6 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
rpc->buf = xmalloc(rpc->alloc);
rpc->in = client.in;
rpc->out = client.out;
- strbuf_init(&rpc->result, 0);
strbuf_addf(&buf, "%s%s", url.buf, svc);
rpc->service_url = strbuf_detach(&buf, NULL);
@@ -874,13 +969,13 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
break;
rpc->pos = 0;
rpc->len = n;
- err |= post_rpc(rpc);
+ err |= post_rpc(rpc, 0);
}
close(client.in);
client.in = -1;
if (!err) {
- strbuf_read(&rpc->result, client.out, 0);
+ strbuf_read(rpc_result, client.out, 0);
} else {
char buf[4096];
for (;;)
@@ -933,6 +1028,7 @@ static int fetch_git(struct discovery *heads,
struct strbuf preamble = STRBUF_INIT;
int i, err;
struct argv_array args = ARGV_ARRAY_INIT;
+ struct strbuf rpc_result = STRBUF_INIT;
argv_array_pushl(&args, "fetch-pack", "--stateless-rpc",
"--stdin", "--lock-pack", NULL);
@@ -978,14 +1074,12 @@ static int fetch_git(struct discovery *heads,
memset(&rpc, 0, sizeof(rpc));
rpc.service_name = "git-upload-pack",
- rpc.argv = args.argv;
- rpc.stdin_preamble = &preamble;
rpc.gzip_request = 1;
- err = rpc_service(&rpc, heads);
- if (rpc.result.len)
- write_or_die(1, rpc.result.buf, rpc.result.len);
- strbuf_release(&rpc.result);
+ err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result);
+ if (rpc_result.len)
+ write_or_die(1, rpc_result.buf, rpc_result.len);
+ strbuf_release(&rpc_result);
strbuf_release(&preamble);
argv_array_clear(&args);
return err;
@@ -1080,6 +1174,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
struct argv_array args;
struct string_list_item *cas_option;
struct strbuf preamble = STRBUF_INIT;
+ struct strbuf rpc_result = STRBUF_INIT;
argv_array_init(&args);
argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
@@ -1112,13 +1207,11 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
memset(&rpc, 0, sizeof(rpc));
rpc.service_name = "git-receive-pack",
- rpc.argv = args.argv;
- rpc.stdin_preamble = &preamble;
- err = rpc_service(&rpc, heads);
- if (rpc.result.len)
- write_or_die(1, rpc.result.buf, rpc.result.len);
- strbuf_release(&rpc.result);
+ err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result);
+ if (rpc_result.len)
+ write_or_die(1, rpc_result.buf, rpc_result.len);
+ strbuf_release(&rpc_result);
strbuf_release(&preamble);
argv_array_clear(&args);
return err;
@@ -1170,165 +1263,11 @@ static void parse_push(struct strbuf *buf)
free(specs);
}
-/*
- * Used to represent the state of a connection to an HTTP server when
- * communicating using git's wire-protocol version 2.
- */
-struct proxy_state {
- char *service_name;
- char *service_url;
- struct curl_slist *headers;
- struct strbuf request_buffer;
- int in;
- int out;
- struct packet_reader reader;
- size_t pos;
- int seen_flush;
-};
-
-static void proxy_state_init(struct proxy_state *p, const char *service_name,
- enum protocol_version version)
-{
- struct strbuf buf = STRBUF_INIT;
-
- memset(p, 0, sizeof(*p));
- p->service_name = xstrdup(service_name);
-
- p->in = 0;
- p->out = 1;
- strbuf_init(&p->request_buffer, 0);
-
- strbuf_addf(&buf, "%s%s", url.buf, p->service_name);
- p->service_url = strbuf_detach(&buf, NULL);
-
- p->headers = http_copy_default_headers();
-
- strbuf_addf(&buf, "Content-Type: application/x-%s-request", p->service_name);
- p->headers = curl_slist_append(p->headers, buf.buf);
- strbuf_reset(&buf);
-
- strbuf_addf(&buf, "Accept: application/x-%s-result", p->service_name);
- p->headers = curl_slist_append(p->headers, buf.buf);
- strbuf_reset(&buf);
-
- p->headers = curl_slist_append(p->headers, "Transfer-Encoding: chunked");
-
- /* Add the Git-Protocol header */
- if (get_protocol_http_header(version, &buf))
- p->headers = curl_slist_append(p->headers, buf.buf);
-
- packet_reader_init(&p->reader, p->in, NULL, 0,
- PACKET_READ_GENTLE_ON_EOF |
- PACKET_READ_DIE_ON_ERR_PACKET);
-
- strbuf_release(&buf);
-}
-
-static void proxy_state_clear(struct proxy_state *p)
-{
- free(p->service_name);
- free(p->service_url);
- curl_slist_free_all(p->headers);
- strbuf_release(&p->request_buffer);
-}
-
-/*
- * CURLOPT_READFUNCTION callback function.
- * Attempts to copy over a single packet-line at a time into the
- * curl provided buffer.
- */
-static size_t proxy_in(char *buffer, size_t eltsize,
- size_t nmemb, void *userdata)
-{
- size_t max;
- struct proxy_state *p = userdata;
- size_t avail = p->request_buffer.len - p->pos;
-
-
- if (eltsize != 1)
- BUG("curl read callback called with size = %"PRIuMAX" != 1",
- (uintmax_t)eltsize);
- max = nmemb;
-
- if (!avail) {
- if (p->seen_flush) {
- p->seen_flush = 0;
- return 0;
- }
-
- strbuf_reset(&p->request_buffer);
- switch (packet_reader_read(&p->reader)) {
- case PACKET_READ_EOF:
- die("unexpected EOF when reading from parent process");
- case PACKET_READ_NORMAL:
- packet_buf_write_len(&p->request_buffer, p->reader.line,
- p->reader.pktlen);
- break;
- case PACKET_READ_DELIM:
- packet_buf_delim(&p->request_buffer);
- break;
- case PACKET_READ_FLUSH:
- packet_buf_flush(&p->request_buffer);
- p->seen_flush = 1;
- break;
- }
- p->pos = 0;
- avail = p->request_buffer.len;
- }
-
- if (max < avail)
- avail = max;
- memcpy(buffer, p->request_buffer.buf + p->pos, avail);
- p->pos += avail;
- return avail;
-}
-
-static size_t proxy_out(char *buffer, size_t eltsize,
- size_t nmemb, void *userdata)
-{
- size_t size;
- struct proxy_state *p = userdata;
-
- if (eltsize != 1)
- BUG("curl read callback called with size = %"PRIuMAX" != 1",
- (uintmax_t)eltsize);
- size = nmemb;
-
- write_or_die(p->out, buffer, size);
- return size;
-}
-
-/* Issues a request to the HTTP server configured in `p` */
-static int proxy_request(struct proxy_state *p)
-{
- struct active_request_slot *slot;
-
- slot = get_active_slot();
-
- curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
- curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
- curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, p->headers);
-
- /* Setup function to read request from client */
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, proxy_in);
- curl_easy_setopt(slot->curl, CURLOPT_READDATA, p);
-
- /* Setup function to write server response to client */
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, proxy_out);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, p);
-
- if (run_slot(slot, NULL) != HTTP_OK)
- return -1;
-
- return 0;
-}
-
static int stateless_connect(const char *service_name)
{
struct discovery *discover;
- struct proxy_state p;
+ struct rpc_state rpc;
+ struct strbuf buf = STRBUF_INIT;
/*
* Run the info/refs request and see if the server supports protocol
@@ -1348,23 +1287,58 @@ static int stateless_connect(const char *service_name)
fflush(stdout);
}
- proxy_state_init(&p, service_name, discover->version);
+ rpc.service_name = service_name;
+ rpc.service_url = xstrfmt("%s%s", url.buf, rpc.service_name);
+ rpc.hdr_content_type = xstrfmt("Content-Type: application/x-%s-request", rpc.service_name);
+ rpc.hdr_accept = xstrfmt("Accept: application/x-%s-result", rpc.service_name);
+ if (get_protocol_http_header(discover->version, &buf)) {
+ rpc.protocol_header = strbuf_detach(&buf, NULL);
+ } else {
+ rpc.protocol_header = NULL;
+ strbuf_release(&buf);
+ }
+ rpc.buf = xmalloc(http_post_buffer);
+ rpc.alloc = http_post_buffer;
+ rpc.len = 0;
+ rpc.pos = 0;
+ rpc.in = 1;
+ rpc.out = 0;
+ rpc.any_written = 0;
+ rpc.gzip_request = 1;
+ rpc.initial_buffer = 0;
+ rpc.write_line_lengths = 1;
+ rpc.flush_read_but_not_sent = 0;
/*
* Dump the capability listing that we got from the server earlier
* during the info/refs request.
*/
- write_or_die(p.out, discover->buf, discover->len);
+ write_or_die(rpc.in, discover->buf, discover->len);
+
+ /* Until we see EOF keep sending POSTs */
+ while (1) {
+ size_t avail;
+ enum packet_read_status status;
- /* Peek the next packet line. Until we see EOF keep sending POSTs */
- while (packet_reader_peek(&p.reader) != PACKET_READ_EOF) {
- if (proxy_request(&p)) {
+ if (!rpc_read_from_out(&rpc, PACKET_READ_GENTLE_ON_EOF, &avail,
+ &status))
+ BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
+ if (status == PACKET_READ_EOF)
+ break;
+ if (post_rpc(&rpc, status == PACKET_READ_FLUSH))
/* We would have an err here */
break;
- }
+ /* Reset the buffer for next request */
+ rpc.len = 0;
}
- proxy_state_clear(&p);
+ free(rpc.service_url);
+ free(rpc.hdr_content_type);
+ free(rpc.hdr_accept);
+ free(rpc.protocol_header);
+ free(rpc.buf);
+ strbuf_release(&buf);
+
return 0;
}
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index db4ae09f2f..e112b6086c 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -542,7 +542,38 @@ test_expect_success 'clone with http:// using protocol v2' '
# Client requested to use protocol v2
grep "Git-Protocol: version=2" log &&
# Server responded using protocol v2
- grep "git< version 2" log
+ grep "git< version 2" log &&
+ # Verify that the chunked encoding sending codepath is NOT exercised
+ ! grep "Send header: Transfer-Encoding: chunked" log
+'
+
+test_expect_success 'clone big repository with http:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/big" &&
+ # Ensure that the list of wants is greater than http.postbuffer below
+ for i in $(test_seq 1 1500)
+ do
+ # do not use here-doc, because it requires a process
+ # per loop iteration
+ echo "commit refs/heads/too-many-refs-$i" &&
+ echo "committer git <git@example.com> $i +0000" &&
+ echo "data 0" &&
+ echo "M 644 inline bla.txt" &&
+ echo "data 4" &&
+ echo "bla"
+ done | git -C "$HTTPD_DOCUMENT_ROOT_PATH/big" fast-import &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git \
+ -c protocol.version=2 -c http.postbuffer=65536 \
+ clone "$HTTPD_URL/smart/big" big_child &&
+
+ # Client requested to use protocol v2
+ grep "Git-Protocol: version=2" log &&
+ # Server responded using protocol v2
+ grep "git< version 2" log &&
+ # Verify that the chunked encoding sending codepath is exercised
+ grep "Send header: Transfer-Encoding: chunked" log
'
test_expect_success 'fetch with http:// using protocol v2' '