1 /*
2  *  Minimal vsnprintf(), snprintf(), sprintf(), and sscanf() for Duktape.
3  *  The supported conversion formats narrowly match what Duktape needs.
4  */
5 
6 #include <stdarg.h>  /* va_list etc */
7 #include <stddef.h>  /* size_t */
8 #include <stdint.h>  /* SIZE_MAX */
9 
10 /* Write character with bound checking.  Offset 'off' is updated regardless
11  * of whether an actual write is made.  This is necessary to satisfy snprintf()
12  * return value semantics.
13  */
14 #define DUK__WRITE_CHAR(c) do { \
15 		if (off < size) { \
16 			str[off] = (char) c; \
17 		} \
18 		off++; \
19 	} while (0)
20 
21 /* Digits up to radix 16. */
22 static const char duk__format_digits[16] = {
23 	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
24 };
25 
26 /* Format an unsigned long with various options.  An unsigned long is large
27  * enough for formatting all supported types.
28  */
duk__format_long(char * str,size_t size,size_t off,int fixed_length,char pad,int radix,int neg_sign,unsigned long v)29 static size_t duk__format_long(char *str,
30                                size_t size,
31                                size_t off,
32                                int fixed_length,
33                                char pad,
34                                int radix,
35                                int neg_sign,
36                                unsigned long v) {
37 	char buf[24];  /* 2^64 = 18446744073709552000, length 20 */
38 	char *required;
39 	char *p;
40 	int i;
41 
42 	/* Format in reverse order first.  Ensure at least one digit is output
43 	 * to handle '0' correctly.  Note that space padding and zero padding
44 	 * handle negative sign differently:
45 	 *
46 	 *     %9d and -321  => '     -321'
47 	 *     %09d and -321 => '-00000321'
48 	 */
49 
50 	for (i = 0; i < (int) sizeof(buf); i++) {
51 		buf[i] = pad;  /* compiles into memset() equivalent, avoid memset() dependency */
52 	}
53 
54 	p = buf;
55 	do {
56 		*p++ = duk__format_digits[v % radix];
57 		v /= radix;
58 	} while (v != 0);
59 
60 	required = buf + fixed_length;
61 	if (p < required && pad == (char) '0') {
62 		/* Zero padding and we didn't reach maximum length: place
63 		 * negative sign at the last position.  We can't get here
64 		 * with fixed_length == 0 so that required[-1] is safe.
65 		 *
66 		 * Technically we should only do this for 'neg_sign == 1',
67 		 * but it's OK to advance the pointer even when that's not
68 		 * the case.
69 		 */
70 		p = required - 1;
71 	}
72 	if (neg_sign) {
73 		*p++ = (char) '-';
74 	}
75 	if (p < required) {
76 		p = required;
77 	}
78 
79 	/* Now [buf,p[ contains the result in reverse; copy into place. */
80 
81 	while (p > buf) {
82 		p--;
83 		DUK__WRITE_CHAR(*p);
84 	}
85 
86 	return off;
87 }
88 
89 /* Parse a pointer.  Must parse whatever is produced by '%p' in sprintf(). */
duk__parse_pointer(const char * str,void ** out)90 static int duk__parse_pointer(const char *str, void **out) {
91 	const unsigned char *p;
92 	unsigned char ch;
93 	int count;
94 	int limit;
95 	long val;  /* assume void * fits into long */
96 
97 	/* We only need to parse what our minimal printf() produces, so that
98 	 * we can check for a '0x' prefix, and assume all hex digits are
99 	 * lowercase.
100 	 */
101 
102 	p = (const unsigned char *) str;
103 	if (p[0] != (unsigned char) '0' || p[1] != (unsigned char) 'x') {
104 		return 0;
105 	}
106 	p += 2;
107 
108 	for (val = 0, count = 0, limit = sizeof(void *) * 2; count < limit; count++) {
109 		ch = *p++;
110 
111 		val <<= 4;
112 		if (ch >= (unsigned char) '0' && ch <= (unsigned char) '9') {
113 			val += ch - (unsigned char) '0';
114 		} else if (ch >= (unsigned char) 'a' && ch <= (unsigned char) 'f') {
115 			val += ch - (unsigned char) 'a' + 0x0a;
116 		} else {
117 			return 0;
118 		}
119 	}
120 
121 	/* The input may end at a NUL or garbage may follow.  As long as we
122 	 * parse the '%p' correctly, garbage is allowed to follow, and the
123 	 * JX pointer parsing also relies on that.
124 	 */
125 
126 	*out = (void *) val;
127 	return 1;
128 }
129 
130 /* Minimal vsnprintf() entry point. */
duk_minimal_vsnprintf(char * str,size_t size,const char * format,va_list ap)131 int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
132 	size_t off = 0;
133 	const char *p;
134 #if 0
135 	const char *p_tmp;
136 	const char *p_fmt_start;
137 #endif
138 	char c;
139 	char pad;
140 	int fixed_length;
141 	int is_long;
142 
143 	/* Assume str != NULL unless size == 0.
144 	 * Assume format != NULL.
145 	 */
146 
147 	p = format;
148 	for (;;) {
149 		c = *p++;
150 		if (c == (char) 0) {
151 			break;
152 		}
153 		if (c != (char) '%') {
154 			DUK__WRITE_CHAR(c);
155 			continue;
156 		}
157 
158 		/* Start format sequence.  Scan flags and format specifier. */
159 
160 #if 0
161 		p_fmt_start = p - 1;
162 #endif
163 		is_long = 0;
164 		pad = ' ';
165 		fixed_length = 0;
166 		for (;;) {
167 			c = *p++;
168 			if (c == (char) 'l') {
169 				is_long = 1;
170 			} else if (c == (char) '0') {
171 				/* Only support pad character '0'. */
172 				pad = '0';
173 			} else if (c >= (char) '1' && c <= (char) '9') {
174 				/* Only support fixed lengths 1-9. */
175 				fixed_length = (int) (c - (char) '0');
176 			} else if (c == (char) 'd') {
177 				long v;
178 				int neg_sign = 0;
179 				if (is_long) {
180 					v = va_arg(ap, long);
181 				} else {
182 					v = (long) va_arg(ap, int);
183 				}
184 				if (v < 0) {
185 					neg_sign = 1;
186 					v = -v;
187 				}
188 				off = duk__format_long(str, size, off, fixed_length, pad, 10, neg_sign, (unsigned long) v);
189 				break;
190 			} else if (c == (char) 'u') {
191 				unsigned long v;
192 				if (is_long) {
193 					v = va_arg(ap, unsigned long);
194 				} else {
195 					v = (unsigned long) va_arg(ap, unsigned int);
196 				}
197 				off = duk__format_long(str, size, off, fixed_length, pad, 10, 0, v);
198 				break;
199 			} else if (c == (char) 'x') {
200 				unsigned long v;
201 				if (is_long) {
202 					v = va_arg(ap, unsigned long);
203 				} else {
204 					v = (unsigned long) va_arg(ap, unsigned int);
205 				}
206 				off = duk__format_long(str, size, off, fixed_length, pad, 16, 0, v);
207 				break;
208 			} else if (c == (char) 'c') {
209 				char v;
210 				v = (char) va_arg(ap, int);  /* intentionally not 'char' */
211 				DUK__WRITE_CHAR(v);
212 				break;
213 			} else if (c == (char) 's') {
214 				const char *v;
215 				char c_tmp;
216 				v = va_arg(ap, const char *);
217 				if (v) {
218 					for (;;) {
219 						c_tmp = *v++;
220 						if (c_tmp) {
221 							DUK__WRITE_CHAR(c_tmp);
222 						} else {
223 							break;
224 						}
225 					}
226 				}
227 				break;
228 			} else if (c == (char) 'p') {
229 				/* Assume a void * can be represented by 'long'.  This is not
230 				 * always the case.  NULL pointer is printed out as 0x0000...
231 				 */
232 				void *v;
233 				v = va_arg(ap, void *);
234 				DUK__WRITE_CHAR('0');
235 				DUK__WRITE_CHAR('x');
236 				off = duk__format_long(str, size, off, sizeof(void *) * 2, '0', 16, 0, (unsigned long) v);
237 				break;
238 			} else {
239 				/* Unrecognized, bail out early.  We could also emit the format
240 				 * specifier verbatim, but it'd be a waste of footprint because
241 				 * this case should never happen in practice.
242 				 */
243 #if 0
244 				DUK__WRITE_CHAR('!');
245 #endif
246 #if 0
247 				for (p_tmp = p_fmt_start; p_tmp != p; p_tmp++) {
248 					DUK__WRITE_CHAR(*p_tmp);
249 				}
250 				break;
251 #endif
252 				goto finish;
253 			}
254 		}
255 	}
256 
257  finish:
258 	if (off < size) {
259 		str[off] = (char) 0;  /* No increment for 'off', not counted in return value. */
260 	} else if (size > 0) {
261 		/* Forced termination. */
262 		str[size - 1] = 0;
263 	}
264 
265 	return (int) off;
266 }
267 
268 /* Minimal snprintf() entry point. */
duk_minimal_snprintf(char * str,size_t size,const char * format,...)269 int duk_minimal_snprintf(char *str, size_t size, const char *format, ...) {
270 	va_list ap;
271 	int ret;
272 
273 	va_start(ap, format);
274 	ret = duk_minimal_vsnprintf(str, size, format, ap);
275 	va_end(ap);
276 
277 	return ret;
278 }
279 
280 /* Minimal sprintf() entry point. */
duk_minimal_sprintf(char * str,const char * format,...)281 int duk_minimal_sprintf(char *str, const char *format, ...) {
282 	va_list ap;
283 	int ret;
284 
285 	va_start(ap, format);
286 	ret = duk_minimal_vsnprintf(str, SIZE_MAX, format, ap);
287 	va_end(ap);
288 
289 	return ret;
290 }
291 
292 /* Minimal sscanf() entry point. */
duk_minimal_sscanf(const char * str,const char * format,...)293 int duk_minimal_sscanf(const char *str, const char *format, ...) {
294 	va_list ap;
295 	int ret;
296 	void **out;
297 
298 	/* Only the exact "%p" format is supported. */
299 	if (format[0] != (char) '%' ||
300 	    format[1] != (char) 'p' ||
301 	    format[2] != (char) 0) {
302 	}
303 
304 	va_start(ap, format);
305 	out = va_arg(ap, void **);
306 	ret = duk__parse_pointer(str, out);
307 	va_end(ap);
308 
309 	return ret;
310 }
311 
312 #undef DUK__WRITE_CHAR
313