|  | #include "builtin.h" | 
|  | #include "transport.h" | 
|  | #include "run-command.h" | 
|  |  | 
|  | /* | 
|  | * 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; | 
|  | size_t psoff = 0; | 
|  | struct strbuf ret = STRBUF_INIT; | 
|  |  | 
|  | /* Calculate prefix length for \s and lengths for \s and \S */ | 
|  | if (!strncmp(service, "git-", 4)) | 
|  | psoff = 4; | 
|  |  | 
|  | /* Pass the service to command. */ | 
|  | setenv("GIT_EXT_SERVICE", service, 1); | 
|  | setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 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; | 
|  | /* Fall-through to error. */ | 
|  | 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 + psoff); | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Should be enough... */ | 
|  | #define MAXARGUMENTS 256 | 
|  |  | 
|  | static const char **parse_argv(const char *arg, const char *service) | 
|  | { | 
|  | int arguments = 0; | 
|  | int i; | 
|  | const char **ret; | 
|  | char *temparray[MAXARGUMENTS + 1]; | 
|  |  | 
|  | while (*arg) { | 
|  | char *expanded; | 
|  | if (arguments == MAXARGUMENTS) | 
|  | die("remote-ext command has too many arguments"); | 
|  | expanded = strip_escapes(arg, service, &arg); | 
|  | if (expanded) | 
|  | temparray[arguments++] = expanded; | 
|  | } | 
|  |  | 
|  | ret = xmalloc((arguments + 1) * sizeof(char *)); | 
|  | for (i = 0; i < arguments; i++) | 
|  | ret[i] = temparray[i]; | 
|  | ret[arguments] = NULL; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void send_git_request(int stdin_fd, const char *serv, const char *repo, | 
|  | const char *vhost) | 
|  | { | 
|  | size_t bufferspace; | 
|  | size_t wpos = 0; | 
|  | char *buffer; | 
|  |  | 
|  | /* | 
|  | * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and | 
|  | * 6 bytes extra (xxxx \0) if there is no vhost. | 
|  | */ | 
|  | if (vhost) | 
|  | bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; | 
|  | else | 
|  | bufferspace = strlen(serv) + strlen(repo) + 6; | 
|  |  | 
|  | if (bufferspace > 0xFFFF) | 
|  | die("Request too large to send"); | 
|  | buffer = xmalloc(bufferspace); | 
|  |  | 
|  | /* Make the packet. */ | 
|  | wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, | 
|  | serv, repo, 0); | 
|  |  | 
|  | /* Add vhost if any. */ | 
|  | if (vhost) | 
|  | sprintf(buffer + wpos, "host=%s%c", vhost, 0); | 
|  |  | 
|  | /* Send the request */ | 
|  | if (write_in_full(stdin_fd, buffer, bufferspace) < 0) | 
|  | die_errno("Failed to send request"); | 
|  |  | 
|  | free(buffer); | 
|  | } | 
|  |  | 
|  | static int run_child(const char *arg, const char *service) | 
|  | { | 
|  | int r; | 
|  | struct child_process child; | 
|  |  | 
|  | memset(&child, 0, sizeof(child)); | 
|  | child.in = -1; | 
|  | child.out = -1; | 
|  | child.err = 0; | 
|  | child.argv = parse_argv(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("Comammand 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) | 
|  | die("Expected two arguments"); | 
|  |  | 
|  | return command_loop(argv[2]); | 
|  | } |