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