| #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 argv_array *out, const char *arg, const char *service) | 
 | { | 
 | 	while (*arg) { | 
 | 		char *expanded = strip_escapes(arg, service, &arg); | 
 | 		if (expanded) | 
 | 			argv_array_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]); | 
 | } |