1/* This file is part of Mailfromd.             -*- c -*-
2   Copyright (C) 2007-2021 Sergey Poznyakoff
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 3, or (at your option)
7   any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17MF_BUILTIN_MODULE
18
19#define FMT_ALTPOS         0x01
20#define FMT_ALTERNATE      0x02
21#define FMT_PADZERO        0x04
22#define FMT_ADJUST_LEFT    0x08
23#define FMT_SPACEPFX       0x10
24#define FMT_SIGNPFX        0x20
25
26typedef enum {
27	fmts_copy,      /* Copy char as is */
28	fmts_pos,       /* Expect argument position -- %_2$ */
29	fmts_flags,     /* Expect flags -- %2$_# */
30	fmts_width,     /* Expect width -- %2$#_8 or %2$#_* */
31	fmts_width_arg, /* Expect width argument position -- %2$#*_1$ */
32	fmts_prec,      /* Expect precision */
33	fmts_prec_arg,  /* Expect precision argument position */
34	fmts_conv       /* Expect conversion specifier */
35} printf_format_state;
36
37static int
38get_num(const char *p, int i, unsigned *pn)
39{
40	unsigned n = 0;
41
42	for (; p[i] && mu_isdigit(p[i]); i++)
43		n = n * 10 + p[i] - '0';
44	*pn = n;
45	return i;
46}
47
48#define __MF_MAX(a,b) ((a)>(b) ? (a) : (b))
49#define SPRINTF_BUF_SIZE (__MF_MAX(3*sizeof(long), NUMERIC_BUFSIZE_BOUND) + 2)
50
51MF_DEFUN_VARARGS_NO_PROM(sprintf, STRING, STRING format)
52{
53	int i = 0;
54	int cur = 0;
55	int start;
56	char buf[SPRINTF_BUF_SIZE];
57	printf_format_state state = fmts_copy;
58	int flags = 0;
59	unsigned width = 0;
60	unsigned prec = 0;
61	unsigned argnum;
62
63	MF_OBSTACK_BEGIN();
64	MF_VA_START();
65	while (format[cur]) {
66		unsigned n;
67		char *str;
68		long num;
69		int negative;
70		char fmtbuf[] = { '%', 'x', 0 };
71
72		switch (state) {
73		case fmts_copy:
74			/* Expect `%', and copy all the rest verbatim */
75			if (format[cur] == '%') {
76				start = cur;
77				state = fmts_pos;
78				flags = 0;
79				width = 0;
80				prec = 0;
81			} else
82				MF_OBSTACK_1GROW(format[cur]);
83			cur++;
84			break;
85
86		case fmts_pos:
87			/* Expect '%' or an argument position -- %_% or %_2$ */
88			if (format[cur] == '%') {
89				MF_OBSTACK_1GROW('%');
90				cur++;
91				state = fmts_copy;
92				break;
93			}
94			if (mu_isdigit(format[cur])) {
95				int pos = get_num(format, cur, &n);
96				if (format[pos] == '$') {
97					argnum = n - 1;
98					flags |= FMT_ALTPOS;
99					cur = pos + 1;
100				}
101			}
102			state = fmts_flags;
103			break;
104
105		case fmts_flags:
106			/* Expect flags -- %2$_# */
107			switch (format[cur]) {
108			case '#':
109				flags |= FMT_ALTERNATE;
110				cur++;
111				break;
112
113			case '0':
114				flags |= FMT_PADZERO;
115				cur++;
116				break;
117
118			case '-':
119				flags |= FMT_ADJUST_LEFT;
120				cur++;
121				break;
122
123			case ' ':
124				flags |= FMT_SPACEPFX;
125				cur++;
126				break;
127
128			case '+':
129				flags |= FMT_SIGNPFX;
130				cur++;
131				break;
132
133			default:
134				state = fmts_width;
135			}
136			break;
137
138		case fmts_width:
139			/* Expect width -- %2$#_8 or %2$#_* */
140			if (mu_isdigit(format[cur])) {
141				cur = get_num(format, cur, &width);
142				state = fmts_prec;
143			} else if (format[cur] == '*') {
144				cur++;
145				state = fmts_width_arg;
146			} else
147				state = fmts_prec;
148			break;
149
150		case fmts_width_arg:
151			/* Expect width argument position -- %2$#*_1$ */
152			state = fmts_prec;
153			if (mu_isdigit(format[cur])) {
154				int pos = get_num(format, cur, &n);
155				if (format[pos] == '$') {
156					MF_VA_ARG(n-1, NUMBER, num);
157					cur = pos + 1;
158					if (num < 0) {
159						flags |= FMT_SPACEPFX;
160						num = - num;
161					}
162					width = (unsigned) num;
163					break;
164				}
165			}
166			MF_VA_ARG(i, NUMBER, num);
167			i++;
168			if (num < 0) {
169				/* A negative field width is taken
170				   as a `-' flag followed by a positive field width. */
171				flags |= FMT_SPACEPFX;
172				num = - num;
173			}
174			width = (unsigned) num;
175			break;
176
177		case fmts_prec:
178			/* Expect precision -- %2$#*1$_. */
179			state = fmts_conv;
180			if (format[cur] == '.') {
181				cur++;
182				if (mu_isdigit(format[cur])) {
183					cur = get_num(format, cur, &prec);
184				} else if (format[cur] == '*') {
185					cur++;
186					state = fmts_prec_arg;
187				}
188			}
189			break;
190
191		case fmts_prec_arg:
192                        /* Expect precision argument position --
193			                %2$#*1$.*_3$ */
194			state = fmts_conv;
195			if (mu_isdigit(format[cur])) {
196				int pos = get_num(format, cur, &n);
197				if (format[pos] == '$') {
198					MF_VA_ARG(n-1, NUMBER, num);
199					if (num > 0)
200						prec = (unsigned) num;
201					cur = pos + 1;
202					break;
203				}
204			}
205			MF_VA_ARG(i, NUMBER, num);
206			i++;
207			if (num > 0)
208				prec = (unsigned) num;
209			break;
210
211		case fmts_conv:       /* Expect conversion specifier */
212			if (!(flags & FMT_ALTPOS))
213				argnum = i++;
214			switch (format[cur]) {
215			case 's':
216				MF_VA_ARG(argnum, STRING, str);
217				n = strlen(str);
218				if (prec && prec < n)
219					n = prec;
220				if (width) {
221					char *q, *s;
222					if (n > width)
223						width = n;
224					q = s = MF_ALLOC_HEAP_TEMP(width + 1);
225					q[width] = 0;
226					memset(q, ' ', width);
227					if (!(flags & FMT_ADJUST_LEFT)
228					    && n < width) {
229						s = q + width - n;
230					}
231					memcpy(s, str, n);
232					str = q;
233					n = width;
234				}
235				MF_OBSTACK_GROW(str, n);
236				break;
237
238			case 'i':
239			case 'd':
240				MF_VA_ARG(argnum, NUMBER, num);
241				if (num < 0) {
242					negative = 1;
243					num = - num;
244				} else
245					negative = 0;
246				/* If a precision is given with a
247				   numeric conversion, the 0 flag is ignored.
248				 */
249				/* A - overrides a 0 if both are given. */
250				if (prec || (flags & FMT_ADJUST_LEFT))
251					flags &= ~FMT_PADZERO;
252				snprintf(buf+1, sizeof(buf)-1, "%ld", num);
253				str = buf + 1;
254				n = strlen(str);
255				if (prec && prec > n) {
256					memmove(str + prec - n, str, n + 1);
257					memset(str, '0', prec - n);
258				}
259
260				if (flags & FMT_SIGNPFX) {
261					buf[0] = negative ? '-' : '+';
262					str = buf;
263				} else if (flags & FMT_SPACEPFX) {
264					buf[0] = negative ? '-' : ' ';
265					str = buf;
266				} else if (negative) {
267					buf[0] = '-';
268					str = buf;
269				} else
270					str = buf + 1;
271				n = strlen(str);
272
273				if (width && width > n) {
274					char *q;
275					MF_OBSTACK_GROW(NULL, width, q);
276					memset(q,
277					       (flags & FMT_PADZERO) ?
278					         '0' : ' ',
279					       width);
280					if (flags & FMT_ADJUST_LEFT)
281						memcpy(q, str, n);
282					else {
283						if ((flags & FMT_PADZERO) &&
284						    str == buf) {
285							q[0] = *str++;
286							n--;
287						}
288						memcpy(q + width - n, str, n);
289					}
290				} else
291					MF_OBSTACK_GROW(str, n);
292				break;
293
294			case 'u':
295				MF_VA_ARG(argnum, NUMBER, num);
296				/* If a precision is given with a
297				   numeric conversion, the 0 flag is ignored.
298				*/
299				/* A - overrides a 0 if both are given.*/
300				if (prec || (flags & FMT_ADJUST_LEFT))
301					flags &= ~FMT_PADZERO;
302				snprintf(buf, sizeof(buf), "%lu", num);
303				str = buf;
304				n = strlen(str);
305				if (prec && prec > n) {
306					memmove(str + prec - n, str, n + 1);
307					memset(str, '0', prec - n);
308					n = prec;
309				}
310
311				if (width && width > n) {
312					char *q;
313					MF_OBSTACK_GROW(NULL, width, q);
314					memset(q,
315					       (flags & FMT_PADZERO) ?
316					         '0' : ' ',
317					       width);
318					if (flags & FMT_ADJUST_LEFT)
319						memcpy(q, str, n);
320					else
321						memcpy(q + width - n, str, n);
322				} else
323					MF_OBSTACK_GROW(str, n);
324				break;
325
326			case 'x':
327			case 'X':
328				MF_VA_ARG(argnum, NUMBER, num);
329				/* If a precision is given with a
330				   numeric conversion, the 0 flag is ignored.
331				*/
332				/* A - overrides a 0 if both are given.*/
333				if (prec || (flags & FMT_ADJUST_LEFT))
334					flags &= ~FMT_PADZERO;
335				fmtbuf[1] = format[cur];
336				snprintf(buf+2, sizeof(buf)-2, fmtbuf, num);
337				str = buf + 2;
338				n = strlen(str);
339				if (prec && prec > n) {
340					memmove(str + prec - n, str, n + 1);
341					memset(str, '0', prec - n);
342					n = prec;
343				}
344
345				if (flags & FMT_ALTERNATE) {
346					*--str = format[cur];
347					*--str = '0';
348					n += 2;
349				}
350
351				if (width && width > n) {
352					char *q;
353					MF_OBSTACK_GROW(NULL, width, q);
354					memset(q,
355					       (flags & FMT_PADZERO) ?
356					         '0' : ' ',
357					       width);
358					if (flags & FMT_ADJUST_LEFT)
359						memcpy(q, str, n);
360					else {
361						if (flags & FMT_ALTERNATE
362						    && flags & FMT_PADZERO) {
363							q[0] = *str++;
364							q[1] = *str++;
365							n -= 2;
366						}
367						memcpy(q + width - n, str, n);
368					}
369				} else
370					MF_OBSTACK_GROW(str, n);
371				break;
372
373			case 'o':
374				MF_VA_ARG(argnum, NUMBER, num);
375				/* If a precision is given with a
376				   numeric conversion, the 0 flag is ignored.
377				*/
378				/* A - overrides a 0 if both are given.*/
379				if (prec || (flags & FMT_ADJUST_LEFT))
380					flags &= ~FMT_PADZERO;
381				snprintf(buf+1, sizeof(buf)-1, "%lo", num);
382				str = buf + 2;
383				n = strlen(str);
384				if (prec && prec > n) {
385					memmove(str + prec - n, str, n + 1);
386					memset(str, '0', prec - n);
387				}
388
389				if ((flags & FMT_ALTERNATE) && *str != '0') {
390					*--str = '0';
391					n++;
392				}
393
394				if (width && width > n) {
395					char *q;
396					MF_OBSTACK_GROW(NULL, width, q);
397					memset(q,
398					       (flags & FMT_PADZERO) ?
399					         '0' : ' ',
400					       width);
401					if (flags & FMT_ADJUST_LEFT)
402						memcpy(q, str, n);
403					else
404						memcpy(q + width - n, str, n);
405				} else
406					MF_OBSTACK_GROW(str, n);
407				break;
408
409			default:
410				MF_OBSTACK_GROW(&format[start],
411						cur - start + 1);
412			}
413
414			cur++;
415			state = fmts_copy;
416		}
417	}
418	MF_OBSTACK_1GROW(0);
419	MF_VA_END();
420	MF_RETURN_OBSTACK();
421}
422END
423
424