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