Git.pm: Use stream-like writing in cat_blob()
[git/dscho.git] / builtin / remote-ext.c
blobea71977c8360e1a7899bbd035cf26f93d889800b
1 #include "git-compat-util.h"
2 #include "transport.h"
3 #include "run-command.h"
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).
24 static char *git_req;
25 static char *git_req_vhost;
27 static char *strip_escapes(const char *str, const char *service,
28 const char **next)
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;
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;
44 /* Pass the service to command. */
45 setenv("GIT_EXT_SERVICE", service, 1);
46 setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
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]);
67 escape = 0;
68 } else
69 escape = (str[rpos] == '%');
70 rpos++;
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 */
79 * Do the actual placeholder substitution. The string will be short
80 * enough not to overflow integers.
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;
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;
108 rpos++;
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);
122 /* Should be enough... */
123 #define MAXARGUMENTS 256
125 static const char **parse_argv(const char *arg, const char *service)
127 int arguments = 0;
128 int i;
129 const char **ret;
130 char *temparray[MAXARGUMENTS + 1];
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;
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;
148 static void send_git_request(int stdin_fd, const char *serv, const char *repo,
149 const char *vhost)
151 size_t bufferspace;
152 size_t wpos = 0;
153 char *buffer;
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.
159 if (vhost)
160 bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
161 else
162 bufferspace = strlen(serv) + strlen(repo) + 6;
164 if (bufferspace > 0xFFFF)
165 die("Request too large to send");
166 buffer = xmalloc(bufferspace);
168 /* Make the packet. */
169 wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
170 serv, repo, 0);
172 /* Add vhost if any. */
173 if (vhost)
174 sprintf(buffer + wpos, "host=%s%c", vhost, 0);
176 /* Send the request */
177 if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
178 die_errno("Failed to send request");
180 free(buffer);
183 static int run_child(const char *arg, const char *service)
185 int r;
186 struct child_process child;
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);
194 if (start_command(&child) < 0)
195 die("Can't run specified command");
197 if (git_req)
198 send_git_request(child.in, service, git_req, git_req_vhost);
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;
208 #define MAXCOMMAND 4096
210 static int command_loop(const char *child)
212 char buffer[MAXCOMMAND];
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);
221 /* Strip end of line characters. */
222 i = strlen(buffer);
223 while (i > 0 && isspace(buffer[i - 1]))
224 buffer[--i] = 0;
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;
240 int cmd_remote_ext(int argc, const char **argv, const char *prefix)
242 if (argc != 3)
243 die("Expected two arguments");
245 return command_loop(argv[2]);