Merge branch 'tc/missing-http-proxyauth'
authorJunio C Hamano <gitster@pobox.com>
Wed, 25 Feb 2015 23:40:12 +0000 (15:40 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 25 Feb 2015 23:40:12 +0000 (15:40 -0800)
We did not check the curl library version before using
CURLOPT_PROXYAUTH feature that may not exist.

* tc/missing-http-proxyauth:
http: support curl < 7.10.7

1  2 
http.c
diff --combined http.c
index efdab0979692153f016bdf1fdb1a2e782738f1bc,93768874b39246549307098667a449ac31755ea9..0153fb0b626d1fc28eba1bece2406b64fcaa0ecb
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -1,13 -1,9 +1,13 @@@
 +#include "git-compat-util.h"
  #include "http.h"
  #include "pack.h"
  #include "sideband.h"
  #include "run-command.h"
  #include "url.h"
 +#include "urlmatch.h"
  #include "credential.h"
 +#include "version.h"
 +#include "pkt-line.h"
  
  int active_requests;
  int http_is_verbose;
@@@ -33,7 -29,6 +33,7 @@@ static CURL *curl_default
  char curl_errorstr[CURL_ERROR_SIZE];
  
  static int curl_ssl_verify = -1;
 +static int curl_ssl_try;
  static const char *ssl_cert;
  #if LIBCURL_VERSION_NUM >= 0x070903
  static const char *ssl_key;
@@@ -47,8 -42,7 +47,8 @@@ static long curl_low_speed_time = -1
  static int curl_ftp_no_epsv;
  static const char *curl_http_proxy;
  static const char *curl_cookie_file;
 -static struct credential http_auth = CREDENTIAL_INIT;
 +static int curl_save_cookies;
 +struct credential http_auth = CREDENTIAL_INIT;
  static int http_proactive_auth;
  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;
@@@ -119,37 -108,6 +119,37 @@@ size_t fwrite_null(char *ptr, size_t el
        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)
  {
@@@ -199,11 -157,8 +199,11 @@@ static int http_options(const char *var
        if (!strcmp("http.sslcainfo", var))
                return git_config_string(&ssl_cainfo, var, value);
        if (!strcmp("http.sslcertpasswordprotected", var)) {
 -              if (git_config_bool(var, value))
 -                      ssl_cert_password_required = 1;
 +              ssl_cert_password_required = git_config_bool(var, value);
 +              return 0;
 +      }
 +      if (!strcmp("http.ssltry", var)) {
 +              curl_ssl_try = git_config_bool(var, value);
                return 0;
        }
        if (!strcmp("http.minsessions", var)) {
  
        if (!strcmp("http.cookiefile", var))
                return git_config_string(&curl_cookie_file, var, value);
 +      if (!strcmp("http.savecookies", var)) {
 +              curl_save_cookies = git_config_bool(var, value);
 +              return 0;
 +      }
  
        if (!strcmp("http.postbuffer", var)) {
                http_post_buffer = git_config_int(var, value);
  
  static void init_curl_http_auth(CURL *result)
  {
 -      if (http_auth.username) {
 -              struct strbuf up = STRBUF_INIT;
 -              credential_fill(&http_auth);
 -              strbuf_addf(&up, "%s:%s",
 -                          http_auth.username, http_auth.password);
 -              curl_easy_setopt(result, CURLOPT_USERPWD,
 -                               strbuf_detach(&up, NULL));
 +      if (!http_auth.username)
 +              return;
 +
 +      credential_fill(&http_auth);
 +
 +#if LIBCURL_VERSION_NUM >= 0x071301
 +      curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
 +      curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
 +#else
 +      {
 +              static struct strbuf up = STRBUF_INIT;
 +              /*
 +               * Note that we assume we only ever have a single set of
 +               * credentials in a given program run, so we do not have
 +               * to worry about updating this buffer, only setting its
 +               * initial value.
 +               */
 +              if (!up.len)
 +                      strbuf_addf(&up, "%s:%s",
 +                              http_auth.username, http_auth.password);
 +              curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
        }
 +#endif
  }
  
  static int has_cert_password(void)
                return 0;
        if (!cert_auth.password) {
                cert_auth.protocol = xstrdup("cert");
 +              cert_auth.username = xstrdup("");
                cert_auth.path = xstrdup(ssl_cert);
                credential_fill(&cert_auth);
        }
        return 1;
  }
  
 +#if LIBCURL_VERSION_NUM >= 0x071900
 +static void set_curl_keepalive(CURL *c)
 +{
 +      curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1);
 +}
 +
 +#elif LIBCURL_VERSION_NUM >= 0x071000
 +static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type)
 +{
 +      int ka = 1;
 +      int rc;
 +      socklen_t len = (socklen_t)sizeof(ka);
 +
 +      if (type != CURLSOCKTYPE_IPCXN)
 +              return 0;
 +
 +      rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&ka, len);
 +      if (rc < 0)
 +              warning("unable to set SO_KEEPALIVE on socket %s",
 +                      strerror(errno));
 +
 +      return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */
 +}
 +
 +static void set_curl_keepalive(CURL *c)
 +{
 +      curl_easy_setopt(c, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
 +}
 +
 +#else
 +static void set_curl_keepalive(CURL *c)
 +{
 +      /* not supported on older curl versions */
 +}
 +#endif
 +
  static CURL *get_curl_handle(void)
  {
        CURL *result = curl_easy_init();
  
 +      if (!result)
 +              die("curl_easy_init failed");
 +
        if (!curl_ssl_verify) {
                curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
                curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0);
  #endif
        if (ssl_cainfo != NULL)
                curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
 -      curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
  
        if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
                curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
  
        curl_easy_setopt(result, CURLOPT_USERAGENT,
 -              user_agent ? user_agent : GIT_HTTP_USER_AGENT);
 +              user_agent ? user_agent : git_user_agent());
  
        if (curl_ftp_no_epsv)
                curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
  
 +#ifdef CURLOPT_USE_SSL
 +      if (curl_ssl_try)
 +              curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY);
 +#endif
 +
        if (curl_http_proxy) {
                curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
+ #if LIBCURL_VERSION_NUM >= 0x070a07
                curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
+ #endif
        }
  
 +      set_curl_keepalive(result);
 +
        return result;
  }
  
@@@ -424,23 -316,12 +426,23 @@@ void http_init(struct remote *remote, c
  {
        char *low_speed_limit;
        char *low_speed_time;
 +      char *normalized_url;
 +      struct urlmatch_config config = { STRING_LIST_INIT_DUP };
 +
 +      config.section = "http";
 +      config.key = NULL;
 +      config.collect_fn = http_options;
 +      config.cascade_fn = git_default_config;
 +      config.cb = NULL;
  
        http_is_verbose = 0;
 +      normalized_url = url_normalize(url, &config.url);
  
 -      git_config(http_options, NULL);
 +      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;
  
        }
  
        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"))
                credential_from_url(&http_auth, url);
                if (!ssl_cert_password_required &&
                    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
 -                  !prefixcmp(url, "https://"))
 +                  starts_with(url, "https://"))
                        ssl_cert_password_required = 1;
        }
  
