1010#include "help.h"
1111#include "pkt-line.h"
1212#include "write-or-die.h"
13+ #include "urlmatch.h"
1314
1415struct keyword_entry {
1516 /*
@@ -26,6 +27,89 @@ static struct keyword_entry keywords[] = {
2627 { "error" , GIT_COLOR_BOLD_RED },
2728};
2829
30+ static enum {
31+ ALLOW_CONTROL_SEQUENCES_UNSET = -1 ,
32+ ALLOW_NO_CONTROL_CHARACTERS = 0 ,
33+ ALLOW_ANSI_COLOR_SEQUENCES = 1 <<0 ,
34+ ALLOW_ANSI_CURSOR_MOVEMENTS = 1 <<1 ,
35+ ALLOW_ANSI_ERASE = 1 <<2 ,
36+ ALLOW_ALL_CONTROL_CHARACTERS = 1 <<3 ,
37+ #ifdef WITH_BREAKING_CHANGES
38+ ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES ,
39+ #else
40+ ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ALL_CONTROL_CHARACTERS ,
41+ #endif
42+ } allow_control_characters = ALLOW_CONTROL_SEQUENCES_UNSET ;
43+
44+ static inline int skip_prefix_in_csv (const char * value , const char * prefix ,
45+ const char * * out )
46+ {
47+ if (!skip_prefix (value , prefix , & value ) ||
48+ (* value && * value != ',' ))
49+ return 0 ;
50+ * out = value + !!* value ;
51+ return 1 ;
52+ }
53+
54+ int sideband_allow_control_characters_config (const char * var , const char * value )
55+ {
56+ switch (git_parse_maybe_bool (value )) {
57+ case 0 :
58+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
59+ return 0 ;
60+ case 1 :
61+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS ;
62+ return 0 ;
63+ default :
64+ break ;
65+ }
66+
67+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
68+ while (* value ) {
69+ if (skip_prefix_in_csv (value , "color" , & value ))
70+ allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES ;
71+ else if (skip_prefix_in_csv (value , "cursor" , & value ))
72+ allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS ;
73+ else if (skip_prefix_in_csv (value , "erase" , & value ))
74+ allow_control_characters |= ALLOW_ANSI_ERASE ;
75+ else if (skip_prefix_in_csv (value , "true" , & value ))
76+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS ;
77+ else if (skip_prefix_in_csv (value , "false" , & value ))
78+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
79+ else
80+ warning (_ ("unrecognized value for '%s': '%s'" ), var , value );
81+ }
82+ return 0 ;
83+ }
84+
85+ static int sideband_config_callback (const char * var , const char * value ,
86+ const struct config_context * ctx UNUSED ,
87+ void * data UNUSED )
88+ {
89+ if (!strcmp (var , "sideband.allowcontrolcharacters" ))
90+ return sideband_allow_control_characters_config (var , value );
91+
92+ return 0 ;
93+ }
94+
95+ void sideband_apply_url_config (const char * url )
96+ {
97+ struct urlmatch_config config = URLMATCH_CONFIG_INIT ;
98+ char * normalized_url ;
99+
100+ if (!url )
101+ BUG ("must not call sideband_apply_url_config(NULL)" );
102+
103+ config .section = "sideband" ;
104+ config .collect_fn = sideband_config_callback ;
105+
106+ normalized_url = url_normalize (url , & config .url );
107+ repo_config (the_repository , urlmatch_config_entry , & config );
108+ free (normalized_url );
109+ string_list_clear (& config .vars , 1 );
110+ urlmatch_config_release (& config );
111+ }
112+
29113/* Returns a color setting (GIT_COLOR_NEVER, etc). */
30114static enum git_colorbool use_sideband_colors (void )
31115{
@@ -39,6 +123,14 @@ static enum git_colorbool use_sideband_colors(void)
39123 if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN )
40124 return use_sideband_colors_cached ;
41125
126+ if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET ) {
127+ if (!repo_config_get_value (the_repository , "sideband.allowcontrolcharacters" , & value ))
128+ sideband_allow_control_characters_config ("sideband.allowcontrolcharacters" , value );
129+
130+ if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET )
131+ allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES ;
132+ }
133+
42134 if (!repo_config_get_string_tmp (the_repository , key , & value ))
43135 use_sideband_colors_cached = git_config_colorbool (key , value );
44136 else if (!repo_config_get_string_tmp (the_repository , "color.ui" , & value ))
@@ -66,6 +158,93 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
66158 list_config_item (list , prefix , keywords [i ].keyword );
67159}
68160
161+ static int handle_ansi_sequence (struct strbuf * dest , const char * src , int n )
162+ {
163+ int i ;
164+
165+ /*
166+ * Valid ANSI color sequences are of the form
167+ *
168+ * ESC [ [<n> [; <n>]*] m
169+ *
170+ * These are part of the Select Graphic Rendition sequences which
171+ * contain more than just color sequences, for more details see
172+ * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
173+ *
174+ * The cursor movement sequences are:
175+ *
176+ * ESC [ n A - Cursor up n lines (CUU)
177+ * ESC [ n B - Cursor down n lines (CUD)
178+ * ESC [ n C - Cursor forward n columns (CUF)
179+ * ESC [ n D - Cursor back n columns (CUB)
180+ * ESC [ n E - Cursor next line, beginning (CNL)
181+ * ESC [ n F - Cursor previous line, beginning (CPL)
182+ * ESC [ n G - Cursor to column n (CHA)
183+ * ESC [ n ; m H - Cursor position (row n, col m) (CUP)
184+ * ESC [ n ; m f - Same as H (HVP)
185+ *
186+ * The sequences to erase characters are:
187+ *
188+ *
189+ * ESC [ 0 J - Clear from cursor to end of screen (ED)
190+ * ESC [ 1 J - Clear from cursor to beginning of screen (ED)
191+ * ESC [ 2 J - Clear entire screen (ED)
192+ * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension
193+ * ESC [ 0 K - Clear from cursor to end of line (EL)
194+ * ESC [ 1 K - Clear from cursor to beginning of line (EL)
195+ * ESC [ 2 K - Clear entire line (EL)
196+ * ESC [ n M - Delete n lines (DL)
197+ * ESC [ n P - Delete n characters (DCH)
198+ * ESC [ n X - Erase n characters (ECH)
199+ *
200+ * For a comprehensive list of common ANSI Escape sequences, see
201+ * https://www.xfree86.org/current/ctlseqs.html
202+ */
203+
204+ if (n < 3 || src [0 ] != '\x1b' || src [1 ] != '[' )
205+ return 0 ;
206+
207+ for (i = 2 ; i < n ; i ++ ) {
208+ if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES ) &&
209+ src [i ] == 'm' ) ||
210+ ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS ) &&
211+ strchr ("ABCDEFGHf" , src [i ])) ||
212+ ((allow_control_characters & ALLOW_ANSI_ERASE ) &&
213+ strchr ("JKMPX" , src [i ]))) {
214+ strbuf_add (dest , src , i + 1 );
215+ return i ;
216+ }
217+ if (!isdigit (src [i ]) && src [i ] != ';' )
218+ break ;
219+ }
220+
221+ return 0 ;
222+ }
223+
224+ static void strbuf_add_sanitized (struct strbuf * dest , const char * src , int n )
225+ {
226+ int i ;
227+
228+ if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS )) {
229+ strbuf_add (dest , src , n );
230+ return ;
231+ }
232+
233+ strbuf_grow (dest , n );
234+ for (; n && * src ; src ++ , n -- ) {
235+ if (!iscntrl (* src ) || * src == '\t' || * src == '\n' ) {
236+ strbuf_addch (dest , * src );
237+ } else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS &&
238+ (i = handle_ansi_sequence (dest , src , n ))) {
239+ src += i ;
240+ n -= i ;
241+ } else {
242+ strbuf_addch (dest , '^' );
243+ strbuf_addch (dest , * src == 0x7f ? '?' : 0x40 + * src );
244+ }
245+ }
246+ }
247+
69248/*
70249 * Optionally highlight one keyword in remote output if it appears at the start
71250 * of the line. This should be called for a single line only, which is
@@ -81,7 +260,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
81260 int i ;
82261
83262 if (!want_color_stderr (use_sideband_colors ())) {
84- strbuf_add (dest , src , n );
263+ strbuf_add_sanitized (dest , src , n );
85264 return ;
86265 }
87266
@@ -114,7 +293,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
114293 }
115294 }
116295
117- strbuf_add (dest , src , n );
296+ strbuf_add_sanitized (dest , src , n );
118297}
119298
120299
0 commit comments