make update-server-info more robust
[gitweb.git] / http-backend.c
index 345c12b79064f23e0ae0a15781731b9a42272d83..80790bbaef95a56ac737c7763e48035e3e0754ee 100644 (file)
@@ -6,6 +6,8 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "string-list.h"
+#include "url.h"
+#include "argv-array.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -25,60 +27,6 @@ static struct rpc_service rpc_service[] = {
        { "receive-pack", "receivepack", -1 },
 };
 
-static int decode_char(const char *q)
-{
-       int i;
-       unsigned char val = 0;
-       for (i = 0; i < 2; i++) {
-               unsigned char c = *q++;
-               val <<= 4;
-               if (c >= '0' && c <= '9')
-                       val += c - '0';
-               else if (c >= 'a' && c <= 'f')
-                       val += c - 'a' + 10;
-               else if (c >= 'A' && c <= 'F')
-                       val += c - 'A' + 10;
-               else
-                       return -1;
-       }
-       return val;
-}
-
-static char *decode_parameter(const char **query, int is_name)
-{
-       const char *q = *query;
-       struct strbuf out;
-
-       strbuf_init(&out, 16);
-       do {
-               unsigned char c = *q;
-
-               if (!c)
-                       break;
-               if (c == '&' || (is_name && c == '=')) {
-                       q++;
-                       break;
-               }
-
-               if (c == '%') {
-                       int val = decode_char(q + 1);
-                       if (0 <= val) {
-                               strbuf_addch(&out, val);
-                               q += 3;
-                               continue;
-                       }
-               }
-
-               if (c == '+')
-                       strbuf_addch(&out, ' ');
-               else
-                       strbuf_addch(&out, c);
-               q++;
-       } while (1);
-       *query = q;
-       return strbuf_detach(&out, NULL);
-}
-
 static struct string_list *get_parameters(void)
 {
        if (!query_params) {
@@ -86,13 +34,13 @@ static struct string_list *get_parameters(void)
 
                query_params = xcalloc(1, sizeof(*query_params));
                while (query && *query) {
-                       char *name = decode_parameter(&query, 1);
-                       char *value = decode_parameter(&query, 0);
+                       char *name = url_decode_parameter_name(&query);
+                       char *value = url_decode_parameter_value(&query);
                        struct string_list_item *i;
 
-                       i = string_list_lookup(name, query_params);
+                       i = string_list_lookup(query_params, name);
                        if (!i)
-                               i = string_list_insert(name, query_params);
+                               i = string_list_insert(query_params, name);
                        else
                                free(i->util);
                        i->util = value;
@@ -104,7 +52,7 @@ static struct string_list *get_parameters(void)
 static const char *get_parameter(const char *name)
 {
        struct string_list_item *i;
-       i = string_list_lookup(name, get_parameters());
+       i = string_list_lookup(get_parameters(), name);
        return i ? i->util : NULL;
 }
 
@@ -122,7 +70,7 @@ static void format_write(int fd, const char *fmt, ...)
        if (n >= sizeof(buffer))
                die("protocol error: impossibly long line");
 
-       safe_write(fd, buffer, n);
+       write_or_die(fd, buffer, n);
 }
 
 static void http_status(unsigned code, const char *msg)
@@ -163,7 +111,7 @@ static void hdr_cache_forever(void)
 
 static void end_headers(void)
 {
-       safe_write(1, "\r\n", 2);
+       write_or_die(1, "\r\n", 2);
 }
 
 __attribute__((format (printf, 1, 2)))
@@ -209,7 +157,7 @@ static void send_strbuf(const char *type, struct strbuf *buf)
        hdr_int(content_length, buf->len);
        hdr_str(content_type, type);
        end_headers();
-       safe_write(1, buf->buf, buf->len);
+       write_or_die(1, buf->buf, buf->len);
 }
 
 static void send_local_file(const char *the_type, const char *name)
@@ -237,7 +185,7 @@ static void send_local_file(const char *the_type, const char *name)
                        die_errno("Cannot read '%s'", p);
                if (!n)
                        break;
-               safe_write(1, buf, n);
+               write_or_die(1, buf, n);
        }
        close(fd);
        free(buf);
