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