200 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
| #include "builtin.h"
 | |
| #include "transport.h"
 | |
| #include "run-command.h"
 | |
| #include "pkt-line.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;
 | |
| 	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;
 | |
| 				/* 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_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)
 | |
| 		die("Expected two arguments");
 | |
| 
 | |
| 	return command_loop(argv[2]);
 | |
| }
 |