http-backend.con commit Fix 'git var' usage synopsis (9fabb6d)
   1#include "cache.h"
   2#include "refs.h"
   3#include "pkt-line.h"
   4#include "object.h"
   5#include "tag.h"
   6#include "exec_cmd.h"
   7#include "run-command.h"
   8#include "string-list.h"
   9
  10static const char content_type[] = "Content-Type";
  11static const char content_length[] = "Content-Length";
  12static const char last_modified[] = "Last-Modified";
  13static int getanyfile = 1;
  14
  15static struct string_list *query_params;
  16
  17struct rpc_service {
  18        const char *name;
  19        const char *config_name;
  20        signed enabled : 2;
  21};
  22
  23static struct rpc_service rpc_service[] = {
  24        { "upload-pack", "uploadpack", 1 },
  25        { "receive-pack", "receivepack", -1 },
  26};
  27
  28static int decode_char(const char *q)
  29{
  30        int i;
  31        unsigned char val = 0;
  32        for (i = 0; i < 2; i++) {
  33                unsigned char c = *q++;
  34                val <<= 4;
  35                if (c >= '0' && c <= '9')
  36                        val += c - '0';
  37                else if (c >= 'a' && c <= 'f')
  38                        val += c - 'a' + 10;
  39                else if (c >= 'A' && c <= 'F')
  40                        val += c - 'A' + 10;
  41                else
  42                        return -1;
  43        }
  44        return val;
  45}
  46
  47static char *decode_parameter(const char **query, int is_name)
  48{
  49        const char *q = *query;
  50        struct strbuf out;
  51
  52        strbuf_init(&out, 16);
  53        do {
  54                unsigned char c = *q;
  55
  56                if (!c)
  57                        break;
  58                if (c == '&' || (is_name && c == '=')) {
  59                        q++;
  60                        break;
  61                }
  62
  63                if (c == '%') {
  64                        int val = decode_char(q + 1);
  65                        if (0 <= val) {
  66                                strbuf_addch(&out, val);
  67                                q += 3;
  68                                continue;
  69                        }
  70                }
  71
  72                if (c == '+')
  73                        strbuf_addch(&out, ' ');
  74                else
  75                        strbuf_addch(&out, c);
  76                q++;
  77        } while (1);
  78        *query = q;
  79        return strbuf_detach(&out, NULL);
  80}
  81
  82static struct string_list *get_parameters(void)
  83{
  84        if (!query_params) {
  85                const char *query = getenv("QUERY_STRING");
  86
  87                query_params = xcalloc(1, sizeof(*query_params));
  88                while (query && *query) {
  89                        char *name = decode_parameter(&query, 1);
  90                        char *value = decode_parameter(&query, 0);
  91                        struct string_list_item *i;
  92
  93                        i = string_list_lookup(name, query_params);
  94                        if (!i)
  95                                i = string_list_insert(name, query_params);
  96                        else
  97                                free(i->util);
  98                        i->util = value;
  99                }
 100        }
 101        return query_params;
 102}
 103
 104static const char *get_parameter(const char *name)
 105{
 106        struct string_list_item *i;
 107        i = string_list_lookup(name, get_parameters());
 108        return i ? i->util : NULL;
 109}
 110
 111__attribute__((format (printf, 2, 3)))
 112static void format_write(int fd, const char *fmt, ...)
 113{
 114        static char buffer[1024];
 115
 116        va_list args;
 117        unsigned n;
 118
 119        va_start(args, fmt);
 120        n = vsnprintf(buffer, sizeof(buffer), fmt, args);
 121        va_end(args);
 122        if (n >= sizeof(buffer))
 123                die("protocol error: impossibly long line");
 124
 125        safe_write(fd, buffer, n);
 126}
 127
 128static void http_status(unsigned code, const char *msg)
 129{
 130        format_write(1, "Status: %u %s\r\n", code, msg);
 131}
 132
 133static void hdr_str(const char *name, const char *value)
 134{
 135        format_write(1, "%s: %s\r\n", name, value);
 136}
 137
 138static void hdr_int(const char *name, uintmax_t value)
 139{
 140        format_write(1, "%s: %" PRIuMAX "\r\n", name, value);
 141}
 142
 143static void hdr_date(const char *name, unsigned long when)
 144{
 145        const char *value = show_date(when, 0, DATE_RFC2822);
 146        hdr_str(name, value);
 147}
 148
 149static void hdr_nocache(void)
 150{
 151        hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
 152        hdr_str("Pragma", "no-cache");
 153        hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate");
 154}
 155
 156static void hdr_cache_forever(void)
 157{
 158        unsigned long now = time(NULL);
 159        hdr_date("Date", now);
 160        hdr_date("Expires", now + 31536000);
 161        hdr_str("Cache-Control", "public, max-age=31536000");
 162}
 163
 164static void end_headers(void)
 165{
 166        safe_write(1, "\r\n", 2);
 167}
 168
 169__attribute__((format (printf, 1, 2)))
 170static NORETURN void not_found(const char *err, ...)
 171{
 172        va_list params;
 173
 174        http_status(404, "Not Found");
 175        hdr_nocache();
 176        end_headers();
 177
 178        va_start(params, err);
 179        if (err && *err)
 180                vfprintf(stderr, err, params);
 181        va_end(params);
 182        exit(0);
 183}
 184
 185__attribute__((format (printf, 1, 2)))
 186static NORETURN void forbidden(const char *err, ...)
 187{
 188        va_list params;
 189
 190        http_status(403, "Forbidden");
 191        hdr_nocache();
 192        end_headers();
 193
 194        va_start(params, err);
 195        if (err && *err)
 196                vfprintf(stderr, err, params);
 197        va_end(params);
 198        exit(0);
 199}
 200
 201static void select_getanyfile(void)
 202{
 203        if (!getanyfile)
 204                forbidden("Unsupported service: getanyfile");
 205}
 206
 207static void send_strbuf(const char *type, struct strbuf *buf)
 208{
 209        hdr_int(content_length, buf->len);
 210        hdr_str(content_type, type);
 211        end_headers();
 212        safe_write(1, buf->buf, buf->len);
 213}
 214
 215static void send_local_file(const char *the_type, const char *name)
 216{
 217        const char *p = git_path("%s", name);
 218        size_t buf_alloc = 8192;
 219        char *buf = xmalloc(buf_alloc);
 220        int fd;
 221        struct stat sb;
 222
 223        fd = open(p, O_RDONLY);
 224        if (fd < 0)
 225                not_found("Cannot open '%s': %s", p, strerror(errno));
 226        if (fstat(fd, &sb) < 0)
 227                die_errno("Cannot stat '%s'", p);
 228
 229        hdr_int(content_length, sb.st_size);
 230        hdr_str(content_type, the_type);
 231        hdr_date(last_modified, sb.st_mtime);
 232        end_headers();
 233
 234        for (;;) {
 235                ssize_t n = xread(fd, buf, buf_alloc);
 236                if (n < 0)
 237                        die_errno("Cannot read '%s'", p);
 238                if (!n)
 239                        break;
 240                safe_write(1, buf, n);
 241        }
 242        close(fd);
 243        free(buf);
 244}
 245
 246static void get_text_file(char *name)
 247{
 248        select_getanyfile();
 249        hdr_nocache();
 250        send_local_file("text/plain", name);
 251}
 252
 253static void get_loose_object(char *name)
 254{
 255        select_getanyfile();
 256        hdr_cache_forever();
 257        send_local_file("application/x-git-loose-object", name);
 258}
 259
 260static void get_pack_file(char *name)
 261{
 262        select_getanyfile();
 263        hdr_cache_forever();
 264        send_local_file("application/x-git-packed-objects", name);
 265}
 266
 267static void get_idx_file(char *name)
 268{
 269        select_getanyfile();
 270        hdr_cache_forever();
 271        send_local_file("application/x-git-packed-objects-toc", name);
 272}
 273
 274static int http_config(const char *var, const char *value, void *cb)
 275{
 276        if (!strcmp(var, "http.getanyfile")) {
 277                getanyfile = git_config_bool(var, value);
 278                return 0;
 279        }
 280
 281        if (!prefixcmp(var, "http.")) {
 282                int i;
 283
 284                for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
 285                        struct rpc_service *svc = &rpc_service[i];
 286                        if (!strcmp(var + 5, svc->config_name)) {
 287                                svc->enabled = git_config_bool(var, value);
 288                                return 0;
 289                        }
 290                }
 291        }
 292
 293        /* we are not interested in parsing any other configuration here */
 294        return 0;
 295}
 296
 297static struct rpc_service *select_service(const char *name)
 298{
 299        struct rpc_service *svc = NULL;
 300        int i;
 301
 302        if (prefixcmp(name, "git-"))
 303                forbidden("Unsupported service: '%s'", name);
 304
 305        for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
 306                struct rpc_service *s = &rpc_service[i];
 307                if (!strcmp(s->name, name + 4)) {
 308                        svc = s;
 309                        break;
 310                }
 311        }
 312
 313        if (!svc)
 314                forbidden("Unsupported service: '%s'", name);
 315
 316        if (svc->enabled < 0) {
 317                const char *user = getenv("REMOTE_USER");
 318                svc->enabled = (user && *user) ? 1 : 0;
 319        }
 320        if (!svc->enabled)
 321                forbidden("Service not enabled: '%s'", svc->name);
 322        return svc;
 323}
 324
 325static void inflate_request(const char *prog_name, int out)
 326{
 327        z_stream stream;
 328        unsigned char in_buf[8192];
 329        unsigned char out_buf[8192];
 330        unsigned long cnt = 0;
 331        int ret;
 332
 333        memset(&stream, 0, sizeof(stream));
 334        ret = inflateInit2(&stream, (15 + 16));
 335        if (ret != Z_OK)
 336                die("cannot start zlib inflater, zlib err %d", ret);
 337
 338        while (1) {
 339                ssize_t n = xread(0, in_buf, sizeof(in_buf));
 340                if (n <= 0)
 341                        die("request ended in the middle of the gzip stream");
 342
 343                stream.next_in = in_buf;
 344                stream.avail_in = n;
 345
 346                while (0 < stream.avail_in) {
 347                        int ret;
 348
 349                        stream.next_out = out_buf;
 350                        stream.avail_out = sizeof(out_buf);
 351
 352                        ret = inflate(&stream, Z_NO_FLUSH);
 353                        if (ret != Z_OK && ret != Z_STREAM_END)
 354                                die("zlib error inflating request, result %d", ret);
 355
 356                        n = stream.total_out - cnt;
 357                        if (write_in_full(out, out_buf, n) != n)
 358                                die("%s aborted reading request", prog_name);
 359                        cnt += n;
 360
 361                        if (ret == Z_STREAM_END)
 362                                goto done;
 363                }
 364        }
 365
 366done:
 367        inflateEnd(&stream);
 368        close(out);
 369}
 370
 371static void run_service(const char **argv)
 372{
 373        const char *encoding = getenv("HTTP_CONTENT_ENCODING");
 374        const char *user = getenv("REMOTE_USER");
 375        const char *host = getenv("REMOTE_ADDR");
 376        char *env[3];
 377        struct strbuf buf = STRBUF_INIT;
 378        int gzipped_request = 0;
 379        struct child_process cld;
 380
 381        if (encoding && !strcmp(encoding, "gzip"))
 382                gzipped_request = 1;
 383        else if (encoding && !strcmp(encoding, "x-gzip"))
 384                gzipped_request = 1;
 385
 386        if (!user || !*user)
 387                user = "anonymous";
 388        if (!host || !*host)
 389                host = "(none)";
 390
 391        memset(&env, 0, sizeof(env));
 392        strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
 393        env[0] = strbuf_detach(&buf, NULL);
 394
 395        strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
 396        env[1] = strbuf_detach(&buf, NULL);
 397        env[2] = NULL;
 398
 399        memset(&cld, 0, sizeof(cld));
 400        cld.argv = argv;
 401        cld.env = (const char *const *)env;
 402        if (gzipped_request)
 403                cld.in = -1;
 404        cld.git_cmd = 1;
 405        if (start_command(&cld))
 406                exit(1);
 407
 408        close(1);
 409        if (gzipped_request)
 410                inflate_request(argv[0], cld.in);
 411        else
 412                close(0);
 413
 414        if (finish_command(&cld))
 415                exit(1);
 416        free(env[0]);
 417        free(env[1]);
 418        strbuf_release(&buf);
 419}
 420
 421static int show_text_ref(const char *name, const unsigned char *sha1,
 422        int flag, void *cb_data)
 423{
 424        struct strbuf *buf = cb_data;
 425        struct object *o = parse_object(sha1);
 426        if (!o)
 427                return 0;
 428
 429        strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name);
 430        if (o->type == OBJ_TAG) {
 431                o = deref_tag(o, name, 0);
 432                if (!o)
 433                        return 0;
 434                strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name);
 435        }
 436        return 0;
 437}
 438
 439static void get_info_refs(char *arg)
 440{
 441        const char *service_name = get_parameter("service");
 442        struct strbuf buf = STRBUF_INIT;
 443
 444        hdr_nocache();
 445
 446        if (service_name) {
 447                const char *argv[] = {NULL /* service name */,
 448                        "--stateless-rpc", "--advertise-refs",
 449                        ".", NULL};
 450                struct rpc_service *svc = select_service(service_name);
 451
 452                strbuf_addf(&buf, "application/x-git-%s-advertisement",
 453                        svc->name);
 454                hdr_str(content_type, buf.buf);
 455                end_headers();
 456
 457                packet_write(1, "# service=git-%s\n", svc->name);
 458                packet_flush(1);
 459
 460                argv[0] = svc->name;
 461                run_service(argv);
 462
 463        } else {
 464                select_getanyfile();
 465                for_each_ref(show_text_ref, &buf);
 466                send_strbuf("text/plain", &buf);
 467        }
 468        strbuf_release(&buf);
 469}
 470
 471static void get_info_packs(char *arg)
 472{
 473        size_t objdirlen = strlen(get_object_directory());
 474        struct strbuf buf = STRBUF_INIT;
 475        struct packed_git *p;
 476        size_t cnt = 0;
 477
 478        select_getanyfile();
 479        prepare_packed_git();
 480        for (p = packed_git; p; p = p->next) {
 481                if (p->pack_local)
 482                        cnt++;
 483        }
 484
 485        strbuf_grow(&buf, cnt * 53 + 2);
 486        for (p = packed_git; p; p = p->next) {
 487                if (p->pack_local)
 488                        strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
 489        }
 490        strbuf_addch(&buf, '\n');
 491
 492        hdr_nocache();
 493        send_strbuf("text/plain; charset=utf-8", &buf);
 494        strbuf_release(&buf);
 495}
 496
 497static void check_content_type(const char *accepted_type)
 498{
 499        const char *actual_type = getenv("CONTENT_TYPE");
 500
 501        if (!actual_type)
 502                actual_type = "";
 503
 504        if (strcmp(actual_type, accepted_type)) {
 505                http_status(415, "Unsupported Media Type");
 506                hdr_nocache();
 507                end_headers();
 508                format_write(1,
 509                        "Expected POST with Content-Type '%s',"
 510                        " but received '%s' instead.\n",
 511                        accepted_type, actual_type);
 512                exit(0);
 513        }
 514}
 515
 516static void service_rpc(char *service_name)
 517{
 518        const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
 519        struct rpc_service *svc = select_service(service_name);
 520        struct strbuf buf = STRBUF_INIT;
 521
 522        strbuf_reset(&buf);
 523        strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
 524        check_content_type(buf.buf);
 525
 526        hdr_nocache();
 527
 528        strbuf_reset(&buf);
 529        strbuf_addf(&buf, "application/x-git-%s-result", svc->name);
 530        hdr_str(content_type, buf.buf);
 531
 532        end_headers();
 533
 534        argv[0] = svc->name;
 535        run_service(argv);
 536        strbuf_release(&buf);
 537}
 538
 539static NORETURN void die_webcgi(const char *err, va_list params)
 540{
 541        char buffer[1000];
 542
 543        http_status(500, "Internal Server Error");
 544        hdr_nocache();
 545        end_headers();
 546
 547        vsnprintf(buffer, sizeof(buffer), err, params);
 548        fprintf(stderr, "fatal: %s\n", buffer);
 549        exit(0);
 550}
 551
 552static char* getdir(void)
 553{
 554        struct strbuf buf = STRBUF_INIT;
 555        char *pathinfo = getenv("PATH_INFO");
 556        char *root = getenv("GIT_PROJECT_ROOT");
 557        char *path = getenv("PATH_TRANSLATED");
 558
 559        if (root && *root) {
 560                if (!pathinfo || !*pathinfo)
 561                        die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
 562                if (daemon_avoid_alias(pathinfo))
 563                        die("'%s': aliased", pathinfo);
 564                strbuf_addstr(&buf, root);
 565                if (buf.buf[buf.len - 1] != '/')
 566                        strbuf_addch(&buf, '/');
 567                if (pathinfo[0] == '/')
 568                        pathinfo++;
 569                strbuf_addstr(&buf, pathinfo);
 570                return strbuf_detach(&buf, NULL);
 571        } else if (path && *path) {
 572                return xstrdup(path);
 573        } else
 574                die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server");
 575        return NULL;
 576}
 577
 578static struct service_cmd {
 579        const char *method;
 580        const char *pattern;
 581        void (*imp)(char *);
 582} services[] = {
 583        {"GET", "/HEAD$", get_text_file},
 584        {"GET", "/info/refs$", get_info_refs},
 585        {"GET", "/objects/info/alternates$", get_text_file},
 586        {"GET", "/objects/info/http-alternates$", get_text_file},
 587        {"GET", "/objects/info/packs$", get_info_packs},
 588        {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
 589        {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file},
 590        {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file},
 591
 592        {"POST", "/git-upload-pack$", service_rpc},
 593        {"POST", "/git-receive-pack$", service_rpc}
 594};
 595
 596int main(int argc, char **argv)
 597{
 598        char *method = getenv("REQUEST_METHOD");
 599        char *dir;
 600        struct service_cmd *cmd = NULL;
 601        char *cmd_arg = NULL;
 602        int i;
 603
 604        git_extract_argv0_path(argv[0]);
 605        set_die_routine(die_webcgi);
 606
 607        if (!method)
 608                die("No REQUEST_METHOD from server");
 609        if (!strcmp(method, "HEAD"))
 610                method = "GET";
 611        dir = getdir();
 612
 613        for (i = 0; i < ARRAY_SIZE(services); i++) {
 614                struct service_cmd *c = &services[i];
 615                regex_t re;
 616                regmatch_t out[1];
 617
 618                if (regcomp(&re, c->pattern, REG_EXTENDED))
 619                        die("Bogus regex in service table: %s", c->pattern);
 620                if (!regexec(&re, dir, 1, out, 0)) {
 621                        size_t n;
 622
 623                        if (strcmp(method, c->method)) {
 624                                const char *proto = getenv("SERVER_PROTOCOL");
 625                                if (proto && !strcmp(proto, "HTTP/1.1"))
 626                                        http_status(405, "Method Not Allowed");
 627                                else
 628                                        http_status(400, "Bad Request");
 629                                hdr_nocache();
 630                                end_headers();
 631                                return 0;
 632                        }
 633
 634                        cmd = c;
 635                        n = out[0].rm_eo - out[0].rm_so;
 636                        cmd_arg = xmalloc(n);
 637                        memcpy(cmd_arg, dir + out[0].rm_so + 1, n-1);
 638                        cmd_arg[n-1] = '\0';
 639                        dir[out[0].rm_so] = 0;
 640                        break;
 641                }
 642                regfree(&re);
 643        }
 644
 645        if (!cmd)
 646                not_found("Request not supported: '%s'", dir);
 647
 648        setup_path();
 649        if (!enter_repo(dir, 0))
 650                not_found("Not a git repository: '%s'", dir);
 651        if (!getenv("GIT_HTTP_EXPORT_ALL") &&
 652            access("git-daemon-export-ok", F_OK) )
 653                not_found("Repository not exported: '%s'", dir);
 654
 655        git_config(http_config, NULL);
 656        cmd->imp(cmd_arg);
 657        return 0;
 658}