Merge branch 'jc/revision-range-unpeel'
[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 }