/src/git/promisor-remote.c
Line | Count | Source |
1 | | #define USE_THE_REPOSITORY_VARIABLE |
2 | | |
3 | | #include "git-compat-util.h" |
4 | | #include "gettext.h" |
5 | | #include "hex.h" |
6 | | #include "odb.h" |
7 | | #include "promisor-remote.h" |
8 | | #include "config.h" |
9 | | #include "trace2.h" |
10 | | #include "transport.h" |
11 | | #include "strvec.h" |
12 | | #include "packfile.h" |
13 | | #include "environment.h" |
14 | | #include "url.h" |
15 | | #include "urlmatch.h" |
16 | | #include "version.h" |
17 | | #include "wildmatch.h" |
18 | | |
19 | | struct promisor_remote_config { |
20 | | struct promisor_remote *promisors; |
21 | | struct promisor_remote **promisors_tail; |
22 | | }; |
23 | | |
24 | | static int fetch_objects(struct repository *repo, |
25 | | const char *remote_name, |
26 | | const struct object_id *oids, |
27 | | int oid_nr, int depth) |
28 | 0 | { |
29 | 0 | struct child_process child = CHILD_PROCESS_INIT; |
30 | 0 | int i; |
31 | 0 | FILE *child_in; |
32 | 0 | int quiet; |
33 | |
|
34 | 0 | child.git_cmd = 1; |
35 | 0 | child.in = -1; |
36 | 0 | if (repo != the_repository) |
37 | 0 | prepare_other_repo_env(&child.env, repo->gitdir); |
38 | 0 | strvec_pushl(&child.args, "-c", "fetch.negotiationAlgorithm=noop", |
39 | 0 | "fetch", remote_name, "--no-tags", |
40 | 0 | "--no-write-fetch-head", "--recurse-submodules=no", |
41 | 0 | "--filter=blob:none", "--stdin", NULL); |
42 | 0 | if (!repo_config_get_bool(repo, "promisor.quiet", &quiet) && quiet) |
43 | 0 | strvec_push(&child.args, "--quiet"); |
44 | 0 | strvec_pushf(&child.env, "%s=%d", LAZY_FETCH_DEPTH_ENVIRONMENT, depth + 1); |
45 | 0 | if (start_command(&child)) |
46 | 0 | die(_("promisor-remote: unable to fork off fetch subprocess")); |
47 | 0 | child_in = xfdopen(child.in, "w"); |
48 | |
|
49 | 0 | trace2_data_intmax("promisor", repo, "fetch_count", oid_nr); |
50 | |
|
51 | 0 | for (i = 0; i < oid_nr; i++) { |
52 | 0 | if (fputs(oid_to_hex(&oids[i]), child_in) < 0) |
53 | 0 | die_errno(_("promisor-remote: could not write to fetch subprocess")); |
54 | 0 | if (fputc('\n', child_in) < 0) |
55 | 0 | die_errno(_("promisor-remote: could not write to fetch subprocess")); |
56 | 0 | } |
57 | | |
58 | 0 | if (fclose(child_in) < 0) |
59 | 0 | die_errno(_("promisor-remote: could not close stdin to fetch subprocess")); |
60 | 0 | return finish_command(&child) ? -1 : 0; |
61 | 0 | } |
62 | | |
63 | | static struct promisor_remote *promisor_remote_new(struct promisor_remote_config *config, |
64 | | const char *remote_name) |
65 | 0 | { |
66 | 0 | struct promisor_remote *r; |
67 | |
|
68 | 0 | if (*remote_name == '/') { |
69 | 0 | warning(_("promisor remote name cannot begin with '/': %s"), |
70 | 0 | remote_name); |
71 | 0 | return NULL; |
72 | 0 | } |
73 | | |
74 | 0 | FLEX_ALLOC_STR(r, name, remote_name); |
75 | |
|
76 | 0 | *config->promisors_tail = r; |
77 | 0 | config->promisors_tail = &r->next; |
78 | |
|
79 | 0 | return r; |
80 | 0 | } |
81 | | |
82 | | static struct promisor_remote *promisor_remote_lookup(struct promisor_remote_config *config, |
83 | | const char *remote_name, |
84 | | struct promisor_remote **previous) |
85 | 0 | { |
86 | 0 | struct promisor_remote *r, *p; |
87 | |
|
88 | 0 | for (p = NULL, r = config->promisors; r; p = r, r = r->next) |
89 | 0 | if (!strcmp(r->name, remote_name)) { |
90 | 0 | if (previous) |
91 | 0 | *previous = p; |
92 | 0 | return r; |
93 | 0 | } |
94 | | |
95 | 0 | return NULL; |
96 | 0 | } |
97 | | |
98 | | static void promisor_remote_move_to_tail(struct promisor_remote_config *config, |
99 | | struct promisor_remote *r, |
100 | | struct promisor_remote *previous) |
101 | 0 | { |
102 | 0 | if (!r->next) |
103 | 0 | return; |
104 | | |
105 | 0 | if (previous) |
106 | 0 | previous->next = r->next; |
107 | 0 | else |
108 | 0 | config->promisors = r->next ? r->next : r; |
109 | 0 | r->next = NULL; |
110 | 0 | *config->promisors_tail = r; |
111 | 0 | config->promisors_tail = &r->next; |
112 | 0 | } |
113 | | |
114 | | static int promisor_remote_config(const char *var, const char *value, |
115 | | const struct config_context *ctx UNUSED, |
116 | | void *data) |
117 | 0 | { |
118 | 0 | struct promisor_remote_config *config = data; |
119 | 0 | const char *name; |
120 | 0 | size_t namelen; |
121 | 0 | const char *subkey; |
122 | |
|
123 | 0 | if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0) |
124 | 0 | return 0; |
125 | | |
126 | 0 | if (!strcmp(subkey, "promisor")) { |
127 | 0 | char *remote_name; |
128 | |
|
129 | 0 | if (!git_config_bool(var, value)) |
130 | 0 | return 0; |
131 | | |
132 | 0 | remote_name = xmemdupz(name, namelen); |
133 | |
|
134 | 0 | if (!promisor_remote_lookup(config, remote_name, NULL)) |
135 | 0 | promisor_remote_new(config, remote_name); |
136 | |
|
137 | 0 | free(remote_name); |
138 | 0 | return 0; |
139 | 0 | } |
140 | 0 | if (!strcmp(subkey, "partialclonefilter")) { |
141 | 0 | struct promisor_remote *r; |
142 | 0 | char *remote_name = xmemdupz(name, namelen); |
143 | |
|
144 | 0 | r = promisor_remote_lookup(config, remote_name, NULL); |
145 | 0 | if (!r) |
146 | 0 | r = promisor_remote_new(config, remote_name); |
147 | |
|
148 | 0 | free(remote_name); |
149 | |
|
150 | 0 | if (!r) |
151 | 0 | return 0; |
152 | | |
153 | 0 | FREE_AND_NULL(r->partial_clone_filter); |
154 | 0 | return git_config_string(&r->partial_clone_filter, var, value); |
155 | 0 | } |
156 | | |
157 | 0 | return 0; |
158 | 0 | } |
159 | | |
160 | | static void promisor_remote_init(struct repository *r) |
161 | 0 | { |
162 | 0 | struct promisor_remote_config *config; |
163 | |
|
164 | 0 | if (r->promisor_remote_config) |
165 | 0 | return; |
166 | 0 | config = r->promisor_remote_config = |
167 | 0 | xcalloc(1, sizeof(*r->promisor_remote_config)); |
168 | 0 | config->promisors_tail = &config->promisors; |
169 | |
|
170 | 0 | repo_config(r, promisor_remote_config, config); |
171 | |
|
172 | 0 | if (r->repository_format_partial_clone) { |
173 | 0 | struct promisor_remote *o, *previous; |
174 | |
|
175 | 0 | o = promisor_remote_lookup(config, |
176 | 0 | r->repository_format_partial_clone, |
177 | 0 | &previous); |
178 | 0 | if (o) |
179 | 0 | promisor_remote_move_to_tail(config, o, previous); |
180 | 0 | else |
181 | 0 | promisor_remote_new(config, r->repository_format_partial_clone); |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | void promisor_remote_clear(struct promisor_remote_config *config) |
186 | 0 | { |
187 | 0 | while (config->promisors) { |
188 | 0 | struct promisor_remote *r = config->promisors; |
189 | 0 | free(r->partial_clone_filter); |
190 | 0 | free(r->advertised_filter); |
191 | 0 | config->promisors = config->promisors->next; |
192 | 0 | free(r); |
193 | 0 | } |
194 | |
|
195 | 0 | config->promisors_tail = &config->promisors; |
196 | 0 | } |
197 | | |
198 | | void repo_promisor_remote_reinit(struct repository *r) |
199 | 0 | { |
200 | 0 | promisor_remote_clear(r->promisor_remote_config); |
201 | 0 | FREE_AND_NULL(r->promisor_remote_config); |
202 | 0 | promisor_remote_init(r); |
203 | 0 | } |
204 | | |
205 | | struct promisor_remote *repo_promisor_remote_find(struct repository *r, |
206 | | const char *remote_name) |
207 | 0 | { |
208 | 0 | promisor_remote_init(r); |
209 | |
|
210 | 0 | if (!remote_name) |
211 | 0 | return r->promisor_remote_config->promisors; |
212 | | |
213 | 0 | return promisor_remote_lookup(r->promisor_remote_config, remote_name, NULL); |
214 | 0 | } |
215 | | |
216 | | int repo_has_promisor_remote(struct repository *r) |
217 | 0 | { |
218 | 0 | return !!repo_promisor_remote_find(r, NULL); |
219 | 0 | } |
220 | | |
221 | | int repo_has_accepted_promisor_remote(struct repository *r) |
222 | 0 | { |
223 | 0 | struct promisor_remote *p; |
224 | |
|
225 | 0 | promisor_remote_init(r); |
226 | |
|
227 | 0 | for (p = r->promisor_remote_config->promisors; p; p = p->next) |
228 | 0 | if (p->accepted) |
229 | 0 | return 1; |
230 | 0 | return 0; |
231 | 0 | } |
232 | | |
233 | | static int remove_fetched_oids(struct repository *repo, |
234 | | struct object_id **oids, |
235 | | int oid_nr, int to_free) |
236 | 0 | { |
237 | 0 | int i, remaining_nr = 0; |
238 | 0 | int *remaining = xcalloc(oid_nr, sizeof(*remaining)); |
239 | 0 | struct object_id *old_oids = *oids; |
240 | 0 | struct object_id *new_oids; |
241 | |
|
242 | 0 | for (i = 0; i < oid_nr; i++) |
243 | 0 | if (odb_read_object_info_extended(repo->objects, &old_oids[i], NULL, |
244 | 0 | OBJECT_INFO_SKIP_FETCH_OBJECT)) { |
245 | 0 | remaining[i] = 1; |
246 | 0 | remaining_nr++; |
247 | 0 | } |
248 | |
|
249 | 0 | if (remaining_nr) { |
250 | 0 | int j = 0; |
251 | 0 | CALLOC_ARRAY(new_oids, remaining_nr); |
252 | 0 | for (i = 0; i < oid_nr; i++) |
253 | 0 | if (remaining[i]) |
254 | 0 | oidcpy(&new_oids[j++], &old_oids[i]); |
255 | 0 | *oids = new_oids; |
256 | 0 | if (to_free) |
257 | 0 | free(old_oids); |
258 | 0 | } |
259 | |
|
260 | 0 | free(remaining); |
261 | |
|
262 | 0 | return remaining_nr; |
263 | 0 | } |
264 | | |
265 | | /* |
266 | | * Fetch the remaining objects (given in '*remaining_oids', which |
267 | | * contains '*remaining_nr' object ids) from the known promisor |
268 | | * remotes. If 'accepted_only' is true, ignore promisor remotes with |
269 | | * their 'accepted' member unset. |
270 | | * |
271 | | * When a fetch from a remote fails, the objects that are still |
272 | | * missing are computed, and '*remaining_oids' and '*remaining_nr' are |
273 | | * updated accordingly before trying the next remote. In that case |
274 | | * '*remaining_oids' points to a new array that this function |
275 | | * allocated, and '*to_free' is set to 1 to tell the caller that it |
276 | | * owns that array and should free it. '*to_free' should be 0 on the |
277 | | * first call. |
278 | | * |
279 | | * Return 1 when all the requested objects have been fetched, 0 |
280 | | * otherwise. |
281 | | */ |
282 | | static int try_promisor_remotes(struct repository *repo, |
283 | | struct object_id **remaining_oids, |
284 | | int *remaining_nr, |
285 | | int *to_free, |
286 | | int depth, |
287 | | bool accepted_only) |
288 | 0 | { |
289 | 0 | struct promisor_remote *r = repo->promisor_remote_config->promisors; |
290 | |
|
291 | 0 | for (; r; r = r->next) { |
292 | 0 | if (accepted_only != r->accepted) |
293 | 0 | continue; |
294 | 0 | if (fetch_objects(repo, r->name, |
295 | 0 | *remaining_oids, *remaining_nr, depth) < 0) { |
296 | 0 | if (*remaining_nr == 1) |
297 | 0 | continue; |
298 | 0 | *remaining_nr = remove_fetched_oids(repo, remaining_oids, |
299 | 0 | *remaining_nr, *to_free); |
300 | 0 | if (*remaining_nr) { |
301 | 0 | *to_free = 1; |
302 | 0 | continue; |
303 | 0 | } |
304 | 0 | } |
305 | 0 | return 1; /* all fetched */ |
306 | 0 | } |
307 | 0 | return 0; |
308 | 0 | } |
309 | | |
310 | 0 | #define MAX_LAZY_FETCH_DEPTH 5 |
311 | | |
312 | | /* |
313 | | * Lazily fetch the objects given in '*remaining_oids' from the |
314 | | * promisor remotes, trying the accepted ones first. See |
315 | | * try_promisor_remotes() above for how '*remaining_oids', |
316 | | * '*remaining_nr' and '*to_free' are used. |
317 | | * |
318 | | * Return 1 when all the requested objects have been fetched, 0 |
319 | | * otherwise. |
320 | | */ |
321 | | static int lazy_fetch_objects(struct repository *repo, |
322 | | struct object_id **remaining_oids, |
323 | | int *remaining_nr, |
324 | | int *to_free) |
325 | 0 | { |
326 | 0 | int depth = (int)git_env_ulong(LAZY_FETCH_DEPTH_ENVIRONMENT, 0); |
327 | |
|
328 | 0 | if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) { |
329 | 0 | static int warning_shown; |
330 | 0 | if (!warning_shown) { |
331 | 0 | warning_shown = 1; |
332 | 0 | warning(_("lazy fetching disabled; some objects may not be available")); |
333 | 0 | } |
334 | 0 | return 0; |
335 | 0 | } |
336 | | |
337 | 0 | if (depth >= MAX_LAZY_FETCH_DEPTH) { |
338 | 0 | static int warning_shown; |
339 | 0 | if (!warning_shown) { |
340 | 0 | warning_shown = 1; |
341 | 0 | warning(_("too many nested lazy fetches (%d); " |
342 | 0 | "is a promisor remote pointing at the repository itself?"), |
343 | 0 | depth); |
344 | 0 | } |
345 | 0 | return 0; |
346 | 0 | } |
347 | | |
348 | 0 | promisor_remote_init(repo); |
349 | | |
350 | | /* Try accepted remotes first (those the server told us to use) */ |
351 | 0 | return try_promisor_remotes(repo, remaining_oids, remaining_nr, |
352 | 0 | to_free, depth, true) || |
353 | 0 | try_promisor_remotes(repo, remaining_oids, remaining_nr, |
354 | 0 | to_free, depth, false); |
355 | 0 | } |
356 | | |
357 | | void promisor_remote_get_direct(struct repository *repo, |
358 | | const struct object_id *oids, |
359 | | int oid_nr) |
360 | 0 | { |
361 | 0 | struct object_id *remaining_oids = (struct object_id *)oids; |
362 | 0 | int remaining_nr = oid_nr; |
363 | 0 | int to_free = 0; |
364 | |
|
365 | 0 | if (oid_nr == 0) |
366 | 0 | return; |
367 | | |
368 | 0 | if (!lazy_fetch_objects(repo, &remaining_oids, &remaining_nr, &to_free)) { |
369 | 0 | for (int i = 0; i < remaining_nr; i++) { |
370 | 0 | if (is_promisor_object(repo, &remaining_oids[i])) |
371 | 0 | die(_("could not fetch %s from promisor remote"), |
372 | 0 | oid_to_hex(&remaining_oids[i])); |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | 0 | if (to_free) |
377 | 0 | free(remaining_oids); |
378 | 0 | } |
379 | | |
380 | | static int allow_unsanitized(char ch) |
381 | 0 | { |
382 | 0 | if (ch == ',' || ch == ';' || ch == '%') |
383 | 0 | return 0; |
384 | 0 | return ch > 32 && ch < 127; |
385 | 0 | } |
386 | | |
387 | | /* |
388 | | * All the fields used in "promisor-remote" protocol capability, |
389 | | * including the mandatory "name" and "url" ones. |
390 | | */ |
391 | | static const char promisor_field_name[] = "name"; |
392 | | static const char promisor_field_url[] = "url"; |
393 | | static const char promisor_field_filter[] = "partialCloneFilter"; |
394 | | static const char promisor_field_token[] = "token"; |
395 | | |
396 | | /* |
397 | | * List of optional field names that can be used in the |
398 | | * "promisor-remote" protocol capability (others must be |
399 | | * ignored). Each field should correspond to a configurable property |
400 | | * of a remote that can be relevant for the client. |
401 | | */ |
402 | | static const char *known_fields[] = { |
403 | | promisor_field_filter, /* Filter used for partial clone */ |
404 | | promisor_field_token, /* Authentication token for the remote */ |
405 | | NULL |
406 | | }; |
407 | | |
408 | | /* |
409 | | * Check if 'field' is in the list of the known field names for the |
410 | | * "promisor-remote" protocol capability. |
411 | | */ |
412 | | static int is_known_field(const char *field) |
413 | 0 | { |
414 | 0 | const char **p; |
415 | |
|
416 | 0 | for (p = known_fields; *p; p++) |
417 | 0 | if (!strcasecmp(*p, field)) |
418 | 0 | return 1; |
419 | 0 | return 0; |
420 | 0 | } |
421 | | |
422 | | static int is_valid_field(struct string_list_item *item, void *cb_data) |
423 | 0 | { |
424 | 0 | const char *field = item->string; |
425 | 0 | const char *config_key = (const char *)cb_data; |
426 | |
|
427 | 0 | if (!is_known_field(field)) { |
428 | 0 | warning(_("unsupported field '%s' in '%s' config"), field, config_key); |
429 | 0 | return 0; |
430 | 0 | } |
431 | 0 | return 1; |
432 | 0 | } |
433 | | |
434 | | static char *fields_from_config(struct string_list *fields_list, const char *config_key) |
435 | 0 | { |
436 | 0 | char *fields = NULL; |
437 | |
|
438 | 0 | if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) { |
439 | 0 | string_list_split_in_place_f(fields_list, fields, ",", -1, |
440 | 0 | STRING_LIST_SPLIT_TRIM | |
441 | 0 | STRING_LIST_SPLIT_NONEMPTY); |
442 | 0 | filter_string_list(fields_list, 0, is_valid_field, (void *)config_key); |
443 | 0 | } |
444 | |
|
445 | 0 | return fields; |
446 | 0 | } |
447 | | |
448 | | static struct string_list *initialize_fields_list(struct string_list *fields_list, int *initialized, |
449 | | const char *config_key) |
450 | 0 | { |
451 | 0 | if (!*initialized) { |
452 | 0 | fields_list->cmp = strcasecmp; |
453 | 0 | fields_from_config(fields_list, config_key); |
454 | 0 | *initialized = 1; |
455 | 0 | } |
456 | |
|
457 | 0 | return fields_list; |
458 | 0 | } |
459 | | |
460 | | static struct string_list *fields_sent(void) |
461 | 0 | { |
462 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
463 | 0 | static int initialized; |
464 | |
|
465 | 0 | return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields"); |
466 | 0 | } |
467 | | |
468 | | static struct string_list *fields_checked(void) |
469 | 0 | { |
470 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
471 | 0 | static int initialized; |
472 | |
|
473 | 0 | return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields"); |
474 | 0 | } |
475 | | |
476 | | static struct string_list *fields_stored(void) |
477 | 0 | { |
478 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
479 | 0 | static int initialized; |
480 | |
|
481 | 0 | return initialize_fields_list(&fields_list, &initialized, "promisor.storeFields"); |
482 | 0 | } |
483 | | |
484 | | /* |
485 | | * Struct for promisor remotes involved in the "promisor-remote" |
486 | | * protocol capability. |
487 | | * |
488 | | * Except for "name" and "local_name", each <member> in this struct |
489 | | * and its <value> should correspond (either on the client side or on |
490 | | * the server side) to a "remote.<name>.<member>" config variable set |
491 | | * to <value> where "<name>" is a promisor remote name. |
492 | | */ |
493 | | struct promisor_info { |
494 | | const char *name; /* name the server advertised */ |
495 | | const char *local_name; /* name used locally (may be auto-generated) */ |
496 | | const char *url; |
497 | | const char *filter; |
498 | | const char *token; |
499 | | }; |
500 | | |
501 | | static void promisor_info_free(struct promisor_info *p) |
502 | 0 | { |
503 | 0 | free((char *)p->name); |
504 | 0 | free((char *)p->local_name); |
505 | 0 | free((char *)p->url); |
506 | 0 | free((char *)p->filter); |
507 | 0 | free((char *)p->token); |
508 | 0 | free(p); |
509 | 0 | } |
510 | | |
511 | | static void promisor_info_list_clear(struct string_list *list) |
512 | 0 | { |
513 | 0 | for (size_t i = 0; i < list->nr; i++) |
514 | 0 | promisor_info_free(list->items[i].util); |
515 | 0 | string_list_clear(list, 0); |
516 | 0 | } |
517 | | |
518 | | static const char *promisor_info_local_name(struct promisor_info *p) |
519 | 0 | { |
520 | 0 | return p->local_name ? p->local_name : p->name; |
521 | 0 | } |
522 | | |
523 | | static void set_one_field(struct promisor_info *p, |
524 | | const char *field, const char *value) |
525 | 0 | { |
526 | 0 | if (!strcasecmp(field, promisor_field_filter)) |
527 | 0 | p->filter = xstrdup(value); |
528 | 0 | else if (!strcasecmp(field, promisor_field_token)) |
529 | 0 | p->token = xstrdup(value); |
530 | 0 | else |
531 | 0 | BUG("invalid field '%s'", field); |
532 | 0 | } |
533 | | |
534 | | static void set_fields(struct promisor_info *p, |
535 | | struct string_list *field_names) |
536 | 0 | { |
537 | 0 | struct string_list_item *item; |
538 | |
|
539 | 0 | for_each_string_list_item(item, field_names) { |
540 | 0 | char *key = xstrfmt("remote.%s.%s", p->name, item->string); |
541 | 0 | const char *val; |
542 | 0 | if (!repo_config_get_string_tmp(the_repository, key, &val) && *val) |
543 | 0 | set_one_field(p, item->string, val); |
544 | 0 | free(key); |
545 | 0 | } |
546 | 0 | } |
547 | | |
548 | | /* |
549 | | * Populate 'list' with promisor remote information from the config. |
550 | | * The 'util' pointer of each list item will hold a 'struct |
551 | | * promisor_info'. Except "name" and "url", only members of that |
552 | | * struct specified by the 'field_names' list are set (using values |
553 | | * from the configuration). |
554 | | */ |
555 | | static void promisor_config_info_list(struct repository *repo, |
556 | | struct string_list *list, |
557 | | struct string_list *field_names) |
558 | 0 | { |
559 | 0 | struct promisor_remote *r; |
560 | |
|
561 | 0 | promisor_remote_init(repo); |
562 | |
|
563 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
564 | 0 | const char *url; |
565 | 0 | char *url_key = xstrfmt("remote.%s.url", r->name); |
566 | | |
567 | | /* Only add remotes with a non empty URL */ |
568 | 0 | if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) { |
569 | 0 | struct promisor_info *new_info = xcalloc(1, sizeof(*new_info)); |
570 | 0 | struct string_list_item *item; |
571 | |
|
572 | 0 | new_info->name = xstrdup(r->name); |
573 | 0 | new_info->url = xstrdup(url); |
574 | |
|
575 | 0 | if (field_names) |
576 | 0 | set_fields(new_info, field_names); |
577 | |
|
578 | 0 | item = string_list_append(list, new_info->name); |
579 | 0 | item->util = new_info; |
580 | 0 | } |
581 | |
|
582 | 0 | free(url_key); |
583 | 0 | } |
584 | 0 | } |
585 | | |
586 | | char *promisor_remote_info(struct repository *repo) |
587 | 0 | { |
588 | 0 | struct strbuf sb = STRBUF_INIT; |
589 | 0 | int advertise_promisors = 0; |
590 | 0 | struct string_list config_info = STRING_LIST_INIT_NODUP; |
591 | 0 | struct string_list_item *item; |
592 | |
|
593 | 0 | repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors); |
594 | |
|
595 | 0 | if (!advertise_promisors) |
596 | 0 | return NULL; |
597 | | |
598 | 0 | promisor_config_info_list(repo, &config_info, fields_sent()); |
599 | |
|
600 | 0 | if (!config_info.nr) |
601 | 0 | return NULL; |
602 | | |
603 | 0 | for_each_string_list_item(item, &config_info) { |
604 | 0 | struct promisor_info *p = item->util; |
605 | |
|
606 | 0 | if (item != config_info.items) |
607 | 0 | strbuf_addch(&sb, ';'); |
608 | |
|
609 | 0 | strbuf_addf(&sb, "%s=", promisor_field_name); |
610 | 0 | strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized); |
611 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_url); |
612 | 0 | strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized); |
613 | |
|
614 | 0 | if (p->filter) { |
615 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_filter); |
616 | 0 | strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized); |
617 | 0 | } |
618 | 0 | if (p->token) { |
619 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_token); |
620 | 0 | strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized); |
621 | 0 | } |
622 | 0 | } |
623 | |
|
624 | 0 | promisor_info_list_clear(&config_info); |
625 | |
|
626 | 0 | return strbuf_detach(&sb, NULL); |
627 | 0 | } |
628 | | |
629 | | enum accept_promisor { |
630 | | ACCEPT_NONE = 0, |
631 | | ACCEPT_KNOWN_URL, |
632 | | ACCEPT_KNOWN_NAME, |
633 | | ACCEPT_ALL |
634 | | }; |
635 | | |
636 | | /* |
637 | | * Check if a specific field and its advertised value match the local |
638 | | * configuration of a given promisor remote. |
639 | | * |
640 | | * Returns 1 if they match, 0 otherwise. |
641 | | */ |
642 | | static int match_field_against_config(const char *field, const char *value, |
643 | | struct promisor_info *config_info) |
644 | 0 | { |
645 | 0 | if (config_info->filter && !strcasecmp(field, promisor_field_filter)) |
646 | 0 | return !strcmp(config_info->filter, value); |
647 | 0 | else if (config_info->token && !strcasecmp(field, promisor_field_token)) |
648 | 0 | return !strcmp(config_info->token, value); |
649 | | |
650 | 0 | return 0; |
651 | 0 | } |
652 | | |
653 | | /* |
654 | | * Check that the advertised fields match the local configuration. |
655 | | * |
656 | | * When 'config_entry' is NULL (ACCEPT_ALL mode), every checked field |
657 | | * must match at least one remote in 'config_info'. |
658 | | * |
659 | | * When 'config_entry' points to a specific remote's config, the |
660 | | * checked fields are compared against that single remote only. |
661 | | */ |
662 | | static int all_fields_match(struct promisor_info *advertised, |
663 | | struct string_list *config_info, |
664 | | struct promisor_info *config_entry) |
665 | 0 | { |
666 | 0 | struct string_list *fields = fields_checked(); |
667 | 0 | struct string_list_item *item_checked; |
668 | |
|
669 | 0 | for_each_string_list_item(item_checked, fields) { |
670 | 0 | int match = 0; |
671 | 0 | const char *field = item_checked->string; |
672 | 0 | const char *value = NULL; |
673 | |
|
674 | 0 | if (!strcasecmp(field, promisor_field_filter)) |
675 | 0 | value = advertised->filter; |
676 | 0 | else if (!strcasecmp(field, promisor_field_token)) |
677 | 0 | value = advertised->token; |
678 | |
|
679 | 0 | if (!value) |
680 | 0 | return 0; |
681 | | |
682 | 0 | if (config_entry) { |
683 | 0 | match = match_field_against_config(field, value, |
684 | 0 | config_entry); |
685 | 0 | } else { |
686 | 0 | struct string_list_item *item; |
687 | 0 | for_each_string_list_item(item, config_info) { |
688 | 0 | struct promisor_info *p = item->util; |
689 | 0 | if (match_field_against_config(field, value, p)) { |
690 | 0 | match = 1; |
691 | 0 | break; |
692 | 0 | } |
693 | 0 | } |
694 | 0 | } |
695 | |
|
696 | 0 | if (!match) |
697 | 0 | return 0; |
698 | 0 | } |
699 | | |
700 | 0 | return 1; |
701 | 0 | } |
702 | | |
703 | | static bool has_control_char(const char *s) |
704 | 0 | { |
705 | 0 | for (const char *c = s; *c; c++) |
706 | 0 | if (iscntrl(*c)) |
707 | 0 | return true; |
708 | 0 | return false; |
709 | 0 | } |
710 | | |
711 | | struct allowed_url { |
712 | | char *remote_name; |
713 | | char *url_pattern; |
714 | | struct url_info pattern_info; |
715 | | }; |
716 | | |
717 | | static void allowed_url_free(void *util, const char *str UNUSED) |
718 | 0 | { |
719 | 0 | struct allowed_url *allowed = util; |
720 | |
|
721 | 0 | if (!allowed) |
722 | 0 | return; |
723 | | |
724 | | /* Depending on prefix, free either remote_name or url_pattern */ |
725 | 0 | free(allowed->remote_name ? allowed->remote_name : allowed->url_pattern); |
726 | 0 | free(allowed->pattern_info.url); |
727 | 0 | free(allowed); |
728 | 0 | } |
729 | | |
730 | | static struct allowed_url *valid_accept_url(const char *url) |
731 | 0 | { |
732 | 0 | char *dup, *p; |
733 | 0 | struct allowed_url *allowed; |
734 | |
|
735 | 0 | if (!url) |
736 | 0 | return NULL; |
737 | | |
738 | 0 | dup = xstrdup(url); |
739 | 0 | p = strchr(dup, '='); |
740 | 0 | if (p) { |
741 | 0 | *p = '\0'; |
742 | 0 | if (!valid_remote_name(dup)) { |
743 | 0 | warning(_("invalid remote name '%s' before '=' sign " |
744 | 0 | "in '%s' from promisor.acceptFromServerUrl config"), |
745 | 0 | dup, url); |
746 | 0 | free(dup); |
747 | 0 | return NULL; |
748 | 0 | } |
749 | 0 | p++; |
750 | 0 | } else { |
751 | 0 | p = dup; |
752 | 0 | } |
753 | | |
754 | 0 | if (has_control_char(p)) { |
755 | 0 | warning(_("invalid url pattern '%s' " |
756 | 0 | "in '%s' from promisor.acceptFromServerUrl config"), p, url); |
757 | 0 | free(dup); |
758 | 0 | return NULL; |
759 | 0 | } |
760 | | |
761 | 0 | allowed = xmalloc(sizeof(*allowed)); |
762 | 0 | allowed->remote_name = (p == dup) ? NULL : dup; |
763 | 0 | allowed->url_pattern = p; |
764 | 0 | allowed->pattern_info.url = url_normalize_pattern(p, &allowed->pattern_info); |
765 | 0 | if (!allowed->pattern_info.url) { |
766 | 0 | warning(_("invalid url pattern '%s' " |
767 | 0 | "in '%s' from promisor.acceptFromServerUrl config"), p, url); |
768 | 0 | free(dup); |
769 | 0 | free(allowed); |
770 | 0 | return NULL; |
771 | 0 | } |
772 | | |
773 | 0 | return allowed; |
774 | 0 | } |
775 | | |
776 | | static void load_accept_from_server_url(struct repository *repo, |
777 | | struct string_list *accept_urls) |
778 | 0 | { |
779 | 0 | const struct string_list *config_urls; |
780 | |
|
781 | 0 | if (!repo_config_get_string_multi(repo, "promisor.acceptfromserverurl", &config_urls)) { |
782 | 0 | struct string_list_item *item; |
783 | |
|
784 | 0 | for_each_string_list_item(item, config_urls) { |
785 | 0 | struct allowed_url *allowed = valid_accept_url(item->string); |
786 | 0 | if (allowed) { |
787 | 0 | struct string_list_item *new; |
788 | 0 | new = string_list_append(accept_urls, item->string); |
789 | 0 | new->util = allowed; |
790 | 0 | } |
791 | 0 | } |
792 | 0 | } |
793 | 0 | } |
794 | | |
795 | | static bool match_pattern_url(const char *pat, size_t pat_len, |
796 | | const char *url, size_t url_len) |
797 | 0 | { |
798 | 0 | char *p_str = xstrndup(pat, pat_len); |
799 | 0 | char *u_str = xstrndup(url, url_len); |
800 | 0 | bool res = !wildmatch(p_str, u_str, 0); |
801 | |
|
802 | 0 | free(p_str); |
803 | 0 | free(u_str); |
804 | |
|
805 | 0 | return res; |
806 | 0 | } |
807 | | |
808 | | static bool match_one_url(const struct url_info *pi, const struct url_info *ui) |
809 | 0 | { |
810 | 0 | const char *pat = pi->url; |
811 | 0 | const char *url = ui->url; |
812 | | |
813 | | /* |
814 | | * Schemes must match exactly. They are case-folded by |
815 | | * url_normalize(), so strncmp() suffices. |
816 | | */ |
817 | 0 | if (pi->scheme_len != ui->scheme_len || strncmp(pat, url, pi->scheme_len)) |
818 | 0 | return false; |
819 | | |
820 | | /* |
821 | | * Ports must match exactly. url_normalize() strips default |
822 | | * ports (like 443 for https), so length and content |
823 | | * comparisons are sufficient. |
824 | | */ |
825 | 0 | if (pi->port_len != ui->port_len || |
826 | 0 | strncmp(pat + pi->port_off, url + ui->port_off, pi->port_len)) |
827 | 0 | return false; |
828 | | |
829 | | /* |
830 | | * Match host and path separately to prevent a '*' in the host |
831 | | * portion of the pattern from matching across the '/' |
832 | | * boundary into the path. |
833 | | */ |
834 | | |
835 | 0 | return match_pattern_url(pat + pi->host_off, pi->host_len, |
836 | 0 | url + ui->host_off, ui->host_len) && |
837 | 0 | match_pattern_url(pat + pi->path_off, pi->path_len, |
838 | 0 | url + ui->path_off, ui->path_len); |
839 | 0 | } |
840 | | |
841 | | static struct allowed_url *url_matches_accept_list( |
842 | | struct string_list *accept_urls, const char *url) |
843 | 0 | { |
844 | 0 | struct string_list_item *item; |
845 | 0 | struct url_info url_info; |
846 | |
|
847 | 0 | url_info.url = url_normalize(url, &url_info); |
848 | |
|
849 | 0 | if (!url_info.url) |
850 | 0 | return NULL; |
851 | | |
852 | 0 | for_each_string_list_item(item, accept_urls) { |
853 | 0 | struct allowed_url *allowed = item->util; |
854 | |
|
855 | 0 | if (match_one_url(&allowed->pattern_info, &url_info)) { |
856 | 0 | free(url_info.url); |
857 | 0 | return allowed; |
858 | 0 | } |
859 | 0 | } |
860 | | |
861 | 0 | free(url_info.url); |
862 | 0 | return NULL; |
863 | 0 | } |
864 | | |
865 | | /* |
866 | | * Sanitize the buffer to make it a valid remote name coming from the |
867 | | * server by: |
868 | | * |
869 | | * - replacing any non alphanumeric character with a '-' |
870 | | * - stripping any leading '-', |
871 | | * - condensing multiple '-' into one, |
872 | | * - prepending "promisor-auto-", |
873 | | * - validating the result. |
874 | | */ |
875 | | static int sanitize_remote_name(struct strbuf *buf, const char *url) |
876 | 0 | { |
877 | 0 | char prev = '-'; |
878 | 0 | for (size_t i = 0; i < buf->len; ) { |
879 | 0 | if (!isalnum(buf->buf[i])) |
880 | 0 | buf->buf[i] = '-'; |
881 | 0 | if (prev == '-' && buf->buf[i] == '-') { |
882 | 0 | strbuf_remove(buf, i, 1); |
883 | 0 | } else { |
884 | 0 | prev = buf->buf[i]; |
885 | 0 | i++; |
886 | 0 | } |
887 | 0 | } |
888 | |
|
889 | 0 | strbuf_strip_suffix(buf, "-"); |
890 | |
|
891 | 0 | if (!buf->len) { |
892 | 0 | warning(_("couldn't generate a valid remote name from " |
893 | 0 | "advertised url '%s', ignoring this remote"), url); |
894 | 0 | return -1; |
895 | 0 | } |
896 | | |
897 | 0 | strbuf_insertstr(buf, 0, "promisor-auto-"); |
898 | |
|
899 | 0 | if (!valid_remote_name(buf->buf)) { |
900 | 0 | warning(_("generated remote name '%s' from advertised url '%s' " |
901 | 0 | "is invalid, ignoring this remote"), buf->buf, url); |
902 | 0 | return -1; |
903 | 0 | } |
904 | | |
905 | 0 | return 0; |
906 | 0 | } |
907 | | |
908 | | static char *promisor_remote_name_from_url(const char *url) |
909 | 0 | { |
910 | 0 | struct url_info url_info = { 0 }; |
911 | 0 | char *normalized = url_normalize(url, &url_info); |
912 | 0 | struct strbuf buf = STRBUF_INIT; |
913 | |
|
914 | 0 | if (!normalized) { |
915 | 0 | warning(_("couldn't normalize advertised url '%s', " |
916 | 0 | "ignoring this remote"), url); |
917 | 0 | return NULL; |
918 | 0 | } |
919 | | |
920 | 0 | if (url_info.host_len) { |
921 | 0 | strbuf_add(&buf, normalized + url_info.host_off, url_info.host_len); |
922 | 0 | strbuf_addch(&buf, '-'); |
923 | 0 | } |
924 | |
|
925 | 0 | if (url_info.port_len) { |
926 | 0 | strbuf_add(&buf, normalized + url_info.port_off, url_info.port_len); |
927 | 0 | strbuf_addch(&buf, '-'); |
928 | 0 | } |
929 | |
|
930 | 0 | if (url_info.path_len) { |
931 | 0 | strbuf_add(&buf, normalized + url_info.path_off, url_info.path_len); |
932 | 0 | strbuf_trim_trailing_dir_sep(&buf); |
933 | 0 | strbuf_strip_suffix(&buf, ".git"); |
934 | 0 | } |
935 | |
|
936 | 0 | free(normalized); |
937 | |
|
938 | 0 | if (sanitize_remote_name(&buf, url)) { |
939 | 0 | strbuf_release(&buf); |
940 | 0 | return NULL; |
941 | 0 | } |
942 | | |
943 | 0 | return strbuf_detach(&buf, NULL); |
944 | 0 | } |
945 | | |
946 | | static void configure_auto_promisor_remote(struct repository *repo, |
947 | | const char *name, |
948 | | const char *url, |
949 | | const char *advertised_as, |
950 | | bool reuse) |
951 | 0 | { |
952 | 0 | char *key; |
953 | |
|
954 | 0 | if (!reuse) { |
955 | 0 | fprintf(stderr, _("Auto-creating promisor remote '%s' for URL '%s'\n"), |
956 | 0 | name, url); |
957 | |
|
958 | 0 | key = xstrfmt("remote.%s.url", name); |
959 | 0 | repo_config_set_gently(repo, key, url); |
960 | 0 | free(key); |
961 | 0 | } |
962 | | |
963 | | /* NB: when reusing, this promotes an existing non-promisor remote */ |
964 | 0 | key = xstrfmt("remote.%s.promisor", name); |
965 | 0 | repo_config_set_gently(repo, key, "true"); |
966 | 0 | free(key); |
967 | |
|
968 | 0 | if (advertised_as) { |
969 | 0 | key = xstrfmt("remote.%s.advertisedAs", name); |
970 | 0 | repo_config_set_gently(repo, key, advertised_as); |
971 | 0 | free(key); |
972 | 0 | } |
973 | 0 | } |
974 | | |
975 | 0 | #define MAX_REMOTES_WITH_SIMILAR_NAMES 20 |
976 | | |
977 | | /* Return the allocated local name, or NULL on failure */ |
978 | | static char *handle_matching_allowed_url(struct repository *repo, |
979 | | char *allowed_name, |
980 | | const char *remote_url, |
981 | | const char *remote_name) |
982 | 0 | { |
983 | 0 | char *name; |
984 | 0 | char *basename = allowed_name ? |
985 | 0 | xstrdup(allowed_name) : |
986 | 0 | promisor_remote_name_from_url(remote_url); |
987 | 0 | int i = 0; |
988 | 0 | bool reuse = false; |
989 | |
|
990 | 0 | if (!basename) |
991 | 0 | return NULL; |
992 | | |
993 | 0 | name = xstrdup(basename); |
994 | |
|
995 | 0 | while (i < MAX_REMOTES_WITH_SIMILAR_NAMES) { |
996 | 0 | char *url_key = xstrfmt("remote.%s.url", name); |
997 | 0 | const char *existing_url; |
998 | 0 | int exists = !repo_config_get_string_tmp(repo, url_key, &existing_url); |
999 | |
|
1000 | 0 | free(url_key); |
1001 | |
|
1002 | 0 | if (!exists) |
1003 | 0 | break; /* Free to use */ |
1004 | | |
1005 | 0 | if (!strcmp(existing_url, remote_url)) { |
1006 | 0 | reuse = true; |
1007 | 0 | break; /* Same URL, so safe to reuse */ |
1008 | 0 | } |
1009 | | |
1010 | 0 | i++; |
1011 | 0 | free(name); |
1012 | 0 | name = xstrfmt("%s-%d", basename, i); |
1013 | 0 | } |
1014 | |
|
1015 | 0 | if (i < MAX_REMOTES_WITH_SIMILAR_NAMES) { |
1016 | 0 | configure_auto_promisor_remote(repo, name, |
1017 | 0 | remote_url, remote_name, |
1018 | 0 | reuse); |
1019 | 0 | } else { |
1020 | 0 | warning(_("too many remotes accepted with name like '%s-X', " |
1021 | 0 | "ignoring this remote"), basename); |
1022 | 0 | FREE_AND_NULL(name); |
1023 | 0 | } |
1024 | |
|
1025 | 0 | free(basename); |
1026 | 0 | return name; |
1027 | 0 | } |
1028 | | |
1029 | | static int should_accept_new_remote_url(struct repository *repo, |
1030 | | struct string_list *accept_urls, |
1031 | | struct promisor_info *advertised) |
1032 | 0 | { |
1033 | 0 | struct allowed_url *allowed = url_matches_accept_list(accept_urls, |
1034 | 0 | advertised->url); |
1035 | 0 | if (allowed) { |
1036 | 0 | char *name = handle_matching_allowed_url(repo, |
1037 | 0 | allowed->remote_name, |
1038 | 0 | advertised->url, |
1039 | 0 | advertised->name); |
1040 | 0 | if (name) { |
1041 | 0 | free((char *)advertised->local_name); |
1042 | 0 | advertised->local_name = name; |
1043 | 0 | return 1; |
1044 | 0 | } |
1045 | 0 | } |
1046 | | |
1047 | 0 | return 0; |
1048 | 0 | } |
1049 | | |
1050 | | static int should_accept_remote(struct repository *repo, |
1051 | | enum accept_promisor accept, |
1052 | | struct promisor_info *advertised, |
1053 | | struct string_list *accept_urls, |
1054 | | struct string_list *config_info, |
1055 | | bool *reload_config) |
1056 | 0 | { |
1057 | 0 | struct promisor_info *p; |
1058 | 0 | struct string_list_item *item; |
1059 | 0 | const char *remote_name = advertised->name; |
1060 | 0 | const char *remote_url = advertised->url; |
1061 | |
|
1062 | 0 | if (!remote_url || !*remote_url) |
1063 | 0 | BUG("no or empty URL advertised for remote '%s'; " |
1064 | 0 | "this remote should have been rejected earlier", |
1065 | 0 | remote_name); |
1066 | | |
1067 | | /* Get config info for that promisor remote */ |
1068 | 0 | item = string_list_lookup(config_info, remote_name); |
1069 | |
|
1070 | 0 | if (!item) { |
1071 | | /* We don't know about that remote */ |
1072 | |
|
1073 | 0 | int res = should_accept_new_remote_url(repo, accept_urls, advertised); |
1074 | 0 | if (res) { |
1075 | 0 | *reload_config = true; |
1076 | 0 | return res; |
1077 | 0 | } |
1078 | | |
1079 | 0 | if (accept == ACCEPT_ALL) |
1080 | 0 | return all_fields_match(advertised, config_info, NULL); |
1081 | 0 | return 0; |
1082 | 0 | } |
1083 | | |
1084 | 0 | p = item->util; |
1085 | | |
1086 | | /* Known remote in the allowlist? */ |
1087 | 0 | if (!strcmp(p->url, remote_url) && url_matches_accept_list(accept_urls, remote_url)) |
1088 | 0 | return all_fields_match(advertised, config_info, p); |
1089 | | |
1090 | 0 | if (accept == ACCEPT_ALL) |
1091 | 0 | return all_fields_match(advertised, config_info, NULL); |
1092 | | |
1093 | 0 | if (accept == ACCEPT_KNOWN_NAME) |
1094 | 0 | return all_fields_match(advertised, config_info, p); |
1095 | | |
1096 | 0 | if (strcmp(p->url, remote_url)) { |
1097 | 0 | warning(_("known remote named '%s' but with URL '%s' instead of '%s', " |
1098 | 0 | "ignoring this remote"), |
1099 | 0 | remote_name, p->url, remote_url); |
1100 | 0 | return 0; |
1101 | 0 | } |
1102 | | |
1103 | 0 | if (accept == ACCEPT_KNOWN_URL) |
1104 | 0 | return all_fields_match(advertised, config_info, p); |
1105 | | |
1106 | 0 | if (accept != ACCEPT_NONE) |
1107 | 0 | BUG("Unhandled 'enum accept_promisor' value '%d'", accept); |
1108 | | |
1109 | 0 | return 0; |
1110 | 0 | } |
1111 | | |
1112 | | static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value) |
1113 | 0 | { |
1114 | 0 | const char *p; |
1115 | 0 | if (!skip_prefix(elem, field_name, &p) || *p != '=') |
1116 | 0 | return 0; |
1117 | 0 | *value = p + 1; |
1118 | 0 | return 1; |
1119 | 0 | } |
1120 | | |
1121 | | static struct promisor_info *parse_one_advertised_remote(const char *remote_info) |
1122 | 0 | { |
1123 | 0 | struct promisor_info *info = xcalloc(1, sizeof(*info)); |
1124 | 0 | struct string_list elem_list = STRING_LIST_INIT_DUP; |
1125 | 0 | struct string_list_item *item; |
1126 | |
|
1127 | 0 | string_list_split(&elem_list, remote_info, ",", -1); |
1128 | |
|
1129 | 0 | for_each_string_list_item(item, &elem_list) { |
1130 | 0 | const char *elem = item->string; |
1131 | 0 | const char *p = strchr(elem, '='); |
1132 | |
|
1133 | 0 | if (!p) { |
1134 | 0 | warning(_("invalid element '%s' from remote info"), elem); |
1135 | 0 | continue; |
1136 | 0 | } |
1137 | | |
1138 | 0 | if (skip_field_name_prefix(elem, promisor_field_name, &p)) |
1139 | 0 | info->name = url_percent_decode(p); |
1140 | 0 | else if (skip_field_name_prefix(elem, promisor_field_url, &p)) |
1141 | 0 | info->url = url_percent_decode(p); |
1142 | 0 | else if (skip_field_name_prefix(elem, promisor_field_filter, &p)) |
1143 | 0 | info->filter = url_percent_decode(p); |
1144 | 0 | else if (skip_field_name_prefix(elem, promisor_field_token, &p)) |
1145 | 0 | info->token = url_percent_decode(p); |
1146 | 0 | } |
1147 | |
|
1148 | 0 | string_list_clear(&elem_list, 0); |
1149 | |
|
1150 | 0 | if (!info->name || !*info->name || !info->url || !*info->url) { |
1151 | 0 | warning(_("server advertised a promisor remote without a name or URL: '%s', " |
1152 | 0 | "ignoring this remote"), remote_info); |
1153 | 0 | promisor_info_free(info); |
1154 | 0 | return NULL; |
1155 | 0 | } |
1156 | | |
1157 | 0 | return info; |
1158 | 0 | } |
1159 | | |
1160 | | static bool store_one_field(struct repository *repo, const char *remote_name, |
1161 | | const char *field_name, const char *field_key, |
1162 | | const char *advertised, const char *current) |
1163 | 0 | { |
1164 | 0 | if (advertised && (!current || strcmp(current, advertised))) { |
1165 | 0 | char *key = xstrfmt("remote.%s.%s", remote_name, field_key); |
1166 | |
|
1167 | 0 | fprintf(stderr, _("Storing new %s from server for remote '%s'.\n" |
1168 | 0 | " '%s' -> '%s'\n"), |
1169 | 0 | field_name, remote_name, |
1170 | 0 | current ? current : "", |
1171 | 0 | advertised); |
1172 | |
|
1173 | 0 | repo_config_set_gently(repo, key, advertised); |
1174 | 0 | free(key); |
1175 | |
|
1176 | 0 | return true; |
1177 | 0 | } |
1178 | | |
1179 | 0 | return false; |
1180 | 0 | } |
1181 | | |
1182 | | /* Check that a filter is valid by parsing it */ |
1183 | | static bool valid_filter(const char *filter, const char *remote_name) |
1184 | 0 | { |
1185 | 0 | struct list_objects_filter_options filter_opts = LIST_OBJECTS_FILTER_INIT; |
1186 | 0 | struct strbuf err = STRBUF_INIT; |
1187 | 0 | int res = gently_parse_list_objects_filter(&filter_opts, filter, &err); |
1188 | |
|
1189 | 0 | if (res) |
1190 | 0 | warning(_("invalid filter '%s' for remote '%s' " |
1191 | 0 | "will not be stored: %s"), |
1192 | 0 | filter, remote_name, err.buf); |
1193 | |
|
1194 | 0 | list_objects_filter_release(&filter_opts); |
1195 | 0 | strbuf_release(&err); |
1196 | |
|
1197 | 0 | return !res; |
1198 | 0 | } |
1199 | | |
1200 | | static bool valid_token(const char *token, const char *remote_name) |
1201 | 0 | { |
1202 | 0 | if (has_control_char(token)) { |
1203 | 0 | warning(_("invalid token '%s' for remote '%s' " |
1204 | 0 | "will not be stored"), |
1205 | 0 | token, remote_name); |
1206 | 0 | return false; |
1207 | 0 | } |
1208 | | |
1209 | 0 | return true; |
1210 | 0 | } |
1211 | | |
1212 | | struct store_info { |
1213 | | struct repository *repo; |
1214 | | struct string_list config_info; |
1215 | | bool store_filter; |
1216 | | bool store_token; |
1217 | | }; |
1218 | | |
1219 | | static struct store_info *store_info_new(struct repository *repo) |
1220 | 0 | { |
1221 | 0 | struct string_list *fields_to_store = fields_stored(); |
1222 | 0 | struct store_info *s = xmalloc(sizeof(*s)); |
1223 | |
|
1224 | 0 | s->repo = repo; |
1225 | |
|
1226 | 0 | string_list_init_nodup(&s->config_info); |
1227 | 0 | promisor_config_info_list(repo, &s->config_info, fields_to_store); |
1228 | 0 | string_list_sort(&s->config_info); |
1229 | |
|
1230 | 0 | s->store_filter = !!string_list_lookup(fields_to_store, promisor_field_filter); |
1231 | 0 | s->store_token = !!string_list_lookup(fields_to_store, promisor_field_token); |
1232 | |
|
1233 | 0 | return s; |
1234 | 0 | } |
1235 | | |
1236 | | static void store_info_free(struct store_info *s) |
1237 | 0 | { |
1238 | 0 | if (s) { |
1239 | 0 | promisor_info_list_clear(&s->config_info); |
1240 | 0 | free(s); |
1241 | 0 | } |
1242 | 0 | } |
1243 | | |
1244 | | static bool promisor_store_advertised_fields(struct promisor_info *advertised, |
1245 | | struct store_info *store_info) |
1246 | 0 | { |
1247 | 0 | struct promisor_info *p; |
1248 | 0 | struct string_list_item *item; |
1249 | 0 | const char *remote_name = promisor_info_local_name(advertised); |
1250 | 0 | bool reload_config = false; |
1251 | |
|
1252 | 0 | if (!(store_info->store_filter || store_info->store_token)) |
1253 | 0 | return false; |
1254 | | |
1255 | | /* |
1256 | | * Get existing config info for the advertised promisor |
1257 | | * remote. This ensures the remote is already configured on |
1258 | | * the client side. |
1259 | | */ |
1260 | 0 | item = string_list_lookup(&store_info->config_info, remote_name); |
1261 | |
|
1262 | 0 | if (!item) |
1263 | 0 | return false; |
1264 | | |
1265 | 0 | p = item->util; |
1266 | |
|
1267 | 0 | if (store_info->store_filter && advertised->filter && |
1268 | 0 | valid_filter(advertised->filter, remote_name)) |
1269 | 0 | reload_config |= store_one_field(store_info->repo, remote_name, |
1270 | 0 | "filter", promisor_field_filter, |
1271 | 0 | advertised->filter, p->filter); |
1272 | |
|
1273 | 0 | if (store_info->store_token && advertised->token && |
1274 | 0 | valid_token(advertised->token, remote_name)) |
1275 | 0 | reload_config |= store_one_field(store_info->repo, remote_name, |
1276 | 0 | "token", promisor_field_token, |
1277 | 0 | advertised->token, p->token); |
1278 | |
|
1279 | 0 | return reload_config; |
1280 | 0 | } |
1281 | | |
1282 | | static enum accept_promisor accept_from_server(struct repository *repo) |
1283 | 0 | { |
1284 | 0 | const char *accept_str; |
1285 | 0 | enum accept_promisor accept = ACCEPT_NONE; |
1286 | |
|
1287 | 0 | if (!repo_config_get_string_tmp(repo, "promisor.acceptfromserver", &accept_str)) { |
1288 | 0 | if (!*accept_str || !strcasecmp("None", accept_str)) |
1289 | 0 | accept = ACCEPT_NONE; |
1290 | 0 | else if (!strcasecmp("KnownUrl", accept_str)) |
1291 | 0 | accept = ACCEPT_KNOWN_URL; |
1292 | 0 | else if (!strcasecmp("KnownName", accept_str)) |
1293 | 0 | accept = ACCEPT_KNOWN_NAME; |
1294 | 0 | else if (!strcasecmp("All", accept_str)) |
1295 | 0 | accept = ACCEPT_ALL; |
1296 | 0 | else |
1297 | 0 | warning(_("unknown '%s' value for '%s' config option"), |
1298 | 0 | accept_str, "promisor.acceptfromserver"); |
1299 | 0 | } |
1300 | |
|
1301 | 0 | return accept; |
1302 | 0 | } |
1303 | | |
1304 | | static void filter_promisor_remote(struct repository *repo, |
1305 | | struct string_list *accepted_remotes, |
1306 | | const char *info) |
1307 | 0 | { |
1308 | 0 | struct string_list config_info = STRING_LIST_INIT_NODUP; |
1309 | 0 | struct string_list remote_info = STRING_LIST_INIT_DUP; |
1310 | 0 | struct store_info *store_info = NULL; |
1311 | 0 | struct string_list_item *item; |
1312 | 0 | bool reload_config = false; |
1313 | 0 | enum accept_promisor accept = accept_from_server(repo); |
1314 | 0 | struct string_list accept_urls = STRING_LIST_INIT_DUP; |
1315 | | |
1316 | | /* Load and validate the acceptFromServerUrl config */ |
1317 | 0 | load_accept_from_server_url(repo, &accept_urls); |
1318 | |
|
1319 | 0 | if (accept == ACCEPT_NONE && !accept_urls.nr) |
1320 | 0 | return; |
1321 | | |
1322 | | /* Parse remote info received */ |
1323 | | |
1324 | 0 | string_list_split(&remote_info, info, ";", -1); |
1325 | |
|
1326 | 0 | for_each_string_list_item(item, &remote_info) { |
1327 | 0 | struct promisor_info *advertised; |
1328 | |
|
1329 | 0 | advertised = parse_one_advertised_remote(item->string); |
1330 | |
|
1331 | 0 | if (!advertised) |
1332 | 0 | continue; |
1333 | | |
1334 | 0 | if (!config_info.nr) { |
1335 | 0 | promisor_config_info_list(repo, &config_info, fields_checked()); |
1336 | 0 | string_list_sort(&config_info); |
1337 | 0 | } |
1338 | |
|
1339 | 0 | if (should_accept_remote(repo, accept, advertised, &accept_urls, |
1340 | 0 | &config_info, &reload_config)) { |
1341 | 0 | if (!store_info) |
1342 | 0 | store_info = store_info_new(repo); |
1343 | 0 | if (promisor_store_advertised_fields(advertised, store_info)) |
1344 | 0 | reload_config = true; |
1345 | |
|
1346 | 0 | string_list_append(accepted_remotes, advertised->name)->util = advertised; |
1347 | 0 | } else { |
1348 | 0 | promisor_info_free(advertised); |
1349 | 0 | } |
1350 | 0 | } |
1351 | |
|
1352 | 0 | string_list_clear_func(&accept_urls, allowed_url_free); |
1353 | 0 | promisor_info_list_clear(&config_info); |
1354 | 0 | string_list_clear(&remote_info, 0); |
1355 | 0 | store_info_free(store_info); |
1356 | |
|
1357 | 0 | if (reload_config) |
1358 | 0 | repo_promisor_remote_reinit(repo); |
1359 | | |
1360 | | /* Apply accepted remotes to the stable repo state */ |
1361 | 0 | for_each_string_list_item(item, accepted_remotes) { |
1362 | 0 | struct promisor_info *info = item->util; |
1363 | 0 | const char *remote_name = promisor_info_local_name(info); |
1364 | 0 | struct promisor_remote *r = repo_promisor_remote_find(repo, remote_name); |
1365 | |
|
1366 | 0 | if (r) { |
1367 | 0 | r->accepted = 1; |
1368 | 0 | if (info->filter) { |
1369 | 0 | free(r->advertised_filter); |
1370 | 0 | r->advertised_filter = xstrdup(info->filter); |
1371 | 0 | } |
1372 | 0 | } |
1373 | 0 | } |
1374 | 0 | } |
1375 | | |
1376 | | void promisor_remote_reply(const char *info, char **accepted_out) |
1377 | 0 | { |
1378 | 0 | struct string_list accepted_remotes = STRING_LIST_INIT_NODUP; |
1379 | |
|
1380 | 0 | filter_promisor_remote(the_repository, &accepted_remotes, info); |
1381 | |
|
1382 | 0 | if (accepted_out) { |
1383 | 0 | if (accepted_remotes.nr) { |
1384 | 0 | struct strbuf reply = STRBUF_INIT; |
1385 | 0 | struct string_list_item *item; |
1386 | |
|
1387 | 0 | for_each_string_list_item(item, &accepted_remotes) { |
1388 | 0 | if (reply.len) |
1389 | 0 | strbuf_addch(&reply, ';'); |
1390 | 0 | strbuf_addstr_urlencode(&reply, item->string, allow_unsanitized); |
1391 | 0 | } |
1392 | 0 | *accepted_out = strbuf_detach(&reply, NULL); |
1393 | 0 | } else { |
1394 | 0 | *accepted_out = NULL; |
1395 | 0 | } |
1396 | 0 | } |
1397 | |
|
1398 | 0 | promisor_info_list_clear(&accepted_remotes); |
1399 | 0 | } |
1400 | | |
1401 | | void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes) |
1402 | 0 | { |
1403 | 0 | struct string_list accepted_remotes = STRING_LIST_INIT_DUP; |
1404 | 0 | struct string_list_item *item; |
1405 | |
|
1406 | 0 | string_list_split(&accepted_remotes, remotes, ";", -1); |
1407 | |
|
1408 | 0 | for_each_string_list_item(item, &accepted_remotes) { |
1409 | 0 | char *decoded_remote = url_percent_decode(item->string); |
1410 | 0 | struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote); |
1411 | |
|
1412 | 0 | if (p) |
1413 | 0 | p->accepted = 1; |
1414 | 0 | else |
1415 | 0 | warning(_("accepted promisor remote '%s' not found"), |
1416 | 0 | decoded_remote); |
1417 | |
|
1418 | 0 | free(decoded_remote); |
1419 | 0 | } |
1420 | |
|
1421 | 0 | string_list_clear(&accepted_remotes, 0); |
1422 | 0 | } |
1423 | | |
1424 | | char *promisor_remote_construct_filter(struct repository *repo) |
1425 | 0 | { |
1426 | 0 | struct promisor_remote *r; |
1427 | 0 | struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; |
1428 | 0 | struct strbuf err = STRBUF_INIT; |
1429 | 0 | char *result = NULL; |
1430 | |
|
1431 | 0 | promisor_remote_init(repo); |
1432 | |
|
1433 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
1434 | 0 | if (r->accepted && r->advertised_filter) |
1435 | 0 | if (gently_parse_list_objects_filter(&filter_options, |
1436 | 0 | r->advertised_filter, |
1437 | 0 | &err)) { |
1438 | 0 | warning(_("promisor remote '%s' advertised invalid filter '%s': %s"), |
1439 | 0 | r->name, r->advertised_filter, err.buf); |
1440 | 0 | strbuf_reset(&err); |
1441 | 0 | continue; |
1442 | 0 | } |
1443 | 0 | } |
1444 | |
|
1445 | 0 | if (filter_options.choice) |
1446 | 0 | result = xstrdup(expand_list_objects_filter_spec(&filter_options)); |
1447 | |
|
1448 | 0 | list_objects_filter_release(&filter_options); |
1449 | 0 | strbuf_release(&err); |
1450 | |
|
1451 | 0 | return result; |
1452 | 0 | } |