summaryrefslogtreecommitdiff
path: root/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'http.c')
-rw-r--r--http.c345
1 files changed, 271 insertions, 74 deletions
diff --git a/http.c b/http.c
index 040f362a6a..0da9e66398 100644
--- a/http.c
+++ b/http.c
@@ -8,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;
@@ -28,13 +30,27 @@ 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 *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 >= 0x070903
static const char *ssl_key;
#endif
@@ -62,12 +78,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;
@@ -114,6 +135,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)
{
@@ -150,6 +202,10 @@ 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.sslversion", var))
+ return git_config_string(&ssl_version, var, value);
if (!strcmp("http.sslcert", var))
return git_config_string(&ssl_cert, var, value);
#if LIBCURL_VERSION_NUM >= 0x070903
@@ -158,10 +214,10 @@ static int http_options(const char *var, const char *value, void *cb)
#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;
@@ -300,6 +356,7 @@ 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");
@@ -324,6 +381,28 @@ static CURL *get_curl_handle(void)
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())
@@ -347,11 +426,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);
@@ -369,8 +464,21 @@ 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 >= 0x071800
+ 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 >= 0x070a07
+ curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
+#endif
set_curl_keepalive(result);
@@ -515,6 +623,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)
@@ -580,6 +691,10 @@ 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);
+#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);
@@ -730,12 +845,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);
@@ -752,31 +861,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;
@@ -839,7 +923,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
@@ -870,6 +954,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 {
@@ -986,6 +1073,124 @@ static void extract_content_type(struct strbuf *raw, struct strbuf *type,
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)
+ ;
+
+ xsnprintf(q_format, sizeof(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;
+}
+
+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
@@ -998,6 +1203,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();
@@ -1010,19 +1216,21 @@ 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);
}
+ 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");
@@ -1174,7 +1382,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);
@@ -1194,7 +1402,7 @@ int http_fetch_ref(const char *base, struct ref *ref)
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);
+ ret = get_oid_hex(buffer.buf, &ref->old_oid);
else if (starts_with(buffer.buf, "ref: ")) {
ref->symref = xstrdup(buffer.buf + 5);
ret = 0;
@@ -1240,7 +1448,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;
@@ -1261,7 +1469,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)
@@ -1322,12 +1530,9 @@ 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;
free(preq->url);
+ free(preq);
}
int finish_http_pack_request(struct http_pack_request *preq)
@@ -1335,6 +1540,7 @@ int finish_http_pack_request(struct http_pack_request *preq)
struct packed_git **lst;
struct packed_git *p = preq->target;
char *tmp_idx;
+ size_t len;
struct child_process ip = CHILD_PROCESS_INIT;
const char *ip_argv[8];
@@ -1348,9 +1554,9 @@ int finish_http_pack_request(struct http_pack_request *preq)
lst = &((*lst)->next);
*lst = (*lst)->next;
- tmp_idx = xstrdup(preq->tmpfile);
- strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"),
- ".idx.temp");
+ if (!strip_suffix(preq->tmpfile, ".pack.temp", &len))
+ die("BUG: pack tmpfile does not end in .pack.temp?");
+ tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
ip_argv[0] = "index-pack";
ip_argv[1] = "-o";
@@ -1372,8 +1578,8 @@ int finish_http_pack_request(struct http_pack_request *preq)
unlink(sha1_pack_index_name(p->sha1));
- if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1))
- || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
+ if (finalize_object_file(preq->tmpfile, sha1_pack_name(p->sha1))
+ || finalize_object_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
free(tmp_idx);
return -1;
}
@@ -1386,8 +1592,7 @@ int finish_http_pack_request(struct http_pack_request *preq)
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];
+ off_t prev_posn = 0;
struct strbuf buf = STRBUF_INIT;
struct http_pack_request *preq;
@@ -1419,16 +1624,13 @@ 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",
+ sha1_to_hex(target->sha1), (uintmax_t)prev_posn);
+ http_opt_request_remainder(preq->slot->curl, prev_posn);
}
return preq;
@@ -1477,9 +1679,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
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));
@@ -1583,12 +1783,9 @@ 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;
@@ -1637,7 +1834,7 @@ int finish_http_object_request(struct http_object_request *freq)
return -1;
}
freq->rename =
- move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1));
+ finalize_object_file(freq->tmpfile, sha1_file_name(freq->sha1));
return freq->rename;
}