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