config: rename "gc.pruneWorktreesExpire" to "gc.worktreePruneExpire"
[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         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
119 static 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
142 static 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
177 static 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
203 static 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
233 int 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 }