@@@ -551,9 -434,6 +553,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)
        slot->callback_data = NULL;
        slot->callback_func = NULL;
        curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
 +      if (curl_save_cookies)
 +              curl_easy_setopt(slot->curl, CURLOPT_COOKIEJAR, 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);
        curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL);
        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);
  
        return slot;
  }
@@@ -749,18 -621,6 +751,18 @@@ void run_active_slot(struct active_requ
                        FD_ZERO(&excfds);
                        curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd);
  
 +                      /*
 +                       * It can happen that curl_multi_timeout returns a pathologically
 +                       * long timeout when curl_multi_fdset returns no file descriptors
 +                       * to read.  See commit message for more details.
 +                       */
 +                      if (max_fd < 0 &&
 +                          (select_timeout.tv_sec > 0 ||
 +                           select_timeout.tv_usec > 50000)) {
 +                              select_timeout.tv_sec  = 0;
 +                              select_timeout.tv_usec = 50000;
 +                      }
 +
                        select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout);
                }
        }
  #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);
  #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;
 -      }
 -
 -      /* 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;
@@@ -850,309 -735,20 +852,309 @@@ char *get_remote_object_url(const char 
        return strbuf_detach(&buf, NULL);
  }
  
 +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.
 +       */
 +      if (results->curl_result == CURLE_OK &&
 +          results->http_code >= 400) {
 +              results->curl_result = CURLE_HTTP_RETURNED_ERROR;
 +              /*
 +               * Normally curl will already have put the "reason phrase"
 +               * from the server into curl_errorstr; unfortunately without
 +               * FAILONERROR it is lost, so we can give only the numeric
 +               * status code.
 +               */
 +              snprintf(curl_errorstr, sizeof(curl_errorstr),
 +                       "The requested URL returned error: %ld",
 +                       results->http_code);
 +      }
 +
 +      if (results->curl_result == CURLE_OK) {
 +              credential_approve(&http_auth);
 +              return HTTP_OK;
 +      } else if (missing_target(results))
 +              return HTTP_MISSING_TARGET;
 +      else if (results->http_code == 401) {
 +              if (http_auth.username && http_auth.password) {
 +                      credential_reject(&http_auth);
 +                      return HTTP_NOAUTH;
 +              } else {
 +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
 +                      http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
 +#endif
 +                      return HTTP_REAUTH;
 +              }
 +      } else {
 +#if LIBCURL_VERSION_NUM >= 0x070c00
 +              if (!curl_errorstr[0])
 +                      strlcpy(curl_errorstr,
 +                              curl_easy_strerror(results->curl_result),
 +                              sizeof(curl_errorstr));
 +#endif
 +              return HTTP_ERROR;
 +      }
 +}
 +
 +int run_one_slot(struct active_request_slot *slot,
 +               struct slot_results *results)
 +{
 +      slot->results = results;
 +      if (!start_active_slot(slot)) {
 +              snprintf(curl_errorstr, sizeof(curl_errorstr),
 +                       "failed to start HTTP request");
 +              return HTTP_START_FAILED;
 +      }
 +
 +      run_active_slot(slot);
 +      return handle_curl_result(results);
 +}
 +
 +static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
 +{
 +      char *ptr;
 +      CURLcode ret;
 +
 +      strbuf_reset(buf);
 +      ret = curl_easy_getinfo(curl, info, &ptr);
 +      if (!ret && ptr)
 +              strbuf_addstr(buf, ptr);
 +      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");
 +}
 +
 +
 +/*
 + * Guess the user's preferred languages from the value in LANGUAGE environment
 + * variable and LC_MESSAGES locale category if NO_GETTEXT is not defined.
 + *
 + * The result can be a colon-separated list like "ko:ja:en".
 + */
 +static const char *get_preferred_languages(void)
 +{
 +      const char *retval;
 +
 +      retval = getenv("LANGUAGE");
 +      if (retval && *retval)
 +              return retval;
 +
 +#ifndef NO_GETTEXT
 +      retval = setlocale(LC_MESSAGES, NULL);
 +      if (retval && *retval &&
 +              strcmp(retval, "C") &&
 +              strcmp(retval, "POSIX"))
 +              return retval;
 +#endif
 +
 +      return NULL;
 +}
 +
 +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
  
 -static int http_request(const char *url, void *result, int target, int options)
 +static int http_request(const char *url,
 +                      void *result, int target,
 +                      const struct http_get_options *options)
  {
        struct active_request_slot *slot;
        struct slot_results results;
        struct curl_slist *headers = NULL;
        struct strbuf buf = STRBUF_INIT;
 +      const char *accept_language;
        int ret;
  
        slot = get_active_slot();
 -      slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
  
        if (result == NULL) {
                                         fwrite_buffer);
        }
  
 +      accept_language = get_accept_language();
 +
 +      if (accept_language)
 +              headers = curl_slist_append(headers, accept_language);
 +
        strbuf_addstr(&buf, "Pragma:");
 -      if (options & HTTP_NO_CACHE)
 +      if (options && options->no_cache)
                strbuf_addstr(&buf, " no-cache");
 +      if (options && options->keep_error)
 +              curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
  
        headers = curl_slist_append(headers, buf.buf);
  
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
 +      curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
  
 -      if (start_active_slot(slot)) {
 -              run_active_slot(slot);
 -              if (results.curl_result == CURLE_OK)
 -                      ret = HTTP_OK;
 -              else if (missing_target(&results))
 -                      ret = HTTP_MISSING_TARGET;
 -              else if (results.http_code == 401) {
 -                      if (http_auth.username && http_auth.password) {
 -                              credential_reject(&http_auth);
 -                              ret = HTTP_NOAUTH;
 -                      } else {
 -                              credential_fill(&http_auth);
 -                              init_curl_http_auth(slot->curl);
 -                              ret = HTTP_REAUTH;
 -                      }
 -              } 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;
 +      ret = run_one_slot(slot, &results);
 +
 +      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,
 +                              options->effective_url);
 +
        curl_slist_free_all(headers);
        strbuf_release(&buf);
  
 -      if (ret == HTTP_OK)
 -              credential_approve(&http_auth);
 -
        return ret;
  }
  
 -static int http_request_reauth(const char *url, void *result, int target,
 -                             int options)
 +/*
 + * Update the "base" url to a more appropriate value, as deduced by
 + * redirects seen when requesting a URL starting with "url".
 + *
 + * The "asked" parameter is a URL that we asked curl to access, and must begin
 + * with "base".
 + *
 + * The "got" parameter is the URL that curl reported to us as where we ended
 + * up.
 + *
 + * Returns 1 if we updated the base url, 0 otherwise.
 + *
 + * Our basic strategy is to compare "base" and "asked" to find the bits
 + * specific to our request. We then strip those bits off of "got" to yield the
 + * new base. So for example, if our base is "http://example.com/foo.git",
 + * and we ask for "http://example.com/foo.git/info/refs", we might end up
 + * with "https://other.example.com/foo.git/info/refs". We would want the
 + * new URL to become "https://other.example.com/foo.git".
 + *
 + * 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).
 + */
 +static int update_url_from_redirect(struct strbuf *base,
 +                                  const char *asked,
 +                                  const struct strbuf *got)
 +{
 +      const char *tail;
 +      size_t tail_len;
 +
 +      if (!strcmp(asked, got->buf))
 +              return 0;
 +
 +      if (!skip_prefix(asked, base->buf, &tail))
 +              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 */
 +
 +      strbuf_reset(base);
 +      strbuf_add(base, got->buf, got->len - tail_len);
 +      return 1;
 +}
 +
 +static int http_request_reauth(const char *url,
 +                             void *result, int target,
 +                             struct http_get_options *options)
  {
        int ret = http_request(url, result, target, options);
 +
 +      if (options && options->effective_url && options->base_url) {
 +              if (update_url_from_redirect(options->base_url,
 +                                           url, options->effective_url)) {
 +                      credential_from_url(&http_auth, options->base_url->buf);
 +                      url = options->effective_url->buf;
 +              }
 +      }
 +
        if (ret != HTTP_REAUTH)
                return ret;
 +
 +      /*
 +       * If we are using KEEP_ERROR, the previous request may have
 +       * put cruft into our output stream; we should clear it out before
 +       * making our next request. We only know how to do this for
 +       * the strbuf case, but that is enough to satisfy current callers.
 +       */
 +      if (options && options->keep_error) {
 +              switch (target) {
 +              case HTTP_REQUEST_STRBUF:
 +                      strbuf_reset(result);
 +                      break;
 +              default:
 +                      die("BUG: HTTP_KEEP_ERROR is only supported with strbufs");
 +              }
 +      }
 +
 +      credential_fill(&http_auth);
 +
        return http_request(url, result, target, options);
  }
  
 -int http_get_strbuf(const char *url, struct strbuf *result, int options)
 +int http_get_strbuf(const char *url,
 +                  struct strbuf *result,
 +                  struct http_get_options *options)
  {
        return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
  }
  
  /*
 - * Downloads an url and stores the result in the given file.
 + * Downloads a URL and stores the result in the given file.
   *
   * If a previous interrupted download is detected (i.e. a previous temporary
   * file is still around) the download is resumed.
   */
 -static int http_get_file(const char *url, const char *filename, int options)
 +static int http_get_file(const char *url, const char *filename,
 +                       struct http_get_options *options)
  {
        int ret;
        struct strbuf tmpfile = STRBUF_INIT;
  
        strbuf_addf(&tmpfile, "%s.temp", filename);
        result = fopen(tmpfile.buf, "a");
 -      if (! result) {
 +      if (!result) {
                error("Unable to open local file %s", tmpfile.buf);
                ret = HTTP_ERROR;
                goto cleanup;
        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 && move_temp_to_file(tmpfile.buf, filename))
                ret = HTTP_ERROR;
  cleanup:
        strbuf_release(&tmpfile);
        return ret;
  }
  
 -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", curl_errorstr, url);
 -
 -      return ret;
 -}
 -
  int http_fetch_ref(const char *base, struct ref *ref)
  {
 +      struct http_get_options options = {0};
        char *url;
        struct strbuf buffer = STRBUF_INIT;
        int ret = -1;
  
 +      options.no_cache = 1;
 +
        url = quote_ref_url(base, ref->name);
 -      if (http_get_strbuf(url, &buffer, HTTP_NO_CACHE) == HTTP_OK) {
 +      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);
 -              else if (!prefixcmp(buffer.buf, "ref: ")) {
 +              else if (starts_with(buffer.buf, "ref: ")) {
                        ref->symref = xstrdup(buffer.buf + 5);
                        ret = 0;
                }
@@@ -1379,8 -907,8 +1381,8 @@@ static char *fetch_pack_index(unsigned 
        strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1));
        tmp = strbuf_detach(&buf, NULL);
  
 -      if (http_get_file(url, tmp, 0) != HTTP_OK) {
 -              error("Unable to get pack index %s\n", url);
 +      if (http_get_file(url, tmp, NULL) != HTTP_OK) {
 +              error("Unable to get pack index %s", url);
                free(tmp);
                tmp = NULL;
        }
@@@ -1397,7 -925,7 +1399,7 @@@ static int fetch_and_setup_pack_index(s
        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;
@@@ -1432,7 -960,6 +1434,7 @@@ add_pack
  
  int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
  {
 +      struct http_get_options options = {0};
        int ret = 0, i = 0;
        char *url, *data;
        struct strbuf buf = STRBUF_INIT;
        strbuf_addstr(&buf, "objects/info/packs");
        url = strbuf_detach(&buf, NULL);
  
 -      ret = http_get_strbuf(url, &buf, HTTP_NO_CACHE);
 +      options.no_cache = 1;
 +      ret = http_get_strbuf(url, &buf, &options);
        if (ret != HTTP_OK)
                goto cleanup;
  
                case 'P':
                        i++;
                        if (i + 52 <= buf.len &&
 -                          !prefixcmp(data + i, " pack-") &&
 -                          !prefixcmp(data + i + 46, ".pack\n")) {
 +                          starts_with(data + i, " pack-") &&
 +                          starts_with(data + i + 46, ".pack\n")) {
                                get_sha1_hex(data + i + 6, sha1);
                                fetch_and_setup_pack_index(packs_head, sha1,
                                                      base_url);
@@@ -1492,7 -1018,7 +1494,7 @@@ int finish_http_pack_request(struct htt
        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);
        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;
@@@ -1629,7 -1156,7 +1631,7 @@@ struct http_object_request *new_http_ob
        unsigned char *sha1)
  {
        char *hex = sha1_to_hex(sha1);
 -      char *filename;
 +      const char *filename;
        char prevfile[PATH_MAX];
        int prevlocal;
        char prev_buf[PREV_BUF_SIZE];