Coverage Report

Created: 2024-09-08 06:23

/src/git/credential.c
Line
Count
Source (jump to first uncovered line)
1
#define USE_THE_REPOSITORY_VARIABLE
2
3
#include "git-compat-util.h"
4
#include "abspath.h"
5
#include "config.h"
6
#include "credential.h"
7
#include "gettext.h"
8
#include "string-list.h"
9
#include "run-command.h"
10
#include "url.h"
11
#include "prompt.h"
12
#include "sigchain.h"
13
#include "strbuf.h"
14
#include "urlmatch.h"
15
#include "git-compat-util.h"
16
17
void credential_init(struct credential *c)
18
0
{
19
0
  struct credential blank = CREDENTIAL_INIT;
20
0
  memcpy(c, &blank, sizeof(*c));
21
0
}
22
23
void credential_clear(struct credential *c)
24
0
{
25
0
  credential_clear_secrets(c);
26
0
  free(c->protocol);
27
0
  free(c->host);
28
0
  free(c->path);
29
0
  free(c->username);
30
0
  free(c->oauth_refresh_token);
31
0
  free(c->authtype);
32
0
  string_list_clear(&c->helpers, 0);
33
0
  strvec_clear(&c->wwwauth_headers);
34
0
  strvec_clear(&c->state_headers);
35
0
  strvec_clear(&c->state_headers_to_send);
36
37
0
  credential_init(c);
38
0
}
39
40
void credential_next_state(struct credential *c)
41
0
{
42
0
  strvec_clear(&c->state_headers_to_send);
43
0
  SWAP(c->state_headers, c->state_headers_to_send);
44
0
}
45
46
void credential_clear_secrets(struct credential *c)
47
0
{
48
0
  FREE_AND_NULL(c->password);
49
0
  FREE_AND_NULL(c->credential);
50
0
}
51
52
static void credential_set_capability(struct credential_capability *capa,
53
              enum credential_op_type op_type)
54
0
{
55
0
  switch (op_type) {
56
0
  case CREDENTIAL_OP_INITIAL:
57
0
    capa->request_initial = 1;
58
0
    break;
59
0
  case CREDENTIAL_OP_HELPER:
60
0
    capa->request_helper = 1;
61
0
    break;
62
0
  case CREDENTIAL_OP_RESPONSE:
63
0
    capa->response = 1;
64
0
    break;
65
0
  }
66
0
}
67
68
69
void credential_set_all_capabilities(struct credential *c,
70
             enum credential_op_type op_type)
71
0
{
72
0
  credential_set_capability(&c->capa_authtype, op_type);
73
0
  credential_set_capability(&c->capa_state, op_type);
74
0
}
75
76
0
static void announce_one(struct credential_capability *cc, const char *name, FILE *fp) {
77
0
  if (cc->request_initial)
78
0
    fprintf(fp, "capability %s\n", name);
79
0
}
80
81
0
void credential_announce_capabilities(struct credential *c, FILE *fp) {
82
0
  fprintf(fp, "version 0\n");
83
0
  announce_one(&c->capa_authtype, "authtype", fp);
84
0
  announce_one(&c->capa_state, "state", fp);
85
0
}
86
87
int credential_match(const struct credential *want,
88
         const struct credential *have, int match_password)
89
0
{
90
0
#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
91
0
  return CHECK(protocol) &&
92
0
         CHECK(host) &&
93
0
         CHECK(path) &&
94
0
         CHECK(username) &&
95
0
         (!match_password || CHECK(password)) &&
96
0
         (!match_password || CHECK(credential));
97
0
#undef CHECK
98
0
}
99
100
101
static int credential_from_potentially_partial_url(struct credential *c,
102
               const char *url);
103
104
static int credential_config_callback(const char *var, const char *value,
105
              const struct config_context *ctx UNUSED,
106
              void *data)