@@ -273,17 +221,19 @@ static void get_idx_file(char *name)
 
 static int http_config(const char *var, const char *value, void *cb)
 {
+       const char *p;
+
        if (!strcmp(var, "http.getanyfile")) {
                getanyfile = git_config_bool(var, value);
                return 0;
        }
 
-       if (!prefixcmp(var, "http.")) {
+       if (skip_prefix(var, "http.", &p)) {
                int i;
 
                for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
                        struct rpc_service *svc = &rpc_service[i];
-                       if (!strcmp(var + 5, svc->config_name)) {
+                       if (!strcmp(p, svc->config_name)) {
                                svc->enabled = git_config_bool(var, value);
                                return 0;
                        }
@@ -296,15 +246,16 @@ static int http_config(const char *var, const char *value, void *cb)
 
 static struct rpc_service *select_service(const char *name)
 {
+       const char *svc_name;
        struct rpc_service *svc = NULL;
        int i;
 
-       if (prefixcmp(name, "git-"))
+       if (!skip_prefix(name, "git-", &svc_name))
                forbidden("Unsupported service: '%s'", name);
 
        for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
                struct rpc_service *s = &rpc_service[i];
-               if (!strcmp(s->name, name + 4)) {
+               if (!strcmp(s->name, svc_name)) {
                        svc = s;
                        break;
                }
@@ -324,16 +275,13 @@ static struct rpc_service *select_service(const char *name)
 
 static void inflate_request(const char *prog_name, int out)
 {
-       z_stream stream;
+       git_zstream stream;
        unsigned char in_buf[8192];
        unsigned char out_buf[8192];
        unsigned long cnt = 0;
-       int ret;
 
        memset(&stream, 0, sizeof(stream));
-       ret = inflateInit2(&stream, (15 + 16));
-       if (ret != Z_OK)
-               die("cannot start zlib inflater, zlib err %d", ret);
+       git_inflate_init_gzip_only(&stream);
 
        while (1) {
                ssize_t n = xread(0, in_buf, sizeof(in_buf));
@@ -349,7 +297,7 @@ static void inflate_request(const char *prog_name, int out)
                        stream.next_out = out_buf;
                        stream.avail_out = sizeof(out_buf);
 
-                       ret = inflate(&stream, Z_NO_FLUSH);
+                       ret = git_inflate(&stream, Z_NO_FLUSH);
                        if (ret != Z_OK && ret != Z_STREAM_END)
                                die("zlib error inflating request, result %d", ret);
 
@@ -364,7 +312,7 @@ static void inflate_request(const char *prog_name, int out)
        }
 
 done:
-       inflateEnd(&stream);
+       git_inflate_end(&stream);
        close(out);
 }
 
@@ -373,8 +321,7 @@ static void run_service(const char **argv)
        const char *encoding = getenv("HTTP_CONTENT_ENCODING");
        const char *user = getenv("REMOTE_USER");
        const char *host = getenv("REMOTE_ADDR");
-       char *env[3];
-       struct strbuf buf = STRBUF_INIT;
+       struct argv_array env = ARGV_ARRAY_INIT;
        int gzipped_request = 0;
        struct child_process cld;
 
@@ -388,17 +335,15 @@ static void run_service(const char **argv)
        if (!host || !*host)
                host = "(none)";
 
-       memset(&env, 0, sizeof(env));
-       strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
-       env[0] = strbuf_detach(&buf, NULL);
-
-       strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
-       env[1] = strbuf_detach(&buf, NULL);
-       env[2] = NULL;
+       if (!getenv("GIT_COMMITTER_NAME"))
+               argv_array_pushf(&env, "GIT_COMMITTER_NAME=%s", user);
+       if (!getenv("GIT_COMMITTER_EMAIL"))
+               argv_array_pushf(&env, "GIT_COMMITTER_EMAIL=%s@http.%s",
+                                user, host);
 
        memset(&cld, 0, sizeof(cld));
        cld.argv = argv;
-       cld.env = (const char *const *)env;
+       cld.env = env.argv;
        if (gzipped_request)
                cld.in = -1;
        cld.git_cmd = 1;
@@ -413,25 +358,25 @@ static void run_service(const char **argv)
 
        if (finish_command(&cld))
                exit(1);
-       free(env[0]);
-       free(env[1]);
-       strbuf_release(&buf);
+       argv_array_clear(&env);
 }
 
 static int show_text_ref(const char *name, const unsigned char *sha1,
        int flag, void *cb_data)
 {
+       const char *name_nons = strip_namespace(name);
        struct strbuf *buf = cb_data;
        struct object *o = parse_object(sha1);
        if (!o)
                return 0;
 
-       strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name);
+       strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name_nons);
        if (o->type == OBJ_TAG) {
                o = deref_tag(o, name, 0);
                if (!o)
                        return 0;
-               strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name);
+               strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1),
+                           name_nons);
        }
        return 0;
 }
