From: Junio C Hamano Date: Mon, 28 Oct 2013 17:43:24 +0000 (-0700) Subject: Merge branch 'ew/keepalive' X-Git-Tag: v1.8.5-rc0~23 X-Git-Url: https://www.git.lorimer.id.au/gitweb.git/diff_plain/bb2fd90c7bbb4261aa1800a4c35b06dc5116a647?hp=-c Merge branch 'ew/keepalive' * ew/keepalive: http: use curl's tcp keepalive if available http: enable keepalive on TCP sockets --- bb2fd90c7bbb4261aa1800a4c35b06dc5116a647 diff --combined http.c index f3e1439d58,6359526196..0ddb164a6f --- a/http.c +++ b/http.c @@@ -3,11 -3,8 +3,11 @@@ #include "sideband.h" #include "run-command.h" #include "url.h" +#include "urlmatch.h" +#include "credential.h" +#include "version.h" +#include "pkt-line.h" -int data_received; int active_requests; int http_is_verbose; size_t http_post_buffer = 16 * LARGE_PACKET_MAX; @@@ -32,7 -29,6 +32,7 @@@ static CURL *curl_default char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; +static int curl_ssl_try; static const char *ssl_cert; #if LIBCURL_VERSION_NUM >= 0x070903 static const char *ssl_key; @@@ -45,10 -41,7 +45,10 @@@ static long curl_low_speed_limit = -1 static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; static const char *curl_http_proxy; -static char *user_name, *user_pass; +static const char *curl_cookie_file; +static int curl_save_cookies; +static struct credential http_auth = CREDENTIAL_INIT; +static int http_proactive_auth; static const char *user_agent; #if LIBCURL_VERSION_NUM >= 0x071700 @@@ -59,7 -52,7 +59,7 @@@ #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD #endif -static char *ssl_cert_password; +static struct credential cert_auth = CREDENTIAL_INIT; static int ssl_cert_password_required; static struct curl_slist *pragma_header; @@@ -105,11 -98,13 +105,11 @@@ size_t fwrite_buffer(char *ptr, size_t struct strbuf *buffer = buffer_; strbuf_add(buffer, ptr, size); - data_received++; return size; } size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) { - data_received++; return eltsize * nmemb; } @@@ -162,11 -157,8 +162,11 @@@ static int http_options(const char *var if (!strcmp("http.sslcainfo", var)) return git_config_string(&ssl_cainfo, var, value); if (!strcmp("http.sslcertpasswordprotected", var)) { - if (git_config_bool(var, value)) - ssl_cert_password_required = 1; + ssl_cert_password_required = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.ssltry", var)) { + curl_ssl_try = git_config_bool(var, value); return 0; } if (!strcmp("http.minsessions", var)) { @@@ -199,13 -191,6 +199,13 @@@ if (!strcmp("http.proxy", var)) return git_config_string(&curl_http_proxy, var, value); + if (!strcmp("http.cookiefile", var)) + return git_config_string(&curl_cookie_file, var, value); + if (!strcmp("http.savecookies", var)) { + curl_save_cookies = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.postbuffer", var)) { http_post_buffer = git_config_int(var, value); if (http_post_buffer < LARGE_PACKET_MAX) @@@ -222,44 -207,68 +222,80 @@@ static void init_curl_http_auth(CURL *result) { - if (user_name) { - struct strbuf up = STRBUF_INIT; - if (!user_pass) - user_pass = xstrdup(git_getpass("Password: ")); - strbuf_addf(&up, "%s:%s", user_name, user_pass); - curl_easy_setopt(result, CURLOPT_USERPWD, - strbuf_detach(&up, NULL)); + if (!http_auth.username) + return; + + credential_fill(&http_auth); + +#if LIBCURL_VERSION_NUM >= 0x071301 + curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); + curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); +#else + { + static struct strbuf up = STRBUF_INIT; + /* + * Note that we assume we only ever have a single set of + * credentials in a given program run, so we do not have + * to worry about updating this buffer, only setting its + * initial value. + */ + if (!up.len) + strbuf_addf(&up, "%s:%s", + http_auth.username, http_auth.password); + curl_easy_setopt(result, CURLOPT_USERPWD, up.buf); } +#endif } static int has_cert_password(void) { - if (ssl_cert_password != NULL) - return 1; if (ssl_cert == NULL || ssl_cert_password_required != 1) return 0; - /* Only prompt the user once. */ - ssl_cert_password_required = -1; - ssl_cert_password = git_getpass("Certificate Password: "); - if (ssl_cert_password != NULL) { - ssl_cert_password = xstrdup(ssl_cert_password); - return 1; - } else - return 0; + if (!cert_auth.password) { + cert_auth.protocol = xstrdup("cert"); + cert_auth.username = xstrdup(""); + cert_auth.path = xstrdup(ssl_cert); + credential_fill(&cert_auth); + } + return 1; } + #if LIBCURL_VERSION_NUM >= 0x071900 + static void set_curl_keepalive(CURL *c) + { + curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1); + } + + #elif LIBCURL_VERSION_NUM >= 0x071000 + static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type) + { + int ka = 1; + int rc; + socklen_t len = (socklen_t)sizeof(ka); + + if (type != CURLSOCKTYPE_IPCXN) + return 0; + + rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&ka, len); + if (rc < 0) + warning("unable to set SO_KEEPALIVE on socket %s", + strerror(errno)); + + return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */ + } + + static void set_curl_keepalive(CURL *c) + { + curl_easy_setopt(c, CURLOPT_SOCKOPTFUNCTION, sockopt_callback); + } + + #else + static void set_curl_keepalive(CURL *c) + { + /* not supported on older curl versions */ + } + #endif + static CURL *get_curl_handle(void) { CURL *result = curl_easy_init(); @@@ -281,13 -290,12 +317,13 @@@ curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); #endif - init_curl_http_auth(result); + if (http_proactive_auth) + init_curl_http_auth(result); if (ssl_cert != NULL) curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); if (has_cert_password()) - curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password); + curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password); #if LIBCURL_VERSION_NUM >= 0x070903 if (ssl_key != NULL) curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); @@@ -298,6 -306,7 +334,6 @@@ #endif if (ssl_cainfo != NULL) curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); - curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, @@@ -317,24 -326,68 +353,26 @@@ curl_easy_setopt(result, CURLOPT_VERBOSE, 1); curl_easy_setopt(result, CURLOPT_USERAGENT, - user_agent ? user_agent : GIT_HTTP_USER_AGENT); + user_agent ? user_agent : git_user_agent()); if (curl_ftp_no_epsv) curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); - if (curl_http_proxy) +#ifdef CURLOPT_USE_SSL + if (curl_ssl_try) + curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY); +#endif + + if (curl_http_proxy) { curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); + curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + } + set_curl_keepalive(result); + return result; } -static void http_auth_init(const char *url) -{ - char *at, *colon, *cp, *slash, *decoded; - int len; - - cp = strstr(url, "://"); - if (!cp) - return; - - /* - * Ok, the URL looks like "proto://something". Which one? - * "proto://:@/...", - * "proto://@/...", or just - * "proto:///..."? - */ - cp += 3; - at = strchr(cp, '@'); - colon = strchr(cp, ':'); - slash = strchrnul(cp, '/'); - if (!at || slash <= at) - return; /* No credentials */ - if (!colon || at <= colon) { - /* Only username */ - len = at - cp; - user_name = xmalloc(len + 1); - memcpy(user_name, cp, len); - user_name[len] = '\0'; - decoded = url_decode(user_name); - free(user_name); - user_name = decoded; - user_pass = NULL; - } else { - len = colon - cp; - user_name = xmalloc(len + 1); - memcpy(user_name, cp, len); - user_name[len] = '\0'; - decoded = url_decode(user_name); - free(user_name); - user_name = decoded; - len = at - (colon + 1); - user_pass = xmalloc(len + 1); - memcpy(user_pass, colon + 1, len); - user_pass[len] = '\0'; - decoded = url_decode(user_pass); - free(user_pass); - user_pass = decoded; - } -} - static void set_from_env(const char **var, const char *envname) { const char *val = getenv(envname); @@@ -342,29 -395,17 +380,29 @@@ *var = val; } -void http_init(struct remote *remote) +void http_init(struct remote *remote, const char *url, int proactive_auth) { char *low_speed_limit; char *low_speed_time; + char *normalized_url; + struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + + config.section = "http"; + config.key = NULL; + config.collect_fn = http_options; + config.cascade_fn = git_default_config; + config.cb = NULL; http_is_verbose = 0; + normalized_url = url_normalize(url, &config.url); - git_config(http_options, NULL); + git_config(urlmatch_config_entry, &config); + free(normalized_url); curl_global_init(CURL_GLOBAL_ALL); + http_proactive_auth = proactive_auth; + if (remote && remote->http_proxy) curl_http_proxy = xstrdup(remote->http_proxy); @@@ -418,11 -459,11 +456,11 @@@ if (getenv("GIT_CURL_FTP_NO_EPSV")) curl_ftp_no_epsv = 1; - if (remote && remote->url && remote->url[0]) { - http_auth_init(remote->url[0]); + if (url) { + credential_from_url(&http_auth, url); if (!ssl_cert_password_required && getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") && - !prefixcmp(remote->url[0], "https://")) + !prefixcmp(url, "https://")) ssl_cert_password_required = 1; } @@@ -468,10 -509,10 +506,10 @@@ void http_cleanup(void curl_http_proxy = NULL; } - if (ssl_cert_password != NULL) { - memset(ssl_cert_password, 0, strlen(ssl_cert_password)); - free(ssl_cert_password); - ssl_cert_password = NULL; + if (cert_auth.password != NULL) { + memset(cert_auth.password, 0, strlen(cert_auth.password)); + free(cert_auth.password); + cert_auth.password = NULL; } ssl_cert_password_required = 0; } @@@ -523,13 -564,11 +561,13 @@@ struct active_request_slot *get_active_ active_requests++; slot->in_use = 1; - slot->local = NULL; slot->results = NULL; slot->finished = NULL; slot->callback_data = NULL; slot->callback_func = NULL; + curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file); + if (curl_save_cookies) + curl_easy_setopt(slot->curl, CURLOPT_COOKIEJAR, curl_cookie_file); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL); @@@ -538,9 -577,6 +576,9 @@@ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1); + if (http_auth.password) + init_curl_http_auth(slot->curl); return slot; } @@@ -631,6 -667,8 +669,6 @@@ void step_active_slots(void void run_active_slot(struct active_request_slot *slot) { #ifdef USE_CURL_MULTI - long last_pos = 0; - long current_pos; fd_set readfds; fd_set writefds; fd_set excfds; @@@ -640,45 -678,25 +678,45 @@@ slot->finished = &finished; while (!finished) { - data_received = 0; step_active_slots(); - if (!data_received && slot->local != NULL) { - current_pos = ftell(slot->local); - if (current_pos > last_pos) - data_received++; - last_pos = current_pos; - } + if (slot->in_use) { +#if LIBCURL_VERSION_NUM >= 0x070f04 + long curl_timeout; + curl_multi_timeout(curlm, &curl_timeout); + if (curl_timeout == 0) { + continue; + } else if (curl_timeout == -1) { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; + } else { + select_timeout.tv_sec = curl_timeout / 1000; + select_timeout.tv_usec = (curl_timeout % 1000) * 1000; + } +#else + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; +#endif - if (slot->in_use && !data_received) { - max_fd = 0; + max_fd = -1; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&excfds); - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 50000; - select(max_fd, &readfds, &writefds, - &excfds, &select_timeout); + curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd); + + /* + * It can happen that curl_multi_timeout returns a pathologically + * long timeout when curl_multi_fdset returns no file descriptors + * to read. See commit message for more details. + */ + if (max_fd < 0 && + (select_timeout.tv_sec > 0 || + select_timeout.tv_usec > 50000)) { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; + } + + select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout); } } #else @@@ -756,6 -774,14 +794,6 @@@ static inline int needs_quote(int ch return 1; } -static inline int hex(int v) -{ - if (v < 10) - return '0' + v; - else - return 'A' + v - 10; -} - static char *quote_ref_url(const char *base, const char *ref) { struct strbuf buf = STRBUF_INIT; @@@ -792,57 -818,11 +830,57 @@@ char *get_remote_object_url(const char return strbuf_detach(&buf, NULL); } +int handle_curl_result(struct slot_results *results) +{ + /* + * If we see a failing http code with CURLE_OK, we have turned off + * FAILONERROR (to keep the server's custom error response), and should + * translate the code into failure here. + */ + if (results->curl_result == CURLE_OK && + results->http_code >= 400) { + results->curl_result = CURLE_HTTP_RETURNED_ERROR; + /* + * Normally curl will already have put the "reason phrase" + * from the server into curl_errorstr; unfortunately without + * FAILONERROR it is lost, so we can give only the numeric + * status code. + */ + snprintf(curl_errorstr, sizeof(curl_errorstr), + "The requested URL returned error: %ld", + results->http_code); + } + + if (results->curl_result == CURLE_OK) { + credential_approve(&http_auth); + return HTTP_OK; + } else if (missing_target(results)) + return HTTP_MISSING_TARGET; + else if (results->http_code == 401) { + if (http_auth.username && http_auth.password) { + credential_reject(&http_auth); + return HTTP_NOAUTH; + } else { + credential_fill(&http_auth); + return HTTP_REAUTH; + } + } else { +#if LIBCURL_VERSION_NUM >= 0x070c00 + if (!curl_errorstr[0]) + strlcpy(curl_errorstr, + curl_easy_strerror(results->curl_result), + sizeof(curl_errorstr)); +#endif + return HTTP_ERROR; + } +} + /* http_request() targets */ #define HTTP_REQUEST_STRBUF 0 #define HTTP_REQUEST_FILE 1 -static int http_request(const char *url, void *result, int target, int options) +static int http_request(const char *url, struct strbuf *type, + void *result, int target, int options) { struct active_request_slot *slot; struct slot_results results; @@@ -869,6 -849,7 +907,6 @@@ headers = curl_slist_append(headers, buf.buf); strbuf_reset(&buf); } - slot->local = result; } else curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); @@@ -877,75 -858,57 +915,75 @@@ strbuf_addstr(&buf, "Pragma:"); if (options & HTTP_NO_CACHE) strbuf_addstr(&buf, " no-cache"); + if (options & HTTP_KEEP_ERROR) + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); headers = curl_slist_append(headers, buf.buf); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip"); if (start_active_slot(slot)) { run_active_slot(slot); - if (results.curl_result == CURLE_OK) - ret = HTTP_OK; - else if (missing_target(&results)) - ret = HTTP_MISSING_TARGET; - else if (results.http_code == 401) { - if (user_name) { - ret = HTTP_NOAUTH; - } else { - /* - * git_getpass is needed here because its very likely stdin/stdout are - * pipes to our parent process. So we instead need to use /dev/tty, - * but that is non-portable. Using git_getpass() can at least be stubbed - * on other platforms with a different implementation if/when necessary. - */ - user_name = xstrdup(git_getpass("Username: ")); - init_curl_http_auth(slot->curl); - ret = HTTP_REAUTH; - } - } else - ret = HTTP_ERROR; + ret = handle_curl_result(&results); } else { - error("Unable to start HTTP request for %s", url); + snprintf(curl_errorstr, sizeof(curl_errorstr), + "failed to start HTTP request"); ret = HTTP_START_FAILED; } - slot->local = NULL; + if (type) { + char *t; + strbuf_reset(type); + curl_easy_getinfo(slot->curl, CURLINFO_CONTENT_TYPE, &t); + if (t) + strbuf_addstr(type, t); + } + curl_slist_free_all(headers); strbuf_release(&buf); return ret; } -int http_get_strbuf(const char *url, struct strbuf *result, int options) +static int http_request_reauth(const char *url, + struct strbuf *type, + void *result, int target, + int options) { - int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); - if (http_ret == HTTP_REAUTH) { - http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); + int ret = http_request(url, type, result, target, options); + if (ret != HTTP_REAUTH) + return ret; + + /* + * If we are using KEEP_ERROR, the previous request may have + * put cruft into our output stream; we should clear it out before + * making our next request. We only know how to do this for + * the strbuf case, but that is enough to satisfy current callers. + */ + if (options & HTTP_KEEP_ERROR) { + switch (target) { + case HTTP_REQUEST_STRBUF: + strbuf_reset(result); + break; + default: + die("BUG: HTTP_KEEP_ERROR is only supported with strbufs"); + } } - return http_ret; + return http_request(url, type, result, target, options); +} + +int http_get_strbuf(const char *url, + struct strbuf *type, + struct strbuf *result, int options) +{ + return http_request_reauth(url, type, result, + HTTP_REQUEST_STRBUF, options); } /* - * Downloads an url and stores the result in the given file. + * Downloads a URL and stores the result in the given file. * * If a previous interrupted download is detected (i.e. a previous temporary * file is still around) the download is resumed. @@@ -964,7 -927,7 +1002,7 @@@ static int http_get_file(const char *ur goto cleanup; } - ret = http_request(url, result, HTTP_REQUEST_FILE, options); + ret = http_request_reauth(url, NULL, result, HTTP_REQUEST_FILE, options); fclose(result); if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename)) @@@ -974,6 -937,15 +1012,6 @@@ cleanup return ret; } -int http_error(const char *url, int ret) -{ - /* http_request has already handled HTTP_START_FAILED. */ - if (ret != HTTP_START_FAILED) - error("%s while accessing %s\n", curl_errorstr, url); - - return ret; -} - int http_fetch_ref(const char *base, struct ref *ref) { char *url; @@@ -981,7 -953,7 +1019,7 @@@ int ret = -1; url = quote_ref_url(base, ref->name); - if (http_get_strbuf(url, &buffer, HTTP_NO_CACHE) == HTTP_OK) { + if (http_get_strbuf(url, NULL, &buffer, HTTP_NO_CACHE) == HTTP_OK) { strbuf_rtrim(&buffer); if (buffer.len == 40) ret = get_sha1_hex(buffer.buf, ref->old_sha1); @@@ -1013,7 -985,7 +1051,7 @@@ static char *fetch_pack_index(unsigned tmp = strbuf_detach(&buf, NULL); if (http_get_file(url, tmp, 0) != HTTP_OK) { - error("Unable to get pack index %s\n", url); + error("Unable to get pack index %s", url); free(tmp); tmp = NULL; } @@@ -1074,7 -1046,7 +1112,7 @@@ int http_get_info_packs(const char *bas strbuf_addstr(&buf, "objects/info/packs"); url = strbuf_detach(&buf, NULL); - ret = http_get_strbuf(url, &buf, HTTP_NO_CACHE); + ret = http_get_strbuf(url, NULL, &buf, HTTP_NO_CACHE); if (ret != HTTP_OK) goto cleanup; @@@ -1109,6 -1081,7 +1147,6 @@@ void release_http_pack_request(struct h if (preq->packfile != NULL) { fclose(preq->packfile); preq->packfile = NULL; - preq->slot->local = NULL; } if (preq->range_header != NULL) { curl_slist_free_all(preq->range_header); @@@ -1130,6 -1103,7 +1168,6 @@@ int finish_http_pack_request(struct htt fclose(preq->packfile); preq->packfile = NULL; - preq->slot->local = NULL; lst = preq->lst; while (*lst != p) @@@ -1180,8 -1154,9 +1218,8 @@@ struct http_pack_request *new_http_pack struct strbuf buf = STRBUF_INIT; struct http_pack_request *preq; - preq = xmalloc(sizeof(*preq)); + preq = xcalloc(1, sizeof(*preq)); preq->target = target; - preq->range_header = NULL; end_url_with_slash(&buf, base_url); strbuf_addf(&buf, "objects/pack/pack-%s.pack", @@@ -1198,6 -1173,7 +1236,6 @@@ } preq->slot = get_active_slot(); - preq->slot->local = preq->packfile; curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile); curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url); @@@ -1254,6 -1230,7 +1292,6 @@@ static size_t fwrite_sha1_file(char *pt git_SHA1_Update(&freq->c, expn, sizeof(expn) - freq->stream.avail_out); } while (freq->stream.avail_in && freq->zret == Z_OK); - data_received++; return size; } @@@ -1271,7 -1248,7 +1309,7 @@@ struct http_object_request *new_http_ob struct curl_slist *range_header = NULL; struct http_object_request *freq; - freq = xmalloc(sizeof(*freq)); + freq = xcalloc(1, sizeof(*freq)); hashcpy(freq->sha1, sha1); freq->localfile = -1; @@@ -1309,6 -1286,8 +1347,6 @@@ goto abort; } - memset(&freq->stream, 0, sizeof(freq->stream)); - git_inflate_init(&freq->stream); git_SHA1_Init(&freq->c); @@@ -1383,6 -1362,7 +1421,6 @@@ return freq; abort: - free(filename); free(freq->url); free(freq); return NULL;