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