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 
  41 typedef void    (*v_check)(CHKARGS);
  42 
  43 static  void      check_par(CHKARGS);
  44 static  void      check_part(CHKARGS);
  45 static  void      check_root(CHKARGS);
  46 static  void      check_text(CHKARGS);
  47 
  48 static  void      post_AT(CHKARGS);
  49 static  void      post_IP(CHKARGS);
  50 static  void      post_OP(CHKARGS);
  51 static  void      post_TH(CHKARGS);
  52 static  void      post_UC(CHKARGS);
  53 static  void      post_UR(CHKARGS);
  54 static  void      post_in(CHKARGS);
  55 static  void      post_vs(CHKARGS);
  56 
  57 static  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 };
  95 static  const v_check *man_valids = __man_valids - MAN_TH;
  96 
  97 
  98 void
  99 man_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 
 149 static void
 150 check_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 
 183 static void
 184 check_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 
 197 static void
 198 post_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 
 211 static void
 212 post_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 
 220 static void
 221 check_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 
 229 static void
 230 check_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 
 256 static void
 257 post_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 
 276 static void
 277 post_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 
 375 static void
 376 post_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 
 412 static void
 413 post_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 
 451 static void
 452 post_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 
 467 static void
 468 post_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 }