[PATCH v2 1/1] config: learn the --stdin option for instructions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When setting values in the git config, the value is part of the
arguments for execution. This potentially leaks the value through
logging, or other programs like `ps`.

Add the `--stdin` option that reads from stdin for instructions to set
and unset values to hide them from prying eyes. The instructions are based
on the `update-ref` DSL, and accept the set and unset commands.

Signed-off-by: Zeger-Jan van de Weg <git@xxxxxxxxxxxxx>
---
 Documentation/git-config.txt |  29 ++++++++++
 builtin/config.c             | 104 +++++++++++++++++++++++++++++++++++
 t/t1300-config.sh            |  60 ++++++++++++++++++++
 3 files changed, 193 insertions(+)

diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 899e92a1c9..9f7462284d 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -23,6 +23,7 @@ SYNOPSIS
 'git config' [<file-option>] [--show-origin] [-z|--null] [--name-only] -l | --list
 'git config' [<file-option>] --get-color name [default]
 'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
+'git config' [<file-option>] [-z|--null] --stdin
 'git config' [<file-option>] -e | --edit
 
 DESCRIPTION
@@ -212,6 +213,10 @@ Valid `<type>`'s include:
 	output without getting confused e.g. by values that
 	contain line breaks.
 
+--stdin::
+	Instructions are read from the standard input, instead of the command
+	line interface. Refer to the STDIN section.
+
 --name-only::
 	Output only the names of config variables for `--list` or
 	`--get-regexp`.
@@ -259,6 +264,30 @@ Valid `<type>`'s include:
   When using `--get`, and the requested variable is not found, behave as if
   <value> were the value assigned to the that variable.
 
+STDIN
+-----
+
+With `--stdin`, config reads instructions from standard input and performs
+all modifications in sequence.
+
+Specify commands of the form:
+
+	set SP <key> SP <newvalue>
+	unset SP <key>
+
+Alternatively, use `-z` or `--null` to specify in NUL-terminated format, without
+quoting:
+
+	set SP <key> NULL <newvalue>
+	unset SP <key>
+
+set::
+	Set or update the value for <key> to <newvalue>.
+
+unset:
+	Remove the value from the configuration by <key>, when the <key> isn't
+	present in the configuration no error is returned.
+
 CONFIGURATION
 -------------
 `pager.config` is only respected when listing configuration, i.e., when
diff --git a/builtin/config.c b/builtin/config.c
index 98d65bc0ad..a449b00b65 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -50,6 +50,7 @@ static int show_origin;
 #define ACTION_GET_COLOR (1<<13)
 #define ACTION_GET_COLORBOOL (1<<14)
 #define ACTION_GET_URLMATCH (1<<15)