107
0
{
108
0
  struct credential *c = data;
109
0
  const char *key;
110
111
0
  if (!skip_prefix(var, "credential.", &key))
112
0
    return 0;
113
114
0
  if (!value)
115
0
    return config_error_nonbool(var);
116
117
0
  if (!strcmp(key, "helper")) {
118
0
    if (*value)
119
0
      string_list_append(&c->helpers, value);
120
0
    else
121
0
      string_list_clear(&c->helpers, 0);
122
0
  } else if (!strcmp(key, "username")) {
123
0
    if (!c->username_from_proto) {
124
0
      free(c->username);
125
0
      c->username = xstrdup(value);
126
0
    }
127
0
  }
128
0
  else if (!strcmp(key, "usehttppath"))
129
0
    c->use_http_path = git_config_bool(var, value);
130
131
0
  return 0;
132
0
}
133
134
static int proto_is_http(const char *s)
135
0
{
136
0
  if (!s)
137
0
    return 0;
138
0
  return !strcmp(s, "https") || !strcmp(s, "http");
139
0
}
140
141
static void credential_describe(struct credential *c, struct strbuf *out);
142
static void credential_format(struct credential *c, struct strbuf *out);
143
144
static int select_all(const struct urlmatch_item *a UNUSED,
145
          const struct urlmatch_item *b UNUSED)
146
0
{
147
0
  return 0;
148
0
}
149
150
static int match_partial_url(const char *url, void *cb)
151
0
{
152
0
  struct credential *c = cb;
153
0
  struct credential want = CREDENTIAL_INIT;
154
0
  int matches = 0;
155
156
0
  if (credential_from_potentially_partial_url(&want, url) < 0)
157
0
    warning(_("skipping credential lookup for key: credential.%s"),
158
0
      url);
159
0
  else
160
0
    matches = credential_match(&want, c, 0);
161
0
  credential_clear(&want);
162
163
0
  return matches;
164
0
}
165
166
static void credential_apply_config(struct credential *c)
167
0
{
168
0
  char *normalized_url;
169
0
  struct urlmatch_config config = URLMATCH_CONFIG_INIT;
170
0
  struct strbuf url = STRBUF_INIT;
171
172
0
  if (!c->host)
173
0
    die(_("refusing to work with credential missing host field"));
174
0
  if (!c->protocol)
175
0
    die(_("refusing to work with credential missing protocol field"));
176
177
0
  if (c->configured)
178
0
    return;
179
180
0
  config.section = "credential";
181
0
  config.key = NULL;
182
0
  config.collect_fn = credential_config_callback;
183
0
  config.cascade_fn = NULL;
184
0
  config.select_fn = select_all;
185
0
  config.fallback_match_fn = match_partial_url;
186
0
  config.cb = c;
187
188
0
  credential_format(c, &url);
189
0
  normalized_url = url_normalize(url.buf, &config.url);
190
191
0
  git_config(urlmatch_config_entry, &config);
192
0
  string_list_clear(&config.vars, 1);
193
0
  free(normalized_url);
194
0
  urlmatch_config_release(&config);
195
0
  strbuf_release(&url);
196
197
0
  c->configured = 1;
198
199
0
  if (!c->use_http_path && proto_is_http(c->protocol)) {
200
0
    FREE_AND_NULL(c->path);
201
0
  }
202
0
}
203
204
static void credential_describe(struct credential *c, struct strbuf *out)
205
0
{
206
0
  if (!c->protocol)
207
0
    return;
208
0
  strbuf_addf(out, "%s://", c->protocol);
209
0
  if (c->username && *c->username)
210
0
    strbuf_addf(out, "%s@", c->username);
211
0
  if (c->host)
212
0
    strbuf_addstr(out, c->host);
213
0
  if (c->path)
214
0
    strbuf_addf(out, "/%s", c->path);
215
0
}
216
217
static void credential_format(struct credential *c, struct strbuf *out)
218
0
{
219
0
  if (!c->protocol)
220
0
    return;
221
0
  strbuf_addf(out, "%s://", c->protocol);
222
0
  if (c->username && *c->username) {
223
0
    strbuf_add_percentencode(out, c->username, STRBUF_ENCODE_SLASH);
224
0
    strbuf_addch(out, '@');
225
0
  }
226
0
  if (c->host)
227
0
    strbuf_addstr(out, c->host);
228
0
  if (c->path) {
229
0
    strbuf_addch(out, '/');
230
0
    strbuf_add_percentencode(out, c->path, 0);
231
0
  }
232
0
}
233
234
static char *credential_ask_one(const char *what, struct credential *c,
235
        int flags)
