1 /****************************************************************************
2  * Copyright 2020 Thomas E. Dickey                                          *
3  * Copyright 2005-2012,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 
30 /****************************************************************************
31  *  Author: Thomas Dickey                                                   *
32  ****************************************************************************/
33 
34 #include <curses.priv.h>
35 
36 #include <ctype.h>
37 
38 #include <tic.h>
39 
40 MODULE_ID("$Id: trim_sgr0.c,v 1.18 2020/02/02 23:34:34 tom Exp $")
41 
42 #undef CUR
43 #define CUR tp->
44 
45 #define CSI       233
46 #define ESC       033		/* ^[ */
47 #define L_BRACK   '['
48 
49 static char *
50 set_attribute_9(TERMTYPE2 *tp, int flag)
51 {
52     const char *value;
53     char *result;
54 
55     value = tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, flag);
56     if (PRESENT(value))
57 	result = strdup(value);
58     else
59 	result = 0;
60     return result;
61 }
62 
63 static int
64 is_csi(const char *s)
65 {
66     int result = 0;
67     if (s != 0) {
68 	if (UChar(s[0]) == CSI)
69 	    result = 1;
70 	else if (s[0] == ESC && s[1] == L_BRACK)
71 	    result = 2;
72     }
73     return result;
74 }
75 
76 static char *
77 skip_zero(char *s)
78 {
79     if (s[0] == '0') {
80 	if (s[1] == ';')
81 	    s += 2;
82 	else if (isalpha(UChar(s[1])))
83 	    s += 1;
84     }
85     return s;
86 }
87 
88 static const char *
89 skip_delay(const char *s)
90 {
91     if (s[0] == '$' && s[1] == '<') {
92 	s += 2;
93 	while (isdigit(UChar(*s)) || *s == '/')
94 	    ++s;
95 	if (*s == '>')
96 	    ++s;
97     }
98     return s;
99 }
100 
101 /*
102  * Improve similar_sgr a little by moving the attr-string from the beginning
103  * to the end of the s-string.
104  */
105 static bool
106 rewrite_sgr(char *s, char *attr)
107 {
108     if (s != 0) {
109 	if (PRESENT(attr)) {
110 	    size_t len_s = strlen(s);
111 	    size_t len_a = strlen(attr);
112 
113 	    if (len_s > len_a && !strncmp(attr, s, len_a)) {
114 		unsigned n;
115 		TR(TRACE_DATABASE, ("rewrite:\n\t%s", s));
116 		for (n = 0; n < len_s - len_a; ++n) {
117 		    s[n] = s[n + len_a];
118 		}
119 		_nc_STRCPY(s + n, attr, strlen(s) + 1);
120 		TR(TRACE_DATABASE, ("to:\n\t%s", s));
121 	    }
122 	}
123 	return TRUE;
124     }
125     return FALSE;		/* oops */
126 }
127 
128 static bool
129 similar_sgr(char *a, char *b)
130 {
131     bool result = FALSE;
132     if (a != 0 && b != 0) {
133 	int csi_a = is_csi(a);
134 	int csi_b = is_csi(b);
135 	size_t len_a;
136 	size_t len_b;
137 
138 	TR(TRACE_DATABASE, ("similar_sgr:\n\t%s\n\t%s",
139 			    _nc_visbuf2(1, a),
140 			    _nc_visbuf2(2, b)));
141 	if (csi_a != 0 && csi_b != 0 && csi_a == csi_b) {
142 	    a += csi_a;
143 	    b += csi_b;
144 	    if (*a != *b) {
145 		a = skip_zero(a);
146 		b = skip_zero(b);
147 	    }
148 	}
149 	len_a = strlen(a);
150 	len_b = strlen(b);
151 	if (len_a && len_b) {
152 	    if (len_a > len_b)
153 		result = (strncmp(a, b, len_b) == 0);
154 	    else
155 		result = (strncmp(a, b, len_a) == 0);
156 	}
157 	TR(TRACE_DATABASE, ("...similar_sgr: %d\n\t%s\n\t%s", result,
158 			    _nc_visbuf2(1, a),
159 			    _nc_visbuf2(2, b)));
160     }
161     return result;
162 }
163 
164 static unsigned
165 chop_out(char *string, unsigned i, unsigned j)
166 {
167     TR(TRACE_DATABASE, ("chop_out %d..%d from %s", i, j, _nc_visbuf(string)));
168     while (string[j] != '\0') {
169 	string[i++] = string[j++];
170     }
171     string[i] = '\0';
172     return i;
173 }
174 
175 /*
176  * Compare, ignoring delays.  Some of the delay values are inconsistent, and
177  * we do not want to be stopped by that.
178  *
179  * Returns the number of chars from 'full' that we matched.  If any mismatch
180  * occurs, return zero.
181  */
182 static unsigned
183 compare_part(const char *part, const char *full)
184 {
185     const char *next_part;
186     const char *next_full;
187     unsigned used_full = 0;
188     unsigned used_delay = 0;
189 
190     while (*part != 0) {
191 	if (*part != *full) {
192 	    used_full = 0;
193 	    break;
194 	}
195 
196 	/*
197 	 * Adjust the return-value to allow the rare case of
198 	 *      string<delay>string
199 	 * to remove the whole piece.  The most common case is a delay at the
200 	 * end of the string.  The adjusted string will retain the delay, which
201 	 * is conservative.
202 	 */
203 	if (used_delay != 0) {
204 	    used_full += used_delay;
205 	    used_delay = 0;
206 	}
207 	if (*part == '$' && *full == '$') {
208 	    next_part = skip_delay(part);
209 	    next_full = skip_delay(full);
210 	    if (next_part != part && next_full != full) {
211 		used_delay += (unsigned) (next_full - full);
212 		full = next_full;
213 		part = next_part;
214 		continue;
215 	    }
216 	}
217 	++used_full;
218 	++part;
219 	++full;
220     }
221     return used_full;
222 }
223 
224 /*
225  * While 'sgr0' is the "same" as termcap 'me', there is a compatibility issue.
226  * The sgr/sgr0 capabilities include setting/clearing alternate character set
227  * mode.  A termcap application cannot use sgr, so sgr0 strings that reset
228  * alternate character set mode will be misinterpreted.  Here, we remove those
229  * from the more common ISO/ANSI/VT100 entries, which have sgr0 agreeing with
230  * sgr.
231  *
232  * This function returns the modified sgr0 if it can be modified, a null if
233  * an error occurs, or the original sgr0 if no change is needed.
234  */
235 NCURSES_EXPORT(char *)
236 _nc_trim_sgr0(TERMTYPE2 *tp)
237 {
238     char *result = exit_attribute_mode;
239 
240     T((T_CALLED("_nc_trim_sgr0()")));
241 
242     if (PRESENT(exit_attribute_mode)
243 	&& PRESENT(set_attributes)) {
244 	bool found = FALSE;
245 	char *on = set_attribute_9(tp, 1);
246 	char *off = set_attribute_9(tp, 0);
247 	char *end = strdup(exit_attribute_mode);
248 	char *tmp;
249 	size_t i, j, k;
250 
251 	TR(TRACE_DATABASE, ("checking if we can trim sgr0 based on sgr"));
252 	TR(TRACE_DATABASE, ("sgr0       %s", _nc_visbuf(end)));
253 	TR(TRACE_DATABASE, ("sgr(9:off) %s", _nc_visbuf(off)));
254 	TR(TRACE_DATABASE, ("sgr(9:on)  %s", _nc_visbuf(on)));
255 
256 	if (!rewrite_sgr(on, enter_alt_charset_mode)
257 	    || !rewrite_sgr(off, exit_alt_charset_mode)
258 	    || !rewrite_sgr(end, exit_alt_charset_mode)) {
259 	    FreeIfNeeded(off);
260 	} else if (similar_sgr(off, end)
261 		   && !similar_sgr(off, on)) {
262 	    TR(TRACE_DATABASE, ("adjusting sgr(9:off) : %s", _nc_visbuf(off)));
263 	    result = off;
264 	    /*
265 	     * If rmacs is a substring of sgr(0), remove that chunk.
266 	     */
267 	    if (PRESENT(exit_alt_charset_mode)) {
268 		TR(TRACE_DATABASE, ("scan for rmacs %s", _nc_visbuf(exit_alt_charset_mode)));
269 		j = strlen(off);
270 		k = strlen(exit_alt_charset_mode);
271 		if (j > k) {
272 		    for (i = 0; i <= (j - k); ++i) {
273 			unsigned k2 = compare_part(exit_alt_charset_mode,
274 						   off + i);
275 			if (k2 != 0) {
276 			    found = TRUE;
277 			    chop_out(off, (unsigned) i, (unsigned) (i + k2));
278 			    break;
279 			}
280 		    }
281 		}
282 	    }
283 	    /*
284 	     * SGR 10 would reset to normal font.
285 	     */
286 	    if (!found) {
287 		if ((i = (size_t) is_csi(off)) != 0
288 		    && off[strlen(off) - 1] == 'm') {
289 		    TR(TRACE_DATABASE, ("looking for SGR 10 in %s",
290 					_nc_visbuf(off)));
291 		    tmp = skip_zero(off + i);
292 		    if (tmp[0] == '1'
293 			&& skip_zero(tmp + 1) != tmp + 1) {
294 			i = (size_t) (tmp - off);
295 			if (off[i - 1] == ';')
296 			    i--;
297 			j = (size_t) (skip_zero(tmp + 1) - off);
298 			(void) chop_out(off, (unsigned) i, (unsigned) j);
299 			found = TRUE;
300 		    }
301 		}
302 	    }
303 	    if (!found
304 		&& (tmp = strstr(end, off)) != 0
305 		&& strcmp(end, off) != 0) {
306 		i = (size_t) (tmp - end);
307 		j = strlen(off);
308 		tmp = strdup(end);
309 		chop_out(tmp, (unsigned) i, (unsigned) j);
310 		free(off);
311 		result = tmp;
312 	    }
313 	    TR(TRACE_DATABASE, ("...adjusted sgr0 : %s", _nc_visbuf(result)));
314 	    if (!strcmp(result, exit_attribute_mode)) {
315 		TR(TRACE_DATABASE, ("...same result, discard"));
316 		free(result);
317 		result = exit_attribute_mode;
318 	    }
319 	} else {
320 	    /*
321 	     * Either the sgr does not reference alternate character set,
322 	     * or it is incorrect.  That's too hard to decide right now.
323 	     */
324 	    free(off);
325 	}
326 	FreeIfNeeded(end);
327 	FreeIfNeeded(on);
328     } else {
329 	/*
330 	 * Possibly some applications are confused if sgr0 contains rmacs,
331 	 * but that would be a different bug report -TD
332 	 */
333     }
334 
335     returnPtr(result);
336 }
337