@@ -462,12 +407,40 @@ static void get_info_refs(char *arg)
 
        } else {
                select_getanyfile();
-               for_each_ref(show_text_ref, &buf);
+               for_each_namespaced_ref(show_text_ref, &buf);
                send_strbuf("text/plain", &buf);
        }
        strbuf_release(&buf);
 }
 
+static int show_head_ref(const char *refname, const unsigned char *sha1,
+       int flag, void *cb_data)
+{
+       struct strbuf *buf = cb_data;
+
+       if (flag & REF_ISSYMREF) {
+               unsigned char unused[20];
+               const char *target = resolve_ref_unsafe(refname, unused, 1, NULL);
+               const char *target_nons = strip_namespace(target);
+
+               strbuf_addf(buf, "ref: %s\n", target_nons);
+       } else {
+               strbuf_addf(buf, "%s\n", sha1_to_hex(sha1));
+       }
+
+       return 0;
+}
+
+static void get_head(char *arg)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       select_getanyfile();
+       head_ref_namespaced(show_head_ref, &buf);
+       send_strbuf("text/plain", &buf);
+       strbuf_release(&buf);
+}
+
 static void get_info_packs(char *arg)
 {
        size_t objdirlen = strlen(get_object_directory());
@@ -538,15 +511,17 @@ static void service_rpc(char *service_name)
 
 static NORETURN void die_webcgi(const char *err, va_list params)
 {
-       char buffer[1000];
+       static int dead;
 
-       http_status(500, "Internal Server Error");
-       hdr_nocache();
-       end_headers();
+       if (!dead) {
+               dead = 1;
+               http_status(500, "Internal Server Error");
+               hdr_nocache();
+               end_headers();
 
-       vsnprintf(buffer, sizeof(buffer), err, params);
-       fprintf(stderr, "fatal: %s\n", buffer);
-       exit(0);
+               vreportf("fatal: ", err, params);
+       }
+       exit(0); /* we successfully reported a failure ;-) */
 }
 
 static char* getdir(void)
@@ -561,9 +536,7 @@ static char* getdir(void)
                        die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
                if (daemon_avoid_alias(pathinfo))
                        die("'%s': aliased", pathinfo);
-               strbuf_addstr(&buf, root);
-               if (buf.buf[buf.len - 1] != '/')
-                       strbuf_addch(&buf, '/');
+               end_url_with_slash(&buf, root);
                if (pathinfo[0] == '/')
                        pathinfo++;
                strbuf_addstr(&buf, pathinfo);
@@ -580,7 +553,7 @@ static struct service_cmd {
        const char *pattern;
        void (*imp)(char *);
 } services[] = {
-       {"GET", "/HEAD$", get_text_file},
+       {"GET", "/HEAD$", get_head},
        {"GET", "/info/refs$", get_info_refs},
        {"GET", "/objects/info/alternates$", get_text_file},
        {"GET", "/objects/info/http-alternates$", get_text_file},
@@ -601,6 +574,8 @@ int main(int argc, char **argv)
        char *cmd_arg = NULL;
        int i;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
        set_die_routine(die_webcgi);
 
@@ -622,9 +597,11 @@ int main(int argc, char **argv)
 
                        if (strcmp(method, c->method)) {
                                const char *proto = getenv("SERVER_PROTOCOL");
-                               if (proto && !strcmp(proto, "HTTP/1.1"))
+                               if (proto && !strcmp(proto, "HTTP/1.1")) {
                                        http_status(405, "Method Not Allowed");
-                               else
+                                       hdr_str("Allow", !strcmp(c->method, "GET") ?
+                                               "GET, HEAD" : c->method);
+                               } else
                                        http_status(400, "Bad Request");
                                hdr_nocache();
                                end_headers();
@@ -633,9 +610,7 @@ int main(int argc, char **argv)
 
                        cmd = c;
                        n = out[0].rm_eo - out[0].rm_so;
-                       cmd_arg = xmalloc(n);
-                       memcpy(cmd_arg, dir + out[0].rm_so + 1, n-1);
-                       cmd_arg[n-1] = '\0';
+                       cmd_arg = xmemdupz(dir + out[0].rm_so + 1, n - 1);
                        dir[out[0].rm_so] = 0;
                        break;
                }