#include "builtin.h" #include "transport.h" #include "run-command.h" #include "pkt-line.h" static const char usage_msg[] = "git remote-ext <remote> <url>"; /* * URL syntax: * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. * Special characters: * '% ': Literal space in argument. * '%%': Literal percent sign. * '%S': Name of service (git-upload-pack/git-upload-archive/ * git-receive-pack. * '%s': Same as \s, but with possible git- prefix stripped. * '%G': Only allowed as first 'character' of argument. Do not pass this * Argument to command, instead send this as name of repository * in in-line git://-style request (also activates sending this * style of request). * '%V': Only allowed as first 'character' of argument. Used in * conjunction with '%G': Do not pass this argument to command, * instead send this as vhost in git://-style request (note: does * not activate sending git:// style request). */ static char *git_req; static char *git_req_vhost; static char *strip_escapes(const char *str, const char *service, const char **next) { size_t rpos = 0; int escape = 0; char special = 0; const char *service_noprefix = service; struct strbuf ret = STRBUF_INIT; skip_prefix(service_noprefix, "git-", &service_noprefix); /* Pass the service to command. */ setenv("GIT_EXT_SERVICE", service, 1); setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1); /* Scan the length of argument. */ while (str[rpos] && (escape || str[rpos] != ' ')) { if (escape) { switch (str[rpos]) { case ' ': case '%': case 's': case 'S': break; case 'G': case 'V': special = str[rpos]; if (rpos == 1) break; /* fallthrough */ default: die("Bad remote-ext placeholder '%%%c'.", str[rpos]); } escape = 0; } else escape = (str[rpos] == '%'); rpos++; } if (escape && !str[rpos]) die("remote-ext command has incomplete placeholder"); *next = str + rpos; if (**next == ' ') ++*next; /* Skip over space */ /* * Do the actual placeholder substitution. The string will be short * enough not to overflow integers. */ rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */ escape = 0; while (str[rpos] && (escape || str[rpos] != ' ')) { if (escape) { switch (str[rpos]) { case ' ': case '%': strbuf_addch(&ret, str[rpos]); break; case 's': strbuf_addstr(&ret, service_noprefix); break; case 'S': strbuf_addstr(&ret, service); break; } escape = 0; } else switch (str[rpos]) { case '%': escape = 1; break; default: strbuf_addch(&ret, str[rpos]); break; } rpos++; } switch (special) { case 'G': git_req = strbuf_detach(&ret, NULL); return NULL; case 'V': git_req_vhost = strbuf_detach(&ret, NULL); return NULL; default: return strbuf_detach(&ret, NULL); } } static void parse_argv(struct strvec *out, const char *arg, const char *service) { while (*arg) { char *expanded = strip_escapes(arg, service, &arg); if (expanded) strvec_push(out, expanded); free(expanded); } } static void send_git_request(int stdin_fd, const char *serv, const char *repo, const char *vhost) { if (!vhost) packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0); else packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, vhost, 0); } static int run_child(const char *arg, const char *service) { int r; struct child_process child = CHILD_PROCESS_INIT; child.in = -1; child.out = -1; child.err = 0; parse_argv(&child.args, arg, service); if (start_command(&child) < 0) die("Can't run specified command"); if (git_req) send_git_request(child.in, service, git_req, git_req_vhost); r = bidirectional_transfer_loop(child.out, child.in); if (!r) r = finish_command(&child); else finish_command(&child); return r; } #define MAXCOMMAND 4096 static int command_loop(const char *child) { char buffer[MAXCOMMAND]; while (1) { size_t i; if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { if (ferror(stdin)) die("Command input error"); exit(0); } /* Strip end of line characters. */ i = strlen(buffer); while (i > 0 && isspace(buffer[i - 1])) buffer[--i] = 0; if (!strcmp(buffer, "capabilities")) { printf("*connect\n\n"); fflush(stdout); } else if (!strncmp(buffer, "connect ", 8)) { printf("\n"); fflush(stdout); return run_child(child, buffer + 8); } else { fprintf(stderr, "Bad command"); return 1; } } } int cmd_remote_ext(int argc, const char **argv, const char *prefix) { if (argc != 3) usage(usage_msg); return command_loop(argv[2]); }