236
0
{
237
0
  struct strbuf desc = STRBUF_INIT;
238
0
  struct strbuf prompt = STRBUF_INIT;
239
0
  char *r;
240
241
0
  credential_describe(c, &desc);
242
0
  if (desc.len)
243
0
    strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
244
0
  else
245
0
    strbuf_addf(&prompt, "%s: ", what);
246
247
0
  r = git_prompt(prompt.buf, flags);
248
249
0
  strbuf_release(&desc);
250
0
  strbuf_release(&prompt);
251
0
  return xstrdup(r);
252
0
}
253
254
static void credential_getpass(struct credential *c)
255
0
{
256
0
  if (!c->username)
257
0
    c->username = credential_ask_one("Username", c,
258
0
             PROMPT_ASKPASS|PROMPT_ECHO);
259
0
  if (!c->password)
260
0
    c->password = credential_ask_one("Password", c,
261
0
             PROMPT_ASKPASS);
262
0
}
263
264
int credential_has_capability(const struct credential_capability *capa,
265
            enum credential_op_type op_type)
266
0
{
267
  /*
268
   * We're checking here if each previous step indicated that we had the
269
   * capability.  If it did, then we want to pass it along; conversely, if
270
   * it did not, we don't want to report that to our caller.
271
   */
272
0
  switch (op_type) {
273
0
  case CREDENTIAL_OP_HELPER:
274
0
    return capa->request_initial;
275
0
  case CREDENTIAL_OP_RESPONSE:
276
0
    return capa->request_initial && capa->request_helper;
277
0
  default:
278
0
    return 0;
279
0
  }
280
0
}
281
282
int credential_read(struct credential *c, FILE *fp,
283
        enum credential_op_type op_type)
284
0
{
285
0
  struct strbuf line = STRBUF_INIT;
286
287
0
  while (strbuf_getline(&line, fp) != EOF) {
288
0
    char *key = line.buf;
289
0
    char *value = strchr(key, '=');
290
291
0
    if (!line.len)
292
0
      break;
293
294
0
    if (!value) {
295
0
      warning("invalid credential line: %s", key);
296
0
      strbuf_release(&line);
297
0
      return -1;
298
0
    }
299
0
    *value++ = '\0';
300
301
0
    if (!strcmp(key, "username")) {
302
0
      free(c->username);
303
0
      c->username = xstrdup(value);
304
0
      c->username_from_proto = 1;
305
0
    } else if (!strcmp(key, "password")) {
306
0
      free(c->password);
307
0
      c->password = xstrdup(value);
308
0
    } else if (!strcmp(key, "credential")) {
309
0
      free(c->credential);
310
0
      c->credential = xstrdup(value);
311
0
    } else if (!strcmp(key, "protocol")) {
312
0
      free(c->protocol);
313
0
      c->protocol = xstrdup(value);
314
0
    } else if (!strcmp(key, "host")) {
315
0
      free(c->host);
316
0
      c->host = xstrdup(value);
317
0
    } else if (!strcmp(key, "path")) {
318
0
      free(c->path);
319
0
      c->path = xstrdup(value);
320
0
    } else if (!strcmp(key, "ephemeral")) {
321
0
      c->ephemeral = !!git_config_bool("ephemeral", value);
322
0
    } else if (!strcmp(key, "wwwauth[]")) {
323
0
      strvec_push(&c->wwwauth_headers, value);
324
0
    } else if (!strcmp(key, "state[]")) {
325
0
      strvec_push(&c->state_headers, value);
326
0
    } else if (!strcmp(key, "capability[]")) {
327
0
      if (!strcmp(value, "authtype"))
328
0
        credential_set_capability(&c->capa_authtype, op_type);
329
0
      else if (!strcmp(value, "state"))
330
0
        credential_set_capability(&c->capa_state, op_type);
331
0
    } else if (!strcmp(key, "continue")) {
332
0
      c->multistage = !!git_config_bool("continue", value);
333
0
    } else if (!strcmp(key, "password_expiry_utc")) {
334
0
      errno = 0;
335
0
      c->password_expiry_utc = parse_timestamp(value, NULL, 10);
336
0
      if (c->password_expiry_utc == 0 || errno == ERANGE)
337
0
        c->password_expiry_utc = TIME_MAX;
338
0
    } else if (!strcmp(key, "oauth_refresh_token")) {
339
0
      free(c->oauth_refresh_token);
340
0
      c->oauth_refresh_token = xstrdup(value);
341
0
    } else if (!strcmp(key, "authtype")) {
342
0
      free(c->authtype);
343
0
      c->authtype = xstrdup(value);
344
0
    } else if (!strcmp(key, "url")) {
345
0
      credential_from_url(c, value);
346
0
    } else if (!strcmp(key, "quit")) {
347
0
      c->quit = !!git_config_bool("quit", value);
348
0
    }
349
    /*
350
     * Ignore other lines; we don't know what they mean, but
351
     * this future-proofs us when later versions of git do
352
     * learn new lines, and the helpers are updated to match.
353
     */
354
0
  }
355
356
0
  strbuf_release(&line);
357
0
  return 0;
358
0
}
359
360
static void credential_write_item(FILE *fp, const char *key, const char *value,
361
          int required)
