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