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,91 @@ 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 , "default" , & value ))
70+ allow_control_characters |= ALLOW_DEFAULT_ANSI_SEQUENCES ;
71+ else if (skip_prefix_in_csv (value , "color" , & value ))
72+ allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES ;
73+ else if (skip_prefix_in_csv (value , "cursor" , & value ))
74+ allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS ;
75+ else if (skip_prefix_in_csv (value , "erase" , & value ))
76+ allow_control_characters |= ALLOW_ANSI_ERASE ;
77+ else if (skip_prefix_in_csv (value , "true" , & value ))
78+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS ;
79+ else if (skip_prefix_in_csv (value , "false" , & value ))
80+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
81+ else
82+ warning (_ ("unrecognized value for '%s': '%s'" ), var , value );
83+ }
84+ return 0 ;
85+ }
86+
87+ static int sideband_config_callback (const char * var , const char * value ,
88+ const struct config_context * ctx UNUSED ,
89+ void * data UNUSED )
90+ {
91+ if (!strcmp (var , "sideband.allowcontrolcharacters" ))
92+ return sideband_allow_control_characters_config (var , value );
93+
94+ return 0 ;
95+ }
96+
97+ void sideband_apply_url_config (const char * url )
98+ {
99+ struct urlmatch_config config = URLMATCH_CONFIG_INIT ;
100+ char * normalized_url ;
101+
102+ if (!url )
103+ BUG ("must not call sideband_apply_url_config(NULL)" );
104+
105+ config .section = "sideband" ;
106+ config .collect_fn = sideband_config_callback ;
107+
108+ normalized_url = url_normalize (url , & config .url );
109+ repo_config (the_repository , urlmatch_config_entry , & config );
110+ free (normalized_url );
111+ string_list_clear (& config .vars , 1 );
112+ urlmatch_config_release (& config );
113+ }
114+
29115/* Returns a color setting (GIT_COLOR_NEVER, etc). */
30116static enum git_colorbool use_sideband_colors (void )
31117{
@@ -39,6 +125,14 @@ static enum git_colorbool use_sideband_colors(void)
39125 if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN )
40126 return use_sideband_colors_cached ;
41127
128+ if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET ) {
129+ if (!repo_config_get_value (the_repository , "sideband.allowcontrolcharacters" , & value ))
130+ sideband_allow_control_characters_config ("sideband.allowcontrolcharacters" , value );
131+
132+ if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET )
133+ allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES ;
134+ }
135+
42136 if (!repo_config_get_string_tmp (the_repository , key , & value ))
43137 use_sideband_colors_cached = git_config_colorbool (key , value );
44138 else if (!repo_config_get_string_tmp (the_repository , "color.ui" , & value ))
@@ -66,6 +160,93 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
66160 list_config_item (list , prefix , keywords [i ].keyword );
67161}
68162
163+ static int handle_ansi_sequence (struct strbuf * dest , const char * src , int n )
164+ {
165+ int i ;
166+
167+ /*
168+ * Valid ANSI color sequences are of the form
169+ *
170+ * ESC [ [<n> [; <n>]*] m
171+ *
172+ * These are part of the Select Graphic Rendition sequences which
173+ * contain more than just color sequences, for more details see
174+ * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
175+ *
176+ * The cursor movement sequences are:
177+ *
178+ * ESC [ n A - Cursor up n lines (CUU)
179+ * ESC [ n B - Cursor down n lines (CUD)
180+ * ESC [ n C - Cursor forward n columns (CUF)
181+ * ESC [ n D - Cursor back n columns (CUB)
182+ * ESC [ n E - Cursor next line, beginning (CNL)
183+ * ESC [ n F - Cursor previous line, beginning (CPL)
184+ * ESC [ n G - Cursor to column n (CHA)
185+ * ESC [ n ; m H - Cursor position (row n, col m) (CUP)
186+ * ESC [ n ; m f - Same as H (HVP)
187+ *
188+ * The sequences to erase characters are:
189+ *
190+ *
191+ * ESC [ 0 J - Clear from cursor to end of screen (ED)
192+ * ESC [ 1 J - Clear from cursor to beginning of screen (ED)
193+ * ESC [ 2 J - Clear entire screen (ED)
194+ * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension
195+ * ESC [ 0 K - Clear from cursor to end of line (EL)
196+ * ESC [ 1 K - Clear from cursor to beginning of line (EL)
197+ * ESC [ 2 K - Clear entire line (EL)
198+ * ESC [ n M - Delete n lines (DL)
199+ * ESC [ n P - Delete n characters (DCH)
200+ * ESC [ n X - Erase n characters (ECH)
201+ *
202+ * For a comprehensive list of common ANSI Escape sequences, see
203+ * https://www.xfree86.org/current/ctlseqs.html
204+ */
205+
206+ if (n < 3 || src [0 ] != '\x1b' || src [1 ] != '[' )
207+ return 0 ;
208+
209+ for (i = 2 ; i < n ; i ++ ) {
210+ if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES ) &&
211+ src [i ] == 'm' ) ||
212+ ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS ) &&
213+ strchr ("ABCDEFGHf" , src [i ])) ||
214+ ((allow_control_characters & ALLOW_ANSI_ERASE ) &&
215+ strchr ("JKMPX" , src [i ]))) {
216+ strbuf_add (dest , src , i + 1 );
217+ return i ;
218+ }
219+ if (!isdigit (src [i ]) && src [i ] != ';' )
220+ break ;
221+ }
222+
223+ return 0 ;
224+ }
225+
226+ static void strbuf_add_sanitized (struct strbuf * dest , const char * src , int n )
227+ {
228+ int i ;
229+
230+ if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS )) {
231+ strbuf_add (dest , src , n );
232+ return ;
233+ }
234+
235+ strbuf_grow (dest , n );
236+ for (; n && * src ; src ++ , n -- ) {
237+ if (!iscntrl (* src ) || * src == '\t' || * src == '\n' ) {
238+ strbuf_addch (dest , * src );
239+ } else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS &&
240+ (i = handle_ansi_sequence (dest , src , n ))) {
241+ src += i ;
242+ n -= i ;
243+ } else {
244+ strbuf_addch (dest , '^' );
245+ strbuf_addch (dest , * src == 0x7f ? '?' : 0x40 + * src );
246+ }
247+ }
248+ }
249+
69250/*
70251 * Optionally highlight one keyword in remote output if it appears at the start
71252 * of the line. This should be called for a single line only, which is
@@ -81,7 +262,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
81262 int i ;
82263
83264 if (!want_color_stderr (use_sideband_colors ())) {
84- strbuf_add (dest , src , n );
265+ strbuf_add_sanitized (dest , src , n );
85266 return ;
86267 }
87268
@@ -114,7 +295,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
114295 }
115296 }
116297
117- strbuf_add (dest , src , n );
298+ strbuf_add_sanitized (dest , src , n );
118299}
119300
120301
0 commit comments