362
0
{
363
0
  if (!value && required)
364
0
    BUG("credential value for %s is missing", key);
365
0
  if (!value)
366
0
    return;
367
0
  if (strchr(value, '\n'))
368
0
    die("credential value for %s contains newline", key);
369
0
  fprintf(fp, "%s=%s\n", key, value);
370
0
}
371
372
void credential_write(const struct credential *c, FILE *fp,
373
          enum credential_op_type op_type)
374
0
{
375
0
  if (credential_has_capability(&c->capa_authtype, op_type))
376
0
    credential_write_item(fp, "capability[]", "authtype", 0);
377
0
  if (credential_has_capability(&c->capa_state, op_type))
378
0
    credential_write_item(fp, "capability[]", "state", 0);
379
380
0
  if (credential_has_capability(&c->capa_authtype, op_type)) {
381
0
    credential_write_item(fp, "authtype", c->authtype, 0);
382
0
    credential_write_item(fp, "credential", c->credential, 0);
383
0
    if (c->ephemeral)
384
0
      credential_write_item(fp, "ephemeral", "1", 0);
385
0
  }
386
0
  credential_write_item(fp, "protocol", c->protocol, 1);
387
0
  credential_write_item(fp, "host", c->host, 1);
388
0
  credential_write_item(fp, "path", c->path, 0);
389
0
  credential_write_item(fp, "username", c->username, 0);
390
0
  credential_write_item(fp, "password", c->password, 0);
391
0
  credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
392
0
  if (c->password_expiry_utc != TIME_MAX) {
393
0
    char *s = xstrfmt("%"PRItime, c->password_expiry_utc);
394
0
    credential_write_item(fp, "password_expiry_utc", s, 0);
395
0
    free(s);
396
0
  }
397
0
  for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
398
0
    credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
399
0
  if (credential_has_capability(&c->capa_state, op_type)) {
400
0
    if (c->multistage)
401
0
      credential_write_item(fp, "continue", "1", 0);
402
0
    for (size_t i = 0; i < c->state_headers_to_send.nr; i++)
403
0
      credential_write_item(fp, "state[]", c->state_headers_to_send.v[i], 0);
404
0
  }
405
0
}
406
407
static int run_credential_helper(struct credential *c,
408
         const char *cmd,
409
         int want_output)
410
0
{
411
0
  struct child_process helper = CHILD_PROCESS_INIT;
412
0
  FILE *fp;
413
414
0
  strvec_push(&helper.args, cmd);
415
0
  helper.use_shell = 1;
416
0
  helper.in = -1;
417
0
  if (want_output)
418
0
    helper.out = -1;
419
0
  else
420
0
    helper.no_stdout = 1;
421
422
0
  if (start_command(&helper) < 0)
423
0
    return -1;
424
425
0
  fp = xfdopen(helper.in, "w");
426
0
  sigchain_push(SIGPIPE, SIG_IGN);
427
0
  credential_write(c, fp, want_output ? CREDENTIAL_OP_HELPER : CREDENTIAL_OP_RESPONSE);
428
0
  fclose(fp);
429
0
  sigchain_pop(SIGPIPE);
430
431
0
  if (want_output) {
432
0
    int r;
433
0
    fp = xfdopen(helper.out, "r");
434
0
    r = credential_read(c, fp, CREDENTIAL_OP_HELPER);
435
0
    fclose(fp);
436
0
    if (r < 0) {
437
0
      finish_command(&helper);
438
0
      return -1;
439
0
    }
440
0
  }
441
442
0
  if (finish_command(&helper))
443
0
    return -1;
444
0
  return 0;
445
0
}
446
447
static int credential_do(struct credential *c, const char *helper,
448
       const char *operation)
