Skip to content

Commit 36a139e

Browse files
committed
Merge branch 'mingit-2.46.x-releases'
Synchronize with Git for Windows' v2.47.x release train. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 2cd2243 + 61df50c commit 36a139e

15 files changed

+244
-42
lines changed

Documentation/config.txt

+2
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ include::config/sequencer.txt[]
524524

525525
include::config/showbranch.txt[]
526526

527+
include::config/sideband.txt[]
528+
527529
include::config/sparse.txt[]
528530

529531
include::config/splitindex.txt[]

Documentation/config/credential.txt

+11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ credential.useHttpPath::
2222
or https URL to be important. Defaults to false. See
2323
linkgit:gitcredentials[7] for more information.
2424

25+
credential.sanitizePrompt::
26+
By default, user names and hosts that are shown as part of the
27+
password prompt are not allowed to contain control characters (they
28+
will be URL-encoded by default). Configure this setting to `false` to
29+
override that behavior.
30+
31+
credential.protectProtocol::
32+
By default, Carriage Return characters are not allowed in the protocol
33+
that is used when Git talks to a credential helper. This setting allows
34+
users to override this default.
35+
2536
credential.username::
2637
If no username is set for a network authentication, use this username
2738
by default. See credential.<context>.* below, and

Documentation/config/sideband.txt

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
sideband.allowControlCharacters::
2+
By default, control characters that are delivered via the sideband
3+
are masked, except ANSI color sequences. This prevents potentially
4+
unwanted ANSI escape sequences from being sent to the terminal. Use
5+
this config setting to override this behavior:
6+
+
7+
--
8+
color::
9+
Allow ANSI color sequences, line feeds and horizontal tabs,
10+
but mask all other control characters. This is the default.
11+
false::
12+
Mask all control characters other than line feeds and
13+
horizontal tabs.
14+
true::
15+
Allow all control characters to be sent to the terminal.
16+
--

credential.c

+31-18
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ static int credential_config_callback(const char *var, const char *value,
129129
}
130130
else if (!strcmp(key, "usehttppath"))
131131
c->use_http_path = git_config_bool(var, value);
132+
else if (!strcmp(key, "sanitizeprompt"))
133+
c->sanitize_prompt = git_config_bool(var, value);
134+
else if (!strcmp(key, "protectprotocol"))
135+
c->protect_protocol = git_config_bool(var, value);
132136

