Commit 7372573b by Edward Thomson

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.
parent 6c21c989
...@@ -203,7 +203,8 @@ typedef enum { ...@@ -203,7 +203,8 @@ typedef enum {
GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY,
GIT_OPT_GET_PACK_MAX_OBJECTS, GIT_OPT_GET_PACK_MAX_OBJECTS,
GIT_OPT_SET_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; } git_libgit2_opt_t;
/** /**
...@@ -397,6 +398,11 @@ typedef enum { ...@@ -397,6 +398,11 @@ typedef enum {
* > This will cause .keep file existence checks to be skipped when * > This will cause .keep file existence checks to be skipped when
* > accessing packfiles, which can help performance with remote filesystems. * > 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 option Option key
* @param ... value to set the option * @param ... value to set the option
* @return 0 on success, <0 on failure * @return 0 on success, <0 on failure
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "refs.h" #include "refs.h"
#include "index.h" #include "index.h"
#include "transports/smart.h" #include "transports/smart.h"
#include "transports/http.h"
#include "streams/openssl.h" #include "streams/openssl.h"
#include "streams/mbedtls.h" #include "streams/mbedtls.h"
...@@ -284,6 +285,10 @@ int git_libgit2_opts(int key, ...) ...@@ -284,6 +285,10 @@ int git_libgit2_opts(int key, ...)
git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0);
break; break;
case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE:
git_http__expect_continue = (va_arg(ap, int) != 0);
break;
default: default:
git_error_set(GIT_ERROR_INVALID, "invalid option key"); git_error_set(GIT_ERROR_INVALID, "invalid option key");
error = -1; error = -1;
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
#include "streams/tls.h" #include "streams/tls.h"
#include "streams/socket.h" #include "streams/socket.h"
bool git_http__expect_continue = false;
git_http_auth_scheme auth_schemes[] = { git_http_auth_scheme auth_schemes[] = {
{ GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, { 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 }, { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm },
...@@ -84,6 +86,7 @@ typedef struct { ...@@ -84,6 +86,7 @@ typedef struct {
git_cred *cred; git_cred *cred;
unsigned url_cred_presented : 1, unsigned url_cred_presented : 1,
authenticated : 1; authenticated : 1;
git_http_authtype_t prior_authtype;
git_vector auth_challenges; git_vector auth_challenges;
git_http_auth_context *auth_context; git_http_auth_context *auth_context;
...@@ -1048,8 +1051,10 @@ static void reset_auth_connection(http_server *server) ...@@ -1048,8 +1051,10 @@ static void reset_auth_connection(http_server *server)
*/ */
if (server->authenticated && if (server->authenticated &&
server->auth_context && server->auth_context &&
server->auth_context->connection_affinity) { server->auth_context->connection_affinity) {
server->prior_authtype = server->auth_context->type;
free_auth_context(server); free_auth_context(server);
server->url_cred_presented = 0; server->url_cred_presented = 0;
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#define GIT_HTTP_REPLAY_MAX 15 #define GIT_HTTP_REPLAY_MAX 15
extern bool git_http__expect_continue;
GIT_INLINE(int) git_http__user_agent(git_buf *buf) GIT_INLINE(int) git_http__user_agent(git_buf *buf)
{ {
const char *ua = git_libgit2__user_agent(); const char *ua = git_libgit2__user_agent();
......
...@@ -824,7 +824,6 @@ GIT_INLINE(int) server_setup_from_url( ...@@ -824,7 +824,6 @@ GIT_INLINE(int) server_setup_from_url(
static void reset_parser(git_http_client *client) static void reset_parser(git_http_client *client)
{ {
http_parser_init(&client->parser, HTTP_RESPONSE); http_parser_init(&client->parser, HTTP_RESPONSE);
git_buf_clear(&client->read_buf);
} }
static int setup_hosts( static int setup_hosts(
...@@ -869,6 +868,17 @@ GIT_INLINE(int) server_create_stream(git_http_server *server) ...@@ -869,6 +868,17 @@ GIT_INLINE(int) server_create_stream(git_http_server *server)
return -1; 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( static int proxy_connect(
git_http_client *client, git_http_client *client,
git_http_request *request) git_http_request *request)
...@@ -905,11 +915,7 @@ static int proxy_connect( ...@@ -905,11 +915,7 @@ static int proxy_connect(
assert(client->state == DONE); assert(client->state == DONE);
if (response.status == 407) { if (response.status == 407) {
/* Buffer the response so we can return it in read_response */ save_early_response(client, &response);
client->state = HAS_EARLY_RESPONSE;
memcpy(&client->early_response, &response, sizeof(response));
memset(&response, 0, sizeof(response));
error = GIT_RETRY; error = GIT_RETRY;
goto done; goto done;
...@@ -1194,6 +1200,7 @@ int git_http_client_send_request( ...@@ -1194,6 +1200,7 @@ int git_http_client_send_request(
git_http_client *client, git_http_client *client,
git_http_request *request) git_http_request *request)
{ {
git_http_response response = {0};
int error = -1; int error = -1;
assert(client && request); assert(client && request);
...@@ -1220,13 +1227,26 @@ int git_http_client_send_request( ...@@ -1220,13 +1227,26 @@ int git_http_client_send_request(
(error = client_write_request(client)) < 0) (error = client_write_request(client)) < 0)
goto done; 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) { if (request->content_length || request->chunked) {
client->state = SENDING_BODY; client->state = SENDING_BODY;
client->request_body_len = request->content_length; client->request_body_len = request->content_length;
client->request_body_remain = request->content_length; client->request_body_remain = request->content_length;
client->request_chunked = request->chunked; client->request_chunked = request->chunked;
} else {
client->state = SENT_REQUEST;
} }
reset_parser(client); reset_parser(client);
...@@ -1235,9 +1255,16 @@ done: ...@@ -1235,9 +1255,16 @@ done:
if (error == GIT_RETRY) if (error == GIT_RETRY)
error = 0; error = 0;
git_http_response_dispose(&response);
return error; 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( int git_http_client_send_body(
git_http_client *client, git_http_client *client,
const char *buffer, const char *buffer,
......
...@@ -91,6 +91,17 @@ extern int git_http_client_send_request( ...@@ -91,6 +91,17 @@ extern int git_http_client_send_request(
git_http_client *client, git_http_client *client,
git_http_request *request); 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 * 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. * request must have specified either a content_length or the chunked flag.
......
...@@ -57,6 +57,8 @@ ...@@ -57,6 +57,8 @@
# define DWORD_MAX 0xffffffff # define DWORD_MAX 0xffffffff
#endif #endif
bool git_http__expect_continue = false;
static const char *prefix_https = "https://"; static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment