cat-file: split --batch input lines on whitespace
[gitweb.git] / connect.c
index 574f42fa47ffa69328217eb25afee6f85db9595e..f57efd06c1d7ab01076b67d37ed24e34e17c4ebb 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -5,6 +5,7 @@
 #include "refs.h"
 #include "run-command.h"
 #include "remote.h"
+#include "url.h"
 
 static char *server_capabilities;
 
@@ -21,7 +22,7 @@ static int check_ref(const char *name, int len, unsigned int flags)
        len -= 5;
 
        /* REF_NORMAL means that we don't want the magic fake tag refs */
-       if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+       if ((flags & REF_NORMAL) && check_refname_format(name, 0))
                return 0;
 
        /* REF_HEADS means that we want regular branch heads */
@@ -41,26 +42,52 @@ int check_ref_type(const struct ref *ref, int flags)
        return check_ref(ref->name, strlen(ref->name), flags);
 }
 
+static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1)
+{
+       ALLOC_GROW(extra->array, extra->nr + 1, extra->alloc);
+       hashcpy(&(extra->array[extra->nr][0]), sha1);
+       extra->nr++;
+}
+
+static void die_initial_contact(int got_at_least_one_head)
+{
+       if (got_at_least_one_head)
+               die("The remote end hung up upon initial contact");
+       else
+               die("Could not read from remote repository.\n\n"
+                   "Please make sure you have the correct access rights\n"
+                   "and the repository exists.");
+}
+
 /*
  * Read all the refs from the other end
  */
-struct ref **get_remote_heads(int in, struct ref **list,
-                             int nr_match, char **match,
-                             unsigned int flags)
+struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+                             struct ref **list, unsigned int flags,
+                             struct extra_have_objects *extra_have)
 {
+       int got_at_least_one_head = 0;
+
        *list = NULL;
        for (;;) {
                struct ref *ref;
                unsigned char old_sha1[20];
-               static char buffer[1000];
                char *name;
                int len, name_len;
+               char *buffer = packet_buffer;
+
+               len = packet_read(in, &src_buf, &src_len,
+                                 packet_buffer, sizeof(packet_buffer),
+                                 PACKET_READ_GENTLE_ON_EOF |
+                                 PACKET_READ_CHOMP_NEWLINE);
+               if (len < 0)
+                       die_initial_contact(got_at_least_one_head);
 
-               len = packet_read_line(in, buffer, sizeof(buffer));
                if (!len)
                        break;
-               if (buffer[len-1] == '\n')
-                       buffer[--len] = 0;
+
+               if (len > 4 && !prefixcmp(buffer, "ERR "))
+                       die("remote error: %s", buffer + 4);
 
                if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
                        die("protocol error: expected sha/ref, got '%s'", buffer);
@@ -72,71 +99,79 @@ struct ref **get_remote_heads(int in, struct ref **list,
                        server_capabilities = xstrdup(name + name_len + 1);
                }
 
-               if (!check_ref(name, name_len, flags))
+               if (extra_have &&
+                   name_len == 5 && !memcmp(".have", name, 5)) {
+                       add_extra_have(extra_have, old_sha1);
                        continue;
-               if (nr_match && !path_match(name, nr_match, match))
+               }
+
+               if (!check_ref(name, name_len, flags))
                        continue;
-               ref = alloc_ref(name_len + 1);
+               ref = alloc_ref(buffer + 41);
                hashcpy(ref->old_sha1, old_sha1);
-               memcpy(ref->name, buffer + 41, name_len + 1);
                *list = ref;
                list = &ref->next;
+               got_at_least_one_head = 1;
        }
        return list;
 }
 
-int server_supports(const char *feature)
+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
 {
-       return server_capabilities &&
-               strstr(server_capabilities, feature) != NULL;
-}
-
-int get_ack(int fd, unsigned char *result_sha1)
-{
-       static char line[1000];
-       int len = packet_read_line(fd, line, sizeof(line));
-
-       if (!len)
-               die("git-fetch-pack: expected ACK/NAK, got EOF");
-       if (line[len-1] == '\n')
-               line[--len] = 0;
-       if (!strcmp(line, "NAK"))
-               return 0;
-       if (!prefixcmp(line, "ACK ")) {
-               if (!get_sha1_hex(line+4, result_sha1)) {
-                       if (strstr(line+45, "continue"))
-                               return 2;
-                       return 1;
+       int len;
+
+       if (!feature_list)
+               return NULL;
+
+       len = strlen(feature);
+       while (*feature_list) {
+               const char *found = strstr(feature_list, feature);
+               if (!found)
+                       return NULL;
+               if (feature_list == found || isspace(found[-1])) {
+                       const char *value = found + len;
+                       /* feature with no value (e.g., "thin-pack") */
+                       if (!*value || isspace(*value)) {
+                               if (lenp)
+                                       *lenp = 0;
+                               return value;
+                       }
+                       /* feature with a value (e.g., "agent=git/1.2.3") */
+                       else if (*value == '=') {
+                               value++;
+                               if (lenp)
+                                       *lenp = strcspn(value, " \t\n");
+                               return value;
+                       }
+                       /*
+                        * otherwise we matched a substring of another feature;
+                        * keep looking
+                        */
                }
+               feature_list = found + 1;
        }
-       die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+       return NULL;
 }
 
-int path_match(const char *path, int nr, char **match)
+int parse_feature_request(const char *feature_list, const char *feature)
 {
-       int i;
-       int pathlen = strlen(path);
+       return !!parse_feature_value(feature_list, feature, NULL);
+}
 
-       for (i = 0; i < nr; i++) {
-               char *s = match[i];
-               int len = strlen(s);
+const char *server_feature_value(const char *feature, int *len)
+{
+       return parse_feature_value(server_capabilities, feature, len);
+}
 
-               if (!len || len > pathlen)
-                       continue;
-               if (memcmp(path + pathlen - len, s, len))
-                       continue;
-               if (pathlen > len && path[pathlen - len - 1] != '/')
-                       continue;
-               *s = 0;
-               return (i + 1);
-       }
-       return 0;
+int server_supports(const char *feature)
+{
+       return !!server_feature_value(feature, NULL);
 }
 
 enum protocol {
        PROTO_LOCAL = 1,
        PROTO_SSH,
-       PROTO_GIT,
+       PROTO_GIT
 };
 
 static enum protocol get_protocol(const char *name)
@@ -157,22 +192,46 @@ static enum protocol get_protocol(const char *name)
 #define STR_(s)        # s
 #define STR(s) STR_(s)
 
+static void get_host_and_port(char **host, const char **port)
+{
+       char *colon, *end;
+
+       if (*host[0] == '[') {
+               end = strchr(*host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       (*host)++;
+               } else
+                       end = *host;
+       } else
+               end = *host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               *port = colon + 1;
+       }
+}
+
+static void enable_keepalive(int sockfd)
+{
+       int ka = 1;
+
+       if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
+               fprintf(stderr, "unable to set SO_KEEPALIVE on socket: %s\n",
+                       strerror(errno));
+}
+
 #ifndef NO_IPV6
 
 static const char *ai_name(const struct addrinfo *ai)
 {
-       static char addr[INET_ADDRSTRLEN];
-       if ( AF_INET == ai->ai_family ) {
-               struct sockaddr_in *in;
-               in = (struct sockaddr_in *)ai->ai_addr;
-               inet_ntop(ai->ai_family, &in->sin_addr, addr, sizeof(addr));
-       } else if ( AF_INET6 == ai->ai_family ) {
-               struct sockaddr_in6 *in;
-               in = (struct sockaddr_in6 *)ai->ai_addr;
-               inet_ntop(ai->ai_family, &in->sin6_addr, addr, sizeof(addr));
-       } else {
+       static char addr[NI_MAXHOST];
+       if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
+                       NI_NUMERICHOST) != 0)
                strcpy(addr, "(unknown)");
-       }
+
        return addr;
 }
 
@@ -181,31 +240,16 @@ static const char *ai_name(const struct addrinfo *ai)
  */
 static int git_tcp_connect_sock(char *host, int flags)
 {
-       int sockfd = -1, saved_errno = 0;
-       char *colon, *end;
+       struct strbuf error_message = STRBUF_INIT;
+       int sockfd = -1;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
        int cnt = 0;
 
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
-
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-               if (!*port)
-                       port = "<none>";
-       }
+       get_host_and_port(&host, &port);
+       if (!*port)
+               port = "<none>";
 
        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
@@ -221,21 +265,15 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
 
-       for (ai0 = ai; ai; ai = ai->ai_next) {
+       for (ai0 = ai; ai; ai = ai->ai_next, cnt++) {
                sockfd = socket(ai->ai_family,
                                ai->ai_socktype, ai->ai_protocol);
-               if (sockfd < 0) {
-                       saved_errno = errno;
-                       continue;
-               }
-               if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
-                       saved_errno = errno;
-                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
-                               host,
-                               cnt,
-                               ai_name(ai),
-                               strerror(saved_errno));
-                       close(sockfd);
+               if ((sockfd < 0) ||
+                   (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)) {
+                       strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
+                                   host, cnt, ai_name(ai), strerror(errno));
+                       if (0 <= sockfd)
+                               close(sockfd);
                        sockfd = -1;
                        continue;
                }
