remote-ext: simplify git pkt-line generation
[git/git.git] / builtin / remote-ext.c
1 #include "builtin.h"
2 #include "transport.h"
3 #include "run-command.h"
4 #include "pkt-line.h"
5
6 /*
7 * URL syntax:
8 * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
9 * Special characters:
10 * '% ': Literal space in argument.
11 * '%%': Literal percent sign.
12 * '%S': Name of service (git-upload-pack/git-upload-archive/
13 * git-receive-pack.
14 * '%s': Same as \s, but with possible git- prefix stripped.
15 * '%G': Only allowed as first 'character' of argument. Do not pass this
16 * Argument to command, instead send this as name of repository
17 * in in-line git://-style request (also activates sending this
18 * style of request).
19 * '%V': Only allowed as first 'character' of argument. Used in
20 * conjunction with '%G': Do not pass this argument to command,
21 * instead send this as vhost in git://-style request (note: does
22 * not activate sending git:// style request).
23 */
24
25 static char *git_req;
26 static char *git_req_vhost;
27
28 static char *strip_escapes(const char *str, const char *service,
29 const char **next)
30 {
31 size_t rpos = 0;
32 int escape = 0;
33 char special = 0;
34 const char *service_noprefix = service;
35 struct strbuf ret = STRBUF_INIT;
36
37 skip_prefix(service_noprefix, "git-", &service_noprefix);
38
39 /* Pass the service to command. */
40 setenv("GIT_EXT_SERVICE", service, 1);
41 setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1);
42
43 /* Scan the length of argument. */
44 while (str[rpos] && (escape || str[rpos] != ' ')) {
45 if (escape) {
46 switch (str[rpos]) {
47 case ' ':
48 case '%':
49 case 's':
50 case 'S':
51 break;
52 case 'G':
53 case 'V':
54 special = str[rpos];
55 if (rpos == 1)
56 break;
57 /* Fall-through to error. */
58 default:
59 die("Bad remote-ext placeholder '%%%c'.",
60 str[rpos]);
61 }
62 escape = 0;
63 } else
64 escape = (str[rpos] == '%');
65 rpos++;
66 }
67 if (escape && !str[rpos])
68 die("remote-ext command has incomplete placeholder");
69 *next = str + rpos;
70 if (**next == ' ')
71 ++*next; /* Skip over space */
72
73 /*
74 * Do the actual placeholder substitution. The string will be short
75 * enough not to overflow integers.
76 */
77 rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
78 escape = 0;
79 while (str[rpos] && (escape || str[rpos] != ' ')) {
80 if (escape) {
81 switch (str[rpos]) {
82 case ' ':
83 case '%':
84 strbuf_addch(&ret, str[rpos]);
85 break;
86 case 's':
87 strbuf_addstr(&ret, service_noprefix);
88 break;
89 case 'S':
90 strbuf_addstr(&ret, service);
91 break;
92 }
93 escape = 0;
94 } else
95 switch (str[rpos]) {
96 case '%':
97 escape = 1;
98 break;
99 default:
100 strbuf_addch(&ret, str[rpos]);
101 break;
102 }
103 rpos++;
104 }
105 switch (special) {
106 case 'G':
107 git_req = strbuf_detach(&ret, NULL);
108 return NULL;
109 case 'V':
110 git_req_vhost = strbuf_detach(&ret, NULL);
111 return NULL;
112 default:
113 return strbuf_detach(&ret, NULL);
114 }
115 }
116
117 /* Should be enough... */
118 #define MAXARGUMENTS 256
119
120 static const char **parse_argv(const char *arg, const char *service)
121 {
122 int arguments = 0;
123 int i;
124 const char **ret;
125 char *temparray[MAXARGUMENTS + 1];
126
127 while (*arg) {
128 char *expanded;
129 if (arguments == MAXARGUMENTS)
130 die("remote-ext command has too many arguments");
131 expanded = strip_escapes(arg, service, &arg);
132 if (expanded)
133 temparray[arguments++] = expanded;
134 }
135
136 ret = xmalloc((arguments + 1) * sizeof(char *));
137 for (i = 0; i < arguments; i++)
138 ret[i] = temparray[i];
139 ret[arguments] = NULL;
140 return ret;
141 }
142
143 static void send_git_request(int stdin_fd, const char *serv, const char *repo,
144 const char *vhost)
145 {
146 if (!vhost)
147 packet_write(stdin_fd, "%s %s%c", serv, repo, 0);
148 else
149 packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
150 vhost, 0);
151 }
152
153 static int run_child(const char *arg, const char *service)
154 {
155 int r;
156 struct child_process child = CHILD_PROCESS_INIT;
157
158 child.in = -1;
159 child.out = -1;
160 child.err = 0;
161 child.argv = parse_argv(arg, service);
162
163 if (start_command(&child) < 0)
164 die("Can't run specified command");
165
166 if (git_req)
167 send_git_request(child.in, service, git_req, git_req_vhost);
168
169 r = bidirectional_transfer_loop(child.out, child.in);
170 if (!r)
171 r = finish_command(&child);
172 else
173 finish_command(&child);
174 return r;
175 }
176
177 #define MAXCOMMAND 4096
178
179 static int command_loop(const char *child)
180 {
181 char buffer[MAXCOMMAND];
182
183 while (1) {
184 size_t i;
185 if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
186 if (ferror(stdin))
187 die("Comammand input error");
188 exit(0);
189 }
190 /* Strip end of line characters. */
191 i = strlen(buffer);
192 while (i > 0 && isspace(buffer[i - 1]))
193 buffer[--i] = 0;
194
195 if (!strcmp(buffer, "capabilities")) {
196 printf("*connect\n\n");
197 fflush(stdout);
198 } else if (!strncmp(buffer, "connect ", 8)) {
199 printf("\n");
200 fflush(stdout);
201 return run_child(child, buffer + 8);
202 } else {
203 fprintf(stderr, "Bad command");
204 return 1;
205 }
206 }
207 }
208
209 int cmd_remote_ext(int argc, const char **argv, const char *prefix)
210 {
211 if (argc != 3)
212 die("Expected two arguments");
213
214 return command_loop(argv[2]);
215 }