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