Skip to content

Commit 1152f36

Browse files
committed
httpclient: consume final chunk message
When sending a new request, ensure that we got the entirety of the response body. Our caller may have decided that they were done reading. If we were not at the end of the message, this means that we need to tear down the connection and cannot do keep-alive. However, if the caller read all of the message, but we still have a final end-of-response chunk signifier (ie, "0\r\n\r\n") on the socket, then we should consider that the response was successfully copmleted. If we're asked to send a new request, try to read from the socket, just to clear out that end-of-chunk message, marking ourselves as disconnected on any errors.
1 parent dcd3b81 commit 1152f36

File tree

1 file changed

+153
-111
lines changed

1 file changed

+153
-111
lines changed

src/transports/httpclient.c

Lines changed: 153 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ static int on_body(http_parser *parser, const char *buf, size_t len)
257257
http_parser_context *ctx = (http_parser_context *) parser->data;
258258
size_t max_len;
259259

260+
/* Saw data when we expected not to (eg, in consume_response_body) */
261+
if (ctx->output_buf == NULL && ctx->output_size == 0) {
262+
ctx->parse_status = PARSE_STATUS_NO_OUTPUT;
263+
return 0;
264+
}
265+
260266
assert(ctx->output_size >= ctx->output_written);
261267

262268
max_len = min(ctx->output_size - ctx->output_written, len);
@@ -556,117 +562,6 @@ static int http_client_connect(git_http_client *client)
556562
return error;
557563
}
558564

559-
int git_http_client_send_request(
560-
git_http_client *client,
561-
git_http_request *request)
562-
{
563-
int error = -1;
564-
565-
assert(client && request);
566-
567-
http_parser_init(&client->parser, HTTP_RESPONSE);
568-
git_buf_clear(&client->read_buf);
569-
570-
if (git_trace_level() >= GIT_TRACE_DEBUG) {
571-
git_buf url = GIT_BUF_INIT;
572-
git_net_url_fmt(&url, request->url);
573-
git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s",
574-
name_for_method(request->method),
575-
url.ptr ? url.ptr : "<invalid>");
576-
git_buf_dispose(&url);
577-
}
578-
579-
if ((error = http_client_setup_hosts(client, request)) < 0 ||
580-
(error = http_client_connect(client)) < 0 ||
581-
(error = generate_request(client, request)) < 0 ||
582-
(error = stream_write(&client->server,
583-
client->request_msg.ptr,
584-
client->request_msg.size)) < 0)
585-
goto done;
586-
587-
if (request->content_length || request->chunked) {
588-
client->state = SENDING_BODY;
589-
client->request_body_len = request->content_length;
590-
client->request_body_remain = request->content_length;
591-
client->request_chunked = request->chunked;
592-
} else {
593-
client->state = SENT_REQUEST;
594-
}
595-
596-
done:
597-
return error;
598-
}
599-
600-
int git_http_client_send_body(
601-
git_http_client *client,
602-
const char *buffer,
603-
size_t buffer_len)
604-
{
605-
git_http_server *server;
606-
git_buf hdr = GIT_BUF_INIT;
607-
int error;
608-
609-
assert(client && client->state == SENDING_BODY);
610-
611-
if (!buffer_len)
612-
return 0;
613-
614-
server = &client->server;
615-
616-
if (client->request_body_len) {
617-
assert(buffer_len <= client->request_body_remain);
618-
619-
if ((error = stream_write(server, buffer, buffer_len)) < 0)
620-
goto done;
621-
622-
client->request_body_remain -= buffer_len;
623-
} else {
624-
if ((error = git_buf_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 ||
625-
(error = stream_write(server, hdr.ptr, hdr.size)) < 0 ||
626-
(error = stream_write(server, buffer, buffer_len)) < 0 ||
627-
(error = stream_write(server, "\r\n", 2)) < 0)
628-
goto done;
629-
}
630-
631-
done:
632-
git_buf_dispose(&hdr);
633-
return error;
634-
}
635-
636-
static int complete_request(git_http_client *client)
637-
{
638-
int error = 0;
639-
640-
assert(client && client->state == SENDING_BODY);
641-
642-
if (client->request_body_len && client->request_body_remain) {
643-
git_error_set(GIT_ERROR_NET, "truncated write");
644-
error = -1;
645-
} else if (client->request_chunked) {
646-
error = stream_write(&client->server, "0\r\n\r\n", 5);
647-
}
648-
649-
return error;
650-
}
651-
652-
static bool parser_settings_initialized;
653-
static http_parser_settings parser_settings;
654-
655-
GIT_INLINE(http_parser_settings *) http_client_parser_settings(void)
656-
{
657-
if (! parser_settings_initialized) {
658-
parser_settings.on_header_field = on_header_field;
659-
parser_settings.on_header_value = on_header_value;
660-
parser_settings.on_headers_complete = on_headers_complete;
661-
parser_settings.on_body = on_body;
662-
parser_settings.on_message_complete = on_message_complete;
663-
664-
parser_settings_initialized = true;
665-
}
666-
667-
return &parser_settings;
668-
}
669-
670565
GIT_INLINE(int) client_read(git_http_client *client)
671566
{
672567
char *buf = client->read_buf.ptr + client->read_buf.size;
@@ -698,6 +593,25 @@ GIT_INLINE(int) client_read(git_http_client *client)
698593
return (int)read_len;
699594
}
700595

596+
static bool parser_settings_initialized;
597+
static http_parser_settings parser_settings;
598+
599+
GIT_INLINE(http_parser_settings *) http_client_parser_settings(void)
600+
{
601+
if (!parser_settings_initialized) {
602+
parser_settings.on_header_field = on_header_field;
603+
parser_settings.on_header_value = on_header_value;
604+
parser_settings.on_headers_complete = on_headers_complete;
605+
parser_settings.on_body = on_body;
606+
parser_settings.on_message_complete = on_message_complete;
607+
608+
parser_settings_initialized = true;
609+
}
610+
611+
return &parser_settings;
612+
}
613+
614+
701615
GIT_INLINE(int) client_read_and_parse(git_http_client *client)
702616
{
703617
http_parser *parser = &client->parser;
@@ -784,6 +698,134 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client)
784698
return (int)parsed_len;
785699
}
786700