@@ -247,11 +285,15 @@ static int git_tcp_connect_sock(char *host, int flags)
        freeaddrinfo(ai0);
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(saved_errno));
+               die("unable to connect to %s:\n%s", host, error_message.buf);
+
+       enable_keepalive(sockfd);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
+       strbuf_release(&error_message);
+
        return sockfd;
 }
 
@@ -262,31 +304,17 @@ static int git_tcp_connect_sock(char *host, int flags)
  */
 static int git_tcp_connect_sock(char *host, int flags)
 {
-       int sockfd = -1, saved_errno = 0;
-       char *colon, *end;
-       char *port = STR(DEFAULT_GIT_PORT), *ep;
+       struct strbuf error_message = STRBUF_INIT;
+       int sockfd = -1;
+       const char *port = STR(DEFAULT_GIT_PORT);
+       char *ep;
        struct hostent *he;
        struct sockaddr_in sa;
        char **ap;
        unsigned int nport;
        int cnt;
 
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
-
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-       }
+       get_host_and_port(&host, &port);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "Looking up %s ... ", host);
@@ -299,7 +327,7 @@ static int git_tcp_connect_sock(char *host, int flags)
                /* Not numeric */
                struct servent *se = getservbyname(port,"tcp");
                if ( !se )
-                       die("Unknown port %s\n", port);
+                       die("Unknown port %s", port);
                nport = se->s_port;
        }
 
@@ -307,25 +335,21 @@ static int git_tcp_connect_sock(char *host, int flags)
                fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
 
        for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
-               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
-               if (sockfd < 0) {
-                       saved_errno = errno;
-                       continue;
-               }
-
                memset(&sa, 0, sizeof sa);
                sa.sin_family = he->h_addrtype;
                sa.sin_port = htons(nport);
                memcpy(&sa.sin_addr, *ap, he->h_length);
 
-               if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
-                       saved_errno = errno;
-                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+               if ((sockfd < 0) ||
+                   connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
                                host,
                                cnt,
                                inet_ntoa(*(struct in_addr *)&sa.sin_addr),
-                               strerror(saved_errno));
-                       close(sockfd);
+                               strerror(errno));
+                       if (0 <= sockfd)
+                               close(sockfd);
                        sockfd = -1;
                        continue;
                }
@@ -336,7 +360,9 @@ static int git_tcp_connect_sock(char *host, int flags)
        }
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(saved_errno));
+               die("unable to connect to %s:\n%s", host, error_message.buf);
+
+       enable_keepalive(sockfd);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
@@ -357,8 +383,6 @@ static void git_tcp_connect(int fd[2], char *host, int flags)
 
 
 static char *git_proxy_command;
-static const char *rhost_name;
-static int rhost_len;
 
 static int git_proxy_command_options(const char *var, const char *value,
                void *cb)