133137
return 0;
134138
}
@@ -226,7 +230,8 @@ static void credential_format(struct credential *c, struct strbuf *out)
226230
strbuf_addch(out, '@');
227231
}
228232
if (c->host)
229-
strbuf_addstr(out, c->host);
233+
strbuf_add_percentencode(out, c->host,
234+
STRBUF_ENCODE_HOST_AND_PORT);
230235
if (c->path) {
231236
strbuf_addch(out, '/');
232237
strbuf_add_percentencode(out, c->path, 0);
@@ -240,7 +245,10 @@ static char *credential_ask_one(const char *what, struct credential *c,
240245
struct strbuf prompt = STRBUF_INIT;
241246
char *r;
242247

243-
credential_describe(c, &desc);
248+
if (c->sanitize_prompt)
249+
credential_format(c, &desc);
250+
else
251+
credential_describe(c, &desc);
244252
if (desc.len)
245253
strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
246254
else
@@ -381,7 +389,8 @@ int credential_read(struct credential *c, FILE *fp,
381389
return 0;
382390
}
383391

384-
static void credential_write_item(FILE *fp, const char *key, const char *value,
392+
static void credential_write_item(const struct credential *c,
393+
FILE *fp, const char *key, const char *value,
385394
int required)
386395
{
387396
if (!value && required)
@@ -390,41 +399,45 @@ static void credential_write_item(FILE *fp, const char *key, const char *value,
390399
return;
391400
if (strchr(value, '\n'))
392401
die("credential value for %s contains newline", key);
402+
if (c->protect_protocol && strchr(value, '\r'))
403+
die("credential value for %s contains carriage return\n"
404+
"If this is intended, set `credential.protectProtocol=false`",
405+
key);
393406
fprintf(fp, "%s=%s\n", key, value);
394407
}
395408

396409
void credential_write(const struct credential *c, FILE *fp,
397410
enum credential_op_type op_type)
398411
{
399412
if (credential_has_capability(&c->capa_authtype, op_type))
400-
credential_write_item(fp, "capability[]", "authtype", 0);
413+
credential_write_item(c, fp, "capability[]", "authtype", 0);
401414
if (credential_has_capability(&c->capa_state, op_type))
402-
credential_write_item(fp, "capability[]", "state", 0);
415+
credential_write_item(c, fp, "capability[]", "state", 0);
403416

404417
if (credential_has_capability(&c->capa_authtype, op_type)) {
405-
credential_write_item(fp, "authtype", c->authtype, 0);
406-
credential_write_item(fp, "credential", c->credential, 0);
418+
credential_write_item(c, fp, "authtype", c->authtype, 0);
419+
credential_write_item(c, fp, "credential", c->credential, 0);
407420
if (c->ephemeral)
408-
credential_write_item(fp, "ephemeral", "1", 0);
421+
credential_write_item(c, fp, "ephemeral", "1", 0);
409422
}
410-
credential_write_item(fp, "protocol", c->protocol, 1);
411-
credential_write_item(fp, "host", c->host, 1);
412-
credential_write_item(fp, "path", c->path, 0);
413-
credential_write_item(fp, "username", c->username, 0);
414-
credential_write_item(fp, "password", c->password, 0);
415-
credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
423+
credential_write_item(c, fp, "protocol", c->protocol, 1);
424+
credential_write_item(c, fp, "host", c->host, 1);
425+
credential_write_item(c, fp, "path", c->path, 0);
426+
credential_write_item(c, fp, "username", c->username, 0);
427+
credential_write_item(c, fp, "password", c->password, 0);
428+
credential_write_item(c, fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
416429
if (c->password_expiry_utc != TIME_MAX) {
417430
char *s = xstrfmt("%"PRItime, c->password_expiry_utc);
418-
credential_write_item(fp, "password_expiry_utc", s, 0);
431+
credential_write_item(c, fp, "password_expiry_utc", s, 0);
419432
free(s);
420433
}
421434
for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
422-
credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
435+
credential_write_item(c, fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
423436
if (credential_has_capability(&c->capa_state, op_type)) {
424437
if (c->multistage)
425-
credential_write_item(fp, "continue", "1", 0);
438+
credential_write_item(c, fp, "continue", "1", 0);
426439
for (size_t i = 0; i < c->state_headers_to_send.nr; i++)
427-
credential_write_item(fp, "state[]", c->state_headers_to_send.v[i], 0);
440+
credential_write_item(c, fp, "state[]", c->state_headers_to_send.v[i], 0);
428441
}
429442
}
430443

credential.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ struct credential {
168168
multistage: 1,
169169
quit:1,
170170
use_http_path:1,
171-
username_from_proto:1;
171+
username_from_proto:1,
172+
sanitize_prompt:1,
173+
protect_protocol:1;
172174

173175
struct credential_capability capa_authtype;
174176
struct credential_capability capa_state;
@@ -195,6 +197,8 @@ struct credential {
195197
.wwwauth_headers = STRVEC_INIT, \
196198
.state_headers = STRVEC_INIT, \
197199
.state_headers_to_send = STRVEC_INIT, \
200+
.sanitize_prompt = 1, \
201+
.protect_protocol = 1, \
198202
}
199203

200204
/* Initialize a credential structure, setting all fields to empty. */

sideband.c

+76-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ static struct keyword_entry keywords[] = {
2525
{ "error", GIT_COLOR_BOLD_RED },
2626
};
2727

28+
static enum {
29+
ALLOW_NO_CONTROL_CHARACTERS = 0,
30+
ALLOW_ALL_CONTROL_CHARACTERS = 1,
31+
ALLOW_ANSI_COLOR_SEQUENCES = 2
32+
} allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
33+
2834
/* Returns a color setting (GIT_COLOR_NEVER, etc). */
2935
static int use_sideband_colors(void)
3036
{
@@ -38,6 +44,25 @@ static int use_sideband_colors(void)
3844
if (use_sideband_colors_cached >= 0)
3945
return use_sideband_colors_cached;
4046

47+
switch (git_config_get_maybe_bool("sideband.allowcontrolcharacters", &i)) {
48+
case 0: /* Boolean value */
49+
allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS :
50+
ALLOW_NO_CONTROL_CHARACTERS;
51+
break;
52+
case -1: /* non-Boolean value */
53+
if (git_config_get_string_tmp("sideband.allowcontrolcharacters",
54+
&value))
55+
; /* huh? `get_maybe_bool()` returned -1 */
56+
else if (!strcmp(value, "color"))
57+
allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
58+
else
59+
warning(_("unrecognized value for `sideband."
60+
"allowControlCharacters`: '%s'"), value);
61+
break;
62+
default:
63+
break; /* not configured */
64+
}
65+
4166
if (!git_config_get_string_tmp(key, &value))
4267
use_sideband_colors_cached = git_config_colorbool(key, value);
4368
else if (!git_config_get_string_tmp("color.ui", &value))
@@ -65,6 +90,55 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
6590
list_config_item(list, prefix, keywords[i].keyword);
6691
}
6792

93+
static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n)
94+
{
95+
int i;
96+
97+
/*
98+
* Valid ANSI color sequences are of the form
99+
*
100+
* ESC [ [<n> [; <n>]*] m
101+
*/
102+
103+
if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES ||
104+
n < 3 || src[0] != '\x1b' || src[1] != '[')
105+
return 0;
106+
107+
for (i = 2; i < n; i++) {
108+
if (src[i] == 'm') {
109+
strbuf_add(dest, src, i + 1);
110+
return i;
111+
}
112+
if (!isdigit(src[i]) && src[i] != ';')
113+
break;
114+
}
115+
116+
return 0;
117+
}
118+
119+
static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
120+
{
121+
int i;
122+
123+
if (allow_control_characters == ALLOW_ALL_CONTROL_CHARACTERS) {
124+
strbuf_add(dest, src, n);
125+
return;
126+
}
127+
128+
strbuf_grow(dest, n);
129+
for (; n && *src; src++, n--) {
130+
if (!iscntrl(*src) || *src == '\t' || *src == '\n')
131+
strbuf_addch(dest, *src);
132+
else if ((i = handle_ansi_color_sequence(dest, src, n))) {
133+
src += i;
134+
n -= i;
135+
} else {
136+
strbuf_addch(dest, '^');
137+
strbuf_addch(dest, 0x40 + *src);
138+
}
139+
}
140+
}
141+
68142
/*
69143
* Optionally highlight one keyword in remote output if it appears at the start
70144
* of the line. This should be called for a single line only, which is
@@ -80,7 +154,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
80154
int i;
81155

82156
if (!want_color_stderr(use_sideband_colors())) {
83-
strbuf_add(dest, src, n);
157+
strbuf_add_sanitized(dest, src, n);
84158
return;
85159
}
86160

@@ -113,7 +187,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
113187
}
114188
}
115189

116-
strbuf_add(dest, src, n);
190+
strbuf_add_sanitized(dest, src, n);
117191
}
118192

119193

strbuf.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,9 @@ void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags)
495495
unsigned char ch = src[i];
496496
if (ch <= 0x1F || ch >= 0x7F ||
497497
(ch == '/' && (flags & STRBUF_ENCODE_SLASH)) ||
498-
strchr(URL_UNSAFE_CHARS, ch))
498+
((flags & STRBUF_ENCODE_HOST_AND_PORT) ?
499+
!isalnum(ch) && !strchr("-.:[]", ch) :
500+
!!strchr(URL_UNSAFE_CHARS, ch)))
499501
strbuf_addf(dst, "%%%02X", (unsigned char)ch);
500502
else
501503
strbuf_addch(dst, ch);

strbuf.h

+1
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ void strbuf_expand_bad_format(const char *format, const char *command);
356356
void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
357357

358358
#define STRBUF_ENCODE_SLASH 1
359+
#define STRBUF_ENCODE_HOST_AND_PORT 2
359360

360361
/**
361362
* Append the contents of a string to a strbuf, percent-encoding any characters

t/t0300-credentials.sh

+49
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ test_expect_success 'setup helper scripts' '
7777
test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
7878
EOF
7979
80+
write_script git-credential-cntrl-in-username <<-\EOF &&
81+
printf "username=\\007latrix Lestrange\\n"
82+
EOF
83+
8084
PATH="$PWD$PATH_SEP$PATH"
8185
'
8286

@@ -697,6 +701,19 @@ test_expect_success 'match percent-encoded values in username' '
697701
EOF
698702
'
699703

704+
test_expect_success 'match percent-encoded values in hostname' '
705+
test_config "credential.https://a%20b%20c/.helper" "$HELPER" &&
706+
check fill <<-\EOF
707+
url=https://a b c/
708+
--
709+
protocol=https
710+
host=a b c
711+
username=foo
712+
password=bar
713+
--
714+
EOF
715+
'
716+
700717
test_expect_success 'fetch with multiple path components' '
701718
test_unconfig credential.helper &&
702719
test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
@@ -886,6 +903,22 @@ test_expect_success 'url parser rejects embedded newlines' '
886903
test_cmp expect stderr
887904
'
888905

906+
test_expect_success 'url parser rejects embedded carriage returns' '
907+
test_config credential.helper "!true" &&
908+
test_must_fail git credential fill 2>stderr <<-\EOF &&
909+
url=https://example%0d.com/
910+
EOF
911+
cat >expect <<-\EOF &&
912+
fatal: credential value for host contains carriage return
913+
If this is intended, set `credential.protectProtocol=false`
914+
EOF
915+
test_cmp expect stderr &&
916+
GIT_ASKPASS=true \
917+
git -c credential.protectProtocol=false credential fill <<-\EOF
918+
url=https://example%0d.com/
919+
EOF
920+
'
921+
889922
test_expect_success 'host-less URLs are parsed as empty host' '
890923
check fill "verbatim foo bar" <<-\EOF
891924
url=cert:///path/to/cert.pem
@@ -995,4 +1028,20 @@ test_expect_success 'credential config with partial URLs' '
9951028
test_grep "skipping credential lookup for key" stderr
9961029
'
9971030

1031+
BEL="$(printf '\007')"
1032+
1033+
test_expect_success 'interactive prompt is sanitized' '
1034+
check fill cntrl-in-username <<-EOF
1035+
protocol=https
1036+
host=example.org
1037+
--
1038+
protocol=https
1039+
host=example.org
1040+
username=${BEL}latrix Lestrange
1041+
password=askpass-password
1042+
--
1043+
askpass: Password for ${SQ}https://%07latrix%[email protected]${SQ}:
1044+
EOF
1045+
'
1046+
9981047
test_done

0 commit comments

Comments
 (0)