builtin / remote-ext.con commit merge-file: clamp exit code to maximum 127 (e34f802)
   1#include "builtin.h"
   2#include "transport.h"
   3#include "run-command.h"
   4
   5/*
   6 * URL syntax:
   7 *      'command [arg1 [arg2 [...]]]'   Invoke command with given arguments.
   8 *      Special characters:
   9 *      '% ': Literal space in argument.
  10 *      '%%': Literal percent sign.
  11 *      '%S': Name of service (git-upload-pack/git-upload-archive/
  12 *              git-receive-pack.
  13 *      '%s': Same as \s, but with possible git- prefix stripped.
  14 *      '%G': Only allowed as first 'character' of argument. Do not pass this
  15 *              Argument to command, instead send this as name of repository
  16 *              in in-line git://-style request (also activates sending this
  17 *              style of request).
  18 *      '%V': Only allowed as first 'character' of argument. Used in
  19 *              conjunction with '%G': Do not pass this argument to command,
  20 *              instead send this as vhost in git://-style request (note: does
  21 *              not activate sending git:// style request).
  22 */
  23
  24static char *git_req;
  25static char *git_req_vhost;
  26
  27static char *strip_escapes(const char *str, const char *service,
  28        const char **next)
  29{
  30        size_t rpos = 0;
  31        int escape = 0;
  32        char special = 0;
  33        size_t psoff = 0;
  34        struct strbuf ret = STRBUF_INIT;
  35
  36        /* Calculate prefix length for \s and lengths for \s and \S */
  37        if (!strncmp(service, "git-", 4))
  38                psoff = 4;
  39
  40        /* Pass the service to command. */
  41        setenv("GIT_EXT_SERVICE", service, 1);
  42        setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
  43
  44        /* Scan the length of argument. */
  45        while (str[rpos] && (escape || str[rpos] != ' ')) {
  46                if (escape) {
  47                        switch (str[rpos]) {
  48                        case ' ':
  49                        case '%':
  50                        case 's':
  51                        case 'S':
  52                                break;
  53                        case 'G':
  54                        case 'V':
  55                                special = str[rpos];
  56                                if (rpos == 1)
  57                                        break;
  58                                /* Fall-through to error. */
  59                        default:
  60                                die("Bad remote-ext placeholder '%%%c'.",
  61                                        str[rpos]);
  62                        }
  63                        escape = 0;
  64                } else
  65                        escape = (str[rpos] == '%');
  66                rpos++;
  67        }
  68        if (escape && !str[rpos])
  69                die("remote-ext command has incomplete placeholder");
  70        *next = str + rpos;
  71        if (**next == ' ')
  72                ++*next;        /* Skip over space */
  73
  74        /*
  75         * Do the actual placeholder substitution. The string will be short
  76         * enough not to overflow integers.
  77         */
  78        rpos = special ? 2 : 0;         /* Skip first 2 bytes in specials. */
  79        escape = 0;
  80        while (str[rpos] && (escape || str[rpos] != ' ')) {
  81                if (escape) {
  82                        switch (str[rpos]) {
  83                        case ' ':
  84                        case '%':
  85                                strbuf_addch(&ret, str[rpos]);
  86                                break;
  87                        case 's':
  88                                strbuf_addstr(&ret, service + psoff);
  89                                break;
  90                        case 'S':
  91                                strbuf_addstr(&ret, service);
  92                                break;
  93                        }
  94                        escape = 0;
  95                } else
  96                        switch (str[rpos]) {
  97                        case '%':
  98                                escape = 1;
  99                                break;
 100                        default:
 101                                strbuf_addch(&ret, str[rpos]);
 102                                break;
 103                        }
 104                rpos++;
 105        }
 106        switch (special) {
 107        case 'G':
 108                git_req = strbuf_detach(&ret, NULL);
 109                return NULL;
 110        case 'V':
 111                git_req_vhost = strbuf_detach(&ret, NULL);
 112                return NULL;
 113        default:
 114                return strbuf_detach(&ret, NULL);
 115        }
 116}
 117
 118/* Should be enough... */
 119#define MAXARGUMENTS 256
 120
 121static const char **parse_argv(const char *arg, const char *service)
 122{
 123        int arguments = 0;
 124        int i;
 125        const char **ret;
 126        char *temparray[MAXARGUMENTS + 1];
 127
 128        while (*arg) {
 129                char *expanded;
 130                if (arguments == MAXARGUMENTS)
 131                        die("remote-ext command has too many arguments");
 132                expanded = strip_escapes(arg, service, &arg);
 133                if (expanded)
 134                        temparray[arguments++] = expanded;
 135        }
 136
 137        ret = xmalloc((arguments + 1) * sizeof(char *));
 138        for (i = 0; i < arguments; i++)
 139                ret[i] = temparray[i];
 140        ret[arguments] = NULL;
 141        return ret;
 142}
 143
 144static void send_git_request(int stdin_fd, const char *serv, const char *repo,
 145        const char *vhost)
 146{
 147        size_t bufferspace;
 148        size_t wpos = 0;
 149        char *buffer;
 150
 151        /*
 152         * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
 153         * 6 bytes extra (xxxx \0) if there is no vhost.
 154         */
 155        if (vhost)
 156                bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
 157        else
 158                bufferspace = strlen(serv) + strlen(repo) + 6;
 159
 160        if (bufferspace > 0xFFFF)
 161                die("Request too large to send");
 162        buffer = xmalloc(bufferspace);
 163
 164        /* Make the packet. */
 165        wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
 166                serv, repo, 0);
 167
 168        /* Add vhost if any. */
 169        if (vhost)
 170                sprintf(buffer + wpos, "host=%s%c", vhost, 0);
 171
 172        /* Send the request */
 173        if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
 174                die_errno("Failed to send request");
 175
 176        free(buffer);
 177}
 178
 179static int run_child(const char *arg, const char *service)
 180{
 181        int r;
 182        struct child_process child;
 183
 184        memset(&child, 0, sizeof(child));
 185        child.in = -1;
 186        child.out = -1;
 187        child.err = 0;
 188        child.argv = parse_argv(arg, service);
 189
 190        if (start_command(&child) < 0)
 191                die("Can't run specified command");
 192
 193        if (git_req)
 194                send_git_request(child.in, service, git_req, git_req_vhost);
 195
 196        r = bidirectional_transfer_loop(child.out, child.in);
 197        if (!r)
 198                r = finish_command(&child);
 199        else
 200                finish_command(&child);
 201        return r;
 202}
 203
 204#define MAXCOMMAND 4096
 205
 206static int command_loop(const char *child)
 207{
 208        char buffer[MAXCOMMAND];
 209
 210        while (1) {
 211                size_t i;
 212                if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
 213                        if (ferror(stdin))
 214                                die("Comammand input error");
 215                        exit(0);
 216                }
 217                /* Strip end of line characters. */
 218                i = strlen(buffer);
 219                while (i > 0 && isspace(buffer[i - 1]))
 220                        buffer[--i] = 0;
 221
 222                if (!strcmp(buffer, "capabilities")) {
 223                        printf("*connect\n\n");
 224                        fflush(stdout);
 225                } else if (!strncmp(buffer, "connect ", 8)) {
 226                        printf("\n");
 227                        fflush(stdout);
 228                        return run_child(child, buffer + 8);
 229                } else {
 230                        fprintf(stderr, "Bad command");
 231                        return 1;
 232                }
 233        }
 234}
 235
 236int cmd_remote_ext(int argc, const char **argv, const char *prefix)
 237{
 238        if (argc != 3)
 239                die("Expected two arguments");
 240
 241        return command_loop(argv[2]);
 242}