@@ -367,6 +391,8 @@ static int git_proxy_command_options(const char *var, const char *value,
                const char *for_pos;
                int matchlen = -1;
                int hostlen;
+               const char *rhost_name = cb;
+               int rhost_len = strlen(rhost_name);
 
                if (git_proxy_command)
                        return 0;
@@ -410,62 +436,45 @@ static int git_proxy_command_options(const char *var, const char *value,
 
 static int git_use_proxy(const char *host)
 {
-       rhost_name = host;
-       rhost_len = strlen(host);
        git_proxy_command = getenv("GIT_PROXY_COMMAND");
-       git_config(git_proxy_command_options, NULL);
-       rhost_name = NULL;
+       git_config(git_proxy_command_options, (void*)host);
        return (git_proxy_command && *git_proxy_command);
 }
 
-static void git_proxy_connect(int fd[2], char *host)
+static struct child_process *git_proxy_connect(int fd[2], char *host)
 {
        const char *port = STR(DEFAULT_GIT_PORT);
-       char *colon, *end;
-       const char *argv[4];
-       struct child_process proxy;
-
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
+       const char **argv;
+       struct child_process *proxy;
 
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-       }
+       get_host_and_port(&host, &port);
 
+       argv = xmalloc(sizeof(*argv) * 4);
        argv[0] = git_proxy_command;
        argv[1] = host;
        argv[2] = port;
        argv[3] = NULL;
-       memset(&proxy, 0, sizeof(proxy));
-       proxy.argv = argv;
-       proxy.in = -1;
-       proxy.out = -1;
-       if (start_command(&proxy))
+       proxy = xcalloc(1, sizeof(*proxy));
+       proxy->argv = argv;
+       proxy->in = -1;
+       proxy->out = -1;
+       if (start_command(proxy))
                die("cannot start proxy %s", argv[0]);
-       fd[0] = proxy.out; /* read from proxy stdout */
-       fd[1] = proxy.in;  /* write to proxy stdin */
+       fd[0] = proxy->out; /* read from proxy stdout */
+       fd[1] = proxy->in;  /* write to proxy stdin */
+       return proxy;
 }
 
 #define MAX_CMD_LEN 1024
 
-char *get_port(char *host)
+static char *get_port(char *host)
 {
        char *end;
        char *p = strchr(host, ':');
 
        if (p) {
-               strtol(p+1, &end, 10);
-               if (*end == '\0') {
+               long port = strtol(p + 1, &end, 10);
+               if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) {
                        *p = '\0';
                        return p+1;
                }
@@ -490,11 +499,11 @@ static struct child_process no_fork;
 struct child_process *git_connect(int fd[2], const char *url_orig,
                                  const char *prog, int flags)
 {
-       char *url = xstrdup(url_orig);
-       char *host, *path = url;
+       char *url;
+       char *host, *path;
        char *end;
        int c;
-       struct child_process *conn;
+       struct child_process *conn = &no_fork;
        enum protocol protocol = PROTO_LOCAL;
        int free_path = 0;
        char *port = NULL;
@@ -506,8 +515,13 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
         */
        signal(SIGCHLD, SIG_DFL);
 
+       if (is_url(url_orig))
+               url = url_decode(url_orig);
+       else
+               url = xstrdup(url_orig);
+
        host = strstr(url, "://");
-       if(host) {
+       if (host) {
                *host = '\0';
                protocol = get_protocol(url);
                host += 3;
@@ -517,12 +531,18 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                c = ':';
        }
 
+       /*
+        * Don't do destructive transforms with git:// as that
+        * protocol code does '[]' unwrapping of its own.
+        */
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
                if (end) {
-                       *end = 0;
+                       if (protocol != PROTO_GIT) {
+                               *end = 0;
+                               host++;
+                       }
                        end++;
-                       host++;
                } else
                        end = host;
        } else
@@ -560,7 +580,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
         * Add support for ssh port: ssh://host.xy:<port>/...
         */
        if (protocol == PROTO_SSH && host != url)
-               port = get_port(host);
+               port = get_port(end);
 
        if (protocol == PROTO_GIT) {
                /* These underlying connection commands die() if they
@@ -568,12 +588,15 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                 */
                char *target_host = xstrdup(host);
                if (git_use_proxy(host))
-                       git_proxy_connect(fd, host);
+                       conn = git_proxy_connect(fd, host);
                else
                        git_tcp_connect(fd, host, flags);
                /*
                 * Separate original protocol components prog and path
-                * from extended components with a NUL byte.
+                * from extended host header with a NUL byte.
+                *
+                * Note: Do not add any other headers here!  Doing so
+                * will cause older git-daemon servers to crash.
                 */
                packet_write(fd[1],
                             "%s %s%chost=%s%c",
@@ -583,7 +606,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                free(url);
                if (free_path)
                        free(path);
-               return &no_fork;
+               return conn;
        }
 
        conn = xcalloc(1, sizeof(*conn));
@@ -596,32 +619,26 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                die("command line too long");
 
        conn->in = conn->out = -1;
-       conn->argv = arg = xcalloc(6, sizeof(*arg));
+       conn->argv = arg = xcalloc(7, sizeof(*arg));
        if (protocol == PROTO_SSH) {
                const char *ssh = getenv("GIT_SSH");
+               int putty = ssh && strcasestr(ssh, "plink");
                if (!ssh) ssh = "ssh";
 
                *arg++ = ssh;
+               if (putty && !strcasestr(ssh, "tortoiseplink"))
+                       *arg++ = "-batch";
                if (port) {
-                       *arg++ = "-p";
+                       /* P is for PuTTY, p is for OpenSSH */
+                       *arg++ = putty ? "-P" : "-p";
                        *arg++ = port;
                }
                *arg++ = host;
        }
        else {
-               /* remove these from the environment */
-               const char *env[] = {
-                       ALTERNATE_DB_ENVIRONMENT,
-                       DB_ENVIRONMENT,
-                       GIT_DIR_ENVIRONMENT,
-                       GIT_WORK_TREE_ENVIRONMENT,
-                       GRAFT_ENVIRONMENT,
-                       INDEX_ENVIRONMENT,
-                       NULL
-               };
-               conn->env = env;
-               *arg++ = "sh";
-               *arg++ = "-c";
+               /* remove repo-local variables from the environment */
+               conn->env = local_repo_env;
+               conn->use_shell = 1;
        }
        *arg++ = cmd.buf;
        *arg = NULL;
@@ -638,10 +655,15 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
        return conn;
 }
 
+int git_connection_is_socket(struct child_process *conn)
+{
+       return conn == &no_fork;
+}
+
 int finish_connect(struct child_process *conn)
 {
        int code;
-       if (!conn || conn == &no_fork)
+       if (!conn || git_connection_is_socket(conn))
                return 0;
 
        code = finish_command(conn);