202 lines
		
	
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#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]);
 | 
						|
}
 |