mirror of
https://github.com/git-for-windows/git.git
synced 2026-04-11 01:53:21 -05:00
When executing our test suite with Dash v0.5.13.2 one can observe several test failures that all have the same symptoms: we have a quoted heredoc that contains multibyte characters, but the final data does not match what we actually wanted to write. One such example is in t0300, where we see the diffs like the following: --- expect-stdout 2026-04-01 07:25:45.249919440 +0000 +++ stdout 2026-04-01 07:25:45.254919509 +0000 @@ -1,5 +1,5 @@ protocol=https host=example.com -path=perú.git +path=perú.git username=foo password=bar While seemingly the same, the data that we've written via the heredoc contains some invisible bytes. The expected hex representation of the string is: 7065 72c3 ba2e 6769 74 per...git But what we actually get instead is this string: 7065 7285 02c3 ba02 852e 6769 74 per.......git What's important to note here is that the multibyte character exists in both versions. But in the broken version we see that the bytes are wrapped in a sequence of "85 02" and "02 85". This is the CTLMBCHAR byte sequence of Dash, which it uses internally to quote multibyte sequences. As it turns out, this bug was introduced in c5bf970 (expand: Add multi-byte support to pmatch, 2024-06-02), which adds multibyte support to more contexts of Dash. One of these contexts seems to be in heredocs, and Dash _does_ correctly unquote these multibyte sequences when using an unquoted heredoc. But the bug seems to be that this unquoting does not happen in quoted heredocs, and the bug still exists on the latest "master" branch. For now, work around the bug by using unquoted heredocs instead. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1060 lines
23 KiB
Bash
Executable File
1060 lines
23 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
test_description='basic credential helper tests'
|
|
|
|
. ./test-lib.sh
|
|
. "$TEST_DIRECTORY"/lib-credential.sh
|
|
|
|
test_expect_success 'setup helper scripts' '
|
|
cat >dump <<-\EOF &&
|
|
whoami=$(echo $0 | sed s/.*git-credential-//)
|
|
echo >&2 "$whoami: $*"
|
|
OIFS=$IFS
|
|
IFS==
|
|
while read key value; do
|
|
echo >&2 "$whoami: $key=$value"
|
|
if test -z "${key%%*\[\]}"
|
|
then
|
|
key=${key%%\[\]}
|
|
eval "$key=\"\$$key $value\""
|
|
else
|
|
eval "$key=$value"
|
|
fi
|
|
done
|
|
IFS=$OIFS
|
|
EOF
|
|
|
|
write_script git-credential-useless <<-\EOF &&
|
|
. ./dump
|
|
exit 0
|
|
EOF
|
|
|
|
write_script git-credential-quit <<-\EOF &&
|
|
. ./dump
|
|
echo quit=1
|
|
EOF
|
|
|
|
write_script git-credential-verbatim <<-\EOF &&
|
|
user=$1; shift
|
|
pass=$1; shift
|
|
. ./dump
|
|
test -z "$user" || echo username=$user
|
|
test -z "$pass" || echo password=$pass
|
|
EOF
|
|
|
|
write_script git-credential-verbatim-cred <<-\EOF &&
|
|
authtype=$1; shift
|
|
credential=$1; shift
|
|
. ./dump
|
|
echo capability[]=authtype
|
|
echo capability[]=state
|
|
test -z "${capability##*authtype*}" || exit 0
|
|
test -z "$authtype" || echo authtype=$authtype
|
|
test -z "$credential" || echo credential=$credential
|
|
test -z "${capability##*state*}" || exit 0
|
|
echo state[]=verbatim-cred:foo
|
|
EOF
|
|
|
|
write_script git-credential-verbatim-ephemeral <<-\EOF &&
|
|
authtype=$1; shift
|
|
credential=$1; shift
|
|
. ./dump
|
|
echo capability[]=authtype
|
|
test -z "${capability##*authtype*}" || exit 0
|
|
test -z "$authtype" || echo authtype=$authtype
|
|
test -z "$credential" || echo credential=$credential
|
|
echo "ephemeral=1"
|
|
EOF
|
|
|
|
write_script git-credential-verbatim-with-expiry <<-\EOF &&
|
|
user=$1; shift
|
|
pass=$1; shift
|
|
pexpiry=$1; shift
|
|
. ./dump
|
|
test -z "$user" || echo username=$user
|
|
test -z "$pass" || echo password=$pass
|
|
test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
|
|
EOF
|
|
|
|
write_script git-credential-cntrl-in-username <<-\EOF &&
|
|
printf "username=\\007latrix Lestrange\\n"
|
|
EOF
|
|
|
|
PATH="$PWD:$PATH"
|
|
'
|
|
|
|
test_expect_success 'credential_fill invokes helper' '
|
|
check fill "verbatim foo bar" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill invokes helper with credential' '
|
|
check fill "verbatim-cred Bearer token" <<-\EOF
|
|
capability[]=authtype
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
capability[]=authtype
|
|
authtype=Bearer
|
|
credential=token
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
verbatim-cred: get
|
|
verbatim-cred: capability[]=authtype
|
|
verbatim-cred: protocol=http
|
|
verbatim-cred: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill invokes helper with ephemeral credential' '
|
|
check fill "verbatim-ephemeral Bearer token" <<-\EOF
|
|
capability[]=authtype
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
capability[]=authtype
|
|
authtype=Bearer
|
|
credential=token
|
|
ephemeral=1
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
verbatim-ephemeral: get
|
|
verbatim-ephemeral: capability[]=authtype
|
|
verbatim-ephemeral: protocol=http
|
|
verbatim-ephemeral: host=example.com
|
|
EOF
|
|
'
|
|
test_expect_success 'credential_fill invokes helper with credential and state' '
|
|
check fill "verbatim-cred Bearer token" <<-\EOF
|
|
capability[]=authtype
|
|
capability[]=state
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
capability[]=authtype
|
|
capability[]=state
|
|
authtype=Bearer
|
|
credential=token
|
|
protocol=http
|
|
host=example.com
|
|
state[]=verbatim-cred:foo
|
|
--
|
|
verbatim-cred: get
|
|
verbatim-cred: capability[]=authtype
|
|
verbatim-cred: capability[]=state
|
|
verbatim-cred: protocol=http
|
|
verbatim-cred: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill invokes multiple helpers' '
|
|
check fill useless "verbatim foo bar" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
useless: get
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill response does not get capabilities when helpers are incapable' '
|
|
check fill useless "verbatim foo bar" <<-\EOF
|
|
capability[]=authtype
|
|
capability[]=state
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
useless: get
|
|
useless: capability[]=authtype
|
|
useless: capability[]=state
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
verbatim: get
|
|
verbatim: capability[]=authtype
|
|
verbatim: capability[]=state
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill response does not get capabilities when caller is incapable' '
|
|
check fill "verbatim-cred Bearer token" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
verbatim-cred: get
|
|
verbatim-cred: protocol=http
|
|
verbatim-cred: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill stops when we get a full response' '
|
|
check fill "verbatim one two" "verbatim three four" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
password=two
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill thinks a credential is a full response' '
|
|
check fill "verbatim-cred Bearer token" "verbatim three four" <<-\EOF
|
|
capability[]=authtype
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
capability[]=authtype
|
|
authtype=Bearer
|
|
credential=token
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
verbatim-cred: get
|
|
verbatim-cred: capability[]=authtype
|
|
verbatim-cred: protocol=http
|
|
verbatim-cred: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill continues through partial response' '
|
|
check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=two
|
|
password=three
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: username=one
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill populates password_expiry_utc' '
|
|
check fill "verbatim-with-expiry one two 9999999999" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
password=two
|
|
password_expiry_utc=9999999999
|
|
--
|
|
verbatim-with-expiry: get
|
|
verbatim-with-expiry: protocol=http
|
|
verbatim-with-expiry: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill ignores expired password' '
|
|
check fill "verbatim-with-expiry one two 5" "verbatim three four" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=three
|
|
password=four
|
|
--
|
|
verbatim-with-expiry: get
|
|
verbatim-with-expiry: protocol=http
|
|
verbatim-with-expiry: host=example.com
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: username=one
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill passes along metadata' '
|
|
check fill "verbatim one two" <<-\EOF
|
|
protocol=ftp
|
|
host=example.com
|
|
path=foo.git
|
|
--
|
|
protocol=ftp
|
|
host=example.com
|
|
path=foo.git
|
|
username=one
|
|
password=two
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=ftp
|
|
verbatim: host=example.com
|
|
verbatim: path=foo.git
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_fill produces no credential without capability' '
|
|
check fill "verbatim-cred Bearer token" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
verbatim-cred: get
|
|
verbatim-cred: protocol=http
|
|
verbatim-cred: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_approve calls all helpers' '
|
|
check approve useless "verbatim one two" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
--
|
|
useless: store
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
useless: username=foo
|
|
useless: password=bar
|
|
verbatim: store
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: username=foo
|
|
verbatim: password=bar
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_approve stores password expiry' '
|
|
check approve useless <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
password_expiry_utc=9999999999
|
|
--
|
|
--
|
|
useless: store
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
useless: username=foo
|
|
useless: password=bar
|
|
useless: password_expiry_utc=9999999999
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_approve stores oauth refresh token' '
|
|
check approve useless <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
oauth_refresh_token=xyzzy
|
|
--
|
|
--
|
|
useless: store
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
useless: username=foo
|
|
useless: password=bar
|
|
useless: oauth_refresh_token=xyzzy
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'do not bother storing password-less credential' '
|
|
check approve useless <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
--
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_approve does not store expired password' '
|
|
check approve useless <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
password_expiry_utc=5
|
|
--
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_reject calls all helpers' '
|
|
check reject useless "verbatim one two" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
--
|
|
useless: erase
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
useless: username=foo
|
|
useless: password=bar
|
|
verbatim: erase
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: username=foo
|
|
verbatim: password=bar
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential_reject erases credential regardless of expiry' '
|
|
check reject useless <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
password_expiry_utc=5
|
|
--
|
|
--
|
|
useless: erase
|
|
useless: protocol=http
|
|
useless: host=example.com
|
|
useless: username=foo
|
|
useless: password=bar
|
|
useless: password_expiry_utc=5
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'usernames can be preserved' '
|
|
check fill "verbatim \"\" three" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
password=three
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: username=one
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'usernames can be overridden' '
|
|
check fill "verbatim two three" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=two
|
|
password=three
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
verbatim: username=one
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'do not bother completing already-full credential' '
|
|
check fill "verbatim three four" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
password=two
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=one
|
|
password=two
|
|
--
|
|
EOF
|
|
'
|
|
|
|
# We can't test the basic terminal password prompt here because
|
|
# getpass() tries too hard to find the real terminal. But if our
|
|
# askpass helper is run, we know the internal getpass is working.
|
|
test_expect_success 'empty helper list falls back to internal getpass' '
|
|
check fill <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=askpass-username
|
|
password=askpass-password
|
|
--
|
|
askpass: Username for '\''http://example.com'\'':
|
|
askpass: Password for '\''http://askpass-username@example.com'\'':
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'internal getpass does not ask for known username' '
|
|
check fill <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=askpass-password
|
|
--
|
|
askpass: Password for '\''http://foo@example.com'\'':
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'git-credential respects core.askPass' '
|
|
write_script alternate-askpass <<-\EOF &&
|
|
echo >&2 "alternate askpass invoked"
|
|
echo alternate-value
|
|
EOF
|
|
test_config core.askpass "$PWD/alternate-askpass" &&
|
|
(
|
|
# unset GIT_ASKPASS set by lib-credential.sh which would
|
|
# override our config, but do so in a subshell so that we do
|
|
# not interfere with other tests
|
|
sane_unset GIT_ASKPASS &&
|
|
check fill <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=alternate-value
|
|
password=alternate-value
|
|
--
|
|
alternate askpass invoked
|
|
alternate askpass invoked
|
|
EOF
|
|
)
|
|
'
|
|
|
|
HELPER="!f() {
|
|
cat >/dev/null
|
|
echo username=foo
|
|
echo password=bar
|
|
}; f"
|
|
test_expect_success 'respect configured credentials' '
|
|
test_config credential.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match configured credential' '
|
|
test_config credential.https://example.com.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
protocol=https
|
|
host=example.com
|
|
path=repo.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'do not match configured credential' '
|
|
test_config credential.https://foo.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
protocol=https
|
|
host=bar
|
|
--
|
|
protocol=https
|
|
host=bar
|
|
username=askpass-username
|
|
password=askpass-password
|
|
--
|
|
askpass: Username for '\''https://bar'\'':
|
|
askpass: Password for '\''https://askpass-username@bar'\'':
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match multiple configured helpers' '
|
|
test_config credential.helper "verbatim \"\" \"\"" &&
|
|
test_config credential.https://example.com.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
protocol=https
|
|
host=example.com
|
|
path=repo.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match multiple configured helpers with URLs' '
|
|
test_config credential.https://example.com/repo.git.helper "verbatim \"\" \"\"" &&
|
|
test_config credential.https://example.com.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
protocol=https
|
|
host=example.com
|
|
path=repo.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match percent-encoded values' '
|
|
test_config credential.https://example.com/%2566.git.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
url=https://example.com/%2566.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match percent-encoded UTF-8 values in path' '
|
|
test_config credential.https://example.com.useHttpPath true &&
|
|
test_config credential.https://example.com/perú.git.helper "$HELPER" &&
|
|
# NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs
|
|
# that contain multibyte chars.
|
|
check fill <<-EOF
|
|
url=https://example.com/per%C3%BA.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
path=perú.git
|
|
username=foo
|
|
password=bar
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match percent-encoded values in username' '
|
|
test_config credential.https://user%2fname@example.com/foo/bar.git.helper "$HELPER" &&
|
|
check fill <<-\EOF
|
|
url=https://user%2fname@example.com/foo/bar.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'match percent-encoded values in hostname' '
|
|
test_config "credential.https://a%20b%20c/.helper" "$HELPER" &&
|
|
check fill <<-\EOF
|
|
url=https://a b c/
|
|
--
|
|
protocol=https
|
|
host=a b c
|
|
username=foo
|
|
password=bar
|
|
--
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'fetch with multiple path components' '
|
|
test_unconfig credential.helper &&
|
|
test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
|
|
check fill <<-\EOF
|
|
url=https://example.com/foo/repo.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'pull username from config' '
|
|
test_config credential.https://example.com.username foo &&
|
|
check fill <<-\EOF
|
|
protocol=https
|
|
host=example.com
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=askpass-password
|
|
--
|
|
askpass: Password for '\''https://foo@example.com'\'':
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'honors username from URL over helper (URL)' '
|
|
test_config credential.https://example.com.username bob &&
|
|
test_config credential.https://example.com.helper "verbatim \"\" bar" &&
|
|
check fill <<-\EOF
|
|
url=https://alice@example.com
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=alice
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
verbatim: username=alice
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'honors username from URL over helper (components)' '
|
|
test_config credential.https://example.com.username bob &&
|
|
test_config credential.https://example.com.helper "verbatim \"\" bar" &&
|
|
check fill <<-\EOF
|
|
protocol=https
|
|
host=example.com
|
|
username=alice
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=alice
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
verbatim: username=alice
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'last matching username wins' '
|
|
test_config credential.https://example.com/path.git.username bob &&
|
|
test_config credential.https://example.com.username alice &&
|
|
test_config credential.https://example.com.helper "verbatim \"\" bar" &&
|
|
check fill <<-\EOF
|
|
url=https://example.com/path.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=alice
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
verbatim: username=alice
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'http paths can be part of context' '
|
|
check fill "verbatim foo bar" <<-\EOF &&
|
|
protocol=https
|
|
host=example.com
|
|
path=foo.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
EOF
|
|
test_config credential.https://example.com.useHttpPath true &&
|
|
check fill "verbatim foo bar" <<-\EOF
|
|
protocol=https
|
|
host=example.com
|
|
path=foo.git
|
|
--
|
|
protocol=https
|
|
host=example.com
|
|
path=foo.git
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.com
|
|
verbatim: path=foo.git
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'context uses urlmatch' '
|
|
test_config "credential.https://*.org.useHttpPath" true &&
|
|
check fill "verbatim foo bar" <<-\EOF
|
|
protocol=https
|
|
host=example.org
|
|
path=foo.git
|
|
--
|
|
protocol=https
|
|
host=example.org
|
|
path=foo.git
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=example.org
|
|
verbatim: path=foo.git
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'helpers can abort the process' '
|
|
test_must_fail git \
|
|
-c credential.helper=quit \
|
|
-c credential.helper="verbatim foo bar" \
|
|
credential fill >stdout 2>stderr <<-\EOF &&
|
|
protocol=http
|
|
host=example.com
|
|
EOF
|
|
test_must_be_empty stdout &&
|
|
cat >expect <<-\EOF &&
|
|
quit: get
|
|
quit: protocol=http
|
|
quit: host=example.com
|
|
fatal: credential helper '\''quit'\'' told us to quit
|
|
EOF
|
|
test_cmp expect stderr
|
|
'
|
|
|
|
test_expect_success 'empty helper spec resets helper list' '
|
|
test_config credential.helper "verbatim file file" &&
|
|
check fill "" "verbatim cmdline cmdline" <<-\EOF
|
|
protocol=http
|
|
host=example.com
|
|
--
|
|
protocol=http
|
|
host=example.com
|
|
username=cmdline
|
|
password=cmdline
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=http
|
|
verbatim: host=example.com
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'url parser rejects embedded newlines' '
|
|
test_must_fail git credential fill 2>stderr <<-\EOF &&
|
|
url=https://one.example.com?%0ahost=two.example.com/
|
|
EOF
|
|
cat >expect <<-\EOF &&
|
|
warning: url contains a newline in its path component: https://one.example.com?%0ahost=two.example.com/
|
|
fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/
|
|
EOF
|
|
test_cmp expect stderr
|
|
'
|
|
|
|
test_expect_success 'url parser rejects embedded carriage returns' '
|
|
test_config credential.helper "!true" &&
|
|
test_must_fail git credential fill 2>stderr <<-\EOF &&
|
|
url=https://example%0d.com/
|
|
EOF
|
|
cat >expect <<-\EOF &&
|
|
fatal: credential value for host contains carriage return
|
|
If this is intended, set `credential.protectProtocol=false`
|
|
EOF
|
|
test_cmp expect stderr &&
|
|
GIT_ASKPASS=true \
|
|
git -c credential.protectProtocol=false credential fill <<-\EOF
|
|
url=https://example%0d.com/
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'host-less URLs are parsed as empty host' '
|
|
check fill "verbatim foo bar" <<-\EOF
|
|
url=cert:///path/to/cert.pem
|
|
--
|
|
protocol=cert
|
|
host=
|
|
path=path/to/cert.pem
|
|
username=foo
|
|
password=bar
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=cert
|
|
verbatim: host=
|
|
verbatim: path=path/to/cert.pem
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'credential system refuses to work with missing host' '
|
|
test_must_fail git credential fill 2>stderr <<-\EOF &&
|
|
protocol=http
|
|
EOF
|
|
cat >expect <<-\EOF &&
|
|
fatal: refusing to work with credential missing host field
|
|
EOF
|
|
test_cmp expect stderr
|
|
'
|
|
|
|
test_expect_success 'credential system refuses to work with missing protocol' '
|
|
test_must_fail git credential fill 2>stderr <<-\EOF &&
|
|
host=example.com
|
|
EOF
|
|
cat >expect <<-\EOF &&
|
|
fatal: refusing to work with credential missing protocol field
|
|
EOF
|
|
test_cmp expect stderr
|
|
'
|
|
|
|
# usage: check_host_and_path <url> <expected-host> <expected-path>
|
|
check_host_and_path () {
|
|
# we always parse the path component, but we need this to make sure it
|
|
# is passed to the helper
|
|
test_config credential.useHTTPPath true &&
|
|
check fill "verbatim user pass" <<-EOF
|
|
url=$1
|
|
--
|
|
protocol=https
|
|
host=$2
|
|
path=$3
|
|
username=user
|
|
password=pass
|
|
--
|
|
verbatim: get
|
|
verbatim: protocol=https
|
|
verbatim: host=$2
|
|
verbatim: path=$3
|
|
EOF
|
|
}
|
|
|
|
test_expect_success 'url parser handles bare query marker' '
|
|
check_host_and_path https://example.com?foo.git example.com ?foo.git
|
|
'
|
|
|
|
test_expect_success 'url parser handles bare fragment marker' '
|
|
check_host_and_path https://example.com#foo.git example.com "#foo.git"
|
|
'
|
|
|
|
test_expect_success 'url parser not confused by encoded markers' '
|
|
check_host_and_path https://example.com%23%3f%2f/foo.git \
|
|
"example.com#?/" foo.git
|
|
'
|
|
|
|
test_expect_success 'credential config with partial URLs' '
|
|
echo "echo password=yep" | write_script git-credential-yep &&
|
|
test_write_lines url=https://user@example.com/org/repo.git >stdin &&
|
|
for partial in \
|
|
example.com \
|
|
example.com/org/repo.git \
|
|
user@example.com \
|
|
user@example.com/org/repo.git \
|
|
https:// \
|
|
https://example.com \
|
|
https://example.com/ \
|
|
https://example.com/org \
|
|
https://example.com/org/ \
|
|
https://example.com/org/repo.git \
|
|
https://user@example.com \
|
|
https://user@example.com/ \
|
|
https://user@example.com/org \
|
|
https://user@example.com/org/ \
|
|
https://user@example.com/org/repo.git \
|
|
/org/repo.git
|
|
do
|
|
git -c credential.$partial.helper=yep \
|
|
credential fill <stdin >stdout &&
|
|
grep yep stdout ||
|
|
return 1
|
|
done &&
|
|
|
|
for partial in \
|
|
dont.use.this \
|
|
example.com/o \
|
|
user@example.com/o \
|
|
http:// \
|
|
https://example.com/o \
|
|
https://user@example.com/o \
|
|
/o \
|
|
/repo
|
|
do
|
|
git -c credential.$partial.helper=yep \
|
|
credential fill <stdin >stdout &&
|
|
! grep yep stdout ||
|
|
return 1
|
|
done &&
|
|
|
|
git -c credential.$partial.helper=yep \
|
|
-c credential.with%0anewline.username=uh-oh \
|
|
credential fill <stdin 2>stderr &&
|
|
test_grep "skipping credential lookup for key" stderr
|
|
'
|
|
|
|
BEL="$(printf '\007')"
|
|
|
|
test_expect_success 'interactive prompt is sanitized' '
|
|
check fill cntrl-in-username <<-EOF
|
|
protocol=https
|
|
host=example.org
|
|
--
|
|
protocol=https
|
|
host=example.org
|
|
username=${BEL}latrix Lestrange
|
|
password=askpass-password
|
|
--
|
|
askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}:
|
|
EOF
|
|
'
|
|
|
|
test_done
|