Code

Merge branch 'jk/http-auth'
authorJunio C Hamano <gitster@pobox.com>
Tue, 18 Oct 2011 04:37:15 +0000 (21:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 18 Oct 2011 04:37:15 +0000 (21:37 -0700)
* jk/http-auth:
  http_init: accept separate URL parameter
  http: use hostname in credential description
  http: retry authentication failures for all http requests
  remote-curl: don't retry auth failures with dumb protocol
  improve httpd auth tests
  url: decode buffers that are not NUL-terminated

1  2 
http-fetch.c
http-push.c
http.c
remote-curl.c
url.c

diff --combined http-fetch.c
index 8c4c5d2224a2493a648e6a34257bc150f2712dd0,e341872a6bcfc5730cbbfd19b97f8cf58865e403..69299b7bd2a956266bf581df9c23589a97fca805
@@@ -56,10 -56,6 +56,10 @@@ int main(int argc, const char **argv
                commits = 1;
        }
  
 +      if (get_all == 0)
 +              warning("http-fetch: use without -a is deprecated.\n"
 +                      "In a future release, -a will become the default.");
 +
        if (argv[arg])
                str_end_url_with_slash(argv[arg], &url);
  
@@@ -67,7 -63,7 +67,7 @@@
  
        git_config(git_default_config, NULL);
  
-       http_init(NULL);
+       http_init(NULL, url);
        walker = get_http_walker(url);
        walker->get_tree = get_tree;
        walker->get_history = get_history;
diff --combined http-push.c
index 44f814cda2a9756a55cb7f332d5fef0e5484256b,ecbfae56da8d0a8fe304bf9d1a00073a3d6a5363..5d01be93440cdb343379fe0b05ff9155727f125c
@@@ -1606,10 -1606,10 +1606,10 @@@ static void fetch_symref(const char *pa
        strbuf_release(&buffer);
  }
  
 -static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
 +static int verify_merge_base(unsigned char *head_sha1, struct ref *remote)
  {
 -      struct commit *head = lookup_commit(head_sha1);
 -      struct commit *branch = lookup_commit(branch_sha1);
 +      struct commit *head = lookup_commit_or_die(head_sha1, "HEAD");
 +      struct commit *branch = lookup_commit_or_die(remote->old_sha1, remote->name);
        struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
  
        return (merge_bases && !merge_bases->next && merge_bases->item == branch);
@@@ -1655,7 -1655,7 +1655,7 @@@ static int delete_remote_branch(const c
                return error("Remote HEAD is not a symref");
  
        /* Remote branch must not be the remote HEAD */
 -      for (i=0; symref && i<MAXDEPTH; i++) {
 +      for (i = 0; symref && i < MAXDEPTH; i++) {
                if (!strcmp(remote_ref->name, symref))
                        return error("Remote branch %s is the current HEAD",
                                     remote_ref->name);
                        return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
  
                /* Remote branch must be an ancestor of remote HEAD */
 -              if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
 +              if (!verify_merge_base(head_sha1, remote_ref)) {
                        return error("The branch '%s' is not an ancestor "
                                     "of your current HEAD.\n"
                                     "If you are sure you want to delete it,"
@@@ -1747,7 -1747,6 +1747,6 @@@ int main(int argc, char **argv
        int i;
        int new_refs;
        struct ref *ref, *local_refs;
-       struct remote *remote;
  
        git_extract_argv0_path(argv[0]);
  
  
        memset(remote_dir_exists, -1, 256);
  
-       /*
-        * Create a minimum remote by hand to give to http_init(),
-        * primarily to allow it to look at the URL.
-        */
-       remote = xcalloc(sizeof(*remote), 1);
-       ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
-       remote->url[remote->url_nr++] = repo->url;
-       http_init(remote);
+       http_init(NULL, repo->url);
  
  #ifdef USE_CURL_MULTI
        is_running_queue = 0;
diff --combined http.c
index fb3465f50c95ab9cb3e6fae1d1e594f6ae0b9c42,00a8553fcc10af2c37520d4a0eaae9e50f8e504c..a4bc770e2d6196958ec5b795ca89be24be182a34
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -42,7 -42,7 +42,7 @@@ 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 char *user_name, *user_pass;
+ static char *user_name, *user_pass, *description;
  static const char *user_agent;
  
  #if LIBCURL_VERSION_NUM >= 0x071700
@@@ -139,6 -139,27 +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)) {
@@@ -214,7 -235,7 +235,7 @@@ static void init_curl_http_auth(CURL *r
        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));
@@@ -229,7 -250,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;
@@@ -307,8 -328,7 +328,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)
        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)
                *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;
        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;
        }
  
@@@ -847,17 -855,12 +855,17 @@@ static int http_request(const char *url
                                 * 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: "));
+                               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;
        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);
  }
  
  /*
@@@ -899,7 -907,7 +912,7 @@@ static int http_get_file(const char *ur
                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))
@@@ -913,7 -921,7 +926,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;
  }
@@@ -1126,8 -1134,9 +1139,8 @@@ struct http_pack_request *new_http_pack
        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",
@@@ -1219,7 -1228,7 +1232,7 @@@ struct http_object_request *new_http_ob
        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;
  
                goto abort;
        }
  
 -      memset(&freq->stream, 0, sizeof(freq->stream));
 -
        git_inflate_init(&freq->stream);
  
        git_SHA1_Init(&freq->c);
        return freq;
  
  abort:
 -      free(filename);
        free(freq->url);
        free(freq);
        return NULL;
diff --combined remote-curl.c
index 0aa4bfed309d6c439fac4ff2a0df6a468307e7bf,d4d0910e60200f0592c46aa2075acbe8394f7f85..0e720ee8bbf4cbc6a50336a1f1c93bfc63842fe3
@@@ -115,7 -115,7 +115,7 @@@ static struct discovery* discover_refs(
        http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
  
        /* try again with "plain" url (no ? or & appended) */
-       if (http_ret != HTTP_OK) {
+       if (http_ret != HTTP_OK && http_ret != HTTP_NOAUTH) {
                free(refs_url);
                strbuf_reset(&buffer);
  
@@@ -227,8 -227,6 +227,8 @@@ static struct ref *parse_info_refs(stru
                if (data[i] == '\t')
                        mid = &data[i];
                if (data[i] == '\n') {
 +                      if (mid - start != 40)
 +                              die("%sinfo/refs not valid: is this a git repository?", url);
                        data[i] = 0;
                        ref_name = mid + 1;
                        ref = xmalloc(sizeof(struct ref) +
@@@ -573,14 -571,7 +573,14 @@@ static int rpc_service(struct rpc_stat
  
        close(client.in);
        client.in = -1;
 -      strbuf_read(&rpc->result, client.out, 0);
 +      if (!err) {
 +              strbuf_read(&rpc->result, client.out, 0);
 +      } else {
 +              char buf[4096];
 +              for (;;)
 +                      if (xread(client.out, buf, sizeof(buf)) <= 0)
 +                              break;
 +      }
  
        close(client.out);
        client.out = -1;
@@@ -859,17 -850,10 +859,17 @@@ int main(int argc, const char **argv
  
        url = strbuf_detach(&buf, NULL);
  
-       http_init(remote);
+       http_init(remote, url);
  
        do {
 -              if (strbuf_getline(&buf, stdin, '\n') == EOF)
 +              if (strbuf_getline(&buf, stdin, '\n') == EOF) {
 +                      if (ferror(stdin))
 +                              fprintf(stderr, "Error reading command stream\n");
 +                      else
 +                              fprintf(stderr, "Unexpected end of command stream\n");
 +                      return 1;
 +              }
 +              if (buf.len == 0)
                        break;
                if (!prefixcmp(buf.buf, "fetch ")) {
                        if (nongit)
                        printf("\n");
                        fflush(stdout);
                } else {
 +                      fprintf(stderr, "Unknown command '%s'\n", buf.buf);
                        return 1;
                }
                strbuf_reset(&buf);
diff --combined url.c
index e4262a0d7a9ef71924b117f4cbf9fe12d0239f0d,389d9dab10e72b8fd94e38828fcf999e78494bdc..335d97d3f74e5b7d7139e223fbe1d19837a72e81
--- 1/url.c
--- 2/url.c
+++ b/url.c
@@@ -18,15 -18,35 +18,15 @@@ int is_urlschemechar(int first_flag, in
  
  int is_url(const char *url)
  {
 -      const char *url2, *first_slash;
 -
 -      if (!url)
 -              return 0;
 -      url2 = url;
 -      first_slash = strchr(url, '/');
 -
 -      /* Input with no slash at all or slash first can't be URL. */
 -      if (!first_slash || first_slash == url)
 -              return 0;
 -      /* Character before must be : and next must be /. */
 -      if (first_slash[-1] != ':' || first_slash[1] != '/')
 +      /* Is "scheme" part reasonable? */
 +      if (!url || !is_urlschemechar(1, *url++))
                return 0;
 -      /* There must be something before the :// */
 -      if (first_slash == url + 1)
 -              return 0;
 -      /*
 -       * Check all characters up to first slash - 1. Only alphanum
 -       * is allowed.
 -       */
 -      url2 = url;
 -      while (url2 < first_slash - 1) {
 -              if (!is_urlschemechar(url2 == url, (unsigned char)*url2))
 +      while (*url && *url != ':') {
 +              if (!is_urlschemechar(0, *url++))
                        return 0;
 -              url2++;
        }
 -
 -      /* Valid enough. */
 -      return 1;
 +      /* We've seen "scheme"; we want colon-slash-slash */
 +      return (url[0] == ':' && url[1] == '/' && url[2] == '/');
  }
  
  static int url_decode_char(const char *q)
        return val;
  }
  
- static char *url_decode_internal(const char **query, const char *stop_at,
-                                struct strbuf *out, int decode_plus)
+ static char *url_decode_internal(const char **query, int len,
+                                const char *stop_at, struct strbuf *out,
+                                int decode_plus)
  {
        const char *q = *query;
  
-       do {
+       while (len) {
                unsigned char c = *q;
  
                if (!c)
                        break;
                if (stop_at && strchr(stop_at, c)) {
                        q++;
+                       len--;
                        break;
                }
  
@@@ -68,6 -90,7 +70,7 @@@
                        if (0 <= val) {
                                strbuf_addch(out, val);
                                q += 3;
+                               len -= 3;
                                continue;
                        }
                }
                else
                        strbuf_addch(out, c);
                q++;
-       } while (1);
+               len--;
+       }
        *query = q;
        return strbuf_detach(out, NULL);
  }
  
  char *url_decode(const char *url)
+ {
+       return url_decode_mem(url, strlen(url));
+ }
+ char *url_decode_mem(const char *url, int len)
  {
        struct strbuf out = STRBUF_INIT;
-       const char *colon = strchr(url, ':');
+       const char *colon = memchr(url, ':', len);
  
        /* Skip protocol part if present */
        if (colon && url < colon) {
                strbuf_add(&out, url, colon - url);
+               len -= colon - url;
                url = colon;
        }
-       return url_decode_internal(&url, NULL, &out, 0);
+       return url_decode_internal(&url, len, NULL, &out, 0);
  }
  
  char *url_decode_parameter_name(const char **query)
  {
        struct strbuf out = STRBUF_INIT;
-       return url_decode_internal(query, "&=", &out, 1);
+       return url_decode_internal(query, -1, "&=", &out, 1);
  }
  
  char *url_decode_parameter_value(const char **query)
  {
        struct strbuf out = STRBUF_INIT;
-       return url_decode_internal(query, "&", &out, 1);
+       return url_decode_internal(query, -1, "&", &out, 1);
  }
  
  void end_url_with_slash(struct strbuf *buf, const char *url)