static enum {
ALLOW_NO_CONTROL_CHARACTERS = 0,
ALLOW_ANSI_COLOR_SEQUENCES = 1<<0,
+ ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1,
+ ALLOW_ANSI_ERASE = 1<<2,
ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES,
- ALLOW_ALL_CONTROL_CHARACTERS = 1<<1,
-} allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
+ ALLOW_ALL_CONTROL_CHARACTERS = 1<<3,
+} allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
+
+static inline int skip_prefix_in_csv(const char *value, const char *prefix,
+ const char **out)
+{
+ if (!skip_prefix(value, prefix, &value) ||
+ (*value && *value != ','))
+ return 0;
+ *out = value + !!*value;
+ return 1;
+}
+
+static void parse_allow_control_characters(const char *value)
+{
+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
+ while (*value) {
+ if (skip_prefix_in_csv(value, "default", &value))
+ allow_control_characters |= ALLOW_DEFAULT_ANSI_SEQUENCES;
+ else if (skip_prefix_in_csv(value, "color", &value))
+ allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES;
+ else if (skip_prefix_in_csv(value, "cursor", &value))
+ allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS;
+ else if (skip_prefix_in_csv(value, "erase", &value))
+ allow_control_characters |= ALLOW_ANSI_ERASE;
+ else if (skip_prefix_in_csv(value, "true", &value))
+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS;
+ else if (skip_prefix_in_csv(value, "false", &value))
+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
+ else
+ warning(_("unrecognized value for `sideband."
+ "allowControlCharacters`: '%s'"), value);
+ }
+}
/* Returns a color setting (GIT_COLOR_NEVER, etc). */
static int use_sideband_colors(void)
if (git_config_get_string_tmp("sideband.allowcontrolcharacters",
&value))
; /* huh? `get_maybe_bool()` returned -1 */
- else if (!strcmp(value, "default"))
- allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
- else if (!strcmp(value, "color"))
- allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
else
- warning(_("unrecognized value for `sideband."
- "allowControlCharacters`: '%s'"), value);
+ parse_allow_control_characters(value);
break;
default:
break; /* not configured */
list_config_item(list, prefix, keywords[i].keyword);
}
-static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n)
+static int handle_ansi_sequence(struct strbuf *dest, const char *src, int n)
{
int i;
* These are part of the Select Graphic Rendition sequences which
* contain more than just color sequences, for more details see
* https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
+ *
+ * The cursor movement sequences are:
+ *
+ * ESC [ n A - Cursor up n lines (CUU)
+ * ESC [ n B - Cursor down n lines (CUD)
+ * ESC [ n C - Cursor forward n columns (CUF)
+ * ESC [ n D - Cursor back n columns (CUB)
+ * ESC [ n E - Cursor next line, beginning (CNL)
+ * ESC [ n F - Cursor previous line, beginning (CPL)
+ * ESC [ n G - Cursor to column n (CHA)
+ * ESC [ n ; m H - Cursor position (row n, col m) (CUP)
+ * ESC [ n ; m f - Same as H (HVP)
+ *
+ * The sequences to erase characters are:
+ *
+ *
+ * ESC [ 0 J - Clear from cursor to end of screen (ED)
+ * ESC [ 1 J - Clear from cursor to beginning of screen (ED)
+ * ESC [ 2 J - Clear entire screen (ED)
+ * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension
+ * ESC [ 0 K - Clear from cursor to end of line (EL)
+ * ESC [ 1 K - Clear from cursor to beginning of line (EL)
+ * ESC [ 2 K - Clear entire line (EL)
+ * ESC [ n M - Delete n lines (DL)
+ * ESC [ n P - Delete n characters (DCH)
+ * ESC [ n X - Erase n characters (ECH)
+ *
+ * For a comprehensive list of common ANSI Escape sequences, see
+ * https://www.xfree86.org/current/ctlseqs.html
*/
- if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES ||
- n < 3 || src[0] != '\x1b' || src[1] != '[')
+ if (n < 3 || src[0] != '\x1b' || src[1] != '[')
return 0;
for (i = 2; i < n; i++) {
- if (src[i] == 'm') {
+ if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES) &&
+ src[i] == 'm') ||
+ ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS) &&
+ strchr("ABCDEFGHf", src[i])) ||
+ ((allow_control_characters & ALLOW_ANSI_ERASE) &&
+ strchr("JKMPX", src[i]))) {
strbuf_add(dest, src, i + 1);
return i;
}
{
int i;
- if (allow_control_characters == ALLOW_ALL_CONTROL_CHARACTERS) {
+ if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS)) {
strbuf_add(dest, src, n);
return;
}
for (; n && *src; src++, n--) {
if (!iscntrl(*src) || *src == '\t' || *src == '\n')
strbuf_addch(dest, *src);
- else if ((i = handle_ansi_color_sequence(dest, src, n))) {
+ else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS &&
+ (i = handle_ansi_sequence(dest, src, n))) {
src += i;
n -= i;
} else {
test_file_not_empty actual
'
+test_decode_csi() {
+ awk '{
+ while (match($0, /\033/) != 0) {
+ printf "%sCSI ", substr($0, 1, RSTART-1);
+ $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
+ }
+ print
+ }'
+}
+
+test_expect_success 'control sequences in sideband allowed by default' '
+ write_script .git/color-me-surprised <<-\EOF &&
+ printf "error: \\033[31mcolor\\033[m\\033[Goverwrite\\033[Gerase\\033[K\\033?25l\\n" >&2
+ exec "$@"
+ EOF
+ test_config_global uploadPack.packObjectsHook ./color-me-surprised &&
+ test_commit need-at-least-one-commit-at-least &&
+
+ rm -rf throw-away &&
+ git clone --no-local . throw-away 2>stderr &&
+ test_decode_color <stderr >color-decoded &&
+ test_decode_csi <color-decoded >decoded &&
+ test_grep ! "CSI \\[K" decoded &&
+ test_grep ! "CSI \\[G" decoded &&
+ test_grep "\\^\\[?25l" decoded &&
+
+ rm -rf throw-away &&
+ git -c sideband.allowControlCharacters=erase,cursor,color \
+ clone --no-local . throw-away 2>stderr &&
+ test_decode_color <stderr >color-decoded &&
+ test_decode_csi <color-decoded >decoded &&
+ test_grep "RED" decoded &&
+ test_grep "CSI \\[K" decoded &&
+ test_grep "CSI \\[G" decoded &&
+ test_grep ! "\\^\\[\\[K" decoded &&
+ test_grep ! "\\^\\[\\[G" decoded
+'
+
test_done