xref: /illumos-gate/usr/src/cmd/mandoc/mandoc.c (revision b70bf3ee79d81df3ccc36e553e0ff11049a2b51a)
1 /*	$Id: mandoc.c,v 1.119 2021/08/10 12:55:03 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011-2015, 2017-2021 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 <stdlib.h>
27 #include <stdio.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 "libmandoc.h"
35 #include "roff_int.h"
36 
37 static	int	 a2time(time_t *, const char *, const char *);
38 static	char	*time2a(time_t);
39 
40 
41 enum mandoc_esc
42 mandoc_font(const char *cp, int sz)
43 {
44 	switch (sz) {
45 	case 0:
46 		return ESCAPE_FONTPREV;
47 	case 1:
48 		switch (cp[0]) {
49 		case 'B':
50 		case '3':
51 			return ESCAPE_FONTBOLD;
52 		case 'I':
53 		case '2':
54 			return ESCAPE_FONTITALIC;
55 		case 'P':
56 			return ESCAPE_FONTPREV;
57 		case 'R':
58 		case '1':
59 			return ESCAPE_FONTROMAN;
60 		case '4':
61 			return ESCAPE_FONTBI;
62 		default:
63 			return ESCAPE_ERROR;
64 		}
65 	case 2:
66 		switch (cp[0]) {
67 		case 'B':
68 			switch (cp[1]) {
69 			case 'I':
70 				return ESCAPE_FONTBI;
71 			default:
72 				return ESCAPE_ERROR;
73 			}
74 		case 'C':
75 			switch (cp[1]) {
76 			case 'B':
77 				return ESCAPE_FONTCB;
78 			case 'I':
79 				return ESCAPE_FONTCI;
80 			case 'R':
81 			case 'W':
82 				return ESCAPE_FONTCR;
83 			default:
84 				return ESCAPE_ERROR;
85 			}
86 		default:
87 			return ESCAPE_ERROR;
88 		}
89 	default:
90 		return ESCAPE_ERROR;
91 	}
92 }
93 
94 enum mandoc_esc
95 mandoc_escape(const char **end, const char **start, int *sz)
96 {
97 	const char	*local_start;
98 	int		 local_sz, c, i;
99 	char		 term;
100 	enum mandoc_esc	 gly;
101 
102 	/*
103 	 * When the caller doesn't provide return storage,
104 	 * use local storage.
105 	 */
106 
107 	if (NULL == start)
108 		start = &local_start;
109 	if (NULL == sz)
110 		sz = &local_sz;
111 
112 	/*
113 	 * Treat "\E" just like "\";
114 	 * it only makes a difference in copy mode.
115 	 */
116 
117 	if (**end == 'E')
118 		++*end;
119 
120 	/*
121 	 * Beyond the backslash, at least one input character
122 	 * is part of the escape sequence.  With one exception
123 	 * (see below), that character won't be returned.
124 	 */
125 
126 	gly = ESCAPE_ERROR;
127 	*start = ++*end;
128 	*sz = 0;
129 	term = '\0';
130 
131 	switch ((*start)[-1]) {
132 	/*
133 	 * First the glyphs.  There are several different forms of
134 	 * these, but each eventually returns a substring of the glyph
135 	 * name.
136 	 */
137 	case '(':
138 		gly = ESCAPE_SPECIAL;
139 		*sz = 2;
140 		break;
141 	case '[':
142 		if (**start == ' ') {
143 			++*end;
144 			return ESCAPE_ERROR;
145 		}
146 		gly = ESCAPE_SPECIAL;
147 		term = ']';
148 		break;
149 	case 'C':
150 		if ('\'' != **start)
151 			return ESCAPE_ERROR;
152 		*start = ++*end;
153 		gly = ESCAPE_SPECIAL;
154 		term = '\'';
155 		break;
156 
157 	/*
158 	 * Escapes taking no arguments at all.
159 	 */
160 	case '!':
161 	case '?':
162 		return ESCAPE_UNSUPP;
163 	case '%':
164 	case '&':
165 	case ')':
166 	case ',':
167 	case '/':
168 	case '^':
169 	case 'a':
170 	case 'd':
171 	case 'r':
172 	case 't':
173 	case 'u':
174 	case '{':
175 	case '|':
176 	case '}':
177 		return ESCAPE_IGNORE;
178 	case 'c':
179 		return ESCAPE_NOSPACE;
180 	case 'p':
181 		return ESCAPE_BREAK;
182 
183 	/*
184 	 * The \z escape is supposed to output the following
185 	 * character without advancing the cursor position.
186 	 * Since we are mostly dealing with terminal mode,
187 	 * let us just skip the next character.
188 	 */
189 	case 'z':
190 		return ESCAPE_SKIPCHAR;
191 
192 	/*
193 	 * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
194 	 * 'X' is the trigger.  These have opaque sub-strings.
195 	 */
196 	case 'F':
197 	case 'f':
198 	case 'g':
199 	case 'k':
200 	case 'M':
201 	case 'm':
202 	case 'n':
203 	case 'O':
204 	case 'V':
205 	case 'Y':
206 	case '*':
207 		switch ((*start)[-1]) {
208 		case 'f':
209 			gly = ESCAPE_FONT;
210 			break;
211 		case '*':
212 			gly = ESCAPE_DEVICE;
213 			break;
214 		default:
215 			gly = ESCAPE_IGNORE;
216 			break;
217 		}
218 		switch (**start) {
219 		case '(':
220 			if ((*start)[-1] == 'O')
221 				gly = ESCAPE_ERROR;
222 			*start = ++*end;
223 			*sz = 2;
224 			break;
225 		case '[':
226 			if ((*start)[-1] == 'O')
227 				gly = (*start)[1] == '5' ?
228 				    ESCAPE_UNSUPP : ESCAPE_ERROR;
229 			*start = ++*end;
230 			term = ']';
231 			break;
232 		default:
233 			if ((*start)[-1] == 'O') {
234 				switch (**start) {
235 				case '0':
236 					gly = ESCAPE_UNSUPP;
237 					break;
238 				case '1':
239 				case '2':
240 				case '3':
241 				case '4':
242 					break;
243 				default:
244 					gly = ESCAPE_ERROR;
245 					break;
246 				}
247 			}
248 			*sz = 1;
249 			break;
250 		}
251 		break;
252 
253 	/*
254 	 * These escapes are of the form \X'Y', where 'X' is the trigger
255 	 * and 'Y' is any string.  These have opaque sub-strings.
256 	 * The \B and \w escapes are handled in roff.c, roff_res().
257 	 */
258 	case 'A':
259 	case 'b':
260 	case 'D':
261 	case 'R':
262 	case 'X':
263 	case 'Z':
264 		gly = ESCAPE_IGNORE;
265 		/* FALLTHROUGH */
266 	case 'o':
267 		if (**start == '\0')
268 			return ESCAPE_ERROR;
269 		if (gly == ESCAPE_ERROR)
270 			gly = ESCAPE_OVERSTRIKE;
271 		term = **start;
272 		*start = ++*end;
273 		break;
274 
275 	/*
276 	 * These escapes are of the form \X'N', where 'X' is the trigger
277 	 * and 'N' resolves to a numerical expression.
278 	 */
279 	case 'h':
280 	case 'H':
281 	case 'L':
282 	case 'l':
283 	case 'S':
284 	case 'v':
285 	case 'x':
286 		if (strchr(" %&()*+-./0123456789:<=>", **start)) {
287 			if ('\0' != **start)
288 				++*end;
289 			return ESCAPE_ERROR;
290 		}
291 		switch ((*start)[-1]) {
292 		case 'h':
293 			gly = ESCAPE_HORIZ;
294 			break;
295 		case 'l':
296 			gly = ESCAPE_HLINE;
297 			break;
298 		default:
299 			gly = ESCAPE_IGNORE;
300 			break;
301 		}
302 		term = **start;
303 		*start = ++*end;
304 		break;
305 
306 	/*
307 	 * Special handling for the numbered character escape.
308 	 * XXX Do any other escapes need similar handling?
309 	 */
310 	case 'N':
311 		if ('\0' == **start)
312 			return ESCAPE_ERROR;
313 		(*end)++;
314 		if (isdigit((unsigned char)**start)) {
315 			*sz = 1;
316 			return ESCAPE_IGNORE;
317 		}
318 		(*start)++;
319 		while (isdigit((unsigned char)**end))
320 			(*end)++;
321 		*sz = *end - *start;
322 		if ('\0' != **end)
323 			(*end)++;
324 		return ESCAPE_NUMBERED;
325 
326 	/*
327 	 * Sizes get a special category of their own.
328 	 */
329 	case 's':
330 		gly = ESCAPE_IGNORE;
331 
332 		/* See +/- counts as a sign. */
333 		if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
334 			*start = ++*end;
335 
336 		switch (**end) {
337 		case '(':
338 			*start = ++*end;
339 			*sz = 2;
340 			break;
341 		case '[':
342 			*start = ++*end;
343 			term = ']';
344 			break;
345 		case '\'':
346 			*start = ++*end;
347 			term = '\'';
348 			break;
349 		case '3':
350 		case '2':
351 		case '1':
352 			*sz = (*end)[-1] == 's' &&
353 			    isdigit((unsigned char)(*end)[1]) ? 2 : 1;
354 			break;
355 		default:
356 			*sz = 1;
357 			break;
358 		}
359 
360 		break;
361 
362 	/*
363 	 * Several special characters can be encoded as
364 	 * one-byte escape sequences without using \[].
365 	 */
366 	case ' ':
367 	case '\'':
368 	case '-':
369 	case '.':
370 	case '0':
371 	case ':':
372 	case '_':
373 	case '`':
374 	case 'e':
375 	case '~':
376 		gly = ESCAPE_SPECIAL;
377 		/* FALLTHROUGH */
378 	default:
379 		if (gly == ESCAPE_ERROR)
380 			gly = ESCAPE_UNDEF;
381 		*start = --*end;
382 		*sz = 1;
383 		break;
384 	}
385 
386 	/*
387 	 * Read up to the terminating character,
388 	 * paying attention to nested escapes.
389 	 */
390 
391 	if ('\0' != term) {
392 		while (**end != term) {
393 			switch (**end) {
394 			case '\0':
395 				return ESCAPE_ERROR;
396 			case '\\':
397 				(*end)++;
398 				if (ESCAPE_ERROR ==
399 				    mandoc_escape(end, NULL, NULL))
400 					return ESCAPE_ERROR;
401 				break;
402 			default:
403 				(*end)++;
404 				break;
405 			}
406 		}
407 		*sz = (*end)++ - *start;
408 
409 		/*
410 		 * The file chars.c only provides one common list
411 		 * of character names, but \[-] == \- is the only
412 		 * one of the characters with one-byte names that
413 		 * allows enclosing the name in brackets.
414 		 */
415 		if (gly == ESCAPE_SPECIAL && *sz == 1 && **start != '-')
416 			return ESCAPE_ERROR;
417 	} else {
418 		assert(*sz > 0);
419 		if ((size_t)*sz > strlen(*start))
420 			return ESCAPE_ERROR;
421 		*end += *sz;
422 	}
423 
424 	/* Run post-processors. */
425 
426 	switch (gly) {
427 	case ESCAPE_FONT:
428 		gly = mandoc_font(*start, *sz);
429 		break;
430 	case ESCAPE_SPECIAL:
431 		if (**start == 'c') {
432 			if (*sz < 6 || *sz > 7 ||
433 			    strncmp(*start, "char", 4) != 0 ||
434 			    (int)strspn(*start + 4, "0123456789") + 4 < *sz)
435 				break;
436 			c = 0;
437 			for (i = 4; i < *sz; i++)
438 				c = 10 * c + ((*start)[i] - '0');
439 			if (c < 0x21 || (c > 0x7e && c < 0xa0) || c > 0xff)
440 				break;
441 			*start += 4;
442 			*sz -= 4;
443 			gly = ESCAPE_NUMBERED;
444 			break;
445 		}
446 
447 		/*
448 		 * Unicode escapes are defined in groff as \[u0000]
449 		 * to \[u10FFFF], where the contained value must be
450 		 * a valid Unicode codepoint.  Here, however, only
451 		 * check the length and range.
452 		 */
453 		if (**start != 'u' || *sz < 5 || *sz > 7)
454 			break;
455 		if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
456 			break;
457 		if (*sz == 6 && (*start)[1] == '0')
458 			break;
459 		if (*sz == 5 && (*start)[1] == 'D' &&
460 		    strchr("89ABCDEF", (*start)[2]) != NULL)
461 			break;
462 		if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
463 		    + 1 == *sz)
464 			gly = ESCAPE_UNICODE;
465 		break;
466 	case ESCAPE_DEVICE:
467 		assert(*sz == 2 && (*start)[0] == '.' && (*start)[1] == 'T');
468 		break;
469 	default:
470 		break;
471 	}
472 
473 	return gly;
474 }
475 
476 static int
477 a2time(time_t *t, const char *fmt, const char *p)
478 {
479 	struct tm	 tm;
480 	char		*pp;
481 
482 	memset(&tm, 0, sizeof(struct tm));
483 
484 	pp = NULL;
485 #if HAVE_STRPTIME
486 	pp = strptime(p, fmt, &tm);
487 #endif
488 	if (NULL != pp && '\0' == *pp) {
489 		*t = mktime(&tm);
490 		return 1;
491 	}
492 
493 	return 0;
494 }
495 
496 static char *
497 time2a(time_t t)
498 {
499 	struct tm	*tm;
500 	char		*buf, *p;
501 	size_t		 ssz;
502 	int		 isz;
503 
504 	buf = NULL;
505 	tm = localtime(&t);
506 	if (tm == NULL)
507 		goto fail;
508 
509 	/*
510 	 * Reserve space:
511 	 * up to 9 characters for the month (September) + blank
512 	 * up to 2 characters for the day + comma + blank
513 	 * 4 characters for the year and a terminating '\0'
514 	 */
515 
516 	p = buf = mandoc_malloc(10 + 4 + 4 + 1);
517 
518 	if ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
519 		goto fail;
520 	p += (int)ssz;
521 
522 	/*
523 	 * The output format is just "%d" here, not "%2d" or "%02d".
524 	 * That's also the reason why we can't just format the
525 	 * date as a whole with "%B %e, %Y" or "%B %d, %Y".
526 	 * Besides, the present approach is less prone to buffer
527 	 * overflows, in case anybody should ever introduce the bug
528 	 * of looking at LC_TIME.
529 	 */
530 
531 	isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday);
532 	if (isz < 0 || isz > 4)
533 		goto fail;
534 	p += isz;
535 
536 	if (strftime(p, 4 + 1, "%Y", tm) == 0)
537 		goto fail;
538 	return buf;
539 
540 fail:
541 	free(buf);
542 	return mandoc_strdup("");
543 }
544 
545 char *
546 mandoc_normdate(struct roff_node *nch, struct roff_node *nbl)
547 {
548 	char		*cp;
549 	time_t		 t;
550 
551 	/* No date specified. */
552 
553 	if (nch == NULL) {
554 		if (nbl == NULL)
555 			mandoc_msg(MANDOCERR_DATE_MISSING, 0, 0, NULL);
556 		else
557 			mandoc_msg(MANDOCERR_DATE_MISSING, nbl->line,
558 			    nbl->pos, "%s", roff_name[nbl->tok]);
559 		return mandoc_strdup("");
560 	}
561 	if (*nch->string == '\0') {
562 		mandoc_msg(MANDOCERR_DATE_MISSING, nch->line,
563 		    nch->pos, "%s", roff_name[nbl->tok]);
564 		return mandoc_strdup("");
565 	}
566 	if (strcmp(nch->string, "$" "Mdocdate$") == 0)
567 		return time2a(time(NULL));
568 
569 	/* Valid mdoc(7) date format. */
570 
571 	if (a2time(&t, "$" "Mdocdate: %b %d %Y $", nch->string) ||
572 	    a2time(&t, "%b %d, %Y", nch->string)) {
573 		cp = time2a(t);
574 		if (t > time(NULL) + 86400)
575 			mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line,
576 			    nch->pos, "%s %s", roff_name[nbl->tok], cp);
577 		else if (*nch->string != '$' &&
578 		    strcmp(nch->string, cp) != 0)
579 			mandoc_msg(MANDOCERR_DATE_NORM, nch->line,
580 			    nch->pos, "%s %s", roff_name[nbl->tok], cp);
581 		return cp;
582 	}
583 
584 	/* In man(7), do not warn about the legacy format. */
585 
586 	if (a2time(&t, "%Y-%m-%d", nch->string) == 0)
587 		mandoc_msg(MANDOCERR_DATE_BAD, nch->line, nch->pos,
588 		    "%s %s", roff_name[nbl->tok], nch->string);
589 	else if (t > time(NULL) + 86400)
590 		mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line, nch->pos,
591 		    "%s %s", roff_name[nbl->tok], nch->string);
592 	else if (nbl->tok == MDOC_Dd)
593 		mandoc_msg(MANDOCERR_DATE_LEGACY, nch->line, nch->pos,
594 		    "Dd %s", nch->string);
595 
596 	/* Use any non-mdoc(7) date verbatim. */
597 
598 	return mandoc_strdup(nch->string);
599 }
600 
601 int
602 mandoc_eos(const char *p, size_t sz)
603 {
604 	const char	*q;
605 	int		 enclosed, found;
606 
607 	if (0 == sz)
608 		return 0;
609 
610 	/*
611 	 * End-of-sentence recognition must include situations where
612 	 * some symbols, such as `)', allow prior EOS punctuation to
613 	 * propagate outward.
614 	 */
615 
616 	enclosed = found = 0;
617 	for (q = p + (int)sz - 1; q >= p; q--) {
618 		switch (*q) {
619 		case '\"':
620 		case '\'':
621 		case ']':
622 		case ')':
623 			if (0 == found)
624 				enclosed = 1;
625 			break;
626 		case '.':
627 		case '!':
628 		case '?':
629 			found = 1;
630 			break;
631 		default:
632 			return found &&
633 			    (!enclosed || isalnum((unsigned char)*q));
634 		}
635 	}
636 
637 	return found && !enclosed;
638 }
639 
640 /*
641  * Convert a string to a long that may not be <0.
642  * If the string is invalid, or is less than 0, return -1.
643  */
644 int
645 mandoc_strntoi(const char *p, size_t sz, int base)
646 {
647 	char		 buf[32];
648 	char		*ep;
649 	long		 v;
650 
651 	if (sz > 31)
652 		return -1;
653 
654 	memcpy(buf, p, sz);
655 	buf[(int)sz] = '\0';
656 
657 	errno = 0;
658 	v = strtol(buf, &ep, base);
659 
660 	if (buf[0] == '\0' || *ep != '\0')
661 		return -1;
662 
663 	if (v > INT_MAX)
664 		v = INT_MAX;
665 	if (v < INT_MIN)
666 		v = INT_MIN;
667 
668 	return (int)v;
669 }
670