]>
Commit | Line | Data |
---|---|---|
c2e86add | 1 | #include "builtin.h" |
7f3ecebf IL |
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; | |
7851b1e6 | 145 | return ret; |
7f3ecebf IL |
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) { | |
60a2e332 | 215 | size_t i; |
7f3ecebf IL |
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. */ | |
60a2e332 JN |
222 | i = strlen(buffer); |
223 | while (i > 0 && isspace(buffer[i - 1])) | |
224 | buffer[--i] = 0; | |
7f3ecebf IL |
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 | { | |
7851b1e6 IL |
242 | if (argc != 3) |
243 | die("Expected two arguments"); | |
7f3ecebf IL |
244 | |
245 | return command_loop(argv[2]); | |
246 | } |