1/*	$OpenBSD$ */
2/*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2012-2017 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#include "config.h"
19
20#include <sys/types.h>
21
22#include <assert.h>
23#include <ctype.h>
24#include <errno.h>
25#include <limits.h>
26#include <stdarg.h>
27#include <stdlib.h>
28#include <string.h>
29#include <time.h>
30
31#include "mandoc_aux.h"
32#include "mandoc.h"
33#include "roff.h"
34#include "man.h"
35#include "libmandoc.h"
36#include "roff_int.h"
37#include "libman.h"
38
39#define	CHKARGS	  struct roff_man *man, struct roff_node *n
40
41typedef	void	(*v_check)(CHKARGS);
42
43static	void	  check_par(CHKARGS);
44static	void	  check_part(CHKARGS);
45static	void	  check_root(CHKARGS);
46static	void	  check_text(CHKARGS);
47
48static	void	  post_AT(CHKARGS);
49static	void	  post_IP(CHKARGS);
50static	void	  post_OP(CHKARGS);
51static	void	  post_TH(CHKARGS);
52static	void	  post_UC(CHKARGS);
53static	void	  post_UR(CHKARGS);
54static	void	  post_in(CHKARGS);
55static	void	  post_vs(CHKARGS);
56
57static	const v_check __man_valids[MAN_MAX - MAN_TH] = {
58	post_TH,    /* TH */
59	NULL,       /* SH */
60	NULL,       /* SS */
61	NULL,       /* TP */
62	check_par,  /* LP */
63	check_par,  /* PP */
64	check_par,  /* P */
65	post_IP,    /* IP */
66	NULL,       /* HP */
67	NULL,       /* SM */
68	NULL,       /* SB */
69	NULL,       /* BI */
70	NULL,       /* IB */
71	NULL,       /* BR */
72	NULL,       /* RB */
73	NULL,       /* R */
74	NULL,       /* B */
75	NULL,       /* I */
76	NULL,       /* IR */
77	NULL,       /* RI */
78	NULL,       /* nf */
79	NULL,       /* fi */
80	NULL,       /* RE */
81	check_part, /* RS */
82	NULL,       /* DT */
83	post_UC,    /* UC */
84	NULL,       /* PD */
85	post_AT,    /* AT */
86	post_in,    /* in */
87	post_OP,    /* OP */
88	NULL,       /* EX */
89	NULL,       /* EE */
90	post_UR,    /* UR */
91	NULL,       /* UE */
92	post_UR,    /* MT */
93	NULL,       /* ME */
94};
95static	const v_check *man_valids = __man_valids - MAN_TH;
96
97
98void
99man_node_validate(struct roff_man *man)
100{
101	struct roff_node *n;
102	const v_check	 *cp;
103
104	n = man->last;
105	man->last = man->last->child;
106	while (man->last != NULL) {
107		man_node_validate(man);
108		if (man->last == n)
109			man->last = man->last->child;
110		else
111			man->last = man->last->next;
112	}
113
114	man->last = n;
115	man->next = ROFF_NEXT_SIBLING;
116	switch (n->type) {
117	case ROFFT_TEXT:
118		check_text(man, n);
119		break;
120	case ROFFT_ROOT:
121		check_root(man, n);
122		break;
123	case ROFFT_EQN:
124	case ROFFT_TBL:
125		break;
126	default:
127		if (n->tok < ROFF_MAX) {
128			switch (n->tok) {
129			case ROFF_br:
130			case ROFF_sp:
131				post_vs(man, n);
132				break;
133			default:
134				roff_validate(man);
135				break;
136			}
137			break;
138		}
139		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
140		cp = man_valids + n->tok;
141		if (*cp)
142			(*cp)(man, n);
143		if (man->last == n)
144			man_state(man, n);
145		break;
146	}
147}
148
149static void
150check_root(CHKARGS)
151{
152
153	assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
154
155	if (NULL == man->first->child)
156		mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
157		    n->line, n->pos, NULL);
158	else
159		man->meta.hasbody = 1;
160
161	if (NULL == man->meta.title) {
162		mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
163		    n->line, n->pos, NULL);
164
165		/*
166		 * If a title hasn't been set, do so now (by
167		 * implication, date and section also aren't set).
168		 */
169
170		man->meta.title = mandoc_strdup("");
171		man->meta.msec = mandoc_strdup("");
172		man->meta.date = man->quick ? mandoc_strdup("") :
173		    mandoc_normdate(man, NULL, n->line, n->pos);
174	}
175
176	if (man->meta.os_e &&
177	    (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
178		mandoc_msg(MANDOCERR_RCS_MISSING, man->parse, 0, 0,
179		    man->meta.os_e == MANDOC_OS_OPENBSD ?
180		    "(OpenBSD)" : "(NetBSD)");
181}
182
183static void
184check_text(CHKARGS)
185{
186	char		*cp, *p;
187
188	if (MAN_LITERAL & man->flags)
189		return;
190
191	cp = n->string;
192	for (p = cp; NULL != (p = strchr(p, '\t')); p++)
193		mandoc_msg(MANDOCERR_FI_TAB, man->parse,
194		    n->line, n->pos + (p - cp), NULL);
195}
196
197static void
198post_OP(CHKARGS)
199{
200
201	if (n->child == NULL)
202		mandoc_msg(MANDOCERR_OP_EMPTY, man->parse,
203		    n->line, n->pos, "OP");
204	else if (n->child->next != NULL && n->child->next->next != NULL) {
205		n = n->child->next->next;
206		mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
207		    n->line, n->pos, "OP ... %s", n->string);
208	}
209}
210
211static void
212post_UR(CHKARGS)
213{
214	if (n->type == ROFFT_HEAD && n->child == NULL)
215		mandoc_msg(MANDOCERR_UR_NOHEAD, man->parse,
216		    n->line, n->pos, roff_name[n->tok]);
217	check_part(man, n);
218}
219
220static void
221check_part(CHKARGS)
222{
223
224	if (n->type == ROFFT_BODY && n->child == NULL)
225		mandoc_msg(MANDOCERR_BLK_EMPTY, man->parse,
226		    n->line, n->pos, roff_name[n->tok]);
227}
228
229static void
230check_par(CHKARGS)
231{
232
233	switch (n->type) {
234	case ROFFT_BLOCK:
235		if (n->body->child == NULL)
236			roff_node_delete(man, n);
237		break;
238	case ROFFT_BODY:
239		if (n->child == NULL)
240			mandoc_vmsg(MANDOCERR_PAR_SKIP,
241			    man->parse, n->line, n->pos,
242			    "%s empty", roff_name[n->tok]);
243		break;
244	case ROFFT_HEAD:
245		if (n->child != NULL)
246			mandoc_vmsg(MANDOCERR_ARG_SKIP,
247			    man->parse, n->line, n->pos, "%s %s%s",
248			    roff_name[n->tok], n->child->string,
249			    n->child->next != NULL ? " ..." : "");
250		break;
251	default:
252		break;
253	}
254}
255
256static void
257post_IP(CHKARGS)
258{
259
260	switch (n->type) {
261	case ROFFT_BLOCK:
262		if (n->head->child == NULL && n->body->child == NULL)
263			roff_node_delete(man, n);
264		break;
265	case ROFFT_BODY:
266		if (n->parent->head->child == NULL && n->child == NULL)
267			mandoc_vmsg(MANDOCERR_PAR_SKIP,
268			    man->parse, n->line, n->pos,
269			    "%s empty", roff_name[n->tok]);
270		break;
271	default:
272		break;
273	}
274}
275
276static void
277post_TH(CHKARGS)
278{
279	struct roff_node *nb;
280	const char	*p;
281
282	free(man->meta.title);
283	free(man->meta.vol);
284	free(man->meta.os);
285	free(man->meta.msec);
286	free(man->meta.date);
287
288	man->meta.title = man->meta.vol = man->meta.date =
289	    man->meta.msec = man->meta.os = NULL;
290
291	nb = n;
292
293	/* ->TITLE<- MSEC DATE OS VOL */
294
295	n = n->child;
296	if (n && n->string) {
297		for (p = n->string; '\0' != *p; p++) {
298			/* Only warn about this once... */
299			if (isalpha((unsigned char)*p) &&
300			    ! isupper((unsigned char)*p)) {
301				mandoc_vmsg(MANDOCERR_TITLE_CASE,
302				    man->parse, n->line,
303				    n->pos + (p - n->string),
304				    "TH %s", n->string);
305				break;
306			}
307		}
308		man->meta.title = mandoc_strdup(n->string);
309	} else {
310		man->meta.title = mandoc_strdup("");
311		mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
312		    nb->line, nb->pos, "TH");
313	}
314
315	/* TITLE ->MSEC<- DATE OS VOL */
316
317	if (n)
318		n = n->next;
319	if (n && n->string)
320		man->meta.msec = mandoc_strdup(n->string);
321	else {
322		man->meta.msec = mandoc_strdup("");
323		mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
324		    nb->line, nb->pos, "TH %s", man->meta.title);
325	}
326
327	/* TITLE MSEC ->DATE<- OS VOL */
328
329	if (n)
330		n = n->next;
331	if (n && n->string && '\0' != n->string[0]) {
332		man->meta.date = man->quick ?
333		    mandoc_strdup(n->string) :
334		    mandoc_normdate(man, n->string, n->line, n->pos);
335	} else {
336		man->meta.date = mandoc_strdup("");
337		mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
338		    n ? n->line : nb->line,
339		    n ? n->pos : nb->pos, "TH");
340	}
341
342	/* TITLE MSEC DATE ->OS<- VOL */
343
344	if (n && (n = n->next))
345		man->meta.os = mandoc_strdup(n->string);
346	else if (man->os_s != NULL)
347		man->meta.os = mandoc_strdup(man->os_s);
348	if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
349		if (strstr(man->meta.os, "OpenBSD") != NULL)
350			man->meta.os_e = MANDOC_OS_OPENBSD;
351		else if (strstr(man->meta.os, "NetBSD") != NULL)
352			man->meta.os_e = MANDOC_OS_NETBSD;
353	}
354
355	/* TITLE MSEC DATE OS ->VOL<- */
356	/* If missing, use the default VOL name for MSEC. */
357
358	if (n && (n = n->next))
359		man->meta.vol = mandoc_strdup(n->string);
360	else if ('\0' != man->meta.msec[0] &&
361	    (NULL != (p = mandoc_a2msec(man->meta.msec))))
362		man->meta.vol = mandoc_strdup(p);
363
364	if (n != NULL && (n = n->next) != NULL)
365		mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
366		    n->line, n->pos, "TH ... %s", n->string);
367
368	/*
369	 * Remove the `TH' node after we've processed it for our
370	 * meta-data.
371	 */
372	roff_node_delete(man, man->last);
373}
374
375static void
376post_UC(CHKARGS)
377{
378	static const char * const bsd_versions[] = {
379	    "3rd Berkeley Distribution",
380	    "4th Berkeley Distribution",
381	    "4.2 Berkeley Distribution",
382	    "4.3 Berkeley Distribution",
383	    "4.4 Berkeley Distribution",
384	};
385
386	const char	*p, *s;
387
388	n = n->child;
389
390	if (n == NULL || n->type != ROFFT_TEXT)
391		p = bsd_versions[0];
392	else {
393		s = n->string;
394		if (0 == strcmp(s, "3"))
395			p = bsd_versions[0];
396		else if (0 == strcmp(s, "4"))
397			p = bsd_versions[1];
398		else if (0 == strcmp(s, "5"))
399			p = bsd_versions[2];
400		else if (0 == strcmp(s, "6"))
401			p = bsd_versions[3];
402		else if (0 == strcmp(s, "7"))
403			p = bsd_versions[4];
404		else
405			p = bsd_versions[0];
406	}
407
408	free(man->meta.os);
409	man->meta.os = mandoc_strdup(p);
410}
411
412static void
413post_AT(CHKARGS)
414{
415	static const char * const unix_versions[] = {
416	    "7th Edition",
417	    "System III",
418	    "System V",
419	    "System V Release 2",
420	};
421
422	struct roff_node *nn;
423	const char	*p, *s;
424
425	n = n->child;
426
427	if (n == NULL || n->type != ROFFT_TEXT)
428		p = unix_versions[0];
429	else {
430		s = n->string;
431		if (0 == strcmp(s, "3"))
432			p = unix_versions[0];
433		else if (0 == strcmp(s, "4"))
434			p = unix_versions[1];
435		else if (0 == strcmp(s, "5")) {
436			nn = n->next;
437			if (nn != NULL &&
438			    nn->type == ROFFT_TEXT &&
439			    nn->string[0] != '\0')
440				p = unix_versions[3];
441			else
442				p = unix_versions[2];
443		} else
444			p = unix_versions[0];
445	}
446
447	free(man->meta.os);
448	man->meta.os = mandoc_strdup(p);
449}
450
451static void
452post_in(CHKARGS)
453{
454	char	*s;
455
456	if (n->parent->tok != MAN_TP ||
457	    n->parent->type != ROFFT_HEAD ||
458	    n->child == NULL ||
459	    *n->child->string == '+' ||
460	    *n->child->string == '-')
461		return;
462	mandoc_asprintf(&s, "+%s", n->child->string);
463	free(n->child->string);
464	n->child->string = s;
465}
466
467static void
468post_vs(CHKARGS)
469{
470
471	if (NULL != n->prev)
472		return;
473
474	switch (n->parent->tok) {
475	case MAN_SH:
476	case MAN_SS:
477	case MAN_PP:
478	case MAN_LP:
479	case MAN_P:
480		mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
481		    "%s after %s", roff_name[n->tok],
482		    roff_name[n->parent->tok]);
483		/* FALLTHROUGH */
484	case TOKEN_NONE:
485		/*
486		 * Don't warn about this because it occurs in pod2man
487		 * and would cause considerable (unfixable) warnage.
488		 */
489		roff_node_delete(man, n);
490		break;
491	default:
492		break;
493	}
494}
495