449
0
{
450
0
  struct strbuf cmd = STRBUF_INIT;
451
0
  int r;
452
453
0
  if (helper[0] == '!')
454
0
    strbuf_addstr(&cmd, helper + 1);
455
0
  else if (is_absolute_path(helper))
456
0
    strbuf_addstr(&cmd, helper);
457
0
  else
458
0
    strbuf_addf(&cmd, "git credential-%s", helper);
459
460
0
  strbuf_addf(&cmd, " %s", operation);
461
0
  r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
462
463
0
  strbuf_release(&cmd);
464
0
  return r;
465
0
}
466
467
void credential_fill(struct credential *c, int all_capabilities)
468
0
{
469
0
  int i;
470
471
0
  if ((c->username && c->password) || c->credential)
472
0
    return;
473
474
0
  credential_next_state(c);
475
0
  c->multistage = 0;
476
477
0
  credential_apply_config(c);
478
0
  if (all_capabilities)
479
0
    credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL);
480
481
0
  for (i = 0; i < c->helpers.nr; i++) {
482
0
    credential_do(c, c->helpers.items[i].string, "get");
483
484
0
    if (c->password_expiry_utc < time(NULL)) {
485
      /*
486
       * Don't use credential_clear() here: callers such as
487
       * cmd_credential() expect to still be able to call
488
       * credential_write() on a struct credential whose
489
       * secrets have expired.
490
       */
491
0
      credential_clear_secrets(c);
492
      /* Reset expiry to maintain consistency */
493
0
      c->password_expiry_utc = TIME_MAX;
494
0
    }
495
0
    if ((c->username && c->password) || c->credential) {
496
0
      strvec_clear(&c->wwwauth_headers);
497
0
      return;
498
0
    }
499
0
    if (c->quit)
500
0
      die("credential helper '%s' told us to quit",
501
0
          c->helpers.items[i].string);
502
0
  }
503
504
0
  credential_getpass(c);
505
0
  if (!c->username && !c->password && !c->credential)
506
0
    die("unable to get password from user");
507
0
}
508
509
void credential_approve(struct credential *c)
510
0
{
511
0
  int i;
512
513
0
  if (c->approved)
514
0
    return;
515
0
  if (((!c->username || !c->password) && !c->credential) || c->password_expiry_utc < time(NULL))
516
0
    return;
517
518
0
  credential_next_state(c);
519
520
0
  credential_apply_config(c);
521
522
0
  for (i = 0; i < c->helpers.nr; i++)
523
0
    credential_do(c, c->helpers.items[i].string, "store");
524
0
  c->approved = 1;
525
0
}
526
527
void credential_reject(struct credential *c)
528
0
{
529
0
  int i;
530
531
0
  credential_next_state(c);
532
533
0
  credential_apply_config(c);
534
535
0
  for (i = 0; i < c->helpers.nr; i++)
536
0
    credential_do(c, c->helpers.items[i].string, "erase");
537
538
0
  credential_clear_secrets(c);
539
0
  FREE_AND_NULL(c->username);
540
0
  FREE_AND_NULL(c->oauth_refresh_token);
541
0
  c->password_expiry_utc = TIME_MAX;
542
0
  c->approved = 0;
543
0
}
544
545
static int check_url_component(const char *url, int quiet,
546
             const char *name, const char *value)
