Skip to content

Commit 7372573

Browse files
committed
httpclient: support expect/continue
Allow users to opt-in to expect/continue handling when sending a POST and we're authenticated with a "connection-based" authentication mechanism like NTLM or Negotiate. If the response is a 100, return to the caller (to allow them to post their body). If the response is *not* a 100, buffer the response for the caller. HTTP expect/continue is generally safe, but some legacy servers have not implemented it correctly. Require it to be opt-in.
1 parent 6c21c98 commit 7372573

File tree

7 files changed

+68
-10
lines changed

7 files changed

+68
-10
lines changed

include/git2/common.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ typedef enum {
203203
GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY,
204204
GIT_OPT_GET_PACK_MAX_OBJECTS,
205205
GIT_OPT_SET_PACK_MAX_OBJECTS,
206-
GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS
206+
GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS,
207+
GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE
207208
} git_libgit2_opt_t;
208209

209210
/**
@@ -397,6 +398,11 @@ typedef enum {
397398
* > This will cause .keep file existence checks to be skipped when
398399
* > accessing packfiles, which can help performance with remote filesystems.
399400
*
401+
* opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, int enabled)
402+
* > When connecting to a server using NTLM or Negotiate
403+
* > authentication, use expect/continue when POSTing data.
404+
* > This option is not available on Windows.
405+
*
400406
* @param option Option key
401407
* @param ... value to set the option
402408
* @return 0 on success, <0 on failure

src/settings.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "refs.h"
2626
#include "index.h"
2727
#include "transports/smart.h"
28+
#include "transports/http.h"
2829
#include "streams/openssl.h"
2930
#include "streams/mbedtls.h"
3031

@@ -284,6 +285,10 @@ int git_libgit2_opts(int key, ...)
284285
git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0);
285286
break;
286287

288+
case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE:
289+
git_http__expect_continue = (va_arg(ap, int) != 0);
290+
break;
291+
287292
default:
288293
git_error_set(GIT_ERROR_INVALID, "invalid option key");
289294
error = -1;

src/transports/http.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "streams/tls.h"
2626
#include "streams/socket.h"
2727

28+
bool git_http__expect_continue = false;
29+
2830
git_http_auth_scheme auth_schemes[] = {
2931
{ GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
3032
{ GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm },
@@ -84,6 +86,7 @@ typedef struct {
8486
git_cred *cred;
8587
unsigned url_cred_presented : 1,
8688
authenticated : 1;
89+
git_http_authtype_t prior_authtype;
8790

8891
git_vector auth_challenges;
8992
git_http_auth_context *auth_context;
@@ -1048,8 +1051,10 @@ static void reset_auth_connection(http_server *server)
10481051
*/
10491052

10501053
if (server->authenticated &&
1051-
server->auth_context &&
1054+
server->auth_context &&
10521055
server->auth_context->connection_affinity) {
1056+
server->prior_authtype = server->auth_context->type;
1057+
10531058
free_auth_context(server);
10541059

10551060
server->url_cred_presented = 0;

src/transports/http.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#define GIT_HTTP_REPLAY_MAX 15
1414

15+
extern bool git_http__expect_continue;
16+
1517
GIT_INLINE(int) git_http__user_agent(git_buf *buf)
1618
{
1719
const char *ua = git_libgit2__user_agent();

src/transports/httpclient.c

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,6 @@ GIT_INLINE(int) server_setup_from_url(
824824
static void reset_parser(git_http_client *client)
825825
{
826826
http_parser_init(&client->parser, HTTP_RESPONSE);
827-
git_buf_clear(&client->read_buf);
828827
}
829828

830829
static int setup_hosts(
@@ -869,6 +868,17 @@ GIT_INLINE(int) server_create_stream(git_http_server *server)
869868
return -1;
870869
}
871870

871+
GIT_INLINE(void) save_early_response(
872+
git_http_client *client,
873+
git_http_response *response)
874+
{
875+
/* Buffer the response so we can return it in read_response */
876+
client->state = HAS_EARLY_RESPONSE;
877+
878+
memcpy(&client->early_response, response, sizeof(git_http_response));
879+
memset(response, 0, sizeof(git_http_response));
880+
}
881+
872882
static int proxy_connect(
873883
git_http_client *client,
874884
git_http_request *request)
@@ -905,11 +915,7 @@ static int proxy_connect(
905915
assert(client->state == DONE);
906916

907917
if (response.status == 407) {
908-
/* Buffer the response so we can return it in read_response */
909-
client->state = HAS_EARLY_RESPONSE;
910-
911-
memcpy(&client->early_response, &response, sizeof(response));
912-
memset(&response, 0, sizeof(response));
918+
save_early_response(client, &response);
913919

914920
error = GIT_RETRY;
915921
goto done;
@@ -1194,6 +1200,7 @@ int git_http_client_send_request(
11941200
git_http_client *client,
11951201
git_http_request *request)
11961202
{
1203+
git_http_response response = {0};
11971204
int error = -1;
11981205

11991206
assert(client && request);
@@ -1220,13 +1227,26 @@ int git_http_client_send_request(
12201227
(error = client_write_request(client)) < 0)
12211228
goto done;
12221229

1230+
client->state = SENT_REQUEST;
1231+
1232+
if (request->expect_continue) {
1233+
if ((error = git_http_client_read_response(&response, client)) < 0 ||
1234+
(error = git_http_client_skip_body(client)) < 0)
1235+
goto done;
1236+
1237+
error = 0;
1238+
1239+
if (response.status != 100) {
1240+
save_early_response(client, &response);
1241+
goto done;
1242+
}
1243+
}
1244+
12231245
if (request->content_length || request->chunked) {
12241246
client->state = SENDING_BODY;
12251247
client->request_body_len = request->content_length;
12261248
client->request_body_remain = request->content_length;
12271249
client->request_chunked = request->chunked;
1228-
} else {
1229-
client->state = SENT_REQUEST;
12301250
}
12311251

12321252
reset_parser(client);
@@ -1235,9 +1255,16 @@ int git_http_client_send_request(
12351255
if (error == GIT_RETRY)
12361256
error = 0;
12371257

1258+
git_http_response_dispose(&response);
12381259
return error;
12391260
}
12401261

1262+
bool git_http_client_has_response(git_http_client *client)
1263+
{
1264+
return (client->state == HAS_EARLY_RESPONSE ||
1265+
client->state > SENT_REQUEST);
1266+
}
1267+
12411268
int git_http_client_send_body(
12421269
git_http_client *client,
12431270
const char *buffer,

src/transports/httpclient.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ extern int git_http_client_send_request(
9191
git_http_client *client,
9292
git_http_request *request);
9393

94+
/*
95+
* After sending a request, there may already be a response to read --
96+
* either because there was a non-continue response to an expect: continue
97+
* request, or because the server pipelined a response to us before we even
98+
* sent the request. Examine the state.
99+
*
100+
* @param client the client to examine
101+
* @return true if there's already a response to read, false otherwise
102+
*/
103+
extern bool git_http_client_has_response(git_http_client *client);
104+
94105
/**
95106
* Sends the given buffer to the remote as part of the request body. The
96107
* request must have specified either a content_length or the chunked flag.

src/transports/winhttp.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
# define DWORD_MAX 0xffffffff
5858
#endif
5959

60+
bool git_http__expect_continue = false;
61+
6062
static const char *prefix_https = "https://";
6163
static const char *upload_pack_service = "upload-pack";
6264
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";

0 commit comments

Comments
 (0)