X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=http.c;h=8b23a546afdf40dc9f15756f637a62286fe1bdd0;hb=a78305ffbca58e49a7cdad901df0ae779bbed8fb;hp=4b179f6fc8f29c1683b187393203d20f95d29157;hpb=919cec9979a865f8169eaa30e5ff6de8e4fe5565;p=platform%2Fupstream%2Fgit.git diff --git a/http.c b/http.c index 4b179f6..8b23a54 100644 --- a/http.c +++ b/http.c @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "http.h" +#include "config.h" #include "pack.h" #include "sideband.h" #include "run-command.h" @@ -9,10 +10,23 @@ #include "version.h" #include "pkt-line.h" #include "gettext.h" - +#include "transport.h" +#include "packfile.h" +#include "protocol.h" +#include "string-list.h" +#include "object-store.h" + +static struct trace_key trace_curl = TRACE_KEY_INIT(CURL); +static int trace_curl_data = 1; +static int trace_curl_redact = 1; +#if LIBCURL_VERSION_NUM >= 0x070a08 +long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER; +#else +long int git_curl_ipresolve; +#endif int active_requests; int http_is_verbose; -size_t http_post_buffer = 16 * LARGE_PACKET_MAX; +ssize_t http_post_buffer = 16 * LARGE_PACKET_MAX; #if LIBCURL_VERSION_NUM >= 0x070a06 #define LIBCURL_CAN_HANDLE_AUTH_ANY @@ -29,29 +43,95 @@ static CURL *curl_default; #endif #define PREV_BUF_SIZE 4096 -#define RANGE_HEADER_SIZE 30 char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; static int curl_ssl_try; +static const char *curl_http_version = NULL; static const char *ssl_cert; +static const char *ssl_cipherlist; +static const char *ssl_version; +static struct { + const char *name; + long ssl_version; +} sslversions[] = { + { "sslv2", CURL_SSLVERSION_SSLv2 }, + { "sslv3", CURL_SSLVERSION_SSLv3 }, + { "tlsv1", CURL_SSLVERSION_TLSv1 }, +#if LIBCURL_VERSION_NUM >= 0x072200 + { "tlsv1.0", CURL_SSLVERSION_TLSv1_0 }, + { "tlsv1.1", CURL_SSLVERSION_TLSv1_1 }, + { "tlsv1.2", CURL_SSLVERSION_TLSv1_2 }, +#endif +#if LIBCURL_VERSION_NUM >= 0x073400 + { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 }, +#endif +}; #if LIBCURL_VERSION_NUM >= 0x070903 static const char *ssl_key; #endif #if LIBCURL_VERSION_NUM >= 0x070908 static const char *ssl_capath; #endif +#if LIBCURL_VERSION_NUM >= 0x071304 +static const char *curl_no_proxy; +#endif +#if LIBCURL_VERSION_NUM >= 0x072c00 +static const char *ssl_pinnedkey; +#endif static const char *ssl_cainfo; 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 const char *http_proxy_authmethod; + +static const char *http_proxy_ssl_cert; +static const char *http_proxy_ssl_key; +static const char *http_proxy_ssl_ca_info; +static struct credential proxy_cert_auth = CREDENTIAL_INIT; +static int proxy_ssl_cert_password_required; + +static struct { + const char *name; + long curlauth_param; +} proxy_authmethods[] = { + { "basic", CURLAUTH_BASIC }, + { "digest", CURLAUTH_DIGEST }, + { "negotiate", CURLAUTH_GSSNEGOTIATE }, + { "ntlm", CURLAUTH_NTLM }, +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY + { "anyauth", CURLAUTH_ANY }, +#endif + /* + * CURLAUTH_DIGEST_IE has no corresponding command-line option in + * curl(1) and is not included in CURLAUTH_ANY, so we leave it out + * here, too + */ +}; +#ifdef CURLGSSAPI_DELEGATION_FLAG +static const char *curl_deleg; +static struct { + const char *name; + long curl_deleg_param; +} curl_deleg_levels[] = { + { "none", CURLGSSAPI_DELEGATION_NONE }, + { "policy", CURLGSSAPI_DELEGATION_POLICY_FLAG }, + { "always", CURLGSSAPI_DELEGATION_FLAG }, +}; +#endif + +static struct credential proxy_auth = CREDENTIAL_INIT; +static const char *curl_proxyuserpwd; static const char *curl_cookie_file; static int curl_save_cookies; struct credential http_auth = CREDENTIAL_INIT; static int http_proactive_auth; static const char *user_agent; +static int curl_empty_auth = -1; + +enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL; #if LIBCURL_VERSION_NUM >= 0x071700 /* Use CURLOPT_KEYPASSWD as is */ @@ -65,15 +145,34 @@ static struct credential cert_auth = CREDENTIAL_INIT; static int ssl_cert_password_required; #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY static unsigned long http_auth_methods = CURLAUTH_ANY; +static int http_auth_methods_restricted; +/* Modes for which empty_auth cannot actually help us. */ +static unsigned long empty_auth_useless = + CURLAUTH_BASIC +#ifdef CURLAUTH_DIGEST_IE + | CURLAUTH_DIGEST_IE +#endif + | CURLAUTH_DIGEST; #endif static struct curl_slist *pragma_header; static struct curl_slist *no_pragma_header; +static struct string_list extra_http_headers = STRING_LIST_INIT_DUP; static struct active_request_slot *active_queue_head; static char *cached_accept_language; +static char *http_ssl_backend; + +static int http_schannel_check_revoke = 1; +/* + * With the backend being set to `schannel`, setting sslCAinfo would override + * the Certificate Store in cURL v7.60.0 and later, which is not what we want + * by default. + */ +static int http_schannel_use_ssl_cainfo; + size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -84,7 +183,7 @@ size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) memcpy(ptr, buffer->buf.buf + buffer->posn, size); buffer->posn += size; - return size; + return size / eltsize; } #ifndef NO_CURL_IOCTL @@ -112,12 +211,12 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) struct strbuf *buffer = buffer_; strbuf_add(buffer, ptr, size); - return size; + return nmemb; } size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) { - return eltsize * nmemb; + return nmemb; } static void closedown_active_slot(struct active_request_slot *slot) @@ -144,6 +243,9 @@ static void finish_active_slot(struct active_request_slot *slot) #else slot->results->auth_avail = 0; #endif + + curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE, + &slot->results->http_connectcode); } /* Run callback if appropriate */ @@ -151,6 +253,13 @@ static void finish_active_slot(struct active_request_slot *slot) slot->callback_func(slot->callback_data); } +static void xmulti_remove_handle(struct active_request_slot *slot) +{ +#ifdef USE_CURL_MULTI + curl_multi_remove_handle(curlm, slot->curl); +#endif +} + #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -166,7 +275,7 @@ static void process_curl_messages(void) slot->curl != curl_message->easy_handle) slot = slot->next; if (slot != NULL) { - curl_multi_remove_handle(curlm, slot->curl); + xmulti_remove_handle(slot); slot->curl_result = curl_result; finish_active_slot(slot); } else { @@ -183,22 +292,29 @@ static void process_curl_messages(void) static int http_options(const char *var, const char *value, void *cb) { + if (!strcmp("http.version", var)) { + return git_config_string(&curl_http_version, var, value); + } if (!strcmp("http.sslverify", var)) { curl_ssl_verify = git_config_bool(var, value); return 0; } + if (!strcmp("http.sslcipherlist", var)) + return git_config_string(&ssl_cipherlist, var, value); + if (!strcmp("http.sslversion", var)) + return git_config_string(&ssl_version, var, value); if (!strcmp("http.sslcert", var)) - return git_config_string(&ssl_cert, var, value); + return git_config_pathname(&ssl_cert, var, value); #if LIBCURL_VERSION_NUM >= 0x070903 if (!strcmp("http.sslkey", var)) - return git_config_string(&ssl_key, var, value); + return git_config_pathname(&ssl_key, var, value); #endif #if LIBCURL_VERSION_NUM >= 0x070908 if (!strcmp("http.sslcapath", var)) - return git_config_string(&ssl_capath, var, value); + return git_config_pathname(&ssl_capath, var, value); #endif if (!strcmp("http.sslcainfo", var)) - return git_config_string(&ssl_cainfo, var, value); + return git_config_pathname(&ssl_cainfo, var, value); if (!strcmp("http.sslcertpasswordprotected", var)) { ssl_cert_password_required = git_config_bool(var, value); return 0; @@ -207,6 +323,22 @@ static int http_options(const char *var, const char *value, void *cb) curl_ssl_try = git_config_bool(var, value); return 0; } + if (!strcmp("http.sslbackend", var)) { + free(http_ssl_backend); + http_ssl_backend = xstrdup_or_null(value); + return 0; + } + + if (!strcmp("http.schannelcheckrevoke", var)) { + http_schannel_check_revoke = git_config_bool(var, value); + return 0; + } + + if (!strcmp("http.schannelusesslcainfo", var)) { + http_schannel_use_ssl_cainfo = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -237,15 +369,34 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.proxy", var)) return git_config_string(&curl_http_proxy, var, value); + if (!strcmp("http.proxyauthmethod", var)) + return git_config_string(&http_proxy_authmethod, var, value); + + if (!strcmp("http.proxysslcert", var)) + return git_config_string(&http_proxy_ssl_cert, var, value); + + if (!strcmp("http.proxysslkey", var)) + return git_config_string(&http_proxy_ssl_key, var, value); + + if (!strcmp("http.proxysslcainfo", var)) + return git_config_string(&http_proxy_ssl_ca_info, var, value); + + if (!strcmp("http.proxysslcertpasswordprotected", var)) { + proxy_ssl_cert_password_required = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.cookiefile", var)) - return git_config_string(&curl_cookie_file, var, value); + return git_config_pathname(&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); + http_post_buffer = git_config_ssize_t(var, value); + if (http_post_buffer < 0) + warning(_("negative value for http.postbuffer; defaulting to %d"), LARGE_PACKET_MAX); if (http_post_buffer < LARGE_PACKET_MAX) http_post_buffer = LARGE_PACKET_MAX; return 0; @@ -254,14 +405,91 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.useragent", var)) return git_config_string(&user_agent, var, value); + if (!strcmp("http.emptyauth", var)) { + if (value && !strcmp("auto", value)) + curl_empty_auth = -1; + else + curl_empty_auth = git_config_bool(var, value); + return 0; + } + + if (!strcmp("http.delegation", var)) { +#ifdef CURLGSSAPI_DELEGATION_FLAG + return git_config_string(&curl_deleg, var, value); +#else + warning(_("Delegation control is not supported with cURL < 7.22.0")); + return 0; +#endif + } + + if (!strcmp("http.pinnedpubkey", var)) { +#if LIBCURL_VERSION_NUM >= 0x072c00 + return git_config_pathname(&ssl_pinnedkey, var, value); +#else + warning(_("Public key pinning not supported with cURL < 7.44.0")); + return 0; +#endif + } + + if (!strcmp("http.extraheader", var)) { + if (!value) { + return config_error_nonbool(var); + } else if (!*value) { + string_list_clear(&extra_http_headers, 0); + } else { + string_list_append(&extra_http_headers, value); + } + return 0; + } + + if (!strcmp("http.followredirects", var)) { + if (value && !strcmp(value, "initial")) + http_follow_config = HTTP_FOLLOW_INITIAL; + else if (git_config_bool(var, value)) + http_follow_config = HTTP_FOLLOW_ALWAYS; + else + http_follow_config = HTTP_FOLLOW_NONE; + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, cb); } +static int curl_empty_auth_enabled(void) +{ + if (curl_empty_auth >= 0) + return curl_empty_auth; + +#ifndef LIBCURL_CAN_HANDLE_AUTH_ANY + /* + * Our libcurl is too old to do AUTH_ANY in the first place; + * just default to turning the feature off. + */ +#else + /* + * In the automatic case, kick in the empty-auth + * hack as long as we would potentially try some + * method more exotic than "Basic" or "Digest". + * + * But only do this when this is our second or + * subsequent request, as by then we know what + * methods are available. + */ + if (http_auth_methods_restricted && + (http_auth_methods & ~empty_auth_useless)) + return 1; +#endif + return 0; +} + static void init_curl_http_auth(CURL *result) { - if (!http_auth.username) + if (!http_auth.username || !*http_auth.username) { + if (curl_empty_auth_enabled()) + curl_easy_setopt(result, CURLOPT_USERPWD, ":"); return; + } credential_fill(&http_auth); @@ -285,12 +513,73 @@ static void init_curl_http_auth(CURL *result) #endif } +/* *var must be free-able */ +static void var_override(const char **var, char *value) +{ + if (value) { + free((void *)*var); + *var = xstrdup(value); + } +} + +static void set_proxyauth_name_password(CURL *result) +{ +#if LIBCURL_VERSION_NUM >= 0x071301 + curl_easy_setopt(result, CURLOPT_PROXYUSERNAME, + proxy_auth.username); + curl_easy_setopt(result, CURLOPT_PROXYPASSWORD, + proxy_auth.password); +#else + struct strbuf s = STRBUF_INIT; + + strbuf_addstr_urlencode(&s, proxy_auth.username, + is_rfc3986_unreserved); + strbuf_addch(&s, ':'); + strbuf_addstr_urlencode(&s, proxy_auth.password, + is_rfc3986_unreserved); + curl_proxyuserpwd = strbuf_detach(&s, NULL); + curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd); +#endif +} + +static void init_curl_proxy_auth(CURL *result) +{ + if (proxy_auth.username) { + if (!proxy_auth.password) + credential_fill(&proxy_auth); + set_proxyauth_name_password(result); + } + + var_override(&http_proxy_authmethod, getenv("GIT_HTTP_PROXY_AUTHMETHOD")); + +#if LIBCURL_VERSION_NUM >= 0x070a07 /* CURLOPT_PROXYAUTH and CURLAUTH_ANY */ + if (http_proxy_authmethod) { + int i; + for (i = 0; i < ARRAY_SIZE(proxy_authmethods); i++) { + if (!strcmp(http_proxy_authmethod, proxy_authmethods[i].name)) { + curl_easy_setopt(result, CURLOPT_PROXYAUTH, + proxy_authmethods[i].curlauth_param); + break; + } + } + if (i == ARRAY_SIZE(proxy_authmethods)) { + warning("unsupported proxy authentication method %s: using anyauth", + http_proxy_authmethod); + curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + } + } + else + curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); +#endif +} + static int has_cert_password(void) { if (ssl_cert == NULL || ssl_cert_password_required != 1) return 0; if (!cert_auth.password) { cert_auth.protocol = xstrdup("cert"); + cert_auth.host = xstrdup(""); cert_auth.username = xstrdup(""); cert_auth.path = xstrdup(ssl_cert); credential_fill(&cert_auth); @@ -298,6 +587,22 @@ static int has_cert_password(void) return 1; } +#if LIBCURL_VERSION_NUM >= 0x073400 +static int has_proxy_cert_password(void) +{ + if (http_proxy_ssl_cert == NULL || proxy_ssl_cert_password_required != 1) + return 0; + if (!proxy_cert_auth.password) { + proxy_cert_auth.protocol = xstrdup("cert"); + proxy_cert_auth.host = xstrdup(""); + proxy_cert_auth.username = xstrdup(""); + proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert); + credential_fill(&proxy_cert_auth); + } + return 1; +} +#endif + #if LIBCURL_VERSION_NUM >= 0x071900 static void set_curl_keepalive(CURL *c) { @@ -316,8 +621,7 @@ static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type) rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&ka, len); if (rc < 0) - warning("unable to set SO_KEEPALIVE on socket %s", - strerror(errno)); + warning_errno("unable to set SO_KEEPALIVE on socket"); return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */ } @@ -334,6 +638,220 @@ static void set_curl_keepalive(CURL *c) } #endif +static void redact_sensitive_header(struct strbuf *header) +{ + const char *sensitive_header; + + if (trace_curl_redact && + (skip_prefix(header->buf, "Authorization:", &sensitive_header) || + skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { + /* The first token is the type, which is OK to log */ + while (isspace(*sensitive_header)) + sensitive_header++; + while (*sensitive_header && !isspace(*sensitive_header)) + sensitive_header++; + /* Everything else is opaque and possibly sensitive */ + strbuf_setlen(header, sensitive_header - header->buf); + strbuf_addstr(header, " "); + } else if (trace_curl_redact && + skip_prefix(header->buf, "Cookie:", &sensitive_header)) { + struct strbuf redacted_header = STRBUF_INIT; + const char *cookie; + + while (isspace(*sensitive_header)) + sensitive_header++; + + cookie = sensitive_header; + + while (cookie) { + char *equals; + char *semicolon = strstr(cookie, "; "); + if (semicolon) + *semicolon = 0; + equals = strchrnul(cookie, '='); + if (!equals) { + /* invalid cookie, just append and continue */ + strbuf_addstr(&redacted_header, cookie); + continue; + } + strbuf_add(&redacted_header, cookie, equals - cookie); + strbuf_addstr(&redacted_header, "="); + if (semicolon) { + /* + * There are more cookies. (Or, for some + * reason, the input string ends in "; ".) + */ + strbuf_addstr(&redacted_header, "; "); + cookie = semicolon + strlen("; "); + } else { + cookie = NULL; + } + } + + strbuf_setlen(header, sensitive_header - header->buf); + strbuf_addbuf(header, &redacted_header); + } +} + +static void curl_dump_header(const char *text, unsigned char *ptr, size_t size, int hide_sensitive_header) +{ + struct strbuf out = STRBUF_INIT; + struct strbuf **headers, **header; + + strbuf_addf(&out, "%s, %10.10ld bytes (0x%8.8lx)\n", + text, (long)size, (long)size); + trace_strbuf(&trace_curl, &out); + strbuf_reset(&out); + strbuf_add(&out, ptr, size); + headers = strbuf_split_max(&out, '\n', 0); + + for (header = headers; *header; header++) { + if (hide_sensitive_header) + redact_sensitive_header(*header); + strbuf_insertstr((*header), 0, text); + strbuf_insertstr((*header), strlen(text), ": "); + strbuf_rtrim((*header)); + strbuf_addch((*header), '\n'); + trace_strbuf(&trace_curl, (*header)); + } + strbuf_list_free(headers); + strbuf_release(&out); +} + +static void curl_dump_data(const char *text, unsigned char *ptr, size_t size) +{ + size_t i; + struct strbuf out = STRBUF_INIT; + unsigned int width = 60; + + strbuf_addf(&out, "%s, %10.10ld bytes (0x%8.8lx)\n", + text, (long)size, (long)size); + trace_strbuf(&trace_curl, &out); + + for (i = 0; i < size; i += width) { + size_t w; + + strbuf_reset(&out); + strbuf_addf(&out, "%s: ", text); + for (w = 0; (w < width) && (i + w < size); w++) { + unsigned char ch = ptr[i + w]; + + strbuf_addch(&out, + (ch >= 0x20) && (ch < 0x80) + ? ch : '.'); + } + strbuf_addch(&out, '\n'); + trace_strbuf(&trace_curl, &out); + } + strbuf_release(&out); +} + +static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp) +{ + const char *text; + enum { NO_FILTER = 0, DO_FILTER = 1 }; + + switch (type) { + case CURLINFO_TEXT: + trace_printf_key(&trace_curl, "== Info: %s", data); + break; + case CURLINFO_HEADER_OUT: + text = "=> Send header"; + curl_dump_header(text, (unsigned char *)data, size, DO_FILTER); + break; + case CURLINFO_DATA_OUT: + if (trace_curl_data) { + text = "=> Send data"; + curl_dump_data(text, (unsigned char *)data, size); + } + break; + case CURLINFO_SSL_DATA_OUT: + if (trace_curl_data) { + text = "=> Send SSL data"; + curl_dump_data(text, (unsigned char *)data, size); + } + break; + case CURLINFO_HEADER_IN: + text = "<= Recv header"; + curl_dump_header(text, (unsigned char *)data, size, NO_FILTER); + break; + case CURLINFO_DATA_IN: + if (trace_curl_data) { + text = "<= Recv data"; + curl_dump_data(text, (unsigned char *)data, size); + } + break; + case CURLINFO_SSL_DATA_IN: + if (trace_curl_data) { + text = "<= Recv SSL data"; + curl_dump_data(text, (unsigned char *)data, size); + } + break; + + default: /* we ignore unknown types by default */ + return 0; + } + return 0; +} + +void http_trace_curl_no_data(void) +{ + trace_override_envvar(&trace_curl, "1"); + trace_curl_data = 0; +} + +void setup_curl_trace(CURL *handle) +{ + if (!trace_want(&trace_curl)) + return; + curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, curl_trace); + curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL); +} + +#ifdef CURLPROTO_HTTP +static long get_curl_allowed_protocols(int from_user) +{ + long allowed_protocols = 0; + + if (is_transport_allowed("http", from_user)) + allowed_protocols |= CURLPROTO_HTTP; + if (is_transport_allowed("https", from_user)) + allowed_protocols |= CURLPROTO_HTTPS; + if (is_transport_allowed("ftp", from_user)) + allowed_protocols |= CURLPROTO_FTP; + if (is_transport_allowed("ftps", from_user)) + allowed_protocols |= CURLPROTO_FTPS; + + return allowed_protocols; +} +#endif + +#if LIBCURL_VERSION_NUM >=0x072f00 +static int get_curl_http_version_opt(const char *version_string, long *opt) +{ + int i; + static struct { + const char *name; + long opt_token; + } choice[] = { + { "HTTP/1.1", CURL_HTTP_VERSION_1_1 }, + { "HTTP/2", CURL_HTTP_VERSION_2 } + }; + + for (i = 0; i < ARRAY_SIZE(choice); i++) { + if (!strcmp(version_string, choice[i].name)) { + *opt = choice[i].opt_token; + return 0; + } + } + + warning("unknown value given to http.version: '%s'", version_string); + return -1; /* not found */ +} + +#endif + static CURL *get_curl_handle(void) { CURL *result = curl_easy_init(); @@ -351,6 +869,16 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2); } +#if LIBCURL_VERSION_NUM >= 0x072f00 // 7.47.0 + if (curl_http_version) { + long opt; + if (!get_curl_http_version_opt(curl_http_version, &opt)) { + /* Set request use http version */ + curl_easy_setopt(result, CURLOPT_HTTP_VERSION, opt); + } + } +#endif + #if LIBCURL_VERSION_NUM >= 0x070907 curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); #endif @@ -358,9 +886,56 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); #endif +#ifdef CURLGSSAPI_DELEGATION_FLAG + if (curl_deleg) { + int i; + for (i = 0; i < ARRAY_SIZE(curl_deleg_levels); i++) { + if (!strcmp(curl_deleg, curl_deleg_levels[i].name)) { + curl_easy_setopt(result, CURLOPT_GSSAPI_DELEGATION, + curl_deleg_levels[i].curl_deleg_param); + break; + } + } + if (i == ARRAY_SIZE(curl_deleg_levels)) + warning("Unknown delegation method '%s': using default", + curl_deleg); + } +#endif + + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && + !http_schannel_check_revoke) { +#if LIBCURL_VERSION_NUM >= 0x072c00 + curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); +#else + warning(_("CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0")); +#endif + } + if (http_proactive_auth) init_curl_http_auth(result); + if (getenv("GIT_SSL_VERSION")) + ssl_version = getenv("GIT_SSL_VERSION"); + if (ssl_version && *ssl_version) { + int i; + for (i = 0; i < ARRAY_SIZE(sslversions); i++) { + if (!strcmp(ssl_version, sslversions[i].name)) { + curl_easy_setopt(result, CURLOPT_SSLVERSION, + sslversions[i].ssl_version); + break; + } + } + if (i == ARRAY_SIZE(sslversions)) + warning("unsupported ssl version %s: using default", + ssl_version); + } + + if (getenv("GIT_SSL_CIPHER_LIST")) + ssl_cipherlist = getenv("GIT_SSL_CIPHER_LIST"); + if (ssl_cipherlist != NULL && *ssl_cipherlist) + curl_easy_setopt(result, CURLOPT_SSL_CIPHER_LIST, + ssl_cipherlist); + if (ssl_cert != NULL) curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); if (has_cert_password()) @@ -373,8 +948,24 @@ static CURL *get_curl_handle(void) if (ssl_capath != NULL) curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); #endif - if (ssl_cainfo != NULL) - curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); +#if LIBCURL_VERSION_NUM >= 0x072c00 + if (ssl_pinnedkey != NULL) + curl_easy_setopt(result, CURLOPT_PINNEDPUBLICKEY, ssl_pinnedkey); +#endif + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && + !http_schannel_use_ssl_cainfo) { + curl_easy_setopt(result, CURLOPT_CAINFO, NULL); +#if LIBCURL_VERSION_NUM >= 0x073400 + curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL); +#endif + } else if (ssl_cainfo != NULL || http_proxy_ssl_ca_info != NULL) { + if (ssl_cainfo != NULL) + curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); +#if LIBCURL_VERSION_NUM >= 0x073400 + if (http_proxy_ssl_ca_info != NULL) + curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info); +#endif + } if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, @@ -383,15 +974,27 @@ static CURL *get_curl_handle(void) curl_low_speed_time); } - curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20); #if LIBCURL_VERSION_NUM >= 0x071301 curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); #elif LIBCURL_VERSION_NUM >= 0x071101 curl_easy_setopt(result, CURLOPT_POST301, 1); #endif - +#ifdef CURLPROTO_HTTP + curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, + get_curl_allowed_protocols(0)); + curl_easy_setopt(result, CURLOPT_PROTOCOLS, + get_curl_allowed_protocols(-1)); +#else + warning(_("Protocol restrictions not supported with cURL < 7.19.4")); +#endif if (getenv("GIT_CURL_VERBOSE")) - curl_easy_setopt(result, CURLOPT_VERBOSE, 1); + http_trace_curl_no_data(); + setup_curl_trace(result); + if (getenv("GIT_TRACE_CURL_NO_DATA")) + trace_curl_data = 0; + if (!git_env_bool("GIT_TRACE_REDACT", 1)) + trace_curl_redact = 0; curl_easy_setopt(result, CURLOPT_USERAGENT, user_agent ? user_agent : git_user_agent()); @@ -404,12 +1007,87 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY); #endif - if (curl_http_proxy) { - curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); -#if LIBCURL_VERSION_NUM >= 0x070a07 - curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + /* + * CURL also examines these variables as a fallback; but we need to query + * them here in order to decide whether to prompt for missing password (cf. + * init_curl_proxy_auth()). + * + * Unlike many other common environment variables, these are historically + * lowercase only. It appears that CURL did not know this and implemented + * only uppercase variants, which was later corrected to take both - with + * the exception of http_proxy, which is lowercase only also in CURL. As + * the lowercase versions are the historical quasi-standard, they take + * precedence here, as in CURL. + */ + if (!curl_http_proxy) { + if (http_auth.protocol && !strcmp(http_auth.protocol, "https")) { + var_override(&curl_http_proxy, getenv("HTTPS_PROXY")); + var_override(&curl_http_proxy, getenv("https_proxy")); + } else { + var_override(&curl_http_proxy, getenv("http_proxy")); + } + if (!curl_http_proxy) { + var_override(&curl_http_proxy, getenv("ALL_PROXY")); + var_override(&curl_http_proxy, getenv("all_proxy")); + } + } + + if (curl_http_proxy && curl_http_proxy[0] == '\0') { + /* + * Handle case with the empty http.proxy value here to keep + * common code clean. + * NB: empty option disables proxying at all. + */ + curl_easy_setopt(result, CURLOPT_PROXY, ""); + } else if (curl_http_proxy) { +#if LIBCURL_VERSION_NUM >= 0x071800 + if (starts_with(curl_http_proxy, "socks5h")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); + else if (starts_with(curl_http_proxy, "socks5")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + else if (starts_with(curl_http_proxy, "socks4a")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4A); + else if (starts_with(curl_http_proxy, "socks")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); +#endif +#if LIBCURL_VERSION_NUM >= 0x073400 + else if (starts_with(curl_http_proxy, "https")) { + curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS); + + if (http_proxy_ssl_cert) + curl_easy_setopt(result, CURLOPT_PROXY_SSLCERT, http_proxy_ssl_cert); + + if (http_proxy_ssl_key) + curl_easy_setopt(result, CURLOPT_PROXY_SSLKEY, http_proxy_ssl_key); + + if (has_proxy_cert_password()) + curl_easy_setopt(result, CURLOPT_PROXY_KEYPASSWD, proxy_cert_auth.password); + } +#endif + if (strstr(curl_http_proxy, "://")) + credential_from_url(&proxy_auth, curl_http_proxy); + else { + struct strbuf url = STRBUF_INIT; + strbuf_addf(&url, "http://%s", curl_http_proxy); + credential_from_url(&proxy_auth, url.buf); + strbuf_release(&url); + } + + if (!proxy_auth.host) + die("Invalid proxy URL '%s'", curl_http_proxy); + + curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host); +#if LIBCURL_VERSION_NUM >= 0x071304 + var_override(&curl_no_proxy, getenv("NO_PROXY")); + var_override(&curl_no_proxy, getenv("no_proxy")); + curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy); #endif } + init_curl_proxy_auth(result); set_curl_keepalive(result); @@ -441,6 +1119,34 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) git_config(urlmatch_config_entry, &config); free(normalized_url); + string_list_clear(&config.vars, 1); + +#if LIBCURL_VERSION_NUM >= 0x073800 + if (http_ssl_backend) { + const curl_ssl_backend **backends; + struct strbuf buf = STRBUF_INIT; + int i; + + switch (curl_global_sslset(-1, http_ssl_backend, &backends)) { + case CURLSSLSET_UNKNOWN_BACKEND: + strbuf_addf(&buf, _("Unsupported SSL backend '%s'. " + "Supported SSL backends:"), + http_ssl_backend); + for (i = 0; backends[i]; i++) + strbuf_addf(&buf, "\n\t%s", backends[i]->name); + die("%s", buf.buf); + case CURLSSLSET_NO_BACKENDS: + die(_("Could not set SSL backend to '%s': " + "cURL was built without SSL backends"), + http_ssl_backend); + case CURLSSLSET_TOO_LATE: + die(_("Could not set SSL backend to '%s': already set"), + http_ssl_backend); + case CURLSSLSET_OK: + break; /* Okay! */ + } + } +#endif if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) die("curl_global_init failed"); @@ -450,8 +1156,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) if (remote && remote->http_proxy) curl_http_proxy = xstrdup(remote->http_proxy); - pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); - no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); + if (remote) + var_override(&http_proxy_authmethod, remote->http_proxy_authmethod); + + pragma_header = curl_slist_append(http_copy_default_headers(), + "Pragma: no-cache"); + no_pragma_header = curl_slist_append(http_copy_default_headers(), + "Pragma:"); #ifdef USE_CURL_MULTI { @@ -495,6 +1206,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) max_requests = DEFAULT_MAX_REQUESTS; #endif + set_from_env(&http_proxy_ssl_cert, "GIT_PROXY_SSL_CERT"); + set_from_env(&http_proxy_ssl_key, "GIT_PROXY_SSL_KEY"); + set_from_env(&http_proxy_ssl_ca_info, "GIT_PROXY_SSL_CAINFO"); + + if (getenv("GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED")) + proxy_ssl_cert_password_required = 1; + if (getenv("GIT_CURL_FTP_NO_EPSV")) curl_ftp_no_epsv = 1; @@ -518,9 +1236,7 @@ void http_cleanup(void) while (slot != NULL) { struct active_request_slot *next = slot->next; if (slot->curl != NULL) { -#ifdef USE_CURL_MULTI - curl_multi_remove_handle(curlm, slot->curl); -#endif + xmulti_remove_handle(slot); curl_easy_cleanup(slot->curl); } free(slot); @@ -537,6 +1253,8 @@ void http_cleanup(void) #endif curl_global_cleanup(); + string_list_clear(&extra_http_headers, 0); + curl_slist_free_all(pragma_header); pragma_header = NULL; @@ -548,15 +1266,30 @@ void http_cleanup(void) curl_http_proxy = NULL; } + if (proxy_auth.password) { + memset(proxy_auth.password, 0, strlen(proxy_auth.password)); + FREE_AND_NULL(proxy_auth.password); + } + + free((void *)curl_proxyuserpwd); + curl_proxyuserpwd = NULL; + + free((void *)http_proxy_authmethod); + http_proxy_authmethod = NULL; + if (cert_auth.password != NULL) { memset(cert_auth.password, 0, strlen(cert_auth.password)); - free(cert_auth.password); - cert_auth.password = NULL; + FREE_AND_NULL(cert_auth.password); } ssl_cert_password_required = 0; - free(cached_accept_language); - cached_accept_language = NULL; + if (proxy_cert_auth.password != NULL) { + memset(proxy_cert_auth.password, 0, strlen(proxy_cert_auth.password)); + FREE_AND_NULL(proxy_cert_auth.password); + } + proxy_ssl_cert_password_required = 0; + + FREE_AND_NULL(cached_accept_language); } struct active_request_slot *get_active_slot(void) @@ -622,10 +1355,25 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(slot->curl, CURLOPT_RANGE, NULL); + + /* + * Default following to off unless "ALWAYS" is configured; this gives + * callers a sane starting point, and they can tweak for individual + * HTTP_FOLLOW_* cases themselves. + */ + if (http_follow_config == HTTP_FOLLOW_ALWAYS) + curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 1); + else + curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 0); + +#if LIBCURL_VERSION_NUM >= 0x070a08 + curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve); +#endif #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods); #endif - if (http_auth.password) + if (http_auth.password || curl_empty_auth_enabled()) init_curl_http_auth(slot->curl); return slot; @@ -639,6 +1387,8 @@ int start_active_slot(struct active_request_slot *slot) if (curlm_result != CURLM_OK && curlm_result != CURLM_CALL_MULTI_PERFORM) { + warning("curl_multi_add_handle failed: %s", + curl_multi_strerror(curlm_result)); active_requests--; slot->in_use = 0; return 0; @@ -664,14 +1414,14 @@ static struct fill_chain *fill_cfg; void add_fill_function(void *data, int (*fill)(void *)) { - struct fill_chain *new = xmalloc(sizeof(*new)); + struct fill_chain *new_fill = xmalloc(sizeof(*new_fill)); struct fill_chain **linkp = &fill_cfg; - new->data = data; - new->fill = fill; - new->next = NULL; + new_fill->data = data; + new_fill->fill = fill; + new_fill->next = NULL; while (*linkp) linkp = &(*linkp)->next; - *linkp = new; + *linkp = new_fill; } void fill_active_slots(void) @@ -778,13 +1528,13 @@ void run_active_slot(struct active_request_slot *slot) static void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); - if (slot->curl && curl_session_count > min_curl_sessions) { -#ifdef USE_CURL_MULTI - curl_multi_remove_handle(curlm, slot->curl); -#endif - curl_easy_cleanup(slot->curl); - slot->curl = NULL; - curl_session_count--; + if (slot->curl) { + xmulti_remove_handle(slot); + if (curl_session_count > min_curl_sessions) { + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + curl_session_count--; + } } #ifdef USE_CURL_MULTI fill_active_slots(); @@ -842,7 +1592,7 @@ void append_remote_object_url(struct strbuf *buf, const char *url, strbuf_addf(buf, "objects/%.*s/", 2, hex); if (!only_two_digit_prefix) - strbuf_addf(buf, "%s", hex+2); + strbuf_addstr(buf, hex + 2); } char *get_remote_object_url(const char *url, const char *hex, @@ -853,29 +1603,40 @@ char *get_remote_object_url(const char *url, const char *hex, return strbuf_detach(&buf, NULL); } -static int handle_curl_result(struct slot_results *results) +void normalize_curl_result(CURLcode *result, long http_code, + char *errorstr, size_t errorlen) { /* * 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. + * + * Likewise, if we see a redirect (30x code), that means we turned off + * redirect-following, and we should treat the result as an error. */ - if (results->curl_result == CURLE_OK && - results->http_code >= 400) { - results->curl_result = CURLE_HTTP_RETURNED_ERROR; + if (*result == CURLE_OK && http_code >= 300) { + *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); + xsnprintf(errorstr, errorlen, + "The requested URL returned error: %ld", + http_code); } +} + +static int handle_curl_result(struct slot_results *results) +{ + normalize_curl_result(&results->curl_result, results->http_code, + curl_errorstr, sizeof(curl_errorstr)); if (results->curl_result == CURLE_OK) { credential_approve(&http_auth); + if (proxy_auth.password) + credential_approve(&proxy_auth); return HTTP_OK; } else if (missing_target(results)) return HTTP_MISSING_TARGET; @@ -886,10 +1647,16 @@ static int handle_curl_result(struct slot_results *results) } else { #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; + if (results->auth_avail) { + http_auth_methods &= results->auth_avail; + http_auth_methods_restricted = 1; + } #endif return HTTP_REAUTH; } } else { + if (results->http_connectcode == 407) + credential_reject(&proxy_auth); #if LIBCURL_VERSION_NUM >= 0x070c00 if (!curl_errorstr[0]) strlcpy(curl_errorstr, @@ -905,8 +1672,8 @@ int run_one_slot(struct active_request_slot *slot, { slot->results = results; if (!start_active_slot(slot)) { - snprintf(curl_errorstr, sizeof(curl_errorstr), - "failed to start HTTP request"); + xsnprintf(curl_errorstr, sizeof(curl_errorstr), + "failed to start HTTP request"); return HTTP_START_FAILED; } @@ -914,6 +1681,17 @@ int run_one_slot(struct active_request_slot *slot, return handle_curl_result(results); } +struct curl_slist *http_copy_default_headers(void) +{ + struct curl_slist *headers = NULL; + const struct string_list_item *item; + + for_each_string_list_item(item, &extra_http_headers) + headers = curl_slist_append(headers, item->string); + + return headers; +} + static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf) { char *ptr; @@ -1063,7 +1841,7 @@ static void write_accept_language(struct strbuf *buf) decimal_places++, max_q *= 10) ; - sprintf(q_format, ";q=0.%%0%dd", decimal_places); + xsnprintf(q_format, sizeof(q_format), ";q=0.%%0%dd", decimal_places); strbuf_addstr(buf, "Accept-Language: "); @@ -1114,6 +1892,13 @@ static const char *get_accept_language(void) return cached_accept_language; } +static void http_opt_request_remainder(CURL *curl, off_t pos) +{ + char buf[128]; + xsnprintf(buf, sizeof(buf), "%"PRIuMAX"-", (uintmax_t)pos); + curl_easy_setopt(curl, CURLOPT_RANGE, buf); +} + /* http_request() targets */ #define HTTP_REQUEST_STRBUF 0 #define HTTP_REQUEST_FILE 1 @@ -1124,7 +1909,7 @@ static int http_request(const char *url, { struct active_request_slot *slot; struct slot_results results; - struct curl_slist *headers = NULL; + struct curl_slist *headers = http_copy_default_headers(); struct strbuf buf = STRBUF_INIT; const char *accept_language; int ret; @@ -1139,14 +1924,11 @@ static int http_request(const char *url, curl_easy_setopt(slot->curl, CURLOPT_FILE, result); if (target == HTTP_REQUEST_FILE) { - long posn = ftell(result); + off_t posn = ftello(result); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); - if (posn > 0) { - strbuf_addf(&buf, "Range: bytes=%ld-", posn); - headers = curl_slist_append(headers, buf.buf); - strbuf_reset(&buf); - } + if (posn > 0) + http_opt_request_remainder(slot->curl, posn); } else curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); @@ -1160,14 +1942,24 @@ static int http_request(const char *url, strbuf_addstr(&buf, "Pragma:"); if (options && options->no_cache) strbuf_addstr(&buf, " no-cache"); - if (options && options->keep_error) - curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); + if (options && options->initial_request && + http_follow_config == HTTP_FOLLOW_INITIAL) + curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 1); headers = curl_slist_append(headers, buf.buf); + /* Add additional headers here */ + if (options && options->extra_headers) { + const struct string_list_item *item; + for_each_string_list_item(item, options->extra_headers) { + headers = curl_slist_append(headers, item->string); + } + } + curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip"); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); ret = run_one_slot(slot, &results); @@ -1210,32 +2002,34 @@ static int http_request(const char *url, * * Note that this assumes a sane redirect scheme. It's entirely possible * in the example above to end up at a URL that does not even end in - * "info/refs". In such a case we simply punt, as there is not much we can - * do (and such a scheme is unlikely to represent a real git repository, - * which means we are likely about to abort anyway). + * "info/refs". In such a case we die. There's not much we can do, such a + * scheme is unlikely to represent a real git repository, and failing to + * rewrite the base opens options for malicious redirects to do funny things. */ static int update_url_from_redirect(struct strbuf *base, const char *asked, const struct strbuf *got) { const char *tail; - size_t tail_len; + size_t new_len; if (!strcmp(asked, got->buf)) return 0; if (!skip_prefix(asked, base->buf, &tail)) - die("BUG: update_url_from_redirect: %s is not a superset of %s", + BUG("update_url_from_redirect: %s is not a superset of %s", asked, base->buf); - tail_len = strlen(tail); - - if (got->len < tail_len || - strcmp(tail, got->buf + got->len - tail_len)) - return 0; /* insane redirect scheme */ + new_len = got->len; + if (!strip_suffix_mem(got->buf, &new_len, tail)) + die(_("unable to update url base from redirection:\n" + " asked for: %s\n" + " redirect: %s"), + asked, got->buf); strbuf_reset(base); - strbuf_add(base, got->buf, got->len - tail_len); + strbuf_add(base, got->buf, new_len); + return 1; } @@ -1245,6 +2039,9 @@ static int http_request_reauth(const char *url, { int ret = http_request(url, result, target, options); + if (ret != HTTP_OK && ret != HTTP_REAUTH) + return ret; + if (options && options->effective_url && options->base_url) { if (update_url_from_redirect(options->base_url, url, options->effective_url)) { @@ -1257,19 +2054,26 @@ static int http_request_reauth(const char *url, 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. + * The previous request may have put cruft into our output stream; we + * should clear it out before making our next request. */ - if (options && options->keep_error) { - switch (target) { - case HTTP_REQUEST_STRBUF: - strbuf_reset(result); - break; - default: - die("BUG: HTTP_KEEP_ERROR is only supported with strbufs"); + switch (target) { + case HTTP_REQUEST_STRBUF: + strbuf_reset(result); + break; + case HTTP_REQUEST_FILE: + if (fflush(result)) { + error_errno("unable to flush a file"); + return HTTP_START_FAILED; + } + rewind(result); + if (ftruncate(fileno(result), 0) < 0) { + error_errno("unable to truncate a file"); + return HTTP_START_FAILED; } + break; + default: + BUG("Unknown http_request target"); } credential_fill(&http_auth); @@ -1308,7 +2112,7 @@ static int http_get_file(const char *url, const char *filename, ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options); fclose(result); - if (ret == HTTP_OK && move_temp_to_file(tmpfile.buf, filename)) + if (ret == HTTP_OK && finalize_object_file(tmpfile.buf, filename)) ret = HTTP_ERROR; cleanup: strbuf_release(&tmpfile); @@ -1327,8 +2131,8 @@ int http_fetch_ref(const char *base, struct ref *ref) url = quote_ref_url(base, ref->name); if (http_get_strbuf(url, &buffer, &options) == HTTP_OK) { strbuf_rtrim(&buffer); - if (buffer.len == 40) - ret = get_sha1_hex(buffer.buf, ref->old_sha1); + if (buffer.len == the_hash_algo->hexsz) + ret = get_oid_hex(buffer.buf, &ref->old_oid); else if (starts_with(buffer.buf, "ref: ")) { ref->symref = xstrdup(buffer.buf + 5); ret = 0; @@ -1341,25 +2145,24 @@ int http_fetch_ref(const char *base, struct ref *ref) } /* Helpers for fetching packs */ -static char *fetch_pack_index(unsigned char *sha1, const char *base_url) +static char *fetch_pack_index(unsigned char *hash, const char *base_url) { char *url, *tmp; struct strbuf buf = STRBUF_INIT; if (http_is_verbose) - fprintf(stderr, "Getting index for pack %s\n", sha1_to_hex(sha1)); + fprintf(stderr, "Getting index for pack %s\n", hash_to_hex(hash)); end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.idx", sha1_to_hex(sha1)); + strbuf_addf(&buf, "objects/pack/pack-%s.idx", hash_to_hex(hash)); url = strbuf_detach(&buf, NULL); - strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1)); + strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(hash)); tmp = strbuf_detach(&buf, NULL); if (http_get_file(url, tmp, NULL) != HTTP_OK) { error("Unable to get pack index %s", url); - free(tmp); - tmp = NULL; + FREE_AND_NULL(tmp); } free(url); @@ -1395,7 +2198,7 @@ static int fetch_and_setup_pack_index(struct packed_git **packs_head, ret = verify_pack_index(new_pack); if (!ret) { close_pack_index(new_pack); - ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1)); + ret = finalize_object_file(tmp_idx, sha1_pack_index_name(sha1)); } free(tmp_idx); if (ret) @@ -1410,10 +2213,11 @@ add_pack: int http_get_info_packs(const char *base_url, struct packed_git **packs_head) { struct http_get_options options = {0}; - int ret = 0, i = 0; - char *url, *data; + int ret = 0; + char *url; + const char *data; struct strbuf buf = STRBUF_INIT; - unsigned char sha1[20]; + struct object_id oid; end_url_with_slash(&buf, base_url); strbuf_addstr(&buf, "objects/info/packs"); @@ -1425,24 +2229,17 @@ int http_get_info_packs(const char *base_url, struct packed_git **packs_head) goto cleanup; data = buf.buf; - while (i < buf.len) { - switch (data[i]) { - case 'P': - i++; - if (i + 52 <= buf.len && - starts_with(data + i, " pack-") && - starts_with(data + i + 46, ".pack\n")) { - get_sha1_hex(data + i + 6, sha1); - fetch_and_setup_pack_index(packs_head, sha1, - base_url); - i += 51; - break; - } - default: - while (i < buf.len && data[i] != '\n') - i++; + while (*data) { + if (skip_prefix(data, "P pack-", &data) && + !parse_oid_hex(data, &oid, &data) && + skip_prefix(data, ".pack", &data) && + (*data == '\n' || *data == '\0')) { + fetch_and_setup_pack_index(packs_head, oid.hash, base_url); + } else { + data = strchrnul(data, '\n'); } - i++; + if (*data) + data++; /* skip past newline */ } cleanup: @@ -1456,90 +2253,86 @@ void release_http_pack_request(struct http_pack_request *preq) fclose(preq->packfile); preq->packfile = NULL; } - if (preq->range_header != NULL) { - curl_slist_free_all(preq->range_header); - preq->range_header = NULL; - } preq->slot = NULL; + strbuf_release(&preq->tmpfile); free(preq->url); free(preq); } int finish_http_pack_request(struct http_pack_request *preq) { - struct packed_git **lst; - struct packed_git *p = preq->target; - char *tmp_idx; struct child_process ip = CHILD_PROCESS_INIT; - const char *ip_argv[8]; - - close_pack_index(p); + int tmpfile_fd; + int ret = 0; fclose(preq->packfile); preq->packfile = NULL; - lst = preq->lst; - while (*lst != p) - lst = &((*lst)->next); - *lst = (*lst)->next; - - tmp_idx = xstrdup(preq->tmpfile); - strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"), - ".idx.temp"); - - ip_argv[0] = "index-pack"; - ip_argv[1] = "-o"; - ip_argv[2] = tmp_idx; - ip_argv[3] = preq->tmpfile; - ip_argv[4] = NULL; + tmpfile_fd = xopen(preq->tmpfile.buf, O_RDONLY); - ip.argv = ip_argv; + strvec_push(&ip.args, "index-pack"); + strvec_push(&ip.args, "--stdin"); ip.git_cmd = 1; - ip.no_stdin = 1; - ip.no_stdout = 1; + ip.in = tmpfile_fd; + if (preq->generate_keep) { + strvec_pushf(&ip.args, "--keep=git %"PRIuMAX, + (uintmax_t)getpid()); + ip.out = 0; + } else { + ip.no_stdout = 1; + } if (run_command(&ip)) { - unlink(preq->tmpfile); - unlink(tmp_idx); - free(tmp_idx); - return -1; + ret = -1; + goto cleanup; } - unlink(sha1_pack_index_name(p->sha1)); +cleanup: + close(tmpfile_fd); + unlink(preq->tmpfile.buf); + return ret; +} - if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1)) - || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) { - free(tmp_idx); - return -1; - } +void http_install_packfile(struct packed_git *p, + struct packed_git **list_to_remove_from) +{ + struct packed_git **lst = list_to_remove_from; - install_packed_git(p); - free(tmp_idx); - return 0; + while (*lst != p) + lst = &((*lst)->next); + *lst = (*lst)->next; + + install_packed_git(the_repository, p); } struct http_pack_request *new_http_pack_request( - struct packed_git *target, const char *base_url) -{ - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; + const unsigned char *packed_git_hash, const char *base_url) { + struct strbuf buf = STRBUF_INIT; + + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.pack", + hash_to_hex(packed_git_hash)); + return new_direct_http_pack_request(packed_git_hash, + strbuf_detach(&buf, NULL)); +} + +struct http_pack_request *new_direct_http_pack_request( + const unsigned char *packed_git_hash, char *url) +{ + off_t prev_posn = 0; struct http_pack_request *preq; preq = xcalloc(1, sizeof(*preq)); - preq->target = target; + strbuf_init(&preq->tmpfile, 0); - end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.pack", - sha1_to_hex(target->sha1)); - preq->url = strbuf_detach(&buf, NULL); + preq->url = url; - snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", - sha1_pack_name(target->sha1)); - preq->packfile = fopen(preq->tmpfile, "a"); + strbuf_addf(&preq->tmpfile, "%s.temp", sha1_pack_name(packed_git_hash)); + preq->packfile = fopen(preq->tmpfile.buf, "a"); if (!preq->packfile) { error("Unable to open local file %s for pack", - preq->tmpfile); + preq->tmpfile.buf); goto abort; } @@ -1554,21 +2347,20 @@ struct http_pack_request *new_http_pack_request( * If there is data present from a previous transfer attempt, * resume where it left off */ - prev_posn = ftell(preq->packfile); + prev_posn = ftello(preq->packfile); if (prev_posn>0) { if (http_is_verbose) fprintf(stderr, - "Resuming fetch of pack %s at byte %ld\n", - sha1_to_hex(target->sha1), prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - preq->range_header = curl_slist_append(NULL, range); - curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, - preq->range_header); + "Resuming fetch of pack %s at byte %"PRIuMAX"\n", + hash_to_hex(packed_git_hash), + (uintmax_t)prev_posn); + http_opt_request_remainder(preq->slot->curl, prev_posn); } return preq; abort: + strbuf_release(&preq->tmpfile); free(preq->url); free(preq); return NULL; @@ -1581,13 +2373,24 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, unsigned char expn[4096]; size_t size = eltsize * nmemb; int posn = 0; - struct http_object_request *freq = - (struct http_object_request *)data; + struct http_object_request *freq = data; + struct active_request_slot *slot = freq->slot; + + if (slot) { + CURLcode c = curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, + &slot->http_code); + if (c != CURLE_OK) + BUG("curl_easy_getinfo for HTTP code failed: %s", + curl_easy_strerror(c)); + if (slot->http_code >= 300) + return nmemb; + } + do { ssize_t retval = xwrite(freq->localfile, (char *) ptr + posn, size - posn); if (retval < 0) - return posn; + return posn / eltsize; posn += retval; } while (posn < size); @@ -1597,67 +2400,66 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, freq->stream.next_out = expn; freq->stream.avail_out = sizeof(expn); freq->zret = git_inflate(&freq->stream, Z_SYNC_FLUSH); - git_SHA1_Update(&freq->c, expn, - sizeof(expn) - freq->stream.avail_out); + the_hash_algo->update_fn(&freq->c, expn, + sizeof(expn) - freq->stream.avail_out); } while (freq->stream.avail_in && freq->zret == Z_OK); - return size; + return nmemb; } struct http_object_request *new_http_object_request(const char *base_url, - unsigned char *sha1) + const struct object_id *oid) { - char *hex = sha1_to_hex(sha1); - const char *filename; - char prevfile[PATH_MAX]; + char *hex = oid_to_hex(oid); + struct strbuf filename = STRBUF_INIT; + struct strbuf prevfile = STRBUF_INIT; int prevlocal; char prev_buf[PREV_BUF_SIZE]; ssize_t prev_read = 0; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; + off_t prev_posn = 0; struct http_object_request *freq; freq = xcalloc(1, sizeof(*freq)); - hashcpy(freq->sha1, sha1); + strbuf_init(&freq->tmpfile, 0); + oidcpy(&freq->oid, oid); freq->localfile = -1; - filename = sha1_file_name(sha1); - snprintf(freq->tmpfile, sizeof(freq->tmpfile), - "%s.temp", filename); + loose_object_path(the_repository, &filename, oid); + strbuf_addf(&freq->tmpfile, "%s.temp", filename.buf); - snprintf(prevfile, sizeof(prevfile), "%s.prev", filename); - unlink_or_warn(prevfile); - rename(freq->tmpfile, prevfile); - unlink_or_warn(freq->tmpfile); + strbuf_addf(&prevfile, "%s.prev", filename.buf); + unlink_or_warn(prevfile.buf); + rename(freq->tmpfile.buf, prevfile.buf); + unlink_or_warn(freq->tmpfile.buf); + strbuf_release(&filename); if (freq->localfile != -1) error("fd leakage in start: %d", freq->localfile); - freq->localfile = open(freq->tmpfile, + freq->localfile = open(freq->tmpfile.buf, O_WRONLY | O_CREAT | O_EXCL, 0666); /* * This could have failed due to the "lazy directory creation"; * try to mkdir the last path component. */ if (freq->localfile < 0 && errno == ENOENT) { - char *dir = strrchr(freq->tmpfile, '/'); + char *dir = strrchr(freq->tmpfile.buf, '/'); if (dir) { *dir = 0; - mkdir(freq->tmpfile, 0777); + mkdir(freq->tmpfile.buf, 0777); *dir = '/'; } - freq->localfile = open(freq->tmpfile, + freq->localfile = open(freq->tmpfile.buf, O_WRONLY | O_CREAT | O_EXCL, 0666); } if (freq->localfile < 0) { - error("Couldn't create temporary file %s: %s", - freq->tmpfile, strerror(errno)); + error_errno("Couldn't create temporary file %s", + freq->tmpfile.buf); goto abort; } git_inflate_init(&freq->stream); - git_SHA1_Init(&freq->c); + the_hash_algo->init_fn(&freq->c); freq->url = get_remote_object_url(base_url, hex, 0); @@ -1665,7 +2467,7 @@ struct http_object_request *new_http_object_request(const char *base_url, * If a previous temp file is present, process what was already * fetched. */ - prevlocal = open(prevfile, O_RDONLY); + prevlocal = open(prevfile.buf, O_RDONLY); if (prevlocal != -1) { do { prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE); @@ -1682,7 +2484,8 @@ struct http_object_request *new_http_object_request(const char *base_url, } while (prev_read > 0); close(prevlocal); } - unlink_or_warn(prevfile); + unlink_or_warn(prevfile.buf); + strbuf_release(&prevfile); /* * Reset inflate/SHA1 if there was an error reading the previous temp @@ -1691,13 +2494,13 @@ struct http_object_request *new_http_object_request(const char *base_url, if (prev_read == -1) { memset(&freq->stream, 0, sizeof(freq->stream)); git_inflate_init(&freq->stream); - git_SHA1_Init(&freq->c); + the_hash_algo->init_fn(&freq->c); if (prev_posn>0) { prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); if (ftruncate(freq->localfile, 0) < 0) { - error("Couldn't truncate temporary file %s: %s", - freq->tmpfile, strerror(errno)); + error_errno("Couldn't truncate temporary file %s", + freq->tmpfile.buf); goto abort; } } @@ -1706,6 +2509,7 @@ struct http_object_request *new_http_object_request(const char *base_url, freq->slot = get_active_slot(); curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq); + curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0); curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr); curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url); @@ -1718,17 +2522,15 @@ struct http_object_request *new_http_object_request(const char *base_url, if (prev_posn>0) { if (http_is_verbose) fprintf(stderr, - "Resuming fetch of object %s at byte %ld\n", - hex, prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(freq->slot->curl, - CURLOPT_HTTPHEADER, range_header); + "Resuming fetch of object %s at byte %"PRIuMAX"\n", + hex, (uintmax_t)prev_posn); + http_opt_request_remainder(freq->slot->curl, prev_posn); } return freq; abort: + strbuf_release(&prevfile); free(freq->url); free(freq); return NULL; @@ -1746,6 +2548,7 @@ void process_http_object_request(struct http_object_request *freq) int finish_http_object_request(struct http_object_request *freq) { struct stat st; + struct strbuf filename = STRBUF_INIT; close(freq->localfile); freq->localfile = -1; @@ -1755,31 +2558,32 @@ int finish_http_object_request(struct http_object_request *freq) if (freq->http_code == 416) { warning("requested range invalid; we may already have all the data."); } else if (freq->curl_result != CURLE_OK) { - if (stat(freq->tmpfile, &st) == 0) + if (stat(freq->tmpfile.buf, &st) == 0) if (st.st_size == 0) - unlink_or_warn(freq->tmpfile); + unlink_or_warn(freq->tmpfile.buf); return -1; } git_inflate_end(&freq->stream); - git_SHA1_Final(freq->real_sha1, &freq->c); + the_hash_algo->final_fn(freq->real_oid.hash, &freq->c); if (freq->zret != Z_STREAM_END) { - unlink_or_warn(freq->tmpfile); + unlink_or_warn(freq->tmpfile.buf); return -1; } - if (hashcmp(freq->sha1, freq->real_sha1)) { - unlink_or_warn(freq->tmpfile); + if (!oideq(&freq->oid, &freq->real_oid)) { + unlink_or_warn(freq->tmpfile.buf); return -1; } - freq->rename = - move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1)); + loose_object_path(the_repository, &filename, &freq->oid); + freq->rename = finalize_object_file(freq->tmpfile.buf, filename.buf); + strbuf_release(&filename); return freq->rename; } void abort_http_object_request(struct http_object_request *freq) { - unlink_or_warn(freq->tmpfile); + unlink_or_warn(freq->tmpfile.buf); release_http_object_request(freq); } @@ -1790,14 +2594,12 @@ void release_http_object_request(struct http_object_request *freq) close(freq->localfile); freq->localfile = -1; } - if (freq->url != NULL) { - free(freq->url); - freq->url = NULL; - } + FREE_AND_NULL(freq->url); if (freq->slot != NULL) { freq->slot->callback_func = NULL; freq->slot->callback_data = NULL; release_active_slot(freq->slot); freq->slot = NULL; } + strbuf_release(&freq->tmpfile); }