xref: /netbsd/lib/libterminfo/tparm.c (revision 33ebc314)
1 /* $NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $ */
2 
3 /*
4  * Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Roy Marples.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $");
32 #include <sys/param.h>
33 
34 #include <assert.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <term_private.h>
42 #include <term.h>
43 
44 #define LONG_STR_MAX ((CHAR_BIT * sizeof(long)) / 3)
45 #define BUFINC 128	/* Size to increment the terminal buffer by */
46 
47 #define VA_LONG_LONG	1
48 #define VA_CHAR_INT	2
49 //#define VA_CHAR_LONG	3	/* No need for this yet */
50 
51 static TERMINAL *dumbterm; /* For non thread safe functions */
52 
53 typedef struct {
54 	long nums[20];
55 	char *strings[20];
56 	size_t offset;
57 } TPSTACK;
58 
59 typedef struct {
60 	long num;
61 	char *string;
62 } TPVAR;
63 
64 static int
push(long num,char * string,TPSTACK * stack)65 push(long num, char *string, TPSTACK *stack)
66 {
67 	if (stack->offset >= sizeof(stack->nums)) {
68 		errno = E2BIG;
69 		return -1;
70 	}
71 	stack->nums[stack->offset] = num;
72 	stack->strings[stack->offset] = string;
73 	stack->offset++;
74 	return 0;
75 }
76 
77 static int
pop(long * num,char ** string,TPSTACK * stack)78 pop(long *num, char **string, TPSTACK *stack)
79 {
80 	if (stack->offset == 0) {
81 		if (num)
82 			*num = 0;
83 		if (string)
84 			*string = NULL;
85 		errno = E2BIG;
86 		return -1;
87 	}
88 	stack->offset--;
89 	if (num)
90 		*num = stack->nums[stack->offset];
91 	if (string)
92 		*string = stack->strings[stack->offset];
93 	return 0;
94 }
95 
96 static char *
checkbuf(TERMINAL * term,size_t len)97 checkbuf(TERMINAL *term, size_t len)
98 {
99 	char *buf;
100 
101 	if (term->_bufpos + len >= term->_buflen) {
102 		len = term->_buflen + MAX(len, BUFINC);
103 		buf = realloc(term->_buf, len);
104 		if (buf == NULL)
105 			return NULL;
106 		term->_buf = buf;
107 		term->_buflen = len;
108 	}
109 	return term->_buf;
110 }
111 
112 static size_t
ochar(TERMINAL * term,int c)113 ochar(TERMINAL *term, int c)
114 {
115 	if (c == 0)
116 		c = 0200;
117 	/* Check we have space and a terminator */
118 	if (checkbuf(term, 2) == NULL)
119 		return 0;
120 	term->_buf[term->_bufpos++] = (char)c;
121 	return 1;
122 }
123 
124 static size_t
onum(TERMINAL * term,const char * fmt,int num,size_t len)125 onum(TERMINAL *term, const char *fmt, int num, size_t len)
126 {
127 	int l;
128 	size_t r;
129 
130 	if (len < LONG_STR_MAX)
131 		len = LONG_STR_MAX;
132 	if (checkbuf(term, len + 2) == NULL)
133 		return 0;
134 	l = snprintf(term->_buf + term->_bufpos, len + 2, fmt, num);
135 	if (l == -1)
136 		return 0;
137 	r = (size_t)l;
138 	term->_bufpos += r;
139 	return r;
140 }
141 
142 /*
143   Make a pass through the string so we can work out
144   which parameters are ints and which are char *.
145   Basically we only use char * if %p[1-9] is followed by %l or %s.
146 */
147 int
_ti_parm_analyse(const char * str,int * piss,int piss_len)148 _ti_parm_analyse(const char *str, int *piss, int piss_len)
149 {
150 	int nparm, lpop;
151 	char c;
152 
153 	nparm = 0;
154 	lpop = -1;
155 	while ((c = *str++) != '\0') {
156 		if (c != '%')
157 			continue;
158 		c = *str++;
159 		switch (c) {
160 			case 'l':
161 			case 's':
162 				if (lpop > 0) {
163 					if (lpop <= piss_len)
164 						piss[lpop - 1] = 1;
165 					else if (piss)
166 						errno = E2BIG;
167 				}
168 				break;
169 			case 'p':
170 				c = *str++;
171 				if (c < '1' || c > '9') {
172 					errno = EINVAL;
173 					continue;
174 				} else {
175 					lpop = c - '0';
176 					if (lpop > nparm)
177 						nparm = lpop;
178 				}
179 				break;
180 			default:
181 				lpop = -1;
182 		}
183 	}
184 
185 	return nparm;
186 }
187 
188 static char *
_ti_tiparm(TERMINAL * term,const char * str,int va_type,va_list parms)189 _ti_tiparm(TERMINAL *term, const char *str, int va_type, va_list parms)
190 {
191 	char c, fmt[64], *fp, *ostr;
192 	long val, val2;
193 	long dnums[26]; /* dynamic variables a-z, not preserved */
194 	size_t l, max, width, precision, olen;
195 	TPSTACK stack;
196 	TPVAR params[TPARM_MAX];
197 	unsigned int done, dot, minus;
198 	int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */
199 
200 	if (str == NULL)
201 		return NULL;
202 
203 	/*
204 	  If not passed a terminal, malloc a dummy one.
205 	  This means we can preserve buffers and variables per terminal and
206 	  still work with non thread safe functions (which sadly are still the
207 	  norm and standard).
208 	*/
209 	if (term == NULL) {
210 		if (dumbterm == NULL) {
211 			dumbterm = malloc(sizeof(*dumbterm));
212 			if (dumbterm == NULL)
213 				return NULL;
214 			dumbterm->_buflen = 0;
215 		}
216 		term = dumbterm;
217 	}
218 
219 	term->_bufpos = 0;
220 	/* Ensure we have an initial buffer */
221 	if (term->_buflen == 0) {
222 		term->_buf = malloc(BUFINC);
223 		if (term->_buf == NULL)
224 			return NULL;
225 		term->_buflen = BUFINC;
226 	}
227 
228 	memset(&piss, 0, sizeof(piss));
229 	max = (size_t)_ti_parm_analyse(str, piss, TPARM_MAX);
230 
231 	/* Put our parameters into variables */
232 	memset(&params, 0, sizeof(params));
233 	for (l = 0; l < max; l++) {
234 		if (piss[l]) {
235 			if (va_type == VA_LONG_LONG) {
236 				/* This only works if char * fits into a long
237 				 * on this platform. */
238 				if (sizeof(char *) <= sizeof(long))
239 					params[l].string =
240 					    (char *)va_arg(parms, long);
241 				else {
242 					errno = ENOTSUP;
243 					return NULL;
244 				}
245 			} else
246 				params[l].string = va_arg(parms, char *);
247 		} else {
248 			if (va_type == VA_CHAR_INT)
249 				params[l].num = (long)va_arg(parms, int);
250 			else
251 				params[l].num = va_arg(parms, long);
252 		}
253 	}
254 
255 	memset(&stack, 0, sizeof(stack));
256 	while ((c = *str++) != '\0') {
257 		if (c != '%' || (c = *str++) == '%') {
258 			if (c == '\0')
259 				break;
260 			if (ochar(term, c) == 0)
261 				return NULL;
262 			continue;
263 		}
264 
265 		/* Handle formatting. */
266 		fp = fmt;
267 		*fp++ = '%';
268 		done = dot = minus = 0;
269 		width = precision = 0;
270 		val = 0;
271 		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
272 			switch (c) {
273 			case 'c':
274 			case 's':
275 				*fp++ = c;
276 				done = 1;
277 				break;
278 			case 'd':
279 			case 'o':
280 			case 'x':
281 			case 'X':
282 				*fp++ = 'l';
283 				*fp++ = c;
284 				done = 1;
285 				break;
286 			case '#':
287 			case ' ':
288 				*fp++ = c;
289 				break;
290 			case '.':
291 				*fp++ = c;
292 				if (dot == 0) {
293 					dot = 1;
294 					width = (size_t)val;
295 				} else
296 					done = 2;
297 				val = 0;
298 				break;
299 			case ':':
300 				minus = 1;
301 				break;
302 			case '-':
303 				if (minus)
304 					*fp++ = c;
305 				else
306 					done = 1;
307 				break;
308 			default:
309 				if (isdigit((unsigned char)c)) {
310 					val = (val * 10) + (c - '0');
311 					if (val > 10000)
312 						done = 2;
313 					else
314 						*fp++ = c;
315 				} else
316 					done = 1;
317 			}
318 			if (done == 0)
319 				c = *str++;
320 		}
321 		if (done == 2) {
322 			/* Found an error in the format */
323 			fp = fmt + 1;
324 			*fp = *str;
325 			olen = 0;
326 		} else {
327 			if (dot == 0)
328 				width = (size_t)val;
329 			else
330 				precision = (size_t)val;
331 			olen = MAX(width, precision);
332 		}
333 		*fp++ = '\0';
334 
335 		/* Handle commands */
336 		switch (c) {
337 		case 'c':
338 			pop(&val, NULL, &stack);
339 			if (ochar(term, (unsigned char)val) == 0)
340 				return NULL;
341 			break;
342 		case 's':
343 			pop(NULL, &ostr, &stack);
344 			if (ostr != NULL) {
345 				int r;
346 
347 				l = strlen(ostr);
348 				if (l < (size_t)olen)
349 					l = olen;
350 				if (checkbuf(term, (size_t)(l + 1)) == NULL)
351 					return NULL;
352 				r = snprintf(term->_buf + term->_bufpos, l + 1,
353 				    fmt, ostr);
354 				if (r != -1)
355 					term->_bufpos += (size_t)r;
356 			}
357 			break;
358 		case 'l':
359 			pop(NULL, &ostr, &stack);
360 			if (ostr == NULL)
361 				l = 0;
362 			else
363 				l = strlen(ostr);
364 #ifdef NCURSES_COMPAT_57
365 			if (onum(term, "%ld", (long)l, 0) == 0)
366 				return NULL;
367 #else
368 			push((long)l, NULL, &stack);
369 #endif
370 			break;
371 		case 'd':
372 		case 'o':
373 		case 'x':
374 		case 'X':
375 			pop(&val, NULL, &stack);
376 			if (onum(term, fmt, (int)val, olen) == 0)
377 				return NULL;
378 			break;
379 		case 'p':
380 			if (*str < '1' || *str > '9')
381 				break;
382 			l = (size_t)(*str++ - '1');
383 			if (push(params[l].num, params[l].string, &stack))
384 				return NULL;
385 			break;
386 		case 'P':
387 			pop(&val, NULL, &stack);
388 			if (*str >= 'a' && *str <= 'z')
389 				dnums[*str - 'a'] = val;
390 			else if (*str >= 'A' && *str <= 'Z')
391 				term->_snums[*str - 'A'] = val;
392 			break;
393 		case 'g':
394 			if (*str >= 'a' && *str <= 'z') {
395 				if (push(dnums[*str - 'a'], NULL, &stack))
396 					return NULL;
397 			} else if (*str >= 'A' && *str <= 'Z') {
398 				if (push(term->_snums[*str - 'A'],
399 					NULL, &stack))
400 					return NULL;
401 			}
402 			break;
403 		case 'i':
404 			if (piss[0] == 0)
405 				params[0].num++;
406 			if (piss[1] == 0)
407 				params[1].num++;
408 			break;
409 		case '\'':
410 			if (push((long)(unsigned char)*str++, NULL, &stack))
411 				return NULL;
412 			while (*str != '\0' && *str != '\'')
413 				str++;
414 			if (*str == '\'')
415 				str++;
416 			break;
417 		case '{':
418 			val = 0;
419 			for (; isdigit((unsigned char)*str);  str++)
420 				val = (val * 10) + (*str - '0');
421 			if (push(val, NULL, &stack))
422 				return NULL;
423 			while (*str != '\0' && *str != '}')
424 				str++;
425 			if (*str == '}')
426 				str++;
427 			break;
428 		case '+':
429 		case '-':
430 		case '*':
431 		case '/':
432 		case 'm':
433 		case 'A':
434 		case 'O':
435 		case '&':
436 		case '|':
437 		case '^':
438 		case '=':
439 		case '<':
440 		case '>':
441 			pop(&val, NULL, &stack);
442 			pop(&val2, NULL, &stack);
443 			switch (c) {
444 			case '+':
445 				val = val + val2;
446 				break;
447 			case '-':
448 				val = val2 - val;
449 				break;
450 			case '*':
451 				val = val * val2;
452 				break;
453 			case '/':
454 				val = val ? val2 / val : 0;
455 				break;
456 			case 'm':
457 				val = val ? val2 % val : 0;
458 				break;
459 			case 'A':
460 				val = val && val2;
461 				break;
462 			case 'O':
463 				val = val || val2;
464 				break;
465 			case '&':
466 				val = val & val2;
467 				break;
468 			case '|':
469 				val = val | val2;
470 				break;
471 			case '^':
472 				val = val ^ val2;
473 				break;
474 			case '=':
475 				val = val == val2;
476 				break;
477 			case '<':
478 				val = val2 < val;
479 				break;
480 			case '>':
481 				val = val2 > val;
482 				break;
483 			}
484 			if (push(val, NULL, &stack))
485 				return NULL;
486 			break;
487 		case '!':
488 		case '~':
489 			pop(&val, NULL, &stack);
490 			switch (c) {
491 			case '!':
492 				val = !val;
493 				break;
494 			case '~':
495 				val = ~val;
496 				break;
497 			}
498 			if (push(val, NULL, &stack))
499 				return NULL;
500 			break;
501 		case '?': /* if */
502 			break;
503 		case 't': /* then */
504 			pop(&val, NULL, &stack);
505 			if (val == 0) {
506 				l = 0;
507 				for (; *str != '\0'; str++) {
508 					if (*str != '%')
509 						continue;
510 					str++;
511 					if (*str == '?')
512 						l++;
513 					else if (*str == ';') {
514 						if (l > 0)
515 							l--;
516 						else {
517 							str++;
518 							break;
519 						}
520 					} else if (*str == 'e' && l == 0) {
521 						str++;
522 						break;
523 					}
524 				}
525 			}
526 			break;
527 		case 'e': /* else */
528 			l = 0;
529 			for (; *str != '\0'; str++) {
530 				if (*str != '%')
531 					continue;
532 				str++;
533 				if (*str == '?')
534 					l++;
535 				else if (*str == ';') {
536 					if (l > 0)
537 						l--;
538 					else {
539 						str++;
540 						break;
541 					}
542 				}
543 			}
544 			break;
545 		case ';': /* fi */
546 			break;
547 		}
548 	}
549 	term->_buf[term->_bufpos] = '\0';
550 	return term->_buf;
551 }
552 
553 char *
ti_tiparm(TERMINAL * term,const char * str,...)554 ti_tiparm(TERMINAL *term, const char *str, ...)
555 {
556 	va_list va;
557 	char *ret;
558 
559 	_DIAGASSERT(term != NULL);
560 	_DIAGASSERT(str != NULL);
561 
562 	va_start(va, str);
563 	ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
564 	va_end(va);
565 	return ret;
566 }
567 
568 char *
tiparm(const char * str,...)569 tiparm(const char *str, ...)
570 {
571 	va_list va;
572 	char *ret;
573 
574 	_DIAGASSERT(str != NULL);
575 
576 	va_start(va, str);
577 	ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
578 	va_end(va);
579 	return ret;
580 }
581 
582 #ifdef VA_CHAR_LONG
583 char *
ti_tlparm(TERMINAL * term,const char * str,...)584 ti_tlparm(TERMINAL *term, const char *str, ...)
585 {
586 	va_list va;
587 	char *ret;
588 
589 	_DIAGASSERT(term != NULL);
590 	_DIAGASSERT(str != NULL);
591 
592 	va_start(va, str);
593 	ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
594 	va_end(va);
595 	return ret;
596 }
597 
598 char *
tlparm(const char * str,...)599 tlparm(const char *str, ...)
600 {
601 	va_list va;
602 	char *ret;
603 
604 	_DIAGASSERT(str != NULL);
605 
606 	va_start(va, str);
607 	ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
608 	va_end(va);
609 	return ret;
610 }
611 #endif
612 
613 static char *
_tparm(const char * str,...)614 _tparm(const char *str, ...)
615 {
616 	va_list va;
617 	char *ret;
618 
619 	_DIAGASSERT(str != NULL);
620 
621 	va_start(va, str);
622 	ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
623 	va_end(va);
624 	return ret;
625 }
626 
627 char *
tparm(const char * str,long p1,long p2,long p3,long p4,long p5,long p6,long p7,long p8,long p9)628 tparm(const char *str,
629     long p1, long p2, long p3, long p4, long p5,
630     long p6, long p7, long p8, long p9)
631 {
632 
633 	return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
634 }
635