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