summaryrefslogtreecommitdiff
path: root/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'http.c')
-rw-r--r--http.c337
1 files changed, 291 insertions, 46 deletions
diff --git a/http.c b/http.c
index 94e1afdee7..45348fb9bd 100644
--- a/http.c
+++ b/http.c
@@ -1,3 +1,4 @@
+#include "git-compat-util.h"
#include "http.h"
#include "pack.h"
#include "sideband.h"
@@ -7,6 +8,8 @@
#include "credential.h"
#include "version.h"
#include "pkt-line.h"
+#include "gettext.h"
+#include "transport.h"
int active_requests;
int http_is_verbose;
@@ -34,6 +37,7 @@ char curl_errorstr[CURL_ERROR_SIZE];
static int curl_ssl_verify = -1;
static int curl_ssl_try;
static const char *ssl_cert;
+static const char *ssl_cipherlist;
#if LIBCURL_VERSION_NUM >= 0x070903
static const char *ssl_key;
#endif
@@ -61,12 +65,17 @@ static const char *user_agent;
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;
+#endif
static struct curl_slist *pragma_header;
static struct curl_slist *no_pragma_header;
static struct active_request_slot *active_queue_head;
+static char *cached_accept_language;
+
size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
@@ -113,6 +122,37 @@ size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
return eltsize * nmemb;
}
+static void closedown_active_slot(struct active_request_slot *slot)
+{
+ active_requests--;
+ slot->in_use = 0;
+}
+
+static void finish_active_slot(struct active_request_slot *slot)
+{
+ closedown_active_slot(slot);
+ curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
+
+ if (slot->finished != NULL)
+ (*slot->finished) = 1;
+
+ /* Store slot results so they can be read after the slot is reused */
+ if (slot->results != NULL) {
+ slot->results->curl_result = slot->curl_result;
+ slot->results->http_code = slot->http_code;
+#if LIBCURL_VERSION_NUM >= 0x070a08
+ curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
+ &slot->results->auth_avail);
+#else
+ slot->results->auth_avail = 0;
+#endif
+ }
+
+ /* Run callback if appropriate */
+ if (slot->callback_func != NULL)
+ slot->callback_func(slot->callback_data);
+}
+
#ifdef USE_CURL_MULTI
static void process_curl_messages(void)
{
@@ -149,6 +189,8 @@ static int http_options(const char *var, const char *value, void *cb)
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.sslcert", var))
return git_config_string(&ssl_cert, var, value);
#if LIBCURL_VERSION_NUM >= 0x070903
@@ -299,6 +341,10 @@ static void set_curl_keepalive(CURL *c)
static CURL *get_curl_handle(void)
{
CURL *result = curl_easy_init();
+ long allowed_protocols = 0;
+
+ if (!result)
+ die("curl_easy_init failed");
if (!curl_ssl_verify) {
curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
@@ -320,6 +366,13 @@ static CURL *get_curl_handle(void)
if (http_proactive_auth)
init_curl_http_auth(result);
+ 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())
@@ -343,11 +396,27 @@ static CURL *get_curl_handle(void)
}
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
+#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);
+#else
+ if (transport_restrict_protocols())
+ 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);
@@ -365,8 +434,10 @@ static CURL *get_curl_handle(void)
if (curl_http_proxy) {
curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
- curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
}
+#if LIBCURL_VERSION_NUM >= 0x070a07
+ curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
+#endif
set_curl_keepalive(result);
@@ -399,7 +470,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
git_config(urlmatch_config_entry, &config);
free(normalized_url);
- curl_global_init(CURL_GLOBAL_ALL);
+ if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
+ die("curl_global_init failed");
http_proactive_auth = proactive_auth;
@@ -417,10 +489,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
}
curlm = curl_multi_init();
- if (curlm == NULL) {
- fprintf(stderr, "Error creating curl multi handle.\n");
- exit(1);
- }
+ if (!curlm)
+ die("curl_multi_init failed");
#endif
if (getenv("GIT_SSL_NO_VERIFY"))
@@ -512,6 +582,9 @@ void http_cleanup(void)
cert_auth.password = NULL;
}
ssl_cert_password_required = 0;
+
+ free(cached_accept_language);
+ cached_accept_language = NULL;
}
struct active_request_slot *get_active_slot(void)
@@ -577,6 +650,9 @@ 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);
+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
+#endif
if (http_auth.password)
init_curl_http_auth(slot->curl);
@@ -727,12 +803,6 @@ void run_active_slot(struct active_request_slot *slot)
#endif
}
-static void closedown_active_slot(struct active_request_slot *slot)
-{
- active_requests--;
- slot->in_use = 0;
-}
-
static void release_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
@@ -749,31 +819,6 @@ static void release_active_slot(struct active_request_slot *slot)
#endif
}
-void finish_active_slot(struct active_request_slot *slot)
-{
- closedown_active_slot(slot);
- curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
-
- if (slot->finished != NULL)
- (*slot->finished) = 1;
-
- /* Store slot results so they can be read after the slot is reused */
- if (slot->results != NULL) {
- slot->results->curl_result = slot->curl_result;
- slot->results->http_code = slot->http_code;
-#if LIBCURL_VERSION_NUM >= 0x070a08
- curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
- &slot->results->auth_avail);
-#else
- slot->results->auth_avail = 0;
-#endif
- }
-
- /* Run callback if appropriate */
- if (slot->callback_func != NULL)
- slot->callback_func(slot->callback_data);
-}
-
void finish_all_active_slots(void)
{
struct active_request_slot *slot = active_queue_head;
@@ -836,7 +881,7 @@ char *get_remote_object_url(const char *url, const char *hex,
return strbuf_detach(&buf, NULL);
}
-int handle_curl_result(struct slot_results *results)
+static int handle_curl_result(struct slot_results *results)
{
/*
* If we see a failing http code with CURLE_OK, we have turned off
@@ -867,6 +912,9 @@ int handle_curl_result(struct slot_results *results)
credential_reject(&http_auth);
return HTTP_NOAUTH;
} else {
+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
+ http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
+#endif
return HTTP_REAUTH;
}
} else {
@@ -906,6 +954,194 @@ static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
return ret;
}
+/*
+ * Check for and extract a content-type parameter. "raw"
+ * should be positioned at the start of the potential
+ * parameter, with any whitespace already removed.
+ *
+ * "name" is the name of the parameter. The value is appended
+ * to "out".
+ */
+static int extract_param(const char *raw, const char *name,
+ struct strbuf *out)
+{
+ size_t len = strlen(name);
+
+ if (strncasecmp(raw, name, len))
+ return -1;
+ raw += len;
+
+ if (*raw != '=')
+ return -1;
+ raw++;
+
+ while (*raw && !isspace(*raw) && *raw != ';')
+ strbuf_addch(out, *raw++);
+ return 0;
+}
+
+/*
+ * Extract a normalized version of the content type, with any
+ * spaces suppressed, all letters lowercased, and no trailing ";"
+ * or parameters.
+ *
+ * Note that we will silently remove even invalid whitespace. For
+ * example, "text / plain" is specifically forbidden by RFC 2616,
+ * but "text/plain" is the only reasonable output, and this keeps
+ * our code simple.
+ *
+ * If the "charset" argument is not NULL, store the value of any
+ * charset parameter there.
+ *
+ * Example:
+ * "TEXT/PLAIN; charset=utf-8" -> "text/plain", "utf-8"
+ * "text / plain" -> "text/plain"
+ */
+static void extract_content_type(struct strbuf *raw, struct strbuf *type,
+ struct strbuf *charset)
+{
+ const char *p;
+
+ strbuf_reset(type);
+ strbuf_grow(type, raw->len);
+ for (p = raw->buf; *p; p++) {
+ if (isspace(*p))
+ continue;
+ if (*p == ';') {
+ p++;
+ break;
+ }
+ strbuf_addch(type, tolower(*p));
+ }
+
+ if (!charset)
+ return;
+
+ strbuf_reset(charset);
+ while (*p) {
+ while (isspace(*p) || *p == ';')
+ p++;
+ if (!extract_param(p, "charset", charset))
+ return;
+ while (*p && !isspace(*p))
+ p++;
+ }
+
+ if (!charset->len && starts_with(type->buf, "text/"))
+ strbuf_addstr(charset, "ISO-8859-1");
+}
+
+static void write_accept_language(struct strbuf *buf)
+{
+ /*
+ * MAX_DECIMAL_PLACES must not be larger than 3. If it is larger than
+ * that, q-value will be smaller than 0.001, the minimum q-value the
+ * HTTP specification allows. See
+ * http://tools.ietf.org/html/rfc7231#section-5.3.1 for q-value.
+ */
+ const int MAX_DECIMAL_PLACES = 3;
+ const int MAX_LANGUAGE_TAGS = 1000;
+ const int MAX_ACCEPT_LANGUAGE_HEADER_SIZE = 4000;
+ char **language_tags = NULL;
+ int num_langs = 0;
+ const char *s = get_preferred_languages();
+ int i;
+ struct strbuf tag = STRBUF_INIT;
+
+ /* Don't add Accept-Language header if no language is preferred. */
+ if (!s)
+ return;
+
+ /*
+ * Split the colon-separated string of preferred languages into
+ * language_tags array.
+ */
+ do {
+ /* collect language tag */
+ for (; *s && (isalnum(*s) || *s == '_'); s++)
+ strbuf_addch(&tag, *s == '_' ? '-' : *s);
+
+ /* skip .codeset, @modifier and any other unnecessary parts */
+ while (*s && *s != ':')
+ s++;
+
+ if (tag.len) {
+ num_langs++;
+ REALLOC_ARRAY(language_tags, num_langs);
+ language_tags[num_langs - 1] = strbuf_detach(&tag, NULL);
+ if (num_langs >= MAX_LANGUAGE_TAGS - 1) /* -1 for '*' */
+ break;
+ }
+ } while (*s++);
+
+ /* write Accept-Language header into buf */
+ if (num_langs) {
+ int last_buf_len = 0;
+ int max_q;
+ int decimal_places;
+ char q_format[32];
+
+ /* add '*' */
+ REALLOC_ARRAY(language_tags, num_langs + 1);
+ language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+
+ /* compute decimal_places */
+ for (max_q = 1, decimal_places = 0;
+ max_q < num_langs && decimal_places <= MAX_DECIMAL_PLACES;
+ decimal_places++, max_q *= 10)
+ ;
+
+ sprintf(q_format, ";q=0.%%0%dd", decimal_places);
+
+ strbuf_addstr(buf, "Accept-Language: ");
+
+ for (i = 0; i < num_langs; i++) {
+ if (i > 0)
+ strbuf_addstr(buf, ", ");
+
+ strbuf_addstr(buf, language_tags[i]);
+
+ if (i > 0)
+ strbuf_addf(buf, q_format, max_q - i);
+
+ if (buf->len > MAX_ACCEPT_LANGUAGE_HEADER_SIZE) {
+ strbuf_remove(buf, last_buf_len, buf->len - last_buf_len);
+ break;
+ }
+
+ last_buf_len = buf->len;
+ }
+ }
+
+ /* free language tags -- last one is a static '*' */
+ for (i = 0; i < num_langs - 1; i++)
+ free(language_tags[i]);
+ free(language_tags);
+}
+
+/*
+ * Get an Accept-Language header which indicates user's preferred languages.
+ *
+ * Examples:
+ * LANGUAGE= -> ""
+ * LANGUAGE=ko:en -> "Accept-Language: ko, en; q=0.9, *; q=0.1"
+ * LANGUAGE=ko_KR.UTF-8:sr@latin -> "Accept-Language: ko-KR, sr; q=0.9, *; q=0.1"
+ * LANGUAGE=ko LANG=en_US.UTF-8 -> "Accept-Language: ko, *; q=0.1"
+ * LANGUAGE= LANG=en_US.UTF-8 -> "Accept-Language: en-US, *; q=0.1"
+ * LANGUAGE= LANG=C -> ""
+ */
+static const char *get_accept_language(void)
+{
+ if (!cached_accept_language) {
+ struct strbuf buf = STRBUF_INIT;
+ write_accept_language(&buf);
+ if (buf.len > 0)
+ cached_accept_language = strbuf_detach(&buf, NULL);
+ }
+
+ return cached_accept_language;
+}
+
/* http_request() targets */
#define HTTP_REQUEST_STRBUF 0
#define HTTP_REQUEST_FILE 1
@@ -918,6 +1154,7 @@ static int http_request(const char *url,
struct slot_results results;
struct curl_slist *headers = NULL;
struct strbuf buf = STRBUF_INIT;
+ const char *accept_language;
int ret;
slot = get_active_slot();
@@ -943,6 +1180,11 @@ static int http_request(const char *url,
fwrite_buffer);
}
+ accept_language = get_accept_language();
+
+ if (accept_language)
+ headers = curl_slist_append(headers, accept_language);
+
strbuf_addstr(&buf, "Pragma:");
if (options && options->no_cache)
strbuf_addstr(&buf, " no-cache");
@@ -957,9 +1199,13 @@ static int http_request(const char *url,
ret = run_one_slot(slot, &results);
- if (options && options->content_type)
- curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE,
- options->content_type);
+ if (options && options->content_type) {
+ struct strbuf raw = STRBUF_INIT;
+ curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE, &raw);
+ extract_content_type(&raw, options->content_type,
+ options->charset);
+ strbuf_release(&raw);
+ }
if (options && options->effective_url)
curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL,
@@ -1006,11 +1252,10 @@ static int update_url_from_redirect(struct strbuf *base,
if (!strcmp(asked, got->buf))
return 0;
- if (!starts_with(asked, base->buf))
+ if (!skip_prefix(asked, base->buf, &tail))
die("BUG: update_url_from_redirect: %s is not a superset of %s",
asked, base->buf);
- tail = asked + base->len;
tail_len = strlen(tail);
if (got->len < tail_len ||
@@ -1157,7 +1402,7 @@ static int fetch_and_setup_pack_index(struct packed_git **packs_head,
int ret;
if (has_pack_index(sha1)) {
- new_pack = parse_pack_index(sha1, NULL);
+ new_pack = parse_pack_index(sha1, sha1_pack_index_name(sha1));
if (!new_pack)
return -1; /* parse_pack_index() already issued error message */
goto add_pack;
@@ -1245,6 +1490,7 @@ void release_http_pack_request(struct http_pack_request *preq)
}
preq->slot = NULL;
free(preq->url);
+ free(preq);
}
int finish_http_pack_request(struct http_pack_request *preq)
@@ -1252,7 +1498,7 @@ 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;
+ struct child_process ip = CHILD_PROCESS_INIT;
const char *ip_argv[8];
close_pack_index(p);
@@ -1275,7 +1521,6 @@ int finish_http_pack_request(struct http_pack_request *preq)
ip_argv[3] = preq->tmpfile;
ip_argv[4] = NULL;
- memset(&ip, 0, sizeof(ip));
ip.argv = ip_argv;
ip.git_cmd = 1;
ip.no_stdin = 1;