547
0
{
548
0
  if (!value)
549
0
    return 0;
550
0
  if (!strchr(value, '\n'))
551
0
    return 0;
552
553
0
  if (!quiet)
554
0
    warning(_("url contains a newline in its %s component: %s"),
555
0
      name, url);
556
0
  return -1;
557
0
}
558
559
/*
560
 * Potentially-partial URLs can, but do not have to, contain
561
 *
562
 * - a protocol (or scheme) of the form "<protocol>://"
563
 *
564
 * - a host name (the part after the protocol and before the first slash after
565
 *   that, if any)
566
 *
567
 * - a user name and potentially a password (as "<user>[:<password>]@" part of
568
 *   the host name)
569
 *
570
 * - a path (the part after the host name, if any, starting with the slash)
571
 *
572
 * Missing parts will be left unset in `struct credential`. Thus, `https://`
573
 * will have only the `protocol` set, `example.com` only the host name, and
574
 * `/git` only the path.
575
 *
576
 * Note that an empty host name in an otherwise fully-qualified URL (e.g.
577
 * `cert:///path/to/cert.pem`) will be treated as unset if we expect the URL to
578
 * be potentially partial, and only then (otherwise, the empty string is used).
579
 *
580
 * The credential_from_url() function does not allow partial URLs.
581
 */
582
static int credential_from_url_1(struct credential *c, const char *url,
583
         int allow_partial_url, int quiet)
584
0
{
585
0
  const char *at, *colon, *cp, *slash, *host, *proto_end;
586
587
0
  credential_clear(c);
588
589
  /*
590
   * Match one of:
591
   *   (1) proto://<host>/...
592
   *   (2) proto://<user>@<host>/...
593
   *   (3) proto://<user>:<pass>@<host>/...
594
   */
595
0
  proto_end = strstr(url, "://");
596
0
  if (!allow_partial_url && (!proto_end || proto_end == url)) {
597
0
    if (!quiet)
598
0
      warning(_("url has no scheme: %s"), url);
599
0
    return -1;
600
0
  }
601
0
  cp = proto_end ? proto_end + 3 : url;
602
0
  at = strchr(cp, '@');
603
0
  colon = strchr(cp, ':');
604
605
  /*
606
   * A query or fragment marker before the slash ends the host portion.
607
   * We'll just continue to call this "slash" for simplicity. Notably our
608
   * "trim leading slashes" part won't skip over this part of the path,
609
   * but that's what we'd want.
610
   */
611
0
  slash = cp + strcspn(cp, "/?#");
612
613
0
  if (!at || slash <= at) {
614
    /* Case (1) */
615
0
    host = cp;
616
0
  }
617
0
  else if (!colon || at <= colon) {
618
    /* Case (2) */
619
0
    c->username = url_decode_mem(cp, at - cp);
620
0
    if (c->username && *c->username)
621
0
      c->username_from_proto = 1;
622
0
    host = at + 1;
623
0
  } else {
624
    /* Case (3) */
625
0
    c->username = url_decode_mem(cp, colon - cp);
626
0
    if (c->username && *c->username)
627
0
      c->username_from_proto = 1;
628
0
    c->password = url_decode_mem(colon + 1, at - (colon + 1));
629
0
    host = at + 1;
630
0
  }
631
632
0
  if (proto_end && proto_end - url > 0)
633
0
    c->protocol = xmemdupz(url, proto_end - url);
634
0
  if (!allow_partial_url || slash - host > 0)
635
0
    c->host = url_decode_mem(host, slash - host);
636
  /* Trim leading and trailing slashes from path */
637
0
  while (*slash == '/')
638
0
    slash++;
639
0
  if (*slash) {
640
0
    char *p;
641
0
    c->path = url_decode(slash);
642
0
    p = c->path + strlen(c->path) - 1;
643
0
    while (p > c->path && *p == '/')
644
0
      *p-- = '\0';
645
0
  }
646
647
0
  if (check_url_component(url, quiet, "username", c->username) < 0 ||
648
0
      check_url_component(url, quiet, "password", c->password) < 0 ||
649
0
      check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
650
0
      check_url_component(url, quiet, "host", c->host) < 0 ||
651
0
      check_url_component(url, quiet, "path", c->path) < 0)
652
0
    return -1;
653
654
0
  return 0;
655
0
}
656
657
static int credential_from_potentially_partial_url(struct credential *c,
658
               const char *url)
659
0
{
660
0
  return credential_from_url_1(c, url, 1, 0);
661
0
}
662
663
int credential_from_url_gently(struct credential *c, const char *url, int quiet)
664
0
{
665
0
  return credential_from_url_1(c, url, 0, quiet);
666
0
}
667
668
void credential_from_url(struct credential *c, const char *url)
669
0
{
670
0
  if (credential_from_url_gently(c, url, 0) < 0)
671
0
    die(_("credential url cannot be parsed: %s"), url);
672
0
}