/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "gnu_msgfmt.h"

#define	OPT_L	0x01
#define	OPT_l	0x02
#define	OPT_ll	0x04
#define	OPT_w	0x08
#define	OPT_h	0x10
#define	OPT_hh	0x20
#define	OPT_j	0x40

static int
extract_format(char *norm, const char *sfmt, size_t sz)
{
	const unsigned char	*fmt = (const unsigned char *)sfmt;
	unsigned char	c;
	int	t, arg, ap;
	int	dotseen;
	char	flag, conv;
	int	lastarg = -1;
	int	prevarg;
	int	max = 0;
	int	lflag;

	for (; *fmt; fmt++) {
		if (*fmt == '%') {
			if (*++fmt == '%')
				continue;
			if (!*fmt)
				break;
			prevarg = lastarg;
			arg = ++lastarg;

			t = 0;
			while (*fmt && isdigit(*fmt))
				t = t * 10 + *fmt++ - '0';

			if (*fmt == '$') {
				lastarg = arg = t - 1;
				fmt++;
			}

			if (!*fmt)
				goto end;

			dotseen = 0;
			flag = 0;
			lflag = 0;
again:
			/* Skip flags */
			while ((c = *fmt) != '\0') {
				if (c == '\'' || c == '+' || c == '-' ||
					c == ' ' || c == '#' || c == '0') {
					fmt++;
					continue;
				}
				break;
			}

			while (*fmt && isdigit(*fmt))
				fmt++;

			if (*fmt == '*') {
				if (isdigit(*(fmt + 1))) {
					fmt++;
					t = 0;
					while (*fmt && isdigit(*fmt))
						t = t * 10 + *fmt++ - '0';

					if (*fmt == '$') {
						/*
						 * %*4$
						 */
						ap = t - 1;
						if ((ap * 2 + 1 >= sz) ||
							(norm[ap * 2] &&
							norm[ap * 2] != '*')) {
							/* error in format */
							return (-1);
						} else {
							if (ap >= max)
								max = ap + 1;
							norm[ap * 2] = '*';
						}
					}
					/*
					 * If digits follow a '*', it is
					 * not loaded as an argument, the
					 * digits are used instead.
					 */
				} else {
					/*
					 * %*
					 */
					if (*(fmt + 1) == '$') {
						fmt++;
					} else {
						ap = arg;
						prevarg = arg;
						lastarg = ++arg;
						if ((ap * 2 + 1 >= sz) ||
							(norm[ap * 2] &&
							norm[ap * 2] != '*')) {
							/* error in format */
							return (-1);
						} else {
							if (ap >= max)
								max = ap + 1;
							norm[ap * 2] = '*';
						}
					}
				}
				fmt++;
			}

			if ((*fmt == '.') || (*fmt == '*')) {
				if (dotseen)
					return (-1);
				dotseen = 1;
				fmt++;
				goto again;
			}

			if (!*fmt)
				goto end;

			while (*fmt) {
				switch (*fmt) {
				case 'l':
					if (!(flag & OPT_ll)) {
						if (lflag) {
							flag &= ~OPT_l;
							flag |= OPT_ll;
						} else {
							flag |= OPT_l;
						}
					}
					lflag++;
					break;
				case 'L':
					flag |= OPT_L;
					break;
				case 'w':
					flag |= OPT_w;
					break;
				case 'h':
					if (flag & (OPT_h|OPT_hh))
						flag |= OPT_hh;
					else
						flag |= OPT_h;
					break;
				case 'j':
					flag |= OPT_j;
					break;
				case 'z':
				case 't':
					if (!(flag & OPT_ll)) {
						flag |= OPT_l;
					}
					break;
				case '\'':
				case '+':
				case '-':
				case ' ':
				case '#':
				case '.':
				case '*':
					goto again;
				default:
					if (isdigit(*fmt))
						goto again;
					else
						goto done;
				}
				fmt++;
			}
done:
			if (!*fmt)
				goto end;

			if ((c = *fmt) == 'C') {
				flag |= OPT_l;
				conv = 'c';
			} else if (c == 'd') {
				conv = 'd';
			} else if (c == 'S') {
				flag |= OPT_l;
				conv = 's';
			} else if (c == 's') {
				conv = 's';
			} else if (c == 'i') {
				conv = 'i';
			} else if (c == 'o') {
				conv = 'o';
			} else if (c == 'u') {
				conv = 'u';
			} else if (c == 'c') {
				conv = 'c';
			} else if (c == 'x') {
				conv = 'x';
			} else if (c == 'X') {
				conv = 'X';
			} else if (c == 'e') {
				conv = 'e';
			} else if (c == 'E') {
				conv = 'E';
			} else if (c == 'f') {
				conv = 'f';
			} else if (c == 'F') {
				conv = 'F';
			} else if (c == 'a') {
				conv = 'a';
			} else if (c == 'A') {
				conv = 'A';
			} else if (c == 'g') {
				conv = 'g';
			} else if (c == 'G') {
				conv = 'G';
			} else if (c == 'p') {
				conv = 'p';
			} else if (c == 'n') {
				conv = 'n';
			} else {
				lastarg = prevarg;
				continue;
			}

			if ((arg * 2 + 1 >= sz) ||
				(norm[arg * 2] &&
				(norm[arg * 2] != conv))) {
				return (-1);
			} else {
				if (arg >= max)
					max = arg + 1;
				norm[arg * 2] = conv;
			}
			norm[arg * 2 + 1] = flag;
		}
	}

end:
	for (arg = 0; arg < max; arg++) {
		if (norm[arg * 2] == '\0')
			return (-1);
	}

	return (max);
}