+#define ACTION_STDIN (1<<16)
 
 /*
  * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than
@@ -143,6 +144,7 @@ static struct option builtin_config_options[] = {
 	OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
 	OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
 	OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
+	OPT_BIT(0, "stdin", &actions, N_("input changes from stdin"), ACTION_STDIN),
 	OPT_GROUP(N_("Type")),
 	OPT_CALLBACK('t', "type", &type, "", N_("value is given this type"), option_parse_type),
 	OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL),
@@ -594,6 +596,102 @@ static char *default_user_config(void)
 	return strbuf_detach(&buf, NULL);
 }
 
+static char *parse_key_or_value(const char **next, const char *max_char) {
+	struct strbuf token = STRBUF_INIT;
+
+	if (term) {
+		if (**next == '"') {
+			const char **orig = next;
+
+			if (unquote_c_style(&token, *next, next))
+				die("badly quoted argument: %s", *orig);
+			if (*next && !isspace(**next))
+				die("unexpected character after quoted argument: %s", *orig);
+		} else {
+			while (*next && !isspace(**next)) {
+				if (*next > max_char)
+					die("unexpected end of buffer");
+
+				strbuf_addch(&token, *(*next)++);
+			}
+		}
+
+		(*next)++;
+	} else {
+		strbuf_addstr(&token, *next);
+		*next += token.len + 1;
+	}
+
+	return strbuf_detach(&token, NULL);
+}
+
+static const char *parse_cmd_set(const char *next, const char *max_char)
+{
+	char *key, *value;
+	int ret;
+
+	key = parse_key_or_value(&next, max_char);
+	if (!key)
+		die(_("set: missing key"));
+
+	value = parse_key_or_value(&next, max_char);
+	if (!value)
+		die(_("set: missing value"));
+
+	ret = git_config_set_in_file_gently(given_config_source.file, key, value);
+	if (ret)
+		die(_("cannot set key value pair: %d"), ret);
+
+	free(key);
+	free(value);
+	return next;
+}
+
+
+static const char *parse_cmd_unset(const char *next, const char *max_char)
+{
+	char *key;
+	int ret;
+
+	key = parse_key_or_value(&next, max_char);
+	if (!key)
+		die(_("no key found to unset"));
+
+	ret = git_config_set_in_file_gently(given_config_source.file, key, NULL);
+	if (ret)
+		die(_("cannot unset key: %d"), ret);
+
+	free(key);
+	return next;
+}
+
+static void update_config_stdin()
+{
+	struct strbuf input = STRBUF_INIT;
+	const char *next;
+	const char *max_char;
+
+	if (strbuf_read(&input, 0, 1000) < 0)
+		die_errno(_("could not read from stdin"));
+	next = input.buf;
+
+	max_char = input.buf + input.len;
+	while(next < max_char) {
+		if (*next == term)
+			die(_("empty command in input"));
+		else if (isspace(*next))
+			die(_("whitespace before command"));
+		else if (skip_prefix(next, "set ", &next))
+			next = parse_cmd_set(next, max_char);
+		else if (skip_prefix(next, "unset ", &next))
+			next = parse_cmd_unset(next, max_char);
+		else
+			die(_("unknown command %s"), next);
+	}
+
+	strbuf_release(&input);
+}
+
 int cmd_config(int argc, const char **argv, const char *prefix)
 {
 	int nongit = !startup_info->have_repository;
@@ -867,6 +965,12 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 			color_stdout_is_tty = git_config_bool("command line", argv[1]);
 		return get_colorbool(argv[0], argc == 2);
 	}
+	else if (actions == ACTION_STDIN) {
+		check_write();
+		check_argc(argc, 0, 0);
+
+		update_config_stdin();
+	}
 
 	return 0;
 }
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 983a0a1583..d8ad82922d 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -380,6 +380,66 @@ test_expect_success '--add' '
 	test_cmp expect output
 '
 
+test_expect_success '--stdin to set a value' '
+	echo "set foo.mepmep true" | git config --stdin &&
+	git config --get foo.mepmep >output &&
+	echo true >expect &&
+	test_cmp expect output
+'
+
+test_expect_success '--stdin multiline support' '
+	cat >stdin <<-EOF &&
+	set bar.mepmep true
+	set foo.mepmep true
+	EOF
+	git config --stdin <stdin &&
+	git config --get bar.mepmep >output &&
+	git config --get foo.mepmep >>output &&
+	echo -e "true\ntrue" > expect &&
+	test_cmp expect output
+'
+
+test_expect_success '--stdin hides input on errors' '
+	cat >stdin <<-EOF &&
+	set bar.mepmep
+	EOF
+	test_must_fail git config --stdin <stdin 2>output &&
+	echo "fatal: unexpected end of buffer" >expect &&
+	test_cmp expect output
+'
+
+test_expect_success '--stdin fails on leading whitespace' '
+	cat >stdin <<-EOF &&
+	 set bar.mepmep
+	EOF
+	test_must_fail git config --stdin <stdin
+'
+
+test_expect_success '--stdin fails on unknown command' '
+	cat >stdin <<-EOF &&
+	foo bar.mepmep
+	EOF
+	test_must_fail git config --stdin <stdin
+'
+
+test_expect_success '--stdin works on no input' '
+	echo -n "" | git config --stdin
+'
+
+test_expect_success '--stdin fails on unbalanced quotes' '
+	cat >stdin <<-EOF &&
+	set "foo.bar
+	EOF
+	test_must_fail git config --stdin <stdin
+'
+
+test_expect_success '--stdin with --null flag' '
+	echo -ne "set bar.baz\0false" | git config --stdin --null &&
+	git config --get bar.baz >output &&
+	echo false >expect &&
+	test_cmp expect output
+'
+
 cat > .git/config << EOF
 [novalue]
 	variable
-- 
2.24.1




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux