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