Home | History | Annotate | Line # | Download | only in dist
man_validate.c revision 1.1.1.11
      1 /*	$Vendor-Id: man_validate.c,v 1.57 2011/01/01 12:59:17 kristaps Exp $ */
      2 /*
      3  * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps (at) bsd.lv>
      4  *
      5  * Permission to use, copy, modify, and distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 #ifdef HAVE_CONFIG_H
     18 #include "config.h"
     19 #endif
     20 
     21 #include <sys/types.h>
     22 
     23 #include <assert.h>
     24 #include <ctype.h>
     25 #include <errno.h>
     26 #include <limits.h>
     27 #include <stdarg.h>
     28 #include <stdlib.h>
     29 #include <string.h>
     30 #include <time.h>
     31 
     32 #include "mandoc.h"
     33 #include "libman.h"
     34 #include "libmandoc.h"
     35 
     36 #define	CHKARGS	  struct man *m, struct man_node *n
     37 
     38 typedef	int	(*v_check)(CHKARGS);
     39 
     40 struct	man_valid {
     41 	v_check	 *pres;
     42 	v_check	 *posts;
     43 };
     44 
     45 static	int	  check_bline(CHKARGS);
     46 static	int	  check_eq0(CHKARGS);
     47 static	int	  check_ft(CHKARGS);
     48 static	int	  check_le1(CHKARGS);
     49 static	int	  check_ge2(CHKARGS);
     50 static	int	  check_le5(CHKARGS);
     51 static	int	  check_par(CHKARGS);
     52 static	int	  check_part(CHKARGS);
     53 static	int	  check_root(CHKARGS);
     54 static	int	  check_sec(CHKARGS);
     55 static	int	  check_text(CHKARGS);
     56 static	int	  check_title(CHKARGS);
     57 
     58 static	int	  post_AT(CHKARGS);
     59 static	int	  post_fi(CHKARGS);
     60 static	int	  post_nf(CHKARGS);
     61 static	int	  post_TH(CHKARGS);
     62 static	int	  post_UC(CHKARGS);
     63 
     64 static	v_check	  posts_at[] = { post_AT, NULL };
     65 static	v_check	  posts_eq0[] = { check_eq0, NULL };
     66 static	v_check	  posts_fi[] = { check_eq0, post_fi, NULL };
     67 static	v_check	  posts_le1[] = { check_le1, NULL };
     68 static	v_check	  posts_ft[] = { check_ft, NULL };
     69 static	v_check	  posts_nf[] = { check_eq0, post_nf, NULL };
     70 static	v_check	  posts_par[] = { check_par, NULL };
     71 static	v_check	  posts_part[] = { check_part, NULL };
     72 static	v_check	  posts_sec[] = { check_sec, NULL };
     73 static	v_check	  posts_th[] = { check_ge2, check_le5, check_title, post_TH, NULL };
     74 static	v_check	  posts_uc[] = { post_UC, NULL };
     75 static	v_check	  pres_bline[] = { check_bline, NULL };
     76 
     77 
     78 static	const struct man_valid man_valids[MAN_MAX] = {
     79 	{ NULL, posts_eq0 }, /* br */
     80 	{ pres_bline, posts_th }, /* TH */
     81 	{ pres_bline, posts_sec }, /* SH */
     82 	{ pres_bline, posts_sec }, /* SS */
     83 	{ pres_bline, posts_par }, /* TP */
     84 	{ pres_bline, posts_par }, /* LP */
     85 	{ pres_bline, posts_par }, /* PP */
     86 	{ pres_bline, posts_par }, /* P */
     87 	{ pres_bline, posts_par }, /* IP */
     88 	{ pres_bline, posts_par }, /* HP */
     89 	{ NULL, NULL }, /* SM */
     90 	{ NULL, NULL }, /* SB */
     91 	{ NULL, NULL }, /* BI */
     92 	{ NULL, NULL }, /* IB */
     93 	{ NULL, NULL }, /* BR */
     94 	{ NULL, NULL }, /* RB */
     95 	{ NULL, NULL }, /* R */
     96 	{ NULL, NULL }, /* B */
     97 	{ NULL, NULL }, /* I */
     98 	{ NULL, NULL }, /* IR */
     99 	{ NULL, NULL }, /* RI */
    100 	{ NULL, posts_eq0 }, /* na */ /* FIXME: should warn only. */
    101 	{ NULL, posts_le1 }, /* sp */ /* FIXME: should warn only. */
    102 	{ pres_bline, posts_nf }, /* nf */
    103 	{ pres_bline, posts_fi }, /* fi */
    104 	{ NULL, NULL }, /* RE */
    105 	{ NULL, posts_part }, /* RS */
    106 	{ NULL, NULL }, /* DT */
    107 	{ NULL, posts_uc }, /* UC */
    108 	{ NULL, NULL }, /* PD */
    109 	{ NULL, posts_at }, /* AT */
    110 	{ NULL, NULL }, /* in */
    111 	{ NULL, posts_ft }, /* ft */
    112 };
    113 
    114 
    115 int
    116 man_valid_pre(struct man *m, struct man_node *n)
    117 {
    118 	v_check		*cp;
    119 
    120 	switch (n->type) {
    121 	case (MAN_TEXT):
    122 		/* FALLTHROUGH */
    123 	case (MAN_ROOT):
    124 		/* FALLTHROUGH */
    125 	case (MAN_TBL):
    126 		return(1);
    127 	default:
    128 		break;
    129 	}
    130 
    131 	if (NULL == (cp = man_valids[n->tok].pres))
    132 		return(1);
    133 	for ( ; *cp; cp++)
    134 		if ( ! (*cp)(m, n))
    135 			return(0);
    136 	return(1);
    137 }
    138 
    139 
    140 int
    141 man_valid_post(struct man *m)
    142 {
    143 	v_check		*cp;
    144 
    145 	if (MAN_VALID & m->last->flags)
    146 		return(1);
    147 	m->last->flags |= MAN_VALID;
    148 
    149 	switch (m->last->type) {
    150 	case (MAN_TEXT):
    151 		return(check_text(m, m->last));
    152 	case (MAN_ROOT):
    153 		return(check_root(m, m->last));
    154 	case (MAN_TBL):
    155 		return(1);
    156 	default:
    157 		break;
    158 	}
    159 
    160 	if (NULL == (cp = man_valids[m->last->tok].posts))
    161 		return(1);
    162 	for ( ; *cp; cp++)
    163 		if ( ! (*cp)(m, m->last))
    164 			return(0);
    165 
    166 	return(1);
    167 }
    168 
    169 
    170 static int
    171 check_root(CHKARGS)
    172 {
    173 
    174 	if (MAN_BLINE & m->flags)
    175 		man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
    176 	else if (MAN_ELINE & m->flags)
    177 		man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
    178 
    179 	m->flags &= ~MAN_BLINE;
    180 	m->flags &= ~MAN_ELINE;
    181 
    182 	if (NULL == m->first->child) {
    183 		man_nmsg(m, n, MANDOCERR_NODOCBODY);
    184 		return(0);
    185 	} else if (NULL == m->meta.title) {
    186 		man_nmsg(m, n, MANDOCERR_NOTITLE);
    187 
    188 		/*
    189 		 * If a title hasn't been set, do so now (by
    190 		 * implication, date and section also aren't set).
    191 		 */
    192 
    193 	        m->meta.title = mandoc_strdup("unknown");
    194 		m->meta.date = time(NULL);
    195 		m->meta.msec = mandoc_strdup("1");
    196 	}
    197 
    198 	return(1);
    199 }
    200 
    201 
    202 static int
    203 check_title(CHKARGS)
    204 {
    205 	const char	*p;
    206 
    207 	assert(n->child);
    208 	/* FIXME: is this sufficient? */
    209 	if ('\0' == *n->child->string) {
    210 		man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT);
    211 		return(0);
    212 	}
    213 
    214 	for (p = n->child->string; '\0' != *p; p++)
    215 		/* Only warn about this once... */
    216 		if (isalpha((u_char)*p) && ! isupper((u_char)*p)) {
    217 			man_nmsg(m, n, MANDOCERR_UPPERCASE);
    218 			break;
    219 		}
    220 
    221 	return(1);
    222 }
    223 
    224 
    225 static int
    226 check_text(CHKARGS)
    227 {
    228 	char		*p;
    229 	int		 pos, c;
    230 	size_t		 sz;
    231 
    232 	for (p = n->string, pos = n->pos + 1; *p; p++, pos++) {
    233 		sz = strcspn(p, "\t\\");
    234 		p += (int)sz;
    235 
    236 		if ('\0' == *p)
    237 			break;
    238 
    239 		pos += (int)sz;
    240 
    241 		if ('\t' == *p) {
    242 			if (MAN_LITERAL & m->flags)
    243 				continue;
    244 			if (man_pmsg(m, n->line, pos, MANDOCERR_BADTAB))
    245 				continue;
    246 			return(0);
    247 		}
    248 
    249 		/* Check the special character. */
    250 
    251 		c = mandoc_special(p);
    252 		if (c) {
    253 			p += c - 1;
    254 			pos += c - 1;
    255 		} else
    256 			man_pmsg(m, n->line, pos, MANDOCERR_BADESCAPE);
    257 	}
    258 
    259 	return(1);
    260 }
    261 
    262 
    263 #define	INEQ_DEFINE(x, ineq, name) \
    264 static int \
    265 check_##name(CHKARGS) \
    266 { \
    267 	if (n->nchild ineq (x)) \
    268 		return(1); \
    269 	man_vmsg(m, MANDOCERR_SYNTARGCOUNT, n->line, n->pos, \
    270 			"line arguments %s %d (have %d)", \
    271 			#ineq, (x), n->nchild); \
    272 	return(0); \
    273 }
    274 
    275 INEQ_DEFINE(0, ==, eq0)
    276 INEQ_DEFINE(1, <=, le1)
    277 INEQ_DEFINE(2, >=, ge2)
    278 INEQ_DEFINE(5, <=, le5)
    279 
    280 static int
    281 check_ft(CHKARGS)
    282 {
    283 	char	*cp;
    284 	int	 ok;
    285 
    286 	if (0 == n->nchild)
    287 		return(1);
    288 
    289 	ok = 0;
    290 	cp = n->child->string;
    291 	switch (*cp) {
    292 	case ('1'):
    293 		/* FALLTHROUGH */
    294 	case ('2'):
    295 		/* FALLTHROUGH */
    296 	case ('3'):
    297 		/* FALLTHROUGH */
    298 	case ('4'):
    299 		/* FALLTHROUGH */
    300 	case ('I'):
    301 		/* FALLTHROUGH */
    302 	case ('P'):
    303 		/* FALLTHROUGH */
    304 	case ('R'):
    305 		if ('\0' == cp[1])
    306 			ok = 1;
    307 		break;
    308 	case ('B'):
    309 		if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
    310 			ok = 1;
    311 		break;
    312 	case ('C'):
    313 		if ('W' == cp[1] && '\0' == cp[2])
    314 			ok = 1;
    315 		break;
    316 	default:
    317 		break;
    318 	}
    319 
    320 	if (0 == ok) {
    321 		man_vmsg(m, MANDOCERR_BADFONT,
    322 				n->line, n->pos, "%s", cp);
    323 		*cp = '\0';
    324 	}
    325 
    326 	if (1 < n->nchild)
    327 		man_vmsg(m, MANDOCERR_ARGCOUNT, n->line, n->pos,
    328 				"want one child (have %d)", n->nchild);
    329 
    330 	return(1);
    331 }
    332 
    333 static int
    334 check_sec(CHKARGS)
    335 {
    336 
    337 	if (MAN_HEAD == n->type && 0 == n->nchild) {
    338 		man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT);
    339 		return(0);
    340 	} else if (MAN_BODY == n->type && 0 == n->nchild)
    341 		man_nmsg(m, n, MANDOCERR_NOBODY);
    342 
    343 	return(1);
    344 }
    345 
    346 
    347 static int
    348 check_part(CHKARGS)
    349 {
    350 
    351 	if (MAN_BODY == n->type && 0 == n->nchild)
    352 		man_nmsg(m, n, MANDOCERR_NOBODY);
    353 
    354 	return(1);
    355 }
    356 
    357 
    358 static int
    359 check_par(CHKARGS)
    360 {
    361 
    362 	if (MAN_BODY == n->type)
    363 		switch (n->tok) {
    364 		case (MAN_IP):
    365 			/* FALLTHROUGH */
    366 		case (MAN_HP):
    367 			/* FALLTHROUGH */
    368 		case (MAN_TP):
    369 			/* Body-less lists are ok. */
    370 			break;
    371 		default:
    372 			if (0 == n->nchild)
    373 				man_nmsg(m, n, MANDOCERR_NOBODY);
    374 			break;
    375 		}
    376 	if (MAN_HEAD == n->type)
    377 		switch (n->tok) {
    378 		case (MAN_PP):
    379 			/* FALLTHROUGH */
    380 		case (MAN_P):
    381 			/* FALLTHROUGH */
    382 		case (MAN_LP):
    383 			if (n->nchild)
    384 				man_nmsg(m, n, MANDOCERR_ARGSLOST);
    385 			break;
    386 		default:
    387 			break;
    388 		}
    389 
    390 	return(1);
    391 }
    392 
    393 
    394 static int
    395 check_bline(CHKARGS)
    396 {
    397 
    398 	assert( ! (MAN_ELINE & m->flags));
    399 	if (MAN_BLINE & m->flags) {
    400 		man_nmsg(m, n, MANDOCERR_SYNTLINESCOPE);
    401 		return(0);
    402 	}
    403 
    404 	return(1);
    405 }
    406 
    407 static int
    408 post_TH(CHKARGS)
    409 {
    410 
    411 	if (m->meta.title)
    412 		free(m->meta.title);
    413 	if (m->meta.vol)
    414 		free(m->meta.vol);
    415 	if (m->meta.source)
    416 		free(m->meta.source);
    417 	if (m->meta.msec)
    418 		free(m->meta.msec);
    419 	if (m->meta.rawdate)
    420 		free(m->meta.rawdate);
    421 
    422 	m->meta.title = m->meta.vol = m->meta.rawdate =
    423 		m->meta.msec = m->meta.source = NULL;
    424 	m->meta.date = 0;
    425 
    426 	/* ->TITLE<- MSEC DATE SOURCE VOL */
    427 
    428 	n = n->child;
    429 	assert(n);
    430 	m->meta.title = mandoc_strdup(n->string);
    431 
    432 	/* TITLE ->MSEC<- DATE SOURCE VOL */
    433 
    434 	n = n->next;
    435 	assert(n);
    436 	m->meta.msec = mandoc_strdup(n->string);
    437 
    438 	/* TITLE MSEC ->DATE<- SOURCE VOL */
    439 
    440 	/*
    441 	 * Try to parse the date.  If this works, stash the epoch (this
    442 	 * is optimal because we can reformat it in the canonical form).
    443 	 * If it doesn't parse, isn't specified at all, or is an empty
    444 	 * string, then use the current date.
    445 	 */
    446 
    447 	n = n->next;
    448 	if (n && n->string && *n->string) {
    449 		m->meta.date = mandoc_a2time
    450 			(MTIME_ISO_8601, n->string);
    451 		if (0 == m->meta.date) {
    452 			man_nmsg(m, n, MANDOCERR_BADDATE);
    453 			m->meta.rawdate = mandoc_strdup(n->string);
    454 		}
    455 	} else
    456 		m->meta.date = time(NULL);
    457 
    458 	/* TITLE MSEC DATE ->SOURCE<- VOL */
    459 
    460 	if (n && (n = n->next))
    461 		m->meta.source = mandoc_strdup(n->string);
    462 
    463 	/* TITLE MSEC DATE SOURCE ->VOL<- */
    464 
    465 	if (n && (n = n->next))
    466 		m->meta.vol = mandoc_strdup(n->string);
    467 
    468 	/*
    469 	 * Remove the `TH' node after we've processed it for our
    470 	 * meta-data.
    471 	 */
    472 	man_node_delete(m, m->last);
    473 	return(1);
    474 }
    475 
    476 static int
    477 post_nf(CHKARGS)
    478 {
    479 
    480 	if (MAN_LITERAL & m->flags)
    481 		man_nmsg(m, n, MANDOCERR_SCOPEREP);
    482 
    483 	m->flags |= MAN_LITERAL;
    484 	return(1);
    485 }
    486 
    487 static int
    488 post_fi(CHKARGS)
    489 {
    490 
    491 	if ( ! (MAN_LITERAL & m->flags))
    492 		man_nmsg(m, n, MANDOCERR_NOSCOPE);
    493 
    494 	m->flags &= ~MAN_LITERAL;
    495 	return(1);
    496 }
    497 
    498 static int
    499 post_UC(CHKARGS)
    500 {
    501 	static const char * const bsd_versions[] = {
    502 	    "3rd Berkeley Distribution",
    503 	    "4th Berkeley Distribution",
    504 	    "4.2 Berkeley Distribution",
    505 	    "4.3 Berkeley Distribution",
    506 	    "4.4 Berkeley Distribution",
    507 	};
    508 
    509 	const char	*p, *s;
    510 
    511 	n = n->child;
    512 	n = m->last->child;
    513 
    514 	if (NULL == n || MAN_TEXT != n->type)
    515 		p = bsd_versions[0];
    516 	else {
    517 		s = n->string;
    518 		if (0 == strcmp(s, "3"))
    519 			p = bsd_versions[0];
    520 		else if (0 == strcmp(s, "4"))
    521 			p = bsd_versions[1];
    522 		else if (0 == strcmp(s, "5"))
    523 			p = bsd_versions[2];
    524 		else if (0 == strcmp(s, "6"))
    525 			p = bsd_versions[3];
    526 		else if (0 == strcmp(s, "7"))
    527 			p = bsd_versions[4];
    528 		else
    529 			p = bsd_versions[0];
    530 	}
    531 
    532 	if (m->meta.source)
    533 		free(m->meta.source);
    534 
    535 	m->meta.source = mandoc_strdup(p);
    536 	return(1);
    537 }
    538 
    539 static int
    540 post_AT(CHKARGS)
    541 {
    542 	static const char * const unix_versions[] = {
    543 	    "7th Edition",
    544 	    "System III",
    545 	    "System V",
    546 	    "System V Release 2",
    547 	};
    548 
    549 	const char	*p, *s;
    550 	struct man_node	*nn;
    551 
    552 	n = n->child;
    553 
    554 	if (NULL == n || MAN_TEXT != n->type)
    555 		p = unix_versions[0];
    556 	else {
    557 		s = n->string;
    558 		if (0 == strcmp(s, "3"))
    559 			p = unix_versions[0];
    560 		else if (0 == strcmp(s, "4"))
    561 			p = unix_versions[1];
    562 		else if (0 == strcmp(s, "5")) {
    563 			nn = n->next;
    564 			if (nn && MAN_TEXT == nn->type && nn->string[0])
    565 				p = unix_versions[3];
    566 			else
    567 				p = unix_versions[2];
    568 		} else
    569 			p = unix_versions[0];
    570 	}
    571 
    572 	if (m->meta.source)
    573 		free(m->meta.source);
    574 
    575 	m->meta.source = mandoc_strdup(p);
    576 	return(1);
    577 }
    578