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,85 @@ 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+ ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES
38+ } allow_control_characters = ALLOW_CONTROL_SEQUENCES_UNSET ;
39+
40+ static inline int skip_prefix_in_csv (const char * value , const char * prefix ,
41+ const char * * out )
42+ {
43+ if (!skip_prefix (value , prefix , & value ) ||
44+ (* value && * value != ',' ))
45+ return 0 ;
46+ * out = value + !!* value ;
47+ return 1 ;
48+ }
49+
50+ int sideband_allow_control_characters_config (const char * var , const char * value )
51+ {
52+ switch (git_parse_maybe_bool (value )) {
53+ case 0 :
54+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
55+ return 0 ;
56+ case 1 :
57+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS ;
58+ return 0 ;
59+ default :
60+ break ;
61+ }
62+
63+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
64+ while (* value ) {
65+ if (skip_prefix_in_csv (value , "color" , & value ))
66+ allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES ;
67+ else if (skip_prefix_in_csv (value , "cursor" , & value ))
68+ allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS ;
69+ else if (skip_prefix_in_csv (value , "erase" , & value ))
70+ allow_control_characters |= ALLOW_ANSI_ERASE ;
71+ else if (skip_prefix_in_csv (value , "true" , & value ))
72+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS ;
73+ else if (skip_prefix_in_csv (value , "false" , & value ))
74+ allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS ;
75+ else
76+ warning (_ ("unrecognized value for '%s': '%s'" ), var , value );
77+ }
78+ return 0 ;
79+ }
80+
81+ static int sideband_config_callback (const char * var , const char * value ,
82+ const struct config_context * ctx UNUSED ,
83+ void * data UNUSED )
84+ {
85+ if (!strcmp (var , "sideband.allowcontrolcharacters" ))
86+ return sideband_allow_control_characters_config (var , value );
87+
88+ return 0 ;
89+ }
90+
91+ void sideband_apply_url_config (const char * url )
92+ {
93+ struct urlmatch_config config = URLMATCH_CONFIG_INIT ;
94+ char * normalized_url ;
95+
96+ if (!url )
97+ BUG ("must not call sideband_apply_url_config(NULL)" );
98+
99+ config .section = "sideband" ;
100+ config .collect_fn = sideband_config_callback ;
101+
102+ normalized_url = url_normalize (url , & config .url );
103+ repo_config (the_repository , urlmatch_config_entry , & config );
104+ free (normalized_url );
105+ string_list_clear (& config .vars , 1 );
106+ urlmatch_config_release (& config );
107+ }
108+
29109/* Returns a color setting (GIT_COLOR_NEVER, etc). */
30110static enum git_colorbool use_sideband_colors (void )
31111{
@@ -39,6 +119,14 @@ static enum git_colorbool use_sideband_colors(void)
39119 if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN )
40120 return use_sideband_colors_cached ;
41121
122+ if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET ) {
123+ if (!repo_config_get_value (the_repository , "sideband.allowcontrolcharacters" , & value ))
124+ sideband_allow_control_characters_config ("sideband.allowcontrolcharacters" , value );
125+
126+ if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET )
127+ allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES ;
128+ }
129+
42130 if (!repo_config_get_string_tmp (the_repository , key , & value ))
43131 use_sideband_colors_cached = git_config_colorbool (key , value );
44132 else if (!repo_config_get_string_tmp (the_repository , "color.ui" , & value ))
@@ -66,6 +154,93 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
66154 list_config_item (list , prefix , keywords [i ].keyword );
67155}
68156
157+ static int handle_ansi_sequence (struct strbuf * dest , const char * src , int n )
158+ {
159+ int i ;
160+
161+ /*
162+ * Valid ANSI color sequences are of the form
163+ *
164+ * ESC [ [<n> [; <n>]*] m
165+ *
166+ * These are part of the Select Graphic Rendition sequences which
167+ * contain more than just color sequences, for more details see
168+ * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
169+ *
170+ * The cursor movement sequences are:
171+ *
172+ * ESC [ n A - Cursor up n lines (CUU)
173+ * ESC [ n B - Cursor down n lines (CUD)
174+ * ESC [ n C - Cursor forward n columns (CUF)
175+ * ESC [ n D - Cursor back n columns (CUB)
176+ * ESC [ n E - Cursor next line, beginning (CNL)
177+ * ESC [ n F - Cursor previous line, beginning (CPL)
178+ * ESC [ n G - Cursor to column n (CHA)
179+ * ESC [ n ; m H - Cursor position (row n, col m) (CUP)
180+ * ESC [ n ; m f - Same as H (HVP)
181+ *
182+ * The sequences to erase characters are:
183+ *
184+ *
185+ * ESC [ 0 J - Clear from cursor to end of screen (ED)
186+ * ESC [ 1 J - Clear from cursor to beginning of screen (ED)
187+ * ESC [ 2 J - Clear entire screen (ED)
188+ * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension
189+ * ESC [ 0 K - Clear from cursor to end of line (EL)
190+ * ESC [ 1 K - Clear from cursor to beginning of line (EL)
191+ * ESC [ 2 K - Clear entire line (EL)
192+ * ESC [ n M - Delete n lines (DL)
193+ * ESC [ n P - Delete n characters (DCH)
194+ * ESC [ n X - Erase n characters (ECH)
195+ *
196+ * For a comprehensive list of common ANSI Escape sequences, see
197+ * https://www.xfree86.org/current/ctlseqs.html
198+ */
199+
200+ if (n < 3 || src [0 ] != '\x1b' || src [1 ] != '[' )
201+ return 0 ;
202+
203+ for (i = 2 ; i < n ; i ++ ) {
204+ if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES ) &&
205+ src [i ] == 'm' ) ||
206+ ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS ) &&
207+ strchr ("ABCDEFGHf" , src [i ])) ||
208+ ((allow_control_characters & ALLOW_ANSI_ERASE ) &&
209+ strchr ("JKMPX" , src [i ]))) {
210+ strbuf_add (dest , src , i + 1 );
211+ return i ;
212+ }
213+ if (!isdigit (src [i ]) && src [i ] != ';' )
214+ break ;
215+ }
216+
217+ return 0 ;
218+ }
219+
220+ static void strbuf_add_sanitized (struct strbuf * dest , const char * src , int n )
221+ {
222+ int i ;
223+
224+ if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS )) {
225+ strbuf_add (dest , src , n );
226+ return ;
227+ }
228+
229+ strbuf_grow (dest , n );
230+ for (; n && * src ; src ++ , n -- ) {
231+ if (!iscntrl (* src ) || * src == '\t' || * src == '\n' ) {
232+ strbuf_addch (dest , * src );
233+ } else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS &&
234+ (i = handle_ansi_sequence (dest , src , n ))) {
235+ src += i ;
236+ n -= i ;
237+ } else {
238+ strbuf_addch (dest , '^' );
239+ strbuf_addch (dest , * src == 0x7f ? '?' : 0x40 + * src );
240+ }
241+ }
242+ }
243+
69244/*
70245 * Optionally highlight one keyword in remote output if it appears at the start
71246 * of the line. This should be called for a single line only, which is
@@ -81,7 +256,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
81256 int i ;
82257
83258 if (!want_color_stderr (use_sideband_colors ())) {
84- strbuf_add (dest , src , n );
259+ strbuf_add_sanitized (dest , src , n );
85260 return ;
86261 }
87262
@@ -114,7 +289,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
114289 }
115290 }
116291
117- strbuf_add (dest , src , n );
292+ strbuf_add_sanitized (dest , src , n );
118293}
119294
120295
0 commit comments