Refactor cache_tree_update idiom from commit
[gitweb.git] / http.c
diff --git a/http.c b/http.c
index b2ae8de16db3abe2cad27249ae767f421aa6bb24..e6c75976e8886321732ef2b0b686b08e04a0c200 100644 (file)
--- a/http.c
+++ b/http.c
@@ -41,7 +41,8 @@ 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 char *user_name, *user_pass;
+static const char *curl_cookie_file;
+static char *user_name, *user_pass, *description;
 static const char *user_agent;
 
 #if LIBCURL_VERSION_NUM >= 0x071700
@@ -138,6 +139,27 @@ static void process_curl_messages(void)
 }
 #endif
 
+static char *git_getpass_with_description(const char *what, const char *desc)
+{
+       struct strbuf prompt = STRBUF_INIT;
+       char *r;
+
+       if (desc)
+               strbuf_addf(&prompt, "%s for '%s': ", what, desc);
+       else
+               strbuf_addf(&prompt, "%s: ", what);
+       /*
+        * NEEDSWORK: for usernames, we should do something less magical that
+        * actually echoes the characters. However, we need to read from
+        * /dev/tty and not stdio, which is not portable (but getpass will do
+        * it for us). http.c uses the same workaround.
+        */
+       r = git_getpass(prompt.buf);
+
+       strbuf_release(&prompt);
+       return xstrdup(r);
+}
+
 static int http_options(const char *var, const char *value, void *cb)
 {
        if (!strcmp("http.sslverify", var)) {
@@ -191,6 +213,9 @@ 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.cookiefile", var))
+               return git_config_string(&curl_cookie_file, var, value);
+
        if (!strcmp("http.postbuffer", var)) {
                http_post_buffer = git_config_int(var, value);
                if (http_post_buffer < LARGE_PACKET_MAX)
@@ -210,7 +235,7 @@ static void init_curl_http_auth(CURL *result)
        if (user_name) {
                struct strbuf up = STRBUF_INIT;
                if (!user_pass)
-                       user_pass = xstrdup(git_getpass("Password: "));
+                       user_pass = xstrdup(git_getpass_with_description("Password", description));
                strbuf_addf(&up, "%s:%s", user_name, user_pass);
                curl_easy_setopt(result, CURLOPT_USERPWD,
                                 strbuf_detach(&up, NULL));
@@ -225,7 +250,7 @@ static int has_cert_password(void)
                return 0;
        /* Only prompt the user once. */
        ssl_cert_password_required = -1;
-       ssl_cert_password = git_getpass("Certificate Password: ");
+       ssl_cert_password = git_getpass_with_description("Certificate Password", description);
        if (ssl_cert_password != NULL) {
                ssl_cert_password = xstrdup(ssl_cert_password);
                return 1;
@@ -254,8 +279,6 @@ static CURL *get_curl_handle(void)
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
 #endif
 
-       init_curl_http_auth(result);
-
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
        if (has_cert_password())
@@ -303,8 +326,7 @@ static CURL *get_curl_handle(void)
 
 static void http_auth_init(const char *url)
 {
-       char *at, *colon, *cp, *slash, *decoded;
-       int len;
+       const char *at, *colon, *cp, *slash, *host;
 
        cp = strstr(url, "://");
        if (!cp)
@@ -320,34 +342,22 @@ static void http_auth_init(const char *url)
        at = strchr(cp, '@');
        colon = strchr(cp, ':');
        slash = strchrnul(cp, '/');
-       if (!at || slash <= at)
-               return; /* No credentials */
-       if (!colon || at <= colon) {
+       if (!at || slash <= at) {
+               /* No credentials, but we may have to ask for some later */
+               host = cp;
+       }
+       else if (!colon || at <= colon) {
                /* Only username */
-               len = at - cp;
-               user_name = xmalloc(len + 1);
-               memcpy(user_name, cp, len);
-               user_name[len] = '\0';
-               decoded = url_decode(user_name);
-               free(user_name);
-               user_name = decoded;
+               user_name = url_decode_mem(cp, at - cp);
                user_pass = NULL;
+               host = at + 1;
        } else {
-               len = colon - cp;
-               user_name = xmalloc(len + 1);
-               memcpy(user_name, cp, len);
-               user_name[len] = '\0';
-               decoded = url_decode(user_name);
-               free(user_name);
-               user_name = decoded;
-               len = at - (colon + 1);
-               user_pass = xmalloc(len + 1);
-               memcpy(user_pass, colon + 1, len);
-               user_pass[len] = '\0';
-               decoded = url_decode(user_pass);
-               free(user_pass);
-               user_pass = decoded;
+               user_name = url_decode_mem(cp, colon - cp);
+               user_pass = url_decode_mem(colon + 1, at - (colon + 1));
+               host = at + 1;
        }
+
+       description = url_decode_mem(host, slash - host);
 }
 
 static void set_from_env(const char **var, const char *envname)
@@ -357,7 +367,7 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
-void http_init(struct remote *remote)
+void http_init(struct remote *remote, const char *url)
 {
        char *low_speed_limit;
        char *low_speed_time;
@@ -421,11 +431,11 @@ void http_init(struct remote *remote)
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
-       if (remote && remote->url && remote->url[0]) {
-               http_auth_init(remote->url[0]);
+       if (url) {
+               http_auth_init(url);
                if (!ssl_cert_password_required &&
                    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
-                   !prefixcmp(remote->url[0], "https://"))
+                   !prefixcmp(url, "https://"))
                        ssl_cert_password_required = 1;
        }
 
@@ -531,6 +541,7 @@ struct active_request_slot *get_active_slot(void)
        slot->finished = NULL;
        slot->callback_data = NULL;
        slot->callback_func = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
@@ -736,14 +747,6 @@ static inline int needs_quote(int ch)
        return 1;
 }
 
-static inline int hex(int v)
-{
-       if (v < 10)
-               return '0' + v;
-       else
-               return 'A' + v - 10;
-}
-
 static char *quote_ref_url(const char *base, const char *ref)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -833,7 +836,7 @@ static int http_request(const char *url, void *result, int target, int options)
                else if (missing_target(&results))
                        ret = HTTP_MISSING_TARGET;
                else if (results.http_code == 401) {
-                       if (user_name) {
+                       if (user_name && user_pass) {
                                ret = HTTP_NOAUTH;
                        } else {
                                /*
@@ -842,12 +845,18 @@ static int http_request(const char *url, void *result, int target, int options)
                                 * but that is non-portable.  Using git_getpass() can at least be stubbed
                                 * on other platforms with a different implementation if/when necessary.
                                 */
-                               user_name = xstrdup(git_getpass("Username: "));
+                               if (!user_name)
+                                       user_name = xstrdup(git_getpass_with_description("Username", description));
                                init_curl_http_auth(slot->curl);
                                ret = HTTP_REAUTH;
                        }
-               } else
+               } else {
+                       if (!curl_errorstr[0])
+                               strlcpy(curl_errorstr,
+                                       curl_easy_strerror(results.curl_result),
+                                       sizeof(curl_errorstr));
                        ret = HTTP_ERROR;
+               }
        } else {
                error("Unable to start HTTP request for %s", url);
                ret = HTTP_START_FAILED;
@@ -860,13 +869,18 @@ static int http_request(const char *url, void *result, int target, int options)
        return ret;
 }
 
+static int http_request_reauth(const char *url, void *result, int target,
+                              int options)
+{
+       int ret = http_request(url, result, target, options);
+       if (ret != HTTP_REAUTH)
+               return ret;
+       return http_request(url, result, target, options);
+}
+
 int http_get_strbuf(const char *url, struct strbuf *result, int options)
 {
-       int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
-       if (http_ret == HTTP_REAUTH) {
-               http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
-       }
-       return http_ret;
+       return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
 }
 
 /*
@@ -889,7 +903,7 @@ static int http_get_file(const char *url, const char *filename, int options)
                goto cleanup;
        }
 
-       ret = http_request(url, result, HTTP_REQUEST_FILE, options);
+       ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);
        fclose(result);
 
        if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename))
@@ -903,7 +917,7 @@ int http_error(const char *url, int ret)
 {
        /* http_request has already handled HTTP_START_FAILED. */
        if (ret != HTTP_START_FAILED)
-               error("%s while accessing %s\n", curl_errorstr, url);
+               error("%s while accessing %s", curl_errorstr, url);
 
        return ret;
 }
@@ -1116,9 +1130,8 @@ struct http_pack_request *new_http_pack_request(
        struct strbuf buf = STRBUF_INIT;
        struct http_pack_request *preq;
 
-       preq = xmalloc(sizeof(*preq));
+       preq = xcalloc(1, sizeof(*preq));
        preq->target = target;
-       preq->range_header = NULL;
 
        end_url_with_slash(&buf, base_url);
        strbuf_addf(&buf, "objects/pack/pack-%s.pack",
@@ -1210,7 +1223,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
        struct curl_slist *range_header = NULL;
        struct http_object_request *freq;
 
-       freq = xmalloc(sizeof(*freq));
+       freq = xcalloc(1, sizeof(*freq));
        hashcpy(freq->sha1, sha1);
        freq->localfile = -1;
 
@@ -1248,8 +1261,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
                goto abort;
        }
 
-       memset(&freq->stream, 0, sizeof(freq->stream));
-
        git_inflate_init(&freq->stream);
 
        git_SHA1_Init(&freq->c);
@@ -1324,7 +1335,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
        return freq;
 
 abort:
-       free(filename);
        free(freq->url);
        free(freq);
        return NULL;