http: respect protocol.*.allow=user for http-alternates
[gitweb.git] / http.c
diff --git a/http.c b/http.c
index df6dd01594a0c7bacbb9beacec683072de38a5b1..2208269b33ae11c8b6371309d961b065e9fe5e05 100644 (file)
--- a/http.c
+++ b/http.c
@@ -98,6 +98,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
@@ -337,6 +339,16 @@ 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);
 }
@@ -477,10 +489,25 @@ static void set_curl_keepalive(CURL *c)
 }
 #endif
 
+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");
@@ -553,7 +580,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,19 +587,13 @@ 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"))
@@ -882,6 +902,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
@@ -1105,7 +1135,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,
@@ -1122,9 +1152,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 +1476,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 +1527,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 +1545,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 +1893,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 +2026,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);