Coverage Report

Created: 2026-01-09 07:10

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