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