diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2019-10-25 12:22:10 +0100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2020-01-24 10:16:36 -0600 |
commit | 7372573b5f1113b8522e2588fac1c529ddcedb0a (patch) | |
tree | 640bffeb289ef588579ae5274a459f0e487f76b9 | |
parent | 6c21c989a3b8cba20ad8cbe806937071a1923832 (diff) | |
download | libgit2-7372573b5f1113b8522e2588fac1c529ddcedb0a.tar.gz |
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.
-rw-r--r-- | include/git2/common.h | 8 | ||||
-rw-r--r-- | src/settings.c | 5 | ||||
-rw-r--r-- | src/transports/http.c | 7 | ||||
-rw-r--r-- | src/transports/http.h | 2 | ||||
-rw-r--r-- | src/transports/httpclient.c | 43 | ||||
-rw-r--r-- | src/transports/httpclient.h | 11 | ||||
-rw-r--r-- | src/transports/winhttp.c | 2 |
7 files changed, 68 insertions, 10 deletions
diff --git a/include/git2/common.h b/include/git2/common.h index 438198295..947e40845 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -203,7 +203,8 @@ typedef enum { GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, GIT_OPT_GET_PACK_MAX_OBJECTS, GIT_OPT_SET_PACK_MAX_OBJECTS, - GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS + GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE } git_libgit2_opt_t; /** @@ -397,6 +398,11 @@ typedef enum { * > This will cause .keep file existence checks to be skipped when * > accessing packfiles, which can help performance with remote filesystems. * + * opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, int enabled) + * > When connecting to a server using NTLM or Negotiate + * > authentication, use expect/continue when POSTing data. + * > This option is not available on Windows. + * * @param option Option key * @param ... value to set the option * @return 0 on success, <0 on failure diff --git a/src/settings.c b/src/settings.c index 28d10eabb..6fae49eaf 100644 --- a/src/settings.c +++ b/src/settings.c @@ -25,6 +25,7 @@ #include "refs.h" #include "index.h" #include "transports/smart.h" +#include "transports/http.h" #include "streams/openssl.h" #include "streams/mbedtls.h" @@ -284,6 +285,10 @@ int git_libgit2_opts(int key, ...) git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); break; + case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: + git_http__expect_continue = (va_arg(ap, int) != 0); + break; + default: git_error_set(GIT_ERROR_INVALID, "invalid option key"); error = -1; diff --git a/src/transports/http.c b/src/transports/http.c index cd209efff..9e35f23c2 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -25,6 +25,8 @@ #include "streams/tls.h" #include "streams/socket.h" +bool git_http__expect_continue = false; + git_http_auth_scheme auth_schemes[] = { { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm }, @@ -84,6 +86,7 @@ typedef struct { git_cred *cred; unsigned url_cred_presented : 1, authenticated : 1; + git_http_authtype_t prior_authtype; git_vector auth_challenges; git_http_auth_context *auth_context; @@ -1048,8 +1051,10 @@ static void reset_auth_connection(http_server *server) */ if (server->authenticated && - server->auth_context && + server->auth_context && server->auth_context->connection_affinity) { + server->prior_authtype = server->auth_context->type; + free_auth_context(server); server->url_cred_presented = 0; diff --git a/src/transports/http.h b/src/transports/http.h index ddaab0b45..6f698c9dd 100644 --- a/src/transports/http.h +++ b/src/transports/http.h @@ -12,6 +12,8 @@ #define GIT_HTTP_REPLAY_MAX 15 +extern bool git_http__expect_continue; + GIT_INLINE(int) git_http__user_agent(git_buf *buf) { const char *ua = git_libgit2__user_agent(); diff --git a/src/transports/httpclient.c b/src/transports/httpclient.c index 32580f5b3..585f04fb2 100644 --- a/src/transports/httpclient.c +++ b/src/transports/httpclient.c @@ -824,7 +824,6 @@ GIT_INLINE(int) server_setup_from_url( static void reset_parser(git_http_client *client) { http_parser_init(&client->parser, HTTP_RESPONSE); - git_buf_clear(&client->read_buf); } static int setup_hosts( @@ -869,6 +868,17 @@ GIT_INLINE(int) server_create_stream(git_http_server *server) return -1; } +GIT_INLINE(void) save_early_response( + git_http_client *client, + git_http_response *response) +{ + /* Buffer the response so we can return it in read_response */ + client->state = HAS_EARLY_RESPONSE; + + memcpy(&client->early_response, response, sizeof(git_http_response)); + memset(response, 0, sizeof(git_http_response)); +} + static int proxy_connect( git_http_client *client, git_http_request *request) @@ -905,11 +915,7 @@ static int proxy_connect( assert(client->state == DONE); if (response.status == 407) { - /* Buffer the response so we can return it in read_response */ - client->state = HAS_EARLY_RESPONSE; - - memcpy(&client->early_response, &response, sizeof(response)); - memset(&response, 0, sizeof(response)); + save_early_response(client, &response); error = GIT_RETRY; goto done; @@ -1194,6 +1200,7 @@ int git_http_client_send_request( git_http_client *client, git_http_request *request) { + git_http_response response = {0}; int error = -1; assert(client && request); @@ -1220,13 +1227,26 @@ int git_http_client_send_request( (error = client_write_request(client)) < 0) goto done; + client->state = SENT_REQUEST; + + if (request->expect_continue) { + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0) + goto done; + + error = 0; + + if (response.status != 100) { + save_early_response(client, &response); + goto done; + } + } + if (request->content_length || request->chunked) { client->state = SENDING_BODY; client->request_body_len = request->content_length; client->request_body_remain = request->content_length; client->request_chunked = request->chunked; - } else { - client->state = SENT_REQUEST; } reset_parser(client); @@ -1235,9 +1255,16 @@ done: if (error == GIT_RETRY) error = 0; + git_http_response_dispose(&response); return error; } +bool git_http_client_has_response(git_http_client *client) +{ + return (client->state == HAS_EARLY_RESPONSE || + client->state > SENT_REQUEST); +} + int git_http_client_send_body( git_http_client *client, const char *buffer, diff --git a/src/transports/httpclient.h b/src/transports/httpclient.h index 4a447cff8..cb2186671 100644 --- a/src/transports/httpclient.h +++ b/src/transports/httpclient.h @@ -91,6 +91,17 @@ extern int git_http_client_send_request( git_http_client *client, git_http_request *request); +/* + * After sending a request, there may already be a response to read -- + * either because there was a non-continue response to an expect: continue + * request, or because the server pipelined a response to us before we even + * sent the request. Examine the state. + * + * @param client the client to examine + * @return true if there's already a response to read, false otherwise + */ +extern bool git_http_client_has_response(git_http_client *client); + /** * Sends the given buffer to the remote as part of the request body. The * request must have specified either a content_length or the chunked flag. diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 3360ec947..d8623bfff 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -57,6 +57,8 @@ # define DWORD_MAX 0xffffffff #endif +bool git_http__expect_continue = false; + static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; |