void
check_format(struct entry *id, struct entry *str, int is_c_format)
{
	int	i, n;
	int	id_b_newline, id_e_newline;
	int	plural_b_newline, plural_e_newline;
	int	str_b_newline, str_e_newline;
	int	id_fmt, plural_fmt, str_fmt;
	int	*pstr_fmt;
	char	*msgid, *plural, *msgstr;
	char	*id_norm, *plural_norm, *str_norm;
	char	**pstr_norm;
	size_t	id_len, id_num;
	size_t	plural_off, plural_len, plural_num;
	size_t	str_len, str_num;
	size_t	osz, nsz;
	struct loc	*p;

	if (id->len == 1) {
		/*
		 * null string: header entry
		 * no check is performed
		 */
		return;
	}

	msgid = id->str;
	id_num = id->num;
	msgstr = str->str;
	if (id->no > 1) {
		/* plural */
		id_len = id->pos[0].len;
		plural_off = id->pos[1].off;
		plural_len = id->pos[1].len;
		plural_num = id->pos[1].num;
		plural = msgid + plural_off;
	} else {
		/* no plural form */
		id_len = id->len;
		str_len = str->len;
		str_num = str->num;
		plural = NULL;
	}

	/*
	 * First checking the newline
	 */

	if (!plural) {
		/* no plural form */
		id_b_newline = (msgid[0] == '\n');
		id_e_newline = (msgid[id_len - 1 - 1] == '\n');

		str_b_newline = (msgstr[0] == '\n');
		str_e_newline = (msgstr[str_len - 1 - 1] == '\n');
		if (id_b_newline && !str_b_newline) {
			diag(gettext(ERR_BEGIN_NEWLINE_1),
				id_num, str_num, cur_po);
			po_error++;
		} else if (!id_b_newline && str_b_newline) {
			diag(gettext(ERR_BEGIN_NEWLINE_2),
				id_num, str_num, cur_po);
			po_error++;
		}
		if (id_e_newline && !str_e_newline) {
			diag(gettext(ERR_END_NEWLINE_1),
				id_num, str_num, cur_po);
			po_error++;
		} else if (!id_e_newline && str_e_newline) {
			diag(gettext(ERR_END_NEWLINE_2),
				id_num, str_num, cur_po);
			po_error++;
		}
	} else {
		/* plural form */
		id_b_newline = (msgid[0] == '\n');
		id_e_newline = (msgid[id_len - 1 - 1] == '\n');

		plural_b_newline = (plural[0] == '\n');
		plural_e_newline = (plural[plural_len - 1 -1 ] == '\n');

		/* between msgid and msgid_plural */
		if (id_b_newline && !plural_b_newline) {
			diag(gettext(ERR_BEGIN_NEWLINE_3),
				id_num, plural_num, cur_po);
			po_error++;
		} else if (!id_b_newline && plural_b_newline) {
			diag(gettext(ERR_BEGIN_NEWLINE_4),
				id_num, plural_num, cur_po);
			po_error++;
		}
		if (id_e_newline && !plural_e_newline) {
			diag(gettext(ERR_END_NEWLINE_3),
				id_num, plural_num, cur_po);
			po_error++;
		} else if (!id_e_newline && plural_e_newline) {
			diag(gettext(ERR_END_NEWLINE_4),
				id_num, plural_num, cur_po);
			po_error++;
		}

		for (i = 0; i < str->no; i++) {
			p = str->pos + i;
			str_b_newline = (msgstr[p->off] == '\n');
			str_e_newline =
				(msgstr[p->off + p->len - 1 - 1] == '\n');

			if (id_b_newline && !str_b_newline) {
				diag(gettext(ERR_BEGIN_NEWLINE_5),
					id_num, p->num, cur_po, i);
				po_error++;
			} else if (!id_b_newline && str_b_newline) {
				diag(gettext(ERR_BEGIN_NEWLINE_6),
					id_num, p->num, cur_po, i);
				po_error++;
			}

			if (id_e_newline && !str_e_newline) {
				diag(gettext(ERR_END_NEWLINE_5),
					id_num, p->num, cur_po, i);
				po_error++;
			} else if (!id_e_newline && str_e_newline) {
				diag(gettext(ERR_END_NEWLINE_6),
					id_num, p->num, cur_po, i);
				po_error++;
			}
		}
	}

	/*
	 * if c-format is not specified, no printf-format check
	 * is performed.
	 */
	if (!is_c_format) {
		return;
	}

	osz = id_len * 2;
	id_norm = (char *)Xcalloc(1, osz);
	id_fmt = extract_format(id_norm, msgid, osz);
	if (id_fmt == -1) {
		diag(gettext(ERR_INVALID_FMT), id_num, cur_po);
		po_error++;
	}

	if (!plural) {
		/* no plural */

		nsz = str_len * 2;
		str_norm = (char *)Xcalloc(1, nsz);
		str_fmt = extract_format(str_norm, msgstr, nsz);
		if (str_fmt == -1) {
			diag(gettext(ERR_INVALID_FMT), str_num, cur_po);
			po_error++;
		}

		if (id_fmt != str_fmt) {
			diag(gettext(ERR_INCMP_FMT),
				id_num, str_num, cur_po);
			diag(gettext(ERR_INCMP_FMT_DIFF_1),
				id_fmt, str_fmt);
			po_error++;
		} else {
			for (n = 0; n < id_fmt; n++) {
				if ((id_norm[n * 2] !=
					str_norm[n * 2]) ||
					(id_norm[n * 2 + 1] !=
					str_norm[n * 2 + 1])) {
					diag(gettext(ERR_INCMP_FMT),
						id_num, str_num, cur_po);
					diag(gettext(ERR_INCMP_FMT_DIFF_2),
						n + 1);
					po_error++;
				}
			}
		}
		free(str_norm);
		free(id_norm);

		return;
	}

	/* plural */
	nsz = plural_len * 2;
	plural_norm = (char *)Xcalloc(1, nsz);
	plural_fmt = extract_format(plural_norm, plural, nsz);
	if (plural_fmt == -1) {
		diag(gettext(ERR_INVALID_FMT), plural_num, cur_po);
		po_error++;
	}

	pstr_norm = (char **)Xcalloc(str->no, sizeof (char *));
	pstr_fmt = (int *)Xcalloc(str->no, sizeof (int));
	for (i = 0; i < str->no; i++) {
		p = str->pos + i;
		nsz = p->len * 2;
		pstr_norm[i] = (char *)Xcalloc(1, nsz);
		pstr_fmt[i] = extract_format(pstr_norm[i],
			msgstr + p->off, nsz);
		if (pstr_fmt[i] == -1) {
			diag(gettext(ERR_INVALID_FMT),
				p->num, cur_po);
			po_error++;
		}
	}

	/* between msgid and msgid_plural */
	if (id_fmt != plural_fmt) {
		diag(gettext(ERR_INCMP_FMT),
			id_num, plural_num, cur_po);
		diag(gettext(ERR_INCMP_FMT_DIFF_1),
			id_fmt, plural_fmt);
		po_error++;
	} else {
		for (n = 0; n < id_fmt; n++) {
			if ((id_norm[n * 2] !=
				plural_norm[n * 2]) ||
				(id_norm[n * 2 + 1] !=
				plural_norm[n * 2 + 1])) {
				diag(gettext(ERR_INCMP_FMT),
					id_num, plural_num, cur_po);
				diag(gettext(ERR_INCMP_FMT_DIFF_2),
					n + 1);
				po_error++;
			}
		}
	}
	free(plural_norm);

	/* between msgid and msgstr */
	for (i = 0; i < str->no; i++) {
		p = str->pos + i;
		if (id_fmt != pstr_fmt[i]) {
			diag(gettext(ERR_INCMP_FMT),
				id_num, p->num, cur_po);
			diag(gettext(ERR_INCMP_FMT_DIFF_1),
				id_fmt, pstr_fmt[i]);
			po_error++;
		} else {
			for (n = 0; n < id_fmt; n++) {
				if ((id_norm[n * 2] !=
					pstr_norm[i][n * 2]) ||
					(id_norm[n * 2 + 1] !=
					pstr_norm[i][n * 2 + 1])) {
					diag(gettext(ERR_INCMP_FMT),
						id_num, p->num, cur_po);
					diag(gettext(ERR_INCMP_FMT_DIFF_2),
						n + 1);
					po_error++;
				}
			}
		}
		free(pstr_norm[i]);
	}
	free(pstr_norm);
	free(pstr_fmt);
	free(id_norm);
}