http: add an "auto" mode for http.emptyauth

This variable needs to be specified to make some types of non-basic authentication work, but ideally this would just work out of the box for everyone. However, simply setting it to "1" by default introduces an extra round-trip for cases where it _isn't_ useful. We end up sending a bogus empty credential that the server rejects. Instead, let's introduce an automatic mode, that works like this: 1. We won't try to send the bogus credential on the first request. We'll wait to get an HTTP 401, as usual. 2. After seeing an HTTP 401, the empty-auth hack will kick in only when we know there is an auth method available that might make use of it (i.e., something besides "Basic" or "Digest"). That should make it work out of the box, without incurring any extra round-trips for people hitting Basic-only servers. This _does_ incur an extra round-trip if you really want to use "Basic" but your server advertises other methods (the emptyauth hack will kick in but fail, and then Git will actually ask for a password). The auto mode may incur an extra round-trip over setting http.emptyauth=true, because part of the emptyauth hack is to feed this blank password to curl even before we've made a single request. Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Feb 25, 2017 at 14:18 UTC 40a18fc77ca3ba1b018f0fbdcbdf4a6d237aadf3
1 file changed +45 -5
http.c
+45 -5
@@ -109,7 +109,7 @@ static int curl_save_cookies;
109 struct credential http_auth = CREDENTIAL_INIT;
110 static int http_proactive_auth;
111 static const char *user_agent;
112 -static int curl_empty_auth;
112 +static int curl_empty_auth = -1;
113
114 enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;
115
@@ -125,6 +125,14 @@ static struct credential cert_auth = CREDENTIAL_INIT;
125 static int ssl_cert_password_required;
126 #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
127 static unsigned long http_auth_methods = CURLAUTH_ANY;
128 +static int http_auth_methods_restricted;
129 +/* Modes for which empty_auth cannot actually help us. */
130 +static unsigned long empty_auth_useless =
131 + CURLAUTH_BASIC
132 +#ifdef CURLAUTH_DIGEST_IE
133 + | CURLAUTH_DIGEST_IE
134 +#endif
135 + | CURLAUTH_DIGEST;
136 #endif
137
138 static struct curl_slist *pragma_header;
@@ -333,7 +341,10 @@ static int http_options(const char *var, const char *value, void *cb)
341 return git_config_string(&user_agent, var, value);
342
343 if (!strcmp("http.emptyauth", var)) {
336 - curl_empty_auth = git_config_bool(var, value);
344 + if (value && !strcmp("auto", value))
345 + curl_empty_auth = -1;
346 + else
347 + curl_empty_auth = git_config_bool(var, value);
348 return 0;
349 }
350
@@ -382,10 +393,37 @@ static int http_options(const char *var, const char *value, void *cb)
393 return git_default_config(var, value, cb);
394 }
395
396 +static int curl_empty_auth_enabled(void)
397 +{
398 + if (curl_empty_auth >= 0)
399 + return curl_empty_auth;
400 +
401 +#ifndef LIBCURL_CAN_HANDLE_AUTH_ANY
402 + /*
403 + * Our libcurl is too old to do AUTH_ANY in the first place;
404 + * just default to turning the feature off.
405 + */
406 +#else
407 + /*
408 + * In the automatic case, kick in the empty-auth
409 + * hack as long as we would potentially try some
410 + * method more exotic than "Basic" or "Digest".
411 + *
412 + * But only do this when this is our second or
413 + * subsequent request, as by then we know what
414 + * methods are available.
415 + */
416 + if (http_auth_methods_restricted &&
417 + (http_auth_methods & ~empty_auth_useless))
418 + return 1;
419 +#endif
420 + return 0;
421 +}
422 +
423 static void init_curl_http_auth(CURL *result)
424 {
425 if (!http_auth.username || !*http_auth.username) {
388 - if (curl_empty_auth)
426 + if (curl_empty_auth_enabled())
427 curl_easy_setopt(result, CURLOPT_USERPWD, ":");
428 return;
429 }
@@ -1072,7 +1110,7 @@ struct active_request_slot *get_active_slot(void)
1110 #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
1111 curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
1112 #endif
1075 - if (http_auth.password || curl_empty_auth)
1113 + if (http_auth.password || curl_empty_auth_enabled())
1114 init_curl_http_auth(slot->curl);
1115
1116 return slot;
@@ -1340,8 +1378,10 @@ static int handle_curl_result(struct slot_results *results)
1378 } else {
1379 #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
1380 http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
1343 - if (results->auth_avail)
1381 + if (results->auth_avail) {
1382 http_auth_methods &= results->auth_avail;
1383 + http_auth_methods_restricted = 1;
1384 + }
1385 #endif
1386 return HTTP_REAUTH;
1387 }