701+
/*
702+
* See if we've consumed the entire response body. If the client was
703+
* reading the body but did not consume it entirely, it's possible that
704+
* they knew that the stream had finished (in a git response, seeing a final
705+
* flush) and stopped reading. But if the response was chunked, we may have
706+
* not consumed the final chunk marker. Consume it to ensure that we don't
707+
* have it waiting in our socket. If there's more than just a chunk marker,
708+
* close the connection.
709+
*/
710+
static void complete_response_body(git_http_client *client)
711+
{
712+
http_parser_context parser_context = {0};
713+
714+
/* If we're not keeping alive, don't bother. */
715+
if (!client->keepalive) {
716+
client->connected = 0;
717+
return;
718+
}
719+
720+
parser_context.client = client;
721+
client->parser.data = &parser_context;
722+
723+
/* If there was an error, just close the connection. */
724+
if (client_read_and_parse(client) < 0 ||
725+
parser_context.error != HPE_OK ||
726+
(parser_context.parse_status != PARSE_STATUS_OK &&
727+
parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
728+
git_error_clear();
729+
client->connected = 0;
730+
}
731+
}
732+
733+
int git_http_client_send_request(
734+
git_http_client *client,
735+
git_http_request *request)
736+
{
737+
int error = -1;
738+
739+
assert(client && request);
740+
741+
/* If the client did not finish reading, clean up the stream. */
742+
if (client->state == READING_BODY)
743+
complete_response_body(client);
744+
745+
http_parser_init(&client->parser, HTTP_RESPONSE);
746+
747+
if (git_trace_level() >= GIT_TRACE_DEBUG) {
748+
git_buf url = GIT_BUF_INIT;
749+
git_net_url_fmt(&url, request->url);
750+
git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s",
751+
name_for_method(request->method),
752+
url.ptr ? url.ptr : "<invalid>");
753+
git_buf_dispose(&url);
754+
}
755+
756+
if ((error = http_client_setup_hosts(client, request)) < 0 ||
757+
(error = http_client_connect(client)) < 0 ||
758+
(error = generate_request(client, request)) < 0 ||
759+
(error = stream_write(&client->server,
760+
client->request_msg.ptr,
761+
client->request_msg.size)) < 0)
762+
goto done;
763+
764+
if (request->content_length || request->chunked) {
765+
client->state = SENDING_BODY;
766+
client->request_body_len = request->content_length;
767+
client->request_body_remain = request->content_length;
768+
client->request_chunked = request->chunked;
769+
} else {
770+
client->state = SENT_REQUEST;
771+
}
772+
773+
done:
774+
return error;
775+
}
776+
777+
int git_http_client_send_body(
778+
git_http_client *client,
779+
const char *buffer,
780+
size_t buffer_len)
781+
{
782+
git_http_server *server;
783+
git_buf hdr = GIT_BUF_INIT;
784+
int error;
785+
786+
assert(client && client->state == SENDING_BODY);
787+
788+
if (!buffer_len)
789+
return 0;
790+
791+
server = &client->server;
792+
793+
if (client->request_body_len) {
794+
assert(buffer_len <= client->request_body_remain);
795+
796+
if ((error = stream_write(server, buffer, buffer_len)) < 0)
797+
goto done;
798+
799+
client->request_body_remain -= buffer_len;
800+
} else {
801+
if ((error = git_buf_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 ||
802+
(error = stream_write(server, hdr.ptr, hdr.size)) < 0 ||
803+
(error = stream_write(server, buffer, buffer_len)) < 0 ||
804+
(error = stream_write(server, "\r\n", 2)) < 0)
805+
goto done;
806+
}
807+
808+
done:
809+
git_buf_dispose(&hdr);
810+
return error;
811+
}
812+
813+
static int complete_request(git_http_client *client)
814+
{
815+
int error = 0;
816+
817+
assert(client && client->state == SENDING_BODY);
818+
819+
if (client->request_body_len && client->request_body_remain) {
820+
git_error_set(GIT_ERROR_NET, "truncated write");
821+
error = -1;
822+
} else if (client->request_chunked) {
823+
error = stream_write(&client->server, "0\r\n\r\n", 5);
824+
}
825+
826+
return error;
827+
}
828+
787829
int git_http_client_read_response(
788830
git_http_response *response,
789831
git_http_client *client)

0 commit comments

Comments
 (0)