]>
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; | |
e3f1da98 | 33 | const char *service_noprefix = service; |
7f3ecebf IL |
34 | struct strbuf ret = STRBUF_INIT; |
35 | ||
e3f1da98 | 36 | skip_prefix(service_noprefix, "git-", &service_noprefix); |
7f3ecebf IL |
37 | |
38 | /* Pass the service to command. */ | |
39 | setenv("GIT_EXT_SERVICE", service, 1); | |
e3f1da98 | 40 | setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1); |
7f3ecebf IL |
41 | |
42 | /* Scan the length of argument. */ | |
43 | while (str[rpos] && (escape || str[rpos] != ' ')) { | |
44 | if (escape) { | |
45 | switch (str[rpos]) { | |
46 | case ' ': | |
47 | case '%': | |
48 | case 's': | |
49 | case 'S': | |
50 | break; | |
51 | case 'G': | |
52 | case 'V': | |
53 | special = str[rpos]; | |
54 | if (rpos == 1) | |
55 | break; | |
56 | /* Fall-through to error. */ | |
57 | default: | |
58 | die("Bad remote-ext placeholder '%%%c'.", | |
59 | str[rpos]); | |
60 | } | |
61 | escape = 0; | |
62 | } else | |
63 | escape = (str[rpos] == '%'); | |
64 | rpos++; | |
65 | } | |
66 | if (escape && !str[rpos]) | |
67 | die("remote-ext command has incomplete placeholder"); | |
68 | *next = str + rpos; | |
69 | if (**next == ' ') | |
70 | ++*next; /* Skip over space */ | |
71 | ||
72 | /* | |
73 | * Do the actual placeholder substitution. The string will be short | |
74 | * enough not to overflow integers. | |
75 | */ | |
76 | rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */ | |
77 | escape = 0; | |
78 | while (str[rpos] && (escape || str[rpos] != ' ')) { | |
79 | if (escape) { | |
80 | switch (str[rpos]) { | |
81 | case ' ': | |
82 | case '%': | |
83 | strbuf_addch(&ret, str[rpos]); | |
84 | break; | |
85 | case 's': | |
e3f1da98 | 86 | strbuf_addstr(&ret, service_noprefix); |
7f3ecebf IL |
87 | break; |
88 | case 'S': | |
89 | strbuf_addstr(&ret, service); | |
90 | break; | |
91 | } | |
92 | escape = 0; | |
93 | } else | |
94 | switch (str[rpos]) { | |
95 | case '%': | |
96 | escape = 1; | |
97 | break; | |
98 | default: | |
99 | strbuf_addch(&ret, str[rpos]); | |
100 | break; | |
101 | } | |
102 | rpos++; | |
103 | } | |
104 | switch (special) { | |
105 | case 'G': | |
106 | git_req = strbuf_detach(&ret, NULL); | |
107 | return NULL; | |
108 | case 'V': | |
109 | git_req_vhost = strbuf_detach(&ret, NULL); | |
110 | return NULL; | |
111 | default: | |
112 | return strbuf_detach(&ret, NULL); | |
113 | } | |
114 | } | |
115 | ||
116 | /* Should be enough... */ | |
117 | #define MAXARGUMENTS 256 | |
118 | ||
119 | static const char **parse_argv(const char *arg, const char *service) | |
120 | { | |
121 | int arguments = 0; | |
122 | int i; | |
123 | const char **ret; | |
124 | char *temparray[MAXARGUMENTS + 1]; | |
125 | ||
126 | while (*arg) { | |
127 | char *expanded; | |
128 | if (arguments == MAXARGUMENTS) | |
129 | die("remote-ext command has too many arguments"); | |
130 | expanded = strip_escapes(arg, service, &arg); | |
131 | if (expanded) | |
132 | temparray[arguments++] = expanded; | |
133 | } | |
134 | ||
135 | ret = xmalloc((arguments + 1) * sizeof(char *)); | |
136 | for (i = 0; i < arguments; i++) | |
137 | ret[i] = temparray[i]; | |
138 | ret[arguments] = NULL; | |
7851b1e6 | 139 | return ret; |
7f3ecebf IL |
140 | } |
141 | ||
142 | static void send_git_request(int stdin_fd, const char *serv, const char *repo, | |
143 | const char *vhost) | |
144 | { | |
145 | size_t bufferspace; | |
146 | size_t wpos = 0; | |
147 | char *buffer; | |
148 | ||
149 | /* | |
150 | * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and | |
151 | * 6 bytes extra (xxxx \0) if there is no vhost. | |
152 | */ | |
153 | if (vhost) | |
154 | bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; | |
155 | else | |
156 | bufferspace = strlen(serv) + strlen(repo) + 6; | |
157 | ||
158 | if (bufferspace > 0xFFFF) | |
159 | die("Request too large to send"); | |
160 | buffer = xmalloc(bufferspace); | |
161 | ||
162 | /* Make the packet. */ | |
163 | wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, | |
164 | serv, repo, 0); | |
165 | ||
166 | /* Add vhost if any. */ | |
167 | if (vhost) | |
168 | sprintf(buffer + wpos, "host=%s%c", vhost, 0); | |
169 | ||
170 | /* Send the request */ | |
171 | if (write_in_full(stdin_fd, buffer, bufferspace) < 0) | |
172 | die_errno("Failed to send request"); | |
173 | ||
174 | free(buffer); | |
175 | } | |
176 | ||
177 | static int run_child(const char *arg, const char *service) | |
178 | { | |
179 | int r; | |
d3180279 | 180 | struct child_process child = CHILD_PROCESS_INIT; |
7f3ecebf | 181 | |
7f3ecebf IL |
182 | child.in = -1; |
183 | child.out = -1; | |
184 | child.err = 0; | |
185 | child.argv = parse_argv(arg, service); | |
186 | ||
187 | if (start_command(&child) < 0) | |
188 | die("Can't run specified command"); | |
189 | ||
190 | if (git_req) | |
191 | send_git_request(child.in, service, git_req, git_req_vhost); | |
192 | ||
193 | r = bidirectional_transfer_loop(child.out, child.in); | |
194 | if (!r) | |
195 | r = finish_command(&child); | |
196 | else | |
197 | finish_command(&child); | |
198 | return r; | |
199 | } | |
200 | ||
201 | #define MAXCOMMAND 4096 | |
202 | ||
203 | static int command_loop(const char *child) | |
204 | { | |
205 | char buffer[MAXCOMMAND]; | |
206 | ||
207 | while (1) { | |
60a2e332 | 208 | size_t i; |
7f3ecebf IL |
209 | if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { |
210 | if (ferror(stdin)) | |
211 | die("Comammand input error"); | |
212 | exit(0); | |
213 | } | |
214 | /* Strip end of line characters. */ | |
60a2e332 JN |
215 | i = strlen(buffer); |
216 | while (i > 0 && isspace(buffer[i - 1])) | |
217 | buffer[--i] = 0; | |
7f3ecebf IL |
218 | |
219 | if (!strcmp(buffer, "capabilities")) { | |
220 | printf("*connect\n\n"); | |
221 | fflush(stdout); | |
222 | } else if (!strncmp(buffer, "connect ", 8)) { | |
223 | printf("\n"); | |
224 | fflush(stdout); | |
225 | return run_child(child, buffer + 8); | |
226 | } else { | |
227 | fprintf(stderr, "Bad command"); | |
228 | return 1; | |
229 | } | |
230 | } | |
231 | } | |
232 | ||
233 | int cmd_remote_ext(int argc, const char **argv, const char *prefix) | |
234 | { | |
7851b1e6 IL |
235 | if (argc != 3) |
236 | die("Expected two arguments"); | |
7f3ecebf IL |
237 | |
238 | return command_loop(argv[2]); | |
239 | } |