summaryrefslogtreecommitdiff
path: root/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'http.c')
-rw-r--r--http.c297
1 files changed, 255 insertions, 42 deletions
diff --git a/http.c b/http.c
index d8e427b..90a1c0f 100644
--- a/http.c
+++ b/http.c
@@ -11,6 +11,7 @@
#include "gettext.h"
#include "transport.h"
+static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
#if LIBCURL_VERSION_NUM >= 0x070a08
long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
#else
@@ -89,6 +90,18 @@ static struct {
* here, too
*/
};
+#if LIBCURL_VERSION_NUM >= 0x071600
+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;
@@ -98,6 +111,8 @@ static int http_proactive_auth;
static const char *user_agent;
static int curl_empty_auth;
+enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;
+
#if LIBCURL_VERSION_NUM >= 0x071700
/* Use CURLOPT_KEYPASSWD as is */
#elif LIBCURL_VERSION_NUM >= 0x070903
@@ -200,6 +215,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)
{
@@ -215,7 +237,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 {
@@ -315,6 +337,15 @@ static int http_options(const char *var, const char *value, void *cb)
return 0;
}
+ if (!strcmp("http.delegation", var)) {
+#if LIBCURL_VERSION_NUM >= 0x071600
+ 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);
@@ -337,13 +368,23 @@ static int http_options(const char *var, const char *value, void *cb)
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 void init_curl_http_auth(CURL *result)
{
- if (!http_auth.username) {
+ if (!http_auth.username || !*http_auth.username) {
if (curl_empty_auth)
curl_easy_setopt(result, CURLOPT_USERPWD, ":");
return;
@@ -477,10 +518,143 @@ static void set_curl_keepalive(CURL *c)
}
#endif
+static void redact_sensitive_header(struct strbuf *header)
+{
+ const char *sensitive_header;
+
+ if (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, " <redacted>");
+ }
+}
+
+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_insert((*header), 0, text, strlen(text));
+ strbuf_insert((*header), strlen(text), ": ", 2);
+ 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);
+ default: /* we ignore unknown types by default */
+ return 0;
+
+ case CURLINFO_HEADER_OUT:
+ text = "=> Send header";
+ curl_dump_header(text, (unsigned char *)data, size, DO_FILTER);
+ break;
+ case CURLINFO_DATA_OUT:
+ text = "=> Send data";
+ curl_dump_data(text, (unsigned char *)data, size);
+ break;
+ case CURLINFO_SSL_DATA_OUT:
+ 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:
+ text = "<= Recv data";
+ curl_dump_data(text, (unsigned char *)data, size);
+ break;
+ case CURLINFO_SSL_DATA_IN:
+ text = "<= Recv SSL data";
+ curl_dump_data(text, (unsigned char *)data, size);
+ break;
+ }
+ return 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);
+}
+
+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;
+}
+
static CURL *get_curl_handle(void)
{
CURL *result = curl_easy_init();
- long allowed_protocols = 0;
if (!result)
die("curl_easy_init failed");
@@ -502,6 +676,22 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
#endif
+#if LIBCURL_VERSION_NUM >= 0x071600
+ 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_proactive_auth)
init_curl_http_auth(result);
@@ -553,7 +743,6 @@ 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);
@@ -561,23 +750,17 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_POST301, 1);
#endif
#if LIBCURL_VERSION_NUM >= 0x071304
- if (is_transport_allowed("http"))
- allowed_protocols |= CURLPROTO_HTTP;
- if (is_transport_allowed("https"))
- allowed_protocols |= CURLPROTO_HTTPS;
- if (is_transport_allowed("ftp"))
- allowed_protocols |= CURLPROTO_FTP;
- if (is_transport_allowed("ftps"))
- allowed_protocols |= CURLPROTO_FTPS;
- curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
+ curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS,
+ get_curl_allowed_protocols(0));
+ curl_easy_setopt(result, CURLOPT_PROTOCOLS,
+ get_curl_allowed_protocols(-1));
#else
- if (transport_restrict_protocols())
- warning("protocol restrictions not applied to curl redirects because\n"
- "your curl version is too old (>= 7.19.4)");
+ warning("protocol restrictions not applied to curl redirects because\n"
+ "your curl version is too old (>= 7.19.4)");
#endif
-
if (getenv("GIT_CURL_VERBOSE"))
- curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(result, CURLOPT_VERBOSE, 1L);
+ setup_curl_trace(result);
curl_easy_setopt(result, CURLOPT_USERAGENT,
user_agent ? user_agent : git_user_agent());
@@ -603,7 +786,7 @@ static CURL *get_curl_handle(void)
* precedence here, as in CURL.
*/
if (!curl_http_proxy) {
- if (!strcmp(http_auth.protocol, "https")) {
+ 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 {
@@ -761,9 +944,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);
@@ -882,6 +1063,16 @@ struct active_request_slot *get_active_slot(void)
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
@@ -902,6 +1093,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;
@@ -1041,13 +1234,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();
@@ -1122,9 +1315,12 @@ static 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.
+ *
+ * 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->http_code >= 300) {
results->curl_result = CURLE_HTTP_RETURNED_ERROR;
/*
* Normally curl will already have put the "reason phrase"
@@ -1443,6 +1639,9 @@ static int http_request(const char *url,
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);
@@ -1491,16 +1690,16 @@ 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;
@@ -1509,14 +1708,16 @@ static int update_url_from_redirect(struct strbuf *base,
die("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;
}
@@ -1855,8 +2056,19 @@ 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)
+ die("BUG: curl_easy_getinfo for HTTP code failed: %s",
+ curl_easy_strerror(c));
+ if (slot->http_code >= 300)
+ return size;
+ }
+
do {
ssize_t retval = xwrite(freq->localfile,
(char *) ptr + posn, size - posn);
@@ -1977,6 +2189,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);