Line | Count | Source (jump to first uncovered line) |
1 | | #define USE_THE_REPOSITORY_VARIABLE |
2 | | |
3 | | #include "git-compat-util.h" |
4 | | #include "repository.h" |
5 | | #include "config.h" |
6 | | #include "hash.h" |
7 | | #include "pkt-line.h" |
8 | | #include "version.h" |
9 | | #include "ls-refs.h" |
10 | | #include "protocol-caps.h" |
11 | | #include "serve.h" |
12 | | #include "upload-pack.h" |
13 | | #include "bundle-uri.h" |
14 | | #include "trace2.h" |
15 | | |
16 | | static int advertise_sid = -1; |
17 | | static int advertise_object_info = -1; |
18 | | static int client_hash_algo = GIT_HASH_SHA1; |
19 | | |
20 | | static int always_advertise(struct repository *r UNUSED, |
21 | | struct strbuf *value UNUSED) |
22 | 0 | { |
23 | 0 | return 1; |
24 | 0 | } |
25 | | |
26 | | static int agent_advertise(struct repository *r UNUSED, |
27 | | struct strbuf *value) |
28 | 0 | { |
29 | 0 | if (value) |
30 | 0 | strbuf_addstr(value, git_user_agent_sanitized()); |
31 | 0 | return 1; |
32 | 0 | } |
33 | | |
34 | | static int object_format_advertise(struct repository *r, |
35 | | struct strbuf *value) |
36 | 0 | { |
37 | 0 | if (value) |
38 | 0 | strbuf_addstr(value, r->hash_algo->name); |
39 | 0 | return 1; |
40 | 0 | } |
41 | | |
42 | | static void object_format_receive(struct repository *r UNUSED, |
43 | | const char *algo_name) |
44 | 0 | { |
45 | 0 | if (!algo_name) |
46 | 0 | die("object-format capability requires an argument"); |
47 | | |
48 | 0 | client_hash_algo = hash_algo_by_name(algo_name); |
49 | 0 | if (client_hash_algo == GIT_HASH_UNKNOWN) |
50 | 0 | die("unknown object format '%s'", algo_name); |
51 | 0 | } |
52 | | |
53 | | static int session_id_advertise(struct repository *r, struct strbuf *value) |
54 | 0 | { |
55 | 0 | if (advertise_sid == -1 && |
56 | 0 | repo_config_get_bool(r, "transfer.advertisesid", &advertise_sid)) |
57 | 0 | advertise_sid = 0; |
58 | 0 | if (!advertise_sid) |
59 | 0 | return 0; |
60 | 0 | if (value) |
61 | 0 | strbuf_addstr(value, trace2_session_id()); |
62 | 0 | return 1; |
63 | 0 | } |
64 | | |
65 | | static void session_id_receive(struct repository *r UNUSED, |
66 | | const char *client_sid) |
67 | 0 | { |
68 | 0 | if (!client_sid) |
69 | 0 | client_sid = ""; |
70 | 0 | trace2_data_string("transfer", NULL, "client-sid", client_sid); |
71 | 0 | } |
72 | | |
73 | | static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED) |
74 | 0 | { |
75 | 0 | if (advertise_object_info == -1 && |
76 | 0 | repo_config_get_bool(r, "transfer.advertiseobjectinfo", |
77 | 0 | &advertise_object_info)) { |
78 | | /* disabled by default */ |
79 | 0 | advertise_object_info = 0; |
80 | 0 | } |
81 | 0 | return advertise_object_info; |
82 | 0 | } |
83 | | |
84 | | struct protocol_capability { |
85 | | /* |
86 | | * The name of the capability. The server uses this name when |
87 | | * advertising this capability, and the client uses this name to |
88 | | * specify this capability. |
89 | | */ |
90 | | const char *name; |
91 | | |
92 | | /* |
93 | | * Function queried to see if a capability should be advertised. |
94 | | * Optionally a value can be specified by adding it to 'value'. |
95 | | * If a value is added to 'value', the server will advertise this |
96 | | * capability as "<name>=<value>" instead of "<name>". |
97 | | */ |
98 | | int (*advertise)(struct repository *r, struct strbuf *value); |
99 | | |
100 | | /* |
101 | | * Function called when a client requests the capability as a command. |
102 | | * Will be provided a struct packet_reader 'request' which it should |
103 | | * use to read the command specific part of the request. Every command |
104 | | * MUST read until a flush packet is seen before sending a response. |
105 | | * |
106 | | * This field should be NULL for capabilities which are not commands. |
107 | | */ |
108 | | int (*command)(struct repository *r, struct packet_reader *request); |
109 | | |
110 | | /* |
111 | | * Function called when a client requests the capability as a |
112 | | * non-command. This may be NULL if the capability does nothing. |
113 | | * |
114 | | * For a capability of the form "foo=bar", the value string points to |
115 | | * the content after the "=" (i.e., "bar"). For simple capabilities |
116 | | * (just "foo"), it is NULL. |
117 | | */ |
118 | | void (*receive)(struct repository *r, const char *value); |
119 | | }; |
120 | | |
121 | | static struct protocol_capability capabilities[] = { |
122 | | { |
123 | | .name = "agent", |
124 | | .advertise = agent_advertise, |
125 | | }, |
126 | | { |
127 | | .name = "ls-refs", |
128 | | .advertise = ls_refs_advertise, |
129 | | .command = ls_refs, |
130 | | }, |
131 | | { |
132 | | .name = "fetch", |
133 | | .advertise = upload_pack_advertise, |
134 | | .command = upload_pack_v2, |
135 | | }, |
136 | | { |
137 | | .name = "server-option", |
138 | | .advertise = always_advertise, |
139 | | }, |
140 | | { |
141 | | .name = "object-format", |
142 | | .advertise = object_format_advertise, |
143 | | .receive = object_format_receive, |
144 | | }, |
145 | | { |
146 | | .name = "session-id", |
147 | | .advertise = session_id_advertise, |
148 | | .receive = session_id_receive, |
149 | | }, |
150 | | { |
151 | | .name = "object-info", |
152 | | .advertise = object_info_advertise, |
153 | | .command = cap_object_info, |
154 | | }, |
155 | | { |
156 | | .name = "bundle-uri", |
157 | | .advertise = bundle_uri_advertise, |
158 | | .command = bundle_uri_command, |
159 | | }, |
160 | | }; |
161 | | |
162 | | void protocol_v2_advertise_capabilities(void) |
163 | 0 | { |
164 | 0 | struct strbuf capability = STRBUF_INIT; |
165 | 0 | struct strbuf value = STRBUF_INIT; |
166 | 0 | int i; |
167 | | |
168 | | /* serve by default supports v2 */ |
169 | 0 | packet_write_fmt(1, "version 2\n"); |
170 | |
|
171 | 0 | for (i = 0; i < ARRAY_SIZE(capabilities); i++) { |
172 | 0 | struct protocol_capability *c = &capabilities[i]; |
173 | |
|
174 | 0 | if (c->advertise(the_repository, &value)) { |
175 | 0 | strbuf_addstr(&capability, c->name); |
176 | |
|
177 | 0 | if (value.len) { |
178 | 0 | strbuf_addch(&capability, '='); |
179 | 0 | strbuf_addbuf(&capability, &value); |
180 | 0 | } |
181 | |
|
182 | 0 | strbuf_addch(&capability, '\n'); |
183 | 0 | packet_write(1, capability.buf, capability.len); |
184 | 0 | } |
185 | |
|
186 | 0 | strbuf_reset(&capability); |
187 | 0 | strbuf_reset(&value); |
188 | 0 | } |
189 | |
|
190 | 0 | packet_flush(1); |
191 | 0 | strbuf_release(&capability); |
192 | 0 | strbuf_release(&value); |
193 | 0 | } |
194 | | |
195 | | static struct protocol_capability *get_capability(const char *key, const char **value) |
196 | 0 | { |
197 | 0 | int i; |
198 | |
|
199 | 0 | if (!key) |
200 | 0 | return NULL; |
201 | | |
202 | 0 | for (i = 0; i < ARRAY_SIZE(capabilities); i++) { |
203 | 0 | struct protocol_capability *c = &capabilities[i]; |
204 | 0 | const char *out; |
205 | 0 | if (!skip_prefix(key, c->name, &out)) |
206 | 0 | continue; |
207 | 0 | if (!*out) { |
208 | 0 | *value = NULL; |
209 | 0 | return c; |
210 | 0 | } |
211 | 0 | if (*out++ == '=') { |
212 | 0 | *value = out; |
213 | 0 | return c; |
214 | 0 | } |
215 | 0 | } |
216 | | |
217 | 0 | return NULL; |
218 | 0 | } |
219 | | |
220 | | static int receive_client_capability(const char *key) |
221 | 0 | { |
222 | 0 | const char *value; |
223 | 0 | const struct protocol_capability *c = get_capability(key, &value); |
224 | |
|
225 | 0 | if (!c || c->command || !c->advertise(the_repository, NULL)) |
226 | 0 | return 0; |
227 | | |
228 | 0 | if (c->receive) |
229 | 0 | c->receive(the_repository, value); |
230 | 0 | return 1; |
231 | 0 | } |
232 | | |
233 | | static int parse_command(const char *key, struct protocol_capability **command) |
234 | 0 | { |
235 | 0 | const char *out; |
236 | |
|
237 | 0 | if (skip_prefix(key, "command=", &out)) { |
238 | 0 | const char *value; |
239 | 0 | struct protocol_capability *cmd = get_capability(out, &value); |
240 | |
|
241 | 0 | if (*command) |
242 | 0 | die("command '%s' requested after already requesting command '%s'", |
243 | 0 | out, (*command)->name); |
244 | 0 | if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command || value) |
245 | 0 | die("invalid command '%s'", out); |
246 | | |
247 | 0 | *command = cmd; |
248 | 0 | return 1; |
249 | 0 | } |
250 | | |
251 | 0 | return 0; |
252 | 0 | } |
253 | | |
254 | | enum request_state { |
255 | | PROCESS_REQUEST_KEYS, |
256 | | PROCESS_REQUEST_DONE, |
257 | | }; |
258 | | |
259 | | static int process_request(void) |
260 | 0 | { |
261 | 0 | enum request_state state = PROCESS_REQUEST_KEYS; |
262 | 0 | struct packet_reader reader; |
263 | 0 | int seen_capability_or_command = 0; |
264 | 0 | struct protocol_capability *command = NULL; |
265 | |
|
266 | 0 | packet_reader_init(&reader, 0, NULL, 0, |
267 | 0 | PACKET_READ_CHOMP_NEWLINE | |
268 | 0 | PACKET_READ_GENTLE_ON_EOF | |
269 | 0 | PACKET_READ_DIE_ON_ERR_PACKET); |
270 | | |
271 | | /* |
272 | | * Check to see if the client closed their end before sending another |
273 | | * request. If so we can terminate the connection. |
274 | | */ |
275 | 0 | if (packet_reader_peek(&reader) == PACKET_READ_EOF) |
276 | 0 | return 1; |
277 | 0 | reader.options &= ~PACKET_READ_GENTLE_ON_EOF; |
278 | |
|
279 | 0 | while (state != PROCESS_REQUEST_DONE) { |
280 | 0 | switch (packet_reader_peek(&reader)) { |
281 | 0 | case PACKET_READ_EOF: |
282 | 0 | BUG("Should have already died when seeing EOF"); |
283 | 0 | case PACKET_READ_NORMAL: |
284 | 0 | if (parse_command(reader.line, &command) || |
285 | 0 | receive_client_capability(reader.line)) |
286 | 0 | seen_capability_or_command = 1; |
287 | 0 | else |
288 | 0 | die("unknown capability '%s'", reader.line); |
289 | | |
290 | | /* Consume the peeked line */ |
291 | 0 | packet_reader_read(&reader); |
292 | 0 | break; |
293 | 0 | case PACKET_READ_FLUSH: |
294 | | /* |
295 | | * If no command and no keys were given then the client |
296 | | * wanted to terminate the connection. |
297 | | */ |
298 | 0 | if (!seen_capability_or_command) |
299 | 0 | return 1; |
300 | | |
301 | | /* |
302 | | * The flush packet isn't consume here like it is in |
303 | | * the other parts of this switch statement. This is |
304 | | * so that the command can read the flush packet and |
305 | | * see the end of the request in the same way it would |
306 | | * if command specific arguments were provided after a |
307 | | * delim packet. |
308 | | */ |
309 | 0 | state = PROCESS_REQUEST_DONE; |
310 | 0 | break; |
311 | 0 | case PACKET_READ_DELIM: |
312 | | /* Consume the peeked line */ |
313 | 0 | packet_reader_read(&reader); |
314 | |
|
315 | 0 | state = PROCESS_REQUEST_DONE; |
316 | 0 | break; |
317 | 0 | case PACKET_READ_RESPONSE_END: |
318 | 0 | BUG("unexpected response end packet"); |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | 0 | if (!command) |
323 | 0 | die("no command requested"); |
324 | | |
325 | 0 | if (client_hash_algo != hash_algo_by_ptr(the_repository->hash_algo)) |
326 | 0 | die("mismatched object format: server %s; client %s", |
327 | 0 | the_repository->hash_algo->name, |
328 | 0 | hash_algos[client_hash_algo].name); |
329 | | |
330 | 0 | command->command(the_repository, &reader); |
331 | |
|
332 | 0 | return 0; |
333 | 0 | } |
334 | | |
335 | | void protocol_v2_serve_loop(int stateless_rpc) |
336 | 0 | { |
337 | 0 | if (!stateless_rpc) |
338 | 0 | protocol_v2_advertise_capabilities(); |
339 | | |
340 | | /* |
341 | | * If stateless-rpc was requested then exit after |
342 | | * a single request/response exchange |
343 | | */ |
344 | 0 | if (stateless_rpc) { |
345 | 0 | process_request(); |
346 | 0 | } else { |
347 | 0 | for (;;) |
348 | 0 | if (process_request()) |
349 | 0 | break; |
350 | 0 | } |
351 | 0 | } |