xref: /illumos-gate/usr/src/cmd/msgfmt/gnu_check.c (revision 86d949f9497332fe19be6b5d711d265eb957439f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include "gnu_msgfmt.h"
28 
29 #define	OPT_L	0x01
30 #define	OPT_l	0x02
31 #define	OPT_ll	0x04
32 #define	OPT_w	0x08
33 #define	OPT_h	0x10
34 #define	OPT_hh	0x20
35 #define	OPT_j	0x40
36 
37 static int
38 extract_format(char *norm, const char *sfmt, size_t sz)
39 {
40 	const unsigned char	*fmt = (const unsigned char *)sfmt;
41 	unsigned char	c;
42 	int	t, arg, ap;
43 	int	dotseen;
44 	char	flag, conv;
45 	int	lastarg = -1;
46 	int	prevarg;
47 	int	max = 0;
48 	int	lflag;
49 
50 	for (; *fmt; fmt++) {
51 		if (*fmt == '%') {
52 			if (*++fmt == '%')
53 				continue;
54 			if (!*fmt)
55 				break;
56 			prevarg = lastarg;
57 			arg = ++lastarg;
58 
59 			t = 0;
60 			while (*fmt && isdigit(*fmt))
61 				t = t * 10 + *fmt++ - '0';
62 
63 			if (*fmt == '$') {
64 				lastarg = arg = t - 1;
65 				fmt++;
66 			}
67 
68 			if (!*fmt)
69 				goto end;
70 
71 			dotseen = 0;
72 			flag = 0;
73 			lflag = 0;
74 again:
75 			/* Skip flags */
76 			while ((c = *fmt) != '\0') {
77 				if (c == '\'' || c == '+' || c == '-' ||
78 					c == ' ' || c == '#' || c == '0') {
79 					fmt++;
80 					continue;
81 				}
82 				break;
83 			}
84 
85 			while (*fmt && isdigit(*fmt))
86 				fmt++;
87 
88 			if (*fmt == '*') {
89 				if (isdigit(*(fmt + 1))) {
90 					fmt++;
91 					t = 0;
92 					while (*fmt && isdigit(*fmt))
93 						t = t * 10 + *fmt++ - '0';
94 
95 					if (*fmt == '$') {
96 						/*
97 						 * %*4$
98 						 */
99 						ap = t - 1;
100 						if ((ap * 2 + 1 >= sz) ||
101 							(norm[ap * 2] &&
102 							norm[ap * 2] != '*')) {
103 							/* error in format */
104 							return (-1);
105 						} else {
106 							if (ap >= max)
107 								max = ap + 1;
108 							norm[ap * 2] = '*';
109 						}
110 					}
111 					/*
112 					 * If digits follow a '*', it is
113 					 * not loaded as an argument, the
114 					 * digits are used instead.
115 					 */
116 				} else {
117 					/*
118 					 * %*
119 					 */
120 					if (*(fmt + 1) == '$') {
121 						fmt++;
122 					} else {
123 						ap = arg;
124 						prevarg = arg;
125 						lastarg = ++arg;
126 						if ((ap * 2 + 1 >= sz) ||
127 							(norm[ap * 2] &&
128 							norm[ap * 2] != '*')) {
129 							/* error in format */
130 							return (-1);
131 						} else {
132 							if (ap >= max)
133 								max = ap + 1;
134 							norm[ap * 2] = '*';
135 						}
136 					}
137 				}
138 				fmt++;
139 			}
140 
141 			if ((*fmt == '.') || (*fmt == '*')) {
142 				if (dotseen)
143 					return (-1);
144 				dotseen = 1;
145 				fmt++;
146 				goto again;
147 			}
148 
149 			if (!*fmt)
150 				goto end;
151 
152 			while (*fmt) {
153 				switch (*fmt) {
154 				case 'l':
155 					if (!(flag & OPT_ll)) {
156 						if (lflag) {
157 							flag &= ~OPT_l;
158 							flag |= OPT_ll;
159 						} else {
160 							flag |= OPT_l;
161 						}
162 					}
163 					lflag++;
164 					break;
165 				case 'L':
166 					flag |= OPT_L;
167 					break;
168 				case 'w':
169 					flag |= OPT_w;
170 					break;
171 				case 'h':
172 					if (flag & (OPT_h|OPT_hh))
173 						flag |= OPT_hh;
174 					else
175 						flag |= OPT_h;
176 					break;
177 				case 'j':
178 					flag |= OPT_j;
179 					break;
180 				case 'z':
181 				case 't':
182 					if (!(flag & OPT_ll)) {
183 						flag |= OPT_l;
184 					}
185 					break;
186 				case '\'':
187 				case '+':
188 				case '-':
189 				case ' ':
190 				case '#':
191 				case '.':
192 				case '*':
193 					goto again;
194 				default:
195 					if (isdigit(*fmt))
196 						goto again;
197 					else
198 						goto done;
199 				}
200 				fmt++;
201 			}
202 done:
203 			if (!*fmt)
204 				goto end;
205 
206 			if ((c = *fmt) == 'C') {
207 				flag |= OPT_l;
208 				conv = 'c';
209 			} else if (c == 'd') {
210 				conv = 'd';
211 			} else if (c == 'S') {
212 				flag |= OPT_l;
213 				conv = 's';
214 			} else if (c == 's') {
215 				conv = 's';
216 			} else if (c == 'i') {
217 				conv = 'i';
218 			} else if (c == 'o') {
219 				conv = 'o';
220 			} else if (c == 'u') {
221 				conv = 'u';
222 			} else if (c == 'c') {
223 				conv = 'c';
224 			} else if (c == 'x') {
225 				conv = 'x';
226 			} else if (c == 'X') {
227 				conv = 'X';
228 			} else if (c == 'e') {
229 				conv = 'e';
230 			} else if (c == 'E') {
231 				conv = 'E';
232 			} else if (c == 'f') {
233 				conv = 'f';
234 			} else if (c == 'F') {
235 				conv = 'F';
236 			} else if (c == 'a') {
237 				conv = 'a';
238 			} else if (c == 'A') {
239 				conv = 'A';
240 			} else if (c == 'g') {
241 				conv = 'g';
242 			} else if (c == 'G') {
243 				conv = 'G';
244 			} else if (c == 'p') {
245 				conv = 'p';
246 			} else if (c == 'n') {
247 				conv = 'n';
248 			} else {
249 				lastarg = prevarg;
250 				continue;
251 			}
252 
253 			if ((arg * 2 + 1 >= sz) ||
254 				(norm[arg * 2] &&
255 				(norm[arg * 2] != conv))) {
256 				return (-1);
257 			} else {
258 				if (arg >= max)
259 					max = arg + 1;
260 				norm[arg * 2] = conv;
261 			}
262 			norm[arg * 2 + 1] = flag;
263 		}
264 	}
265 
266 end:
267 	for (arg = 0; arg < max; arg++) {
268 		if (norm[arg * 2] == '\0')
269 			return (-1);
270 	}
271 
272 	return (max);
273 }
274 
275 
276 void
277 check_format(struct entry *id, struct entry *str, int is_c_format)
278 {
279 	int	i, n;
280 	int	id_b_newline, id_e_newline;
281 	int	plural_b_newline, plural_e_newline;
282 	int	str_b_newline, str_e_newline;
283 	int	id_fmt, plural_fmt, str_fmt;
284 	int	*pstr_fmt;
285 	char	*msgid, *plural, *msgstr;
286 	char	*id_norm, *plural_norm, *str_norm;
287 	char	**pstr_norm;
288 	size_t	id_len, id_num;
289 	size_t	plural_off, plural_len, plural_num;
290 	size_t	str_len, str_num;
291 	size_t	osz, nsz;
292 	struct loc	*p;
293 
294 	if (id->len == 1) {
295 		/*
296 		 * null string: header entry
297 		 * no check is performed
298 		 */
299 		return;
300 	}
301 
302 	msgid = id->str;
303 	id_num = id->num;
304 	msgstr = str->str;
305 	if (id->no > 1) {
306 		/* plural */
307 		id_len = id->pos[0].len;
308 		plural_off = id->pos[1].off;
309 		plural_len = id->pos[1].len;
310 		plural_num = id->pos[1].num;
311 		plural = msgid + plural_off;
312 	} else {
313 		/* no plural form */
314 		id_len = id->len;
315 		str_len = str->len;
316 		str_num = str->num;
317 		plural = NULL;
318 	}
319 
320 	/*
321 	 * First checking the newline
322 	 */
323 
324 	if (!plural) {
325 		/* no plural form */
326 		id_b_newline = (msgid[0] == '\n');
327 		id_e_newline = (msgid[id_len - 1 - 1] == '\n');
328 
329 		str_b_newline = (msgstr[0] == '\n');
330 		str_e_newline = (msgstr[str_len - 1 - 1] == '\n');
331 		if (id_b_newline && !str_b_newline) {
332 			diag(gettext(ERR_BEGIN_NEWLINE_1),
333 				id_num, str_num, cur_po);
334 			po_error++;
335 		} else if (!id_b_newline && str_b_newline) {
336 			diag(gettext(ERR_BEGIN_NEWLINE_2),
337 				id_num, str_num, cur_po);
338 			po_error++;
339 		}
340 		if (id_e_newline && !str_e_newline) {
341 			diag(gettext(ERR_END_NEWLINE_1),
342 				id_num, str_num, cur_po);
343 			po_error++;
344 		} else if (!id_e_newline && str_e_newline) {
345 			diag(gettext(ERR_END_NEWLINE_2),
346 				id_num, str_num, cur_po);
347 			po_error++;
348 		}
349 	} else {
350 		/* plural form */
351 		id_b_newline = (msgid[0] == '\n');
352 		id_e_newline = (msgid[id_len - 1 - 1] == '\n');
353 
354 		plural_b_newline = (plural[0] == '\n');
355 		plural_e_newline = (plural[plural_len - 1 -1 ] == '\n');
356 
357 		/* between msgid and msgid_plural */
358 		if (id_b_newline && !plural_b_newline) {
359 			diag(gettext(ERR_BEGIN_NEWLINE_3),
360 				id_num, plural_num, cur_po);
361 			po_error++;
362 		} else if (!id_b_newline && plural_b_newline) {
363 			diag(gettext(ERR_BEGIN_NEWLINE_4),
364 				id_num, plural_num, cur_po);
365 			po_error++;
366 		}
367 		if (id_e_newline && !plural_e_newline) {
368 			diag(gettext(ERR_END_NEWLINE_3),
369 				id_num, plural_num, cur_po);
370 			po_error++;
371 		} else if (!id_e_newline && plural_e_newline) {
372 			diag(gettext(ERR_END_NEWLINE_4),
373 				id_num, plural_num, cur_po);
374 			po_error++;
375 		}
376 
377 		for (i = 0; i < str->no; i++) {
378 			p = str->pos + i;
379 			str_b_newline = (msgstr[p->off] == '\n');
380 			str_e_newline =
381 				(msgstr[p->off + p->len - 1 - 1] == '\n');
382 
383 			if (id_b_newline && !str_b_newline) {
384 				diag(gettext(ERR_BEGIN_NEWLINE_5),
385 					id_num, p->num, cur_po, i);
386 				po_error++;
387 			} else if (!id_b_newline && str_b_newline) {
388 				diag(gettext(ERR_BEGIN_NEWLINE_6),
389 					id_num, p->num, cur_po, i);
390 				po_error++;
391 			}
392 
393 			if (id_e_newline && !str_e_newline) {
394 				diag(gettext(ERR_END_NEWLINE_5),
395 					id_num, p->num, cur_po, i);
396 				po_error++;
397 			} else if (!id_e_newline && str_e_newline) {
398 				diag(gettext(ERR_END_NEWLINE_6),
399 					id_num, p->num, cur_po, i);
400 				po_error++;
401 			}
402 		}
403 	}
404 
405 	/*
406 	 * if c-format is not specified, no printf-format check
407 	 * is performed.
408 	 */
409 	if (!is_c_format) {
410 		return;
411 	}
412 
413 	osz = id_len * 2;
414 	id_norm = (char *)Xcalloc(1, osz);
415 	id_fmt = extract_format(id_norm, msgid, osz);
416 	if (id_fmt == -1) {
417 		diag(gettext(ERR_INVALID_FMT), id_num, cur_po);
418 		po_error++;
419 	}
420 
421 	if (!plural) {
422 		/* no plural */
423 
424 		nsz = str_len * 2;
425 		str_norm = (char *)Xcalloc(1, nsz);
426 		str_fmt = extract_format(str_norm, msgstr, nsz);
427 		if (str_fmt == -1) {
428 			diag(gettext(ERR_INVALID_FMT), str_num, cur_po);
429 			po_error++;
430 		}
431 
432 		if (id_fmt != str_fmt) {
433 			diag(gettext(ERR_INCMP_FMT),
434 				id_num, str_num, cur_po);
435 			diag(gettext(ERR_INCMP_FMT_DIFF_1),
436 				id_fmt, str_fmt);
437 			po_error++;
438 		} else {
439 			for (n = 0; n < id_fmt; n++) {
440 				if ((id_norm[n * 2] !=
441 					str_norm[n * 2]) ||
442 					(id_norm[n * 2 + 1] !=
443 					str_norm[n * 2 + 1])) {
444 					diag(gettext(ERR_INCMP_FMT),
445 						id_num, str_num, cur_po);
446 					diag(gettext(ERR_INCMP_FMT_DIFF_2),
447 						n + 1);
448 					po_error++;
449 				}
450 			}
451 		}
452 		free(str_norm);
453 		free(id_norm);
454 
455 		return;
456 	}
457 
458 	/* plural */
459 	nsz = plural_len * 2;
460 	plural_norm = (char *)Xcalloc(1, nsz);
461 	plural_fmt = extract_format(plural_norm, plural, nsz);
462 	if (plural_fmt == -1) {
463 		diag(gettext(ERR_INVALID_FMT), plural_num, cur_po);
464 		po_error++;
465 	}
466 
467 	pstr_norm = (char **)Xcalloc(str->no, sizeof (char *));
468 	pstr_fmt = (int *)Xcalloc(str->no, sizeof (int));
469 	for (i = 0; i < str->no; i++) {
470 		p = str->pos + i;
471 		nsz = p->len * 2;
472 		pstr_norm[i] = (char *)Xcalloc(1, nsz);
473 		pstr_fmt[i] = extract_format(pstr_norm[i],
474 			msgstr + p->off, nsz);
475 		if (pstr_fmt[i] == -1) {
476 			diag(gettext(ERR_INVALID_FMT),
477 				p->num, cur_po);
478 			po_error++;
479 		}
480 	}
481 
482 	/* between msgid and msgid_plural */
483 	if (id_fmt != plural_fmt) {
484 		diag(gettext(ERR_INCMP_FMT),
485 			id_num, plural_num, cur_po);
486 		diag(gettext(ERR_INCMP_FMT_DIFF_1),
487 			id_fmt, plural_fmt);
488 		po_error++;
489 	} else {
490 		for (n = 0; n < id_fmt; n++) {
491 			if ((id_norm[n * 2] !=
492 				plural_norm[n * 2]) ||
493 				(id_norm[n * 2 + 1] !=
494 				plural_norm[n * 2 + 1])) {
495 				diag(gettext(ERR_INCMP_FMT),
496 					id_num, plural_num, cur_po);
497 				diag(gettext(ERR_INCMP_FMT_DIFF_2),
498 					n + 1);
499 				po_error++;
500 			}
501 		}
502 	}
503 	free(plural_norm);
504 
505 	/* between msgid and msgstr */
506 	for (i = 0; i < str->no; i++) {
507 		p = str->pos + i;
508 		if (id_fmt != pstr_fmt[i]) {
509 			diag(gettext(ERR_INCMP_FMT),
510 				id_num, p->num, cur_po);
511 			diag(gettext(ERR_INCMP_FMT_DIFF_1),
512 				id_fmt, pstr_fmt[i]);
513 			po_error++;
514 		} else {
515 			for (n = 0; n < id_fmt; n++) {
516 				if ((id_norm[n * 2] !=
517 					pstr_norm[i][n * 2]) ||
518 					(id_norm[n * 2 + 1] !=
519 					pstr_norm[i][n * 2 + 1])) {
520 					diag(gettext(ERR_INCMP_FMT),
521 						id_num, p->num, cur_po);
522 					diag(gettext(ERR_INCMP_FMT_DIFF_2),
523 						n + 1);
524 					po_error++;
525 				}
526 			}
527 		}
528 		free(pstr_norm[i]);
529 	}
530 	free(pstr_norm);
531 	free(pstr_fmt);
532 	free(id_norm);
533 }
534