1/****************************************************************************
2 * Copyright 2020-2021,2023 Thomas E. Dickey                                *
3 * Copyright 1998-2016,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 E. Dickey                    1998                        *
32 ****************************************************************************/
33
34#include <curses.priv.h>
35
36#include <ctype.h>
37#include <tic.h>
38
39MODULE_ID("$Id: comp_expand.c,v 1.35 2023/04/28 20:59:06 tom Exp $")
40
41#if 0
42#define DEBUG_THIS(p) DEBUG(9, p)
43#else
44#define DEBUG_THIS(p)		/* nothing */
45#endif
46
47static int
48trailing_spaces(const char *src)
49{
50    while (*src == ' ')
51	src++;
52    return *src == 0;
53}
54
55/* this deals with differences over whether 0x7f and 0x80..0x9f are controls */
56#define REALPRINT(s) (UChar(*(s)) < 127 && isprint(UChar(*(s))))
57
58#define P_LIMIT(p)   (length - (size_t)(p))
59
60NCURSES_EXPORT(char *)
61_nc_tic_expand(const char *srcp, bool tic_format, int numbers)
62{
63    static char *buffer;
64    static size_t length;
65
66    int bufp;
67    const char *str = VALID_STRING(srcp) ? srcp : "\0\0";
68    size_t need = (2 + strlen(str)) * 4;
69    int ch;
70    int octals = 0;
71    struct {
72	int ch;
73	int offset;
74    } fixups[MAX_TC_FIXUPS];
75
76    if (srcp == 0) {
77#if NO_LEAKS
78	if (buffer != 0) {
79	    FreeAndNull(buffer);
80	    length = 0;
81	}
82#endif
83	return 0;
84    }
85    if (buffer == 0 || need > length) {
86	if ((buffer = typeRealloc(char, length = need, buffer)) == 0)
87	      return 0;
88    }
89
90    DEBUG_THIS(("_nc_tic_expand %s:%s:%s",
91		tic_format ? "ti" : "tc",
92		numbers ? "#" : "",
93		_nc_visbuf(srcp)));
94    bufp = 0;
95    while ((ch = UChar(*str)) != 0) {
96	if (ch == '%' && REALPRINT(str + 1)) {
97	    buffer[bufp++] = *str++;
98	    /*
99	     * Though the character literals are more compact, most
100	     * terminal descriptions use numbers and are not easy
101	     * to read in character-literal form.
102	     */
103	    switch (numbers) {
104	    case -1:
105		if (str[0] == S_QUOTE
106		    && str[1] != '\\'
107		    && REALPRINT(str + 1)
108		    && str[2] == S_QUOTE) {
109		    _nc_SPRINTF(buffer + bufp, _nc_SLIMIT(P_LIMIT(bufp))
110				"{%d}", str[1]);
111		    bufp += (int) strlen(buffer + bufp);
112		    str += 2;
113		} else {
114		    buffer[bufp++] = *str;
115		}
116		break;
117		/*
118		 * If we have a "%{number}", try to translate it into
119		 * a "%'char'" form, since that will run a little faster
120		 * when we're interpreting it.  Also, having one form
121		 * for the constant makes it simpler to compare terminal
122		 * descriptions.
123		 */
124	    case 1:
125		if (str[0] == L_BRACE
126		    && isdigit(UChar(str[1]))) {
127		    char *dst = 0;
128		    long value = strtol(str + 1, &dst, 0);
129		    if (dst != 0
130			&& *dst == R_BRACE
131			&& value < 127
132			&& isprint((int) value)) {
133			ch = (int) value;
134			buffer[bufp++] = S_QUOTE;
135			if (ch == '\\'
136			    || ch == S_QUOTE)
137			    buffer[bufp++] = '\\';
138			buffer[bufp++] = (char) ch;
139			buffer[bufp++] = S_QUOTE;
140			str = dst;
141		    } else {
142			buffer[bufp++] = *str;
143		    }
144		} else {
145		    buffer[bufp++] = *str;
146		}
147		break;
148	    default:
149		if (*str == ',')	/* minitel1 uses this */
150		    buffer[bufp++] = '\\';
151		buffer[bufp++] = *str;
152		break;
153	    }
154	} else if (ch == 128) {
155	    buffer[bufp++] = '\\';
156	    buffer[bufp++] = '0';
157	} else if (ch == '\033') {
158	    buffer[bufp++] = '\\';
159	    buffer[bufp++] = 'E';
160	} else if (ch == '\\' && tic_format && (str == srcp || str[-1] != '^')) {
161	    buffer[bufp++] = '\\';
162	    buffer[bufp++] = '\\';
163	} else if (ch == ' ' && tic_format && (str == srcp ||
164					       trailing_spaces(str))) {
165	    buffer[bufp++] = '\\';
166	    buffer[bufp++] = 's';
167	} else if ((ch == ',' || ch == '^') && tic_format) {
168	    buffer[bufp++] = '\\';
169	    buffer[bufp++] = (char) ch;
170	} else if (REALPRINT(str)
171		   && (ch != ','
172		       && !(ch == ':' && !tic_format)
173		       && !(ch == '!' && !tic_format)
174		       && ch != '^'))
175	    buffer[bufp++] = (char) ch;
176	else if (ch == '\r') {
177	    buffer[bufp++] = '\\';
178	    buffer[bufp++] = 'r';
179	} else if (ch == '\n') {
180	    buffer[bufp++] = '\\';
181	    buffer[bufp++] = 'n';
182	}
183#define UnCtl(c) ((c) + '@')
184	else if (UChar(ch) < 32
185		 && isdigit(UChar(str[1]))) {
186	    _nc_SPRINTF(&buffer[bufp], _nc_SLIMIT(P_LIMIT(bufp))
187			"^%c", UnCtl(ch));
188	    bufp += 2;
189	} else {
190	    _nc_SPRINTF(&buffer[bufp], _nc_SLIMIT(P_LIMIT(bufp))
191			"\\%03o", ch);
192	    if ((octals < MAX_TC_FIXUPS) &&
193		((tic_format && (ch == 127)) || ch < 32)) {
194		fixups[octals].ch = UChar(ch);
195		fixups[octals].offset = bufp;
196		++octals;
197	    }
198	    bufp += 4;
199	}
200
201	str++;
202    }
203
204    buffer[bufp] = '\0';
205
206    /*
207     * If most of a short string is ASCII control characters, reformat the
208     * string to show those in up-arrow format.  For longer strings, it is
209     * more likely that the characters are just binary coding.
210     *
211     * If we're formatting termcap, just use the shorter format (up-arrows).
212     */
213    if (octals != 0 && (!tic_format || (bufp - (4 * octals)) < MIN_TC_FIXUPS)) {
214	while (--octals >= 0) {
215	    char *p = buffer + fixups[octals].offset;
216	    *p++ = '^';
217	    *p++ = (char) ((fixups[octals].ch == 127)
218			   ? '?'
219			   : (fixups[octals].ch + (int) '@'));
220	    while ((p[0] = p[2]) != 0) {
221		++p;
222	    }
223	}
224    }
225    DEBUG_THIS(("... %s", _nc_visbuf(buffer)));
226    return (buffer);
227}
228