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