trying to fall back). After line feed terminating the positive
(empty) response, the output of the service starts. Messages
(both request and response) must consist of zero or more
- PKT-LINEs, terminating in a flush packet. The client must not
+ PKT-LINEs, terminating in a flush packet. Response messages will
+ then have a response end packet after the flush packet to
+ indicate the end of a response. The client must not
expect the server to store any state in between request-response
pairs. After the connection ends, the remote helper exits.
+
* '0000' Flush Packet (flush-pkt) - indicates the end of a message
* '0001' Delimiter Packet (delim-pkt) - separates sections of a message
+ * '0002' Message Packet (response-end-pkt) - indicates the end of a response
+ for stateless connections
Initial Client Request
----------------------
version = discover_version(&reader);
switch (version) {
case protocol_v2:
- get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL);
+ get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);
break;
case protocol_v1:
case protocol_v0:
die_initial_contact(0);
case PACKET_READ_FLUSH:
case PACKET_READ_DELIM:
+ case PACKET_READ_RESPONSE_END:
version = protocol_v0;
break;
case PACKET_READ_NORMAL:
state = EXPECTING_DONE;
break;
case PACKET_READ_DELIM:
+ case PACKET_READ_RESPONSE_END:
die(_("invalid packet"));
}
return ret;
}
+void check_stateless_delimiter(int stateless_rpc,
+ struct packet_reader *reader,
+ const char *error)
+{
+ if (!stateless_rpc)
+ return; /* not in stateless mode, no delimiter expected */
+ if (packet_reader_read(reader) != PACKET_READ_RESPONSE_END)
+ die("%s", error);
+}
+
struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
struct ref **list, int for_push,
const struct argv_array *ref_prefixes,
- const struct string_list *server_options)
+ const struct string_list *server_options,
+ int stateless_rpc)
{
int i;
*list = NULL;
if (reader->status != PACKET_READ_FLUSH)
die(_("expected flush after ref listing"));
+ check_stateless_delimiter(stateless_rpc, reader,
+ _("expected response end packet after ref listing"));
+
return list;
}
int server_supports_feature(const char *c, const char *feature,
int die_on_error);
+void check_stateless_delimiter(int stateless_rpc,
+ struct packet_reader *reader,
+ const char *error);
+
#endif
FETCH_DONE,
};
+static void do_check_stateless_delimiter(const struct fetch_pack_args *args,
+ struct packet_reader *reader)
+{
+ check_stateless_delimiter(args->stateless_rpc, reader,
+ _("git fetch-pack: expected response end packet"));
+}
+
static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
int fd[2],
const struct ref *orig_ref,
/* Process ACKs/NAKs */
switch (process_acks(negotiator, &reader, &common)) {
case READY:
+ /*
+ * Don't check for response delimiter; get_pack() will
+ * read the rest of this response.
+ */
state = FETCH_GET_PACK;
break;
case COMMON_FOUND:
seen_ack = 1;
/* fallthrough */
case NO_COMMON_FOUND:
+ do_check_stateless_delimiter(args, &reader);
state = FETCH_SEND_REQUEST;
break;
}
process_section_header(&reader, "packfile", 0);
if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
die(_("git fetch-pack: fetch failed."));
+ do_check_stateless_delimiter(args, &reader);
state = FETCH_DONE;
break;
die_errno(_("unable to write delim packet"));
}
+void packet_response_end(int fd)
+{
+ packet_trace("0002", 4, 1);
+ if (write_in_full(fd, "0002", 4) < 0)
+ die_errno(_("unable to write stateless separator packet"));
+}
+
int packet_flush_gently(int fd)
{
packet_trace("0000", 4, 1);
return ret;
}
-static int packet_length(const char *linelen)
+int packet_length(const char lenbuf_hex[4])
{
- int val = hex2chr(linelen);
- return (val < 0) ? val : (val << 8) | hex2chr(linelen + 2);
+ int val = hex2chr(lenbuf_hex);
+ return (val < 0) ? val : (val << 8) | hex2chr(lenbuf_hex + 2);
}
enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
packet_trace("0001", 4, 0);
*pktlen = 0;
return PACKET_READ_DELIM;
+ } else if (len == 2) {
+ packet_trace("0002", 4, 0);
+ *pktlen = 0;
+ return PACKET_READ_RESPONSE_END;
} else if (len < 4) {
die(_("protocol error: bad line length %d"), len);
}
*/
void packet_flush(int fd);
void packet_delim(int fd);
+void packet_response_end(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);
int packet_read(int fd, char **src_buffer, size_t *src_len, char
*buffer, unsigned size, int options);
+/*
+ * Convert a four hex digit packet line length header into its numeric
+ * representation.
+ *
+ * If lenbuf_hex contains non-hex characters, return -1. Otherwise, return the
+ * numeric value of the length header.
+ */
+int packet_length(const char lenbuf_hex[4]);
+
/*
* Read a packetized line into a buffer like the 'packet_read()' function but
* returns an 'enum packet_read_status' which indicates the status of the read.
PACKET_READ_NORMAL,
PACKET_READ_FLUSH,
PACKET_READ_DELIM,
+ PACKET_READ_RESPONSE_END,
};
enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
size_t *src_len, char *buffer,
case PACKET_READ_FLUSH:
memcpy(buf - 4, "0000", 4);
break;
+ case PACKET_READ_RESPONSE_END:
+ die(_("remote server sent stateless separator"));
}
}
return 0;
}
/*
- * If avail is non-zerp, the line length for the flush still
+ * If avail is non-zero, the line length for the flush still
* hasn't been fully sent. Proceed with sending the line
* length.
*/
}
#endif
+struct check_pktline_state {
+ char len_buf[4];
+ int len_filled;
+ int remaining;
+};
+
+static void check_pktline(struct check_pktline_state *state, const char *ptr, size_t size)
+{
+ while (size) {
+ if (!state->remaining) {
+ int digits_remaining = 4 - state->len_filled;
+ if (digits_remaining > size)
+ digits_remaining = size;
+ memcpy(&state->len_buf[state->len_filled], ptr, digits_remaining);
+ state->len_filled += digits_remaining;
+ ptr += digits_remaining;
+ size -= digits_remaining;
+
+ if (state->len_filled == 4) {
+ state->remaining = packet_length(state->len_buf);
+ if (state->remaining < 0) {
+ die(_("remote-curl: bad line length character: %.4s"), state->len_buf);
+ } else if (state->remaining == 2) {
+ die(_("remote-curl: unexpected response end packet"));
+ } else if (state->remaining < 4) {
+ state->remaining = 0;
+ } else {
+ state->remaining -= 4;
+ }
+ state->len_filled = 0;
+ }
+ }
+
+ if (state->remaining) {
+ int remaining = state->remaining;
+ if (remaining > size)
+ remaining = size;
+ ptr += remaining;
+ size -= remaining;
+ state->remaining -= remaining;
+ }
+ }
+}
+
struct rpc_in_data {
struct rpc_state *rpc;
struct active_request_slot *slot;
+ int check_pktline;
+ struct check_pktline_state pktline_state;
};
/*
return size;
if (size)
data->rpc->any_written = 1;
+ if (data->check_pktline)
+ check_pktline(&data->pktline_state, ptr, size);
write_or_die(data->rpc->in, ptr, size);
return size;
}
* 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)
+static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_received)
{
struct active_request_slot *slot;
struct curl_slist *headers = http_copy_default_headers();
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
rpc_in_data.rpc = rpc;
rpc_in_data.slot = slot;
+ rpc_in_data.check_pktline = stateless_connect;
+ memset(&rpc_in_data.pktline_state, 0, sizeof(rpc_in_data.pktline_state));
curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
if (!rpc->any_written)
err = -1;
+ if (rpc_in_data.pktline_state.len_filled)
+ err = error(_("%d bytes of length header were received"), rpc_in_data.pktline_state.len_filled);
+ if (rpc_in_data.pktline_state.remaining)
+ err = error(_("%d bytes of body are still expected"), rpc_in_data.pktline_state.remaining);
+
+ if (stateless_connect)
+ packet_response_end(rpc->in);
+
curl_slist_free_all(headers);
free(gzip_body);
return err;
break;
rpc->pos = 0;
rpc->len = n;
- err |= post_rpc(rpc, 0);
+ err |= post_rpc(rpc, 0, 0);
}
close(client.in);
if (ret)
exit(128); /* error already reported */
- free_specs:
+free_specs:
argv_array_clear(&specs);
}
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))
+ if (post_rpc(&rpc, 1, status == PACKET_READ_FLUSH))
/* We would have an err here */
break;
/* Reset the buffer for next request */
struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
struct ref **list, int for_push,
const struct argv_array *ref_prefixes,
- const struct string_list *server_options);
+ const struct string_list *server_options,
+ int stateless_rpc);
int resolve_remote_symref(struct ref *ref, struct ref *list);
state = PROCESS_REQUEST_DONE;
break;
+ case PACKET_READ_RESPONSE_END:
+ BUG("unexpected stateless separator packet");
}
}
case PACKET_READ_DELIM:
printf("0001\n");
break;
+ case PACKET_READ_RESPONSE_END:
+ printf("0002\n");
+ break;
}
}
}
case PACKET_READ_FLUSH:
return;
case PACKET_READ_DELIM:
+ case PACKET_READ_RESPONSE_END:
break;
}
}
prepare_httpd() {
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
+ install_script incomplete-length-upload-pack-v2-http.sh
+ install_script incomplete-body-upload-pack-v2-http.sh
install_script broken-smart-http.sh
install_script error-smart-http.sh
install_script error.sh
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
</LocationMatch>
+ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
+ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
ScriptAlias /broken_smart/ broken-smart-http.sh/
<Directory ${GIT_EXEC_PATH}>
Options FollowSymlinks
</Directory>
+<Files incomplete-length-upload-pack-v2-http.sh>
+ Options ExecCGI
+</Files>
+<Files incomplete-body-upload-pack-v2-http.sh>
+ Options ExecCGI
+</Files>
<Files broken-smart-http.sh>
Options ExecCGI
</Files>
--- /dev/null
+printf "Content-Type: text/%s\n" "application/x-git-upload-pack-result"
+echo
+printf "%s%s" "0079" "45"
--- /dev/null
+printf "Content-Type: text/%s\n" "application/x-git-upload-pack-result"
+echo
+printf "%s" "00"
! grep "Send header: Transfer-Encoding: chunked" log
'
+test_expect_success 'clone repository with http:// using protocol v2 with incomplete pktline length' '
+ test_when_finished "rm -f log" &&
+
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_length" &&
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_length" file &&
+
+ test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+ clone "$HTTPD_URL/smart/incomplete_length" incomplete_length_child 2>err &&
+
+ # Client requested to use protocol v2
+ grep "Git-Protocol: version=2" log &&
+ # Server responded using protocol v2
+ grep "git< version 2" log &&
+ # Client reported appropriate failure
+ test_i18ngrep "bytes of length header were received" err
+'
+
+test_expect_success 'clone repository with http:// using protocol v2 with incomplete pktline body' '
+ test_when_finished "rm -f log" &&
+
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_body" &&
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_body" file &&
+
+ test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+ clone "$HTTPD_URL/smart/incomplete_body" incomplete_body_child 2>err &&
+
+ # Client requested to use protocol v2
+ grep "Git-Protocol: version=2" log &&
+ # Server responded using protocol v2
+ grep "git< version 2" log &&
+ # Client reported appropriate failure
+ test_i18ngrep "bytes of body are still expected" err
+'
+
+test_expect_success 'clone with http:// using protocol v2 and invalid parameters' '
+ test_when_finished "rm -f log" &&
+
+ test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" \
+ git -c protocol.version=2 \
+ clone --shallow-since=20151012 "$HTTPD_URL/smart/http_parent" http_child_invalid &&
+
+ # Client requested to use protocol v2
+ grep "Git-Protocol: version=2" log &&
+ # Server responded using protocol v2
+ grep "git< version 2" log
+'
+
test_expect_success 'clone big repository with http:// using protocol v2' '
test_when_finished "rm -f log" &&
if (must_list_refs)
get_remote_refs(data->fd[1], &reader, &refs, for_push,
ref_prefixes,
- transport->server_options);
+ transport->server_options,
+ transport->stateless_rpc);
break;
case protocol_v1:
case protocol_v0:
refs_tmp = handshake(transport, 0, NULL, must_list_refs);
}
- switch (data->version) {
- case protocol_v2:
- refs = fetch_pack(&args, data->fd,
- refs_tmp ? refs_tmp : transport->remote_refs,
- to_fetch, nr_heads, &data->shallow,
- &transport->pack_lockfile, data->version);
- break;
- case protocol_v1:
- case protocol_v0:
- die_if_server_options(transport);
- refs = fetch_pack(&args, data->fd,
- refs_tmp ? refs_tmp : transport->remote_refs,
- to_fetch, nr_heads, &data->shallow,
- &transport->pack_lockfile, data->version);
- break;
- case protocol_unknown_version:
+ if (data->version == protocol_unknown_version)
BUG("unknown protocol version");
- }
+ else if (data->version <= protocol_v1)
+ die_if_server_options(transport);
+
+ refs = fetch_pack(&args, data->fd,
+ refs_tmp ? refs_tmp : transport->remote_refs,
+ to_fetch, nr_heads, &data->shallow,
+ &transport->pack_lockfile, data->version);
close(data->fd[0]);
close(data->fd[1]);