]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mdocml/cgi.c
Copy elftoolchain readelf from vendor branch
[FreeBSD/FreeBSD.git] / contrib / mdocml / cgi.c
1 /*      $Id: cgi.c,v 1.102 2014/11/26 17:55:27 schwarze Exp $ */
2 /*
3  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
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 #include "config.h"
19
20 #include <sys/types.h>
21 #include <sys/time.h>
22
23 #include <ctype.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "mandoc.h"
34 #include "mandoc_aux.h"
35 #include "main.h"
36 #include "manpath.h"
37 #include "mansearch.h"
38 #include "cgi.h"
39
40 /*
41  * A query as passed to the search function.
42  */
43 struct  query {
44         char            *manpath; /* desired manual directory */
45         char            *arch; /* architecture */
46         char            *sec; /* manual section */
47         char            *query; /* unparsed query expression */
48         int              equal; /* match whole names, not substrings */
49 };
50
51 struct  req {
52         struct query      q;
53         char            **p; /* array of available manpaths */
54         size_t            psz; /* number of available manpaths */
55 };
56
57 static  void             catman(const struct req *, const char *);
58 static  void             format(const struct req *, const char *);
59 static  void             html_print(const char *);
60 static  void             html_putchar(char);
61 static  int              http_decode(char *);
62 static  void             http_parse(struct req *, const char *);
63 static  void             http_print(const char *);
64 static  void             http_putchar(char);
65 static  void             http_printquery(const struct req *, const char *);
66 static  void             pathgen(struct req *);
67 static  void             pg_error_badrequest(const char *);
68 static  void             pg_error_internal(void);
69 static  void             pg_index(const struct req *);
70 static  void             pg_noresult(const struct req *, const char *);
71 static  void             pg_search(const struct req *);
72 static  void             pg_searchres(const struct req *,
73                                 struct manpage *, size_t);
74 static  void             pg_show(struct req *, const char *);
75 static  void             resp_begin_html(int, const char *);
76 static  void             resp_begin_http(int, const char *);
77 static  void             resp_end_html(void);
78 static  void             resp_searchform(const struct req *);
79 static  void             resp_show(const struct req *, const char *);
80 static  void             set_query_attr(char **, char **);
81 static  int              validate_filename(const char *);
82 static  int              validate_manpath(const struct req *, const char *);
83 static  int              validate_urifrag(const char *);
84
85 static  const char       *scriptname; /* CGI script name */
86
87 static  const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
88 static  const char *const sec_numbers[] = {
89     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
90 };
91 static  const char *const sec_names[] = {
92     "All Sections",
93     "1 - General Commands",
94     "2 - System Calls",
95     "3 - Library Functions",
96     "3p - Perl Library",
97     "4 - Device Drivers",
98     "5 - File Formats",
99     "6 - Games",
100     "7 - Miscellaneous Information",
101     "8 - System Manager\'s Manual",
102     "9 - Kernel Developer\'s Manual"
103 };
104 static  const int sec_MAX = sizeof(sec_names) / sizeof(char *);
105
106 static  const char *const arch_names[] = {
107     "amd64",       "alpha",       "armish",      "armv7",
108     "aviion",      "hppa",        "hppa64",      "i386",
109     "ia64",        "landisk",     "loongson",    "luna88k",
110     "macppc",      "mips64",      "octeon",      "sgi",
111     "socppc",      "solbourne",   "sparc",       "sparc64",
112     "vax",         "zaurus",
113     "amiga",       "arc",         "arm32",       "atari",
114     "beagle",      "cats",        "hp300",       "mac68k",
115     "mvme68k",     "mvme88k",     "mvmeppc",     "palm",
116     "pc532",       "pegasos",     "pmax",        "powerpc",
117     "sun3",        "wgrisc",      "x68k"
118 };
119 static  const int arch_MAX = sizeof(arch_names) / sizeof(char *);
120
121 /*
122  * Print a character, escaping HTML along the way.
123  * This will pass non-ASCII straight to output: be warned!
124  */
125 static void
126 html_putchar(char c)
127 {
128
129         switch (c) {
130         case ('"'):
131                 printf("&quote;");
132                 break;
133         case ('&'):
134                 printf("&amp;");
135                 break;
136         case ('>'):
137                 printf("&gt;");
138                 break;
139         case ('<'):
140                 printf("&lt;");
141                 break;
142         default:
143                 putchar((unsigned char)c);
144                 break;
145         }
146 }
147
148 static void
149 http_printquery(const struct req *req, const char *sep)
150 {
151
152         if (NULL != req->q.query) {
153                 printf("query=");
154                 http_print(req->q.query);
155         }
156         if (0 == req->q.equal)
157                 printf("%sapropos=1", sep);
158         if (NULL != req->q.sec) {
159                 printf("%ssec=", sep);
160                 http_print(req->q.sec);
161         }
162         if (NULL != req->q.arch) {
163                 printf("%sarch=", sep);
164                 http_print(req->q.arch);
165         }
166         if (strcmp(req->q.manpath, req->p[0])) {
167                 printf("%smanpath=", sep);
168                 http_print(req->q.manpath);
169         }
170 }
171
172 static void
173 http_print(const char *p)
174 {
175
176         if (NULL == p)
177                 return;
178         while ('\0' != *p)
179                 http_putchar(*p++);
180 }
181
182 /*
183  * Call through to html_putchar().
184  * Accepts NULL strings.
185  */
186 static void
187 html_print(const char *p)
188 {
189         
190         if (NULL == p)
191                 return;
192         while ('\0' != *p)
193                 html_putchar(*p++);
194 }
195
196 /*
197  * Transfer the responsibility for the allocated string *val
198  * to the query structure.
199  */
200 static void
201 set_query_attr(char **attr, char **val)
202 {
203
204         free(*attr);
205         if (**val == '\0') {
206                 *attr = NULL;
207                 free(*val);
208         } else
209                 *attr = *val;
210         *val = NULL;
211 }
212
213 /*
214  * Parse the QUERY_STRING for key-value pairs
215  * and store the values into the query structure.
216  */
217 static void
218 http_parse(struct req *req, const char *qs)
219 {
220         char            *key, *val;
221         size_t           keysz, valsz;
222
223         req->q.manpath  = NULL;
224         req->q.arch     = NULL;
225         req->q.sec      = NULL;
226         req->q.query    = NULL;
227         req->q.equal    = 1;
228
229         key = val = NULL;
230         while (*qs != '\0') {
231
232                 /* Parse one key. */
233
234                 keysz = strcspn(qs, "=;&");
235                 key = mandoc_strndup(qs, keysz);
236                 qs += keysz;
237                 if (*qs != '=')
238                         goto next;
239
240                 /* Parse one value. */
241
242                 valsz = strcspn(++qs, ";&");
243                 val = mandoc_strndup(qs, valsz);
244                 qs += valsz;
245
246                 /* Decode and catch encoding errors. */
247
248                 if ( ! (http_decode(key) && http_decode(val)))
249                         goto next;
250
251                 /* Handle key-value pairs. */
252
253                 if ( ! strcmp(key, "query"))
254                         set_query_attr(&req->q.query, &val);
255
256                 else if ( ! strcmp(key, "apropos"))
257                         req->q.equal = !strcmp(val, "0");
258
259                 else if ( ! strcmp(key, "manpath")) {
260 #ifdef COMPAT_OLDURI
261                         if ( ! strncmp(val, "OpenBSD ", 8)) {
262                                 val[7] = '-';
263                                 if ('C' == val[8])
264                                         val[8] = 'c';
265                         }
266 #endif
267                         set_query_attr(&req->q.manpath, &val);
268                 }
269
270                 else if ( ! (strcmp(key, "sec")
271 #ifdef COMPAT_OLDURI
272                     && strcmp(key, "sektion")
273 #endif
274                     )) {
275                         if ( ! strcmp(val, "0"))
276                                 *val = '\0';
277                         set_query_attr(&req->q.sec, &val);
278                 }
279
280                 else if ( ! strcmp(key, "arch")) {
281                         if ( ! strcmp(val, "default"))
282                                 *val = '\0';
283                         set_query_attr(&req->q.arch, &val);
284                 }
285
286                 /*
287                  * The key must be freed in any case.
288                  * The val may have been handed over to the query
289                  * structure, in which case it is now NULL.
290                  */
291 next:
292                 free(key);
293                 key = NULL;
294                 free(val);
295                 val = NULL;
296
297                 if (*qs != '\0')
298                         qs++;
299         }
300 }
301
302 static void
303 http_putchar(char c)
304 {
305
306         if (isalnum((unsigned char)c)) {
307                 putchar((unsigned char)c);
308                 return;
309         } else if (' ' == c) {
310                 putchar('+');
311                 return;
312         }
313         printf("%%%.2x", c);
314 }
315
316 /*
317  * HTTP-decode a string.  The standard explanation is that this turns
318  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
319  * over the allocated string.
320  */
321 static int
322 http_decode(char *p)
323 {
324         char             hex[3];
325         char            *q;
326         int              c;
327
328         hex[2] = '\0';
329
330         q = p;
331         for ( ; '\0' != *p; p++, q++) {
332                 if ('%' == *p) {
333                         if ('\0' == (hex[0] = *(p + 1)))
334                                 return(0);
335                         if ('\0' == (hex[1] = *(p + 2)))
336                                 return(0);
337                         if (1 != sscanf(hex, "%x", &c))
338                                 return(0);
339                         if ('\0' == c)
340                                 return(0);
341
342                         *q = (char)c;
343                         p += 2;
344                 } else
345                         *q = '+' == *p ? ' ' : *p;
346         }
347
348         *q = '\0';
349         return(1);
350 }
351
352 static void
353 resp_begin_http(int code, const char *msg)
354 {
355
356         if (200 != code)
357                 printf("Status: %d %s\r\n", code, msg);
358
359         printf("Content-Type: text/html; charset=utf-8\r\n"
360              "Cache-Control: no-cache\r\n"
361              "Pragma: no-cache\r\n"
362              "\r\n");
363
364         fflush(stdout);
365 }
366
367 static void
368 resp_begin_html(int code, const char *msg)
369 {
370
371         resp_begin_http(code, msg);
372
373         printf("<!DOCTYPE html>\n"
374                "<HTML>\n"
375                "<HEAD>\n"
376                "<META CHARSET=\"UTF-8\" />\n"
377                "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
378                " TYPE=\"text/css\" media=\"all\">\n"
379                "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
380                " TYPE=\"text/css\" media=\"all\">\n"
381                "<TITLE>%s</TITLE>\n"
382                "</HEAD>\n"
383                "<BODY>\n"
384                "<!-- Begin page content. //-->\n",
385                CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
386 }
387
388 static void
389 resp_end_html(void)
390 {
391
392         puts("</BODY>\n"
393              "</HTML>");
394 }
395
396 static void
397 resp_searchform(const struct req *req)
398 {
399         int              i;
400
401         puts(CUSTOMIZE_BEGIN);
402         puts("<!-- Begin search form. //-->");
403         printf("<DIV ID=\"mancgi\">\n"
404                "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
405                "<FIELDSET>\n"
406                "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
407                scriptname);
408
409         /* Write query input box. */
410
411         printf( "<TABLE><TR><TD>\n"
412                 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
413         if (NULL != req->q.query)
414                 html_print(req->q.query);
415         puts("\" SIZE=\"40\">");
416
417         /* Write submission and reset buttons. */
418
419         printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
420                 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
421
422         /* Write show radio button */
423
424         printf( "</TD><TD>\n"
425                 "<INPUT TYPE=\"radio\" ");
426         if (req->q.equal)
427                 printf("CHECKED=\"checked\" ");
428         printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
429                 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
430
431         /* Write section selector. */
432
433         puts(   "</TD></TR><TR><TD>\n"
434                 "<SELECT NAME=\"sec\">");
435         for (i = 0; i < sec_MAX; i++) {
436                 printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
437                 if (NULL != req->q.sec &&
438                     0 == strcmp(sec_numbers[i], req->q.sec))
439                         printf(" SELECTED=\"selected\"");
440                 printf(">%s</OPTION>\n", sec_names[i]);
441         }
442         puts("</SELECT>");
443
444         /* Write architecture selector. */
445
446         printf( "<SELECT NAME=\"arch\">\n"
447                 "<OPTION VALUE=\"default\"");
448         if (NULL == req->q.arch)
449                 printf(" SELECTED=\"selected\"");
450         puts(">All Architectures</OPTION>");
451         for (i = 0; i < arch_MAX; i++) {
452                 printf("<OPTION VALUE=\"%s\"", arch_names[i]);
453                 if (NULL != req->q.arch &&
454                     0 == strcmp(arch_names[i], req->q.arch))
455                         printf(" SELECTED=\"selected\"");
456                 printf(">%s</OPTION>\n", arch_names[i]);
457         }
458         puts("</SELECT>");
459
460         /* Write manpath selector. */
461
462         if (req->psz > 1) {
463                 puts("<SELECT NAME=\"manpath\">");
464                 for (i = 0; i < (int)req->psz; i++) {
465                         printf("<OPTION ");
466                         if (strcmp(req->q.manpath, req->p[i]) == 0)
467                                 printf("SELECTED=\"selected\" ");
468                         printf("VALUE=\"");
469                         html_print(req->p[i]);
470                         printf("\">");
471                         html_print(req->p[i]);
472                         puts("</OPTION>");
473                 }
474                 puts("</SELECT>");
475         }
476
477         /* Write search radio button */
478
479         printf( "</TD><TD>\n"
480                 "<INPUT TYPE=\"radio\" ");
481         if (0 == req->q.equal)
482                 printf("CHECKED=\"checked\" ");
483         printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
484                 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
485
486         puts("</TD></TR></TABLE>\n"
487              "</FIELDSET>\n"
488              "</FORM>\n"
489              "</DIV>");
490         puts("<!-- End search form. //-->");
491 }
492
493 static int
494 validate_urifrag(const char *frag)
495 {
496
497         while ('\0' != *frag) {
498                 if ( ! (isalnum((unsigned char)*frag) ||
499                     '-' == *frag || '.' == *frag ||
500                     '/' == *frag || '_' == *frag))
501                         return(0);
502                 frag++;
503         }
504         return(1);
505 }
506
507 static int
508 validate_manpath(const struct req *req, const char* manpath)
509 {
510         size_t   i;
511
512         if ( ! strcmp(manpath, "mandoc"))
513                 return(1);
514
515         for (i = 0; i < req->psz; i++)
516                 if ( ! strcmp(manpath, req->p[i]))
517                         return(1);
518
519         return(0);
520 }
521
522 static int
523 validate_filename(const char *file)
524 {
525
526         if ('.' == file[0] && '/' == file[1])
527                 file += 2;
528
529         return ( ! (strstr(file, "../") || strstr(file, "/..") ||
530             (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
531 }
532
533 static void
534 pg_index(const struct req *req)
535 {
536
537         resp_begin_html(200, NULL);
538         resp_searchform(req);
539         printf("<P>\n"
540                "This web interface is documented in the\n"
541                "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A>\n"
542                "manual, and the\n"
543                "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A>\n"
544                "manual explains the query syntax.\n"
545                "</P>\n",
546                scriptname, scriptname);
547         resp_end_html();
548 }
549
550 static void
551 pg_noresult(const struct req *req, const char *msg)
552 {
553         resp_begin_html(200, NULL);
554         resp_searchform(req);
555         puts("<P>");
556         puts(msg);
557         puts("</P>");
558         resp_end_html();
559 }
560
561 static void
562 pg_error_badrequest(const char *msg)
563 {
564
565         resp_begin_html(400, "Bad Request");
566         puts("<H1>Bad Request</H1>\n"
567              "<P>\n");
568         puts(msg);
569         printf("Try again from the\n"
570                "<A HREF=\"%s\">main page</A>.\n"
571                "</P>", scriptname);
572         resp_end_html();
573 }
574
575 static void
576 pg_error_internal(void)
577 {
578         resp_begin_html(500, "Internal Server Error");
579         puts("<P>Internal Server Error</P>");
580         resp_end_html();
581 }
582
583 static void
584 pg_searchres(const struct req *req, struct manpage *r, size_t sz)
585 {
586         char            *arch, *archend;
587         size_t           i, iuse, isec;
588         int              archprio, archpriouse;
589         int              prio, priouse;
590         char             sec;
591
592         for (i = 0; i < sz; i++) {
593                 if (validate_filename(r[i].file))
594                         continue;
595                 fprintf(stderr, "invalid filename %s in %s database\n",
596                     r[i].file, req->q.manpath);
597                 pg_error_internal();
598                 return;
599         }
600
601         if (1 == sz) {
602                 /*
603                  * If we have just one result, then jump there now
604                  * without any delay.
605                  */
606                 printf("Status: 303 See Other\r\n");
607                 printf("Location: http://%s%s/%s/%s?",
608                     HTTP_HOST, scriptname, req->q.manpath, r[0].file);
609                 http_printquery(req, "&");
610                 printf("\r\n"
611                      "Content-Type: text/html; charset=utf-8\r\n"
612                      "\r\n");
613                 return;
614         }
615
616         resp_begin_html(200, NULL);
617         resp_searchform(req);
618         puts("<DIV CLASS=\"results\">");
619         puts("<TABLE>");
620
621         for (i = 0; i < sz; i++) {
622                 printf("<TR>\n"
623                        "<TD CLASS=\"title\">\n"
624                        "<A HREF=\"%s/%s/%s?", 
625                     scriptname, req->q.manpath, r[i].file);
626                 http_printquery(req, "&amp;");
627                 printf("\">");
628                 html_print(r[i].names);
629                 printf("</A>\n"
630                        "</TD>\n"
631                        "<TD CLASS=\"desc\">");
632                 html_print(r[i].output);
633                 puts("</TD>\n"
634                      "</TR>");
635         }
636
637         puts("</TABLE>\n"
638              "</DIV>");
639
640         /*
641          * In man(1) mode, show one of the pages
642          * even if more than one is found.
643          */
644
645         if (req->q.equal) {
646                 puts("<HR>");
647                 iuse = 0;
648                 priouse = 10;
649                 archpriouse = 3;
650                 for (i = 0; i < sz; i++) {
651                         isec = strcspn(r[i].file, "123456789");
652                         sec = r[i].file[isec];
653                         if ('\0' == sec)
654                                 continue;
655                         prio = sec_prios[sec - '1'];
656                         if (NULL == req->q.arch) {
657                                 archprio =
658                                     (NULL == (arch = strchr(
659                                         r[i].file + isec, '/'))) ? 3 :
660                                     (NULL == (archend = strchr(
661                                         arch + 1, '/'))) ? 0 :
662                                     strncmp(arch, "amd64/",
663                                         archend - arch) ? 2 : 1;
664                                 if (archprio < archpriouse) {
665                                         archpriouse = archprio;
666                                         priouse = prio;
667                                         iuse = i;
668                                         continue;
669                                 }
670                                 if (archprio > archpriouse)
671                                         continue;
672                         }
673                         if (prio >= priouse)
674                                 continue;
675                         priouse = prio;
676                         iuse = i;
677                 }
678                 resp_show(req, r[iuse].file);
679         }
680
681         resp_end_html();
682 }
683
684 static void
685 catman(const struct req *req, const char *file)
686 {
687         FILE            *f;
688         size_t           len;
689         int              i;
690         char            *p;
691         int              italic, bold;
692
693         if (NULL == (f = fopen(file, "r"))) {
694                 puts("<P>You specified an invalid manual file.</P>");
695                 return;
696         }
697
698         puts("<DIV CLASS=\"catman\">\n"
699              "<PRE>");
700
701         while (NULL != (p = fgetln(f, &len))) {
702                 bold = italic = 0;
703                 for (i = 0; i < (int)len - 1; i++) {
704                         /* 
705                          * This means that the catpage is out of state.
706                          * Ignore it and keep going (although the
707                          * catpage is bogus).
708                          */
709
710                         if ('\b' == p[i] || '\n' == p[i])
711                                 continue;
712
713                         /*
714                          * Print a regular character.
715                          * Close out any bold/italic scopes.
716                          * If we're in back-space mode, make sure we'll
717                          * have something to enter when we backspace.
718                          */
719
720                         if ('\b' != p[i + 1]) {
721                                 if (italic)
722                                         printf("</I>");
723                                 if (bold)
724                                         printf("</B>");
725                                 italic = bold = 0;
726                                 html_putchar(p[i]);
727                                 continue;
728                         } else if (i + 2 >= (int)len)
729                                 continue;
730
731                         /* Italic mode. */
732
733                         if ('_' == p[i]) {
734                                 if (bold)
735                                         printf("</B>");
736                                 if ( ! italic)
737                                         printf("<I>");
738                                 bold = 0;
739                                 italic = 1;
740                                 i += 2;
741                                 html_putchar(p[i]);
742                                 continue;
743                         }
744
745                         /* 
746                          * Handle funny behaviour troff-isms.
747                          * These grok'd from the original man2html.c.
748                          */
749
750                         if (('+' == p[i] && 'o' == p[i + 2]) ||
751                                         ('o' == p[i] && '+' == p[i + 2]) ||
752                                         ('|' == p[i] && '=' == p[i + 2]) ||
753                                         ('=' == p[i] && '|' == p[i + 2]) ||
754                                         ('*' == p[i] && '=' == p[i + 2]) ||
755                                         ('=' == p[i] && '*' == p[i + 2]) ||
756                                         ('*' == p[i] && '|' == p[i + 2]) ||
757                                         ('|' == p[i] && '*' == p[i + 2]))  {
758                                 if (italic)
759                                         printf("</I>");
760                                 if (bold)
761                                         printf("</B>");
762                                 italic = bold = 0;
763                                 putchar('*');
764                                 i += 2;
765                                 continue;
766                         } else if (('|' == p[i] && '-' == p[i + 2]) ||
767                                         ('-' == p[i] && '|' == p[i + 1]) ||
768                                         ('+' == p[i] && '-' == p[i + 1]) ||
769                                         ('-' == p[i] && '+' == p[i + 1]) ||
770                                         ('+' == p[i] && '|' == p[i + 1]) ||
771                                         ('|' == p[i] && '+' == p[i + 1]))  {
772                                 if (italic)
773                                         printf("</I>");
774                                 if (bold)
775                                         printf("</B>");
776                                 italic = bold = 0;
777                                 putchar('+');
778                                 i += 2;
779                                 continue;
780                         }
781
782                         /* Bold mode. */
783                         
784                         if (italic)
785                                 printf("</I>");
786                         if ( ! bold)
787                                 printf("<B>");
788                         bold = 1;
789                         italic = 0;
790                         i += 2;
791                         html_putchar(p[i]);
792                 }
793
794                 /* 
795                  * Clean up the last character.
796                  * We can get to a newline; don't print that. 
797                  */
798
799                 if (italic)
800                         printf("</I>");
801                 if (bold)
802                         printf("</B>");
803
804                 if (i == (int)len - 1 && '\n' != p[i])
805                         html_putchar(p[i]);
806
807                 putchar('\n');
808         }
809
810         puts("</PRE>\n"
811              "</DIV>");
812
813         fclose(f);
814 }
815
816 static void
817 format(const struct req *req, const char *file)
818 {
819         struct mparse   *mp;
820         struct mchars   *mchars;
821         struct mdoc     *mdoc;
822         struct man      *man;
823         void            *vp;
824         char            *opts;
825         enum mandoclevel rc;
826         int              fd;
827         int              usepath;
828
829         if (-1 == (fd = open(file, O_RDONLY, 0))) {
830                 puts("<P>You specified an invalid manual file.</P>");
831                 return;
832         }
833
834         mchars = mchars_alloc();
835         mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
836             mchars, req->q.manpath);
837         rc = mparse_readfd(mp, fd, file);
838         close(fd);
839
840         if (rc >= MANDOCLEVEL_FATAL) {
841                 fprintf(stderr, "fatal mandoc error: %s/%s\n",
842                     req->q.manpath, file);
843                 pg_error_internal();
844                 return;
845         }
846
847         usepath = strcmp(req->q.manpath, req->p[0]);
848         mandoc_asprintf(&opts,
849             "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s",
850             scriptname,
851             req->q.arch ? "&arch="       : "",
852             req->q.arch ? req->q.arch    : "",
853             usepath     ? "&manpath="    : "",
854             usepath     ? req->q.manpath : "");
855
856         mparse_result(mp, &mdoc, &man, NULL);
857         if (NULL == man && NULL == mdoc) {
858                 fprintf(stderr, "fatal mandoc error: %s/%s\n",
859                     req->q.manpath, file);
860                 pg_error_internal();
861                 mparse_free(mp);
862                 mchars_free(mchars);
863                 return;
864         }
865
866         vp = html_alloc(mchars, opts);
867
868         if (NULL != mdoc)
869                 html_mdoc(vp, mdoc);
870         else
871                 html_man(vp, man);
872
873         html_free(vp);
874         mparse_free(mp);
875         mchars_free(mchars);
876         free(opts);
877 }
878
879 static void
880 resp_show(const struct req *req, const char *file)
881 {
882
883         if ('.' == file[0] && '/' == file[1])
884                 file += 2;
885
886         if ('c' == *file)
887                 catman(req, file);
888         else
889                 format(req, file);
890 }
891
892 static void
893 pg_show(struct req *req, const char *fullpath)
894 {
895         char            *manpath;
896         const char      *file;
897
898         if ((file = strchr(fullpath, '/')) == NULL) {
899                 pg_error_badrequest(
900                     "You did not specify a page to show.");
901                 return;
902         } 
903         manpath = mandoc_strndup(fullpath, file - fullpath);
904         file++;
905
906         if ( ! validate_manpath(req, manpath)) {
907                 pg_error_badrequest(
908                     "You specified an invalid manpath.");
909                 free(manpath);
910                 return;
911         }
912
913         /*
914          * Begin by chdir()ing into the manpath.
915          * This way we can pick up the database files, which are
916          * relative to the manpath root.
917          */
918
919         if (chdir(manpath) == -1) {
920                 fprintf(stderr, "chdir %s: %s\n",
921                     manpath, strerror(errno));
922                 pg_error_internal();
923                 free(manpath);
924                 return;
925         }
926
927         if (strcmp(manpath, "mandoc")) {
928                 free(req->q.manpath);
929                 req->q.manpath = manpath;
930         } else
931                 free(manpath);
932
933         if ( ! validate_filename(file)) {
934                 pg_error_badrequest(
935                     "You specified an invalid manual file.");
936                 return;
937         }
938
939         resp_begin_html(200, NULL);
940         resp_searchform(req);
941         resp_show(req, file);
942         resp_end_html();
943 }
944
945 static void
946 pg_search(const struct req *req)
947 {
948         struct mansearch          search;
949         struct manpaths           paths;
950         struct manpage           *res;
951         char                    **argv;
952         char                     *query, *rp, *wp;
953         size_t                    ressz;
954         int                       argc;
955
956         /*
957          * Begin by chdir()ing into the root of the manpath.
958          * This way we can pick up the database files, which are
959          * relative to the manpath root.
960          */
961
962         if (-1 == (chdir(req->q.manpath))) {
963                 fprintf(stderr, "chdir %s: %s\n",
964                     req->q.manpath, strerror(errno));
965                 pg_error_internal();
966                 return;
967         }
968
969         search.arch = req->q.arch;
970         search.sec = req->q.sec;
971         search.outkey = "Nd";
972         search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
973         search.firstmatch = 1;
974
975         paths.sz = 1;
976         paths.paths = mandoc_malloc(sizeof(char *));
977         paths.paths[0] = mandoc_strdup(".");
978
979         /*
980          * Break apart at spaces with backslash-escaping.
981          */
982
983         argc = 0;
984         argv = NULL;
985         rp = query = mandoc_strdup(req->q.query);
986         for (;;) {
987                 while (isspace((unsigned char)*rp))
988                         rp++;
989                 if (*rp == '\0')
990                         break;
991                 argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
992                 argv[argc++] = wp = rp;
993                 for (;;) {
994                         if (isspace((unsigned char)*rp)) {
995                                 *wp = '\0';
996                                 rp++;
997                                 break;
998                         }
999                         if (rp[0] == '\\' && rp[1] != '\0')
1000                                 rp++;
1001                         if (wp != rp)
1002                                 *wp = *rp;
1003                         if (*rp == '\0')
1004                                 break;
1005                         wp++;
1006                         rp++;
1007                 }
1008         }
1009
1010         if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
1011                 pg_noresult(req, "You entered an invalid query.");
1012         else if (0 == ressz)
1013                 pg_noresult(req, "No results found.");
1014         else
1015                 pg_searchres(req, res, ressz);
1016
1017         free(query);
1018         mansearch_free(res, ressz);
1019         free(paths.paths[0]);
1020         free(paths.paths);
1021 }
1022
1023 int
1024 main(void)
1025 {
1026         struct req       req;
1027         struct itimerval itimer;
1028         const char      *path;
1029         const char      *querystring;
1030         int              i;
1031
1032         /* Poor man's ReDoS mitigation. */
1033
1034         itimer.it_value.tv_sec = 2;
1035         itimer.it_value.tv_usec = 0;
1036         itimer.it_interval.tv_sec = 2;
1037         itimer.it_interval.tv_usec = 0;
1038         if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1039                 fprintf(stderr, "setitimer: %s\n", strerror(errno));
1040                 pg_error_internal();
1041                 return(EXIT_FAILURE);
1042         }
1043
1044         /* Scan our run-time environment. */
1045
1046         if (NULL == (scriptname = getenv("SCRIPT_NAME")))
1047                 scriptname = "";
1048
1049         if ( ! validate_urifrag(scriptname)) {
1050                 fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
1051                     scriptname);
1052                 pg_error_internal();
1053                 return(EXIT_FAILURE);
1054         }
1055
1056         /*
1057          * First we change directory into the MAN_DIR so that
1058          * subsequent scanning for manpath directories is rooted
1059          * relative to the same position.
1060          */
1061
1062         if (-1 == chdir(MAN_DIR)) {
1063                 fprintf(stderr, "MAN_DIR: %s: %s\n",
1064                     MAN_DIR, strerror(errno));
1065                 pg_error_internal();
1066                 return(EXIT_FAILURE);
1067         } 
1068
1069         memset(&req, 0, sizeof(struct req));
1070         pathgen(&req);
1071
1072         /* Next parse out the query string. */
1073
1074         if (NULL != (querystring = getenv("QUERY_STRING")))
1075                 http_parse(&req, querystring);
1076
1077         if (req.q.manpath == NULL)
1078                 req.q.manpath = mandoc_strdup(req.p[0]);
1079         else if ( ! validate_manpath(&req, req.q.manpath)) {
1080                 pg_error_badrequest(
1081                     "You specified an invalid manpath.");
1082                 return(EXIT_FAILURE);
1083         }
1084
1085         if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1086                 pg_error_badrequest(
1087                     "You specified an invalid architecture.");
1088                 return(EXIT_FAILURE);
1089         }
1090
1091         /* Dispatch to the three different pages. */
1092
1093         path = getenv("PATH_INFO");
1094         if (NULL == path)
1095                 path = "";
1096         else if ('/' == *path)
1097                 path++;
1098
1099         if ('\0' != *path)
1100                 pg_show(&req, path);
1101         else if (NULL != req.q.query)
1102                 pg_search(&req);
1103         else
1104                 pg_index(&req);
1105
1106         free(req.q.manpath);
1107         free(req.q.arch);
1108         free(req.q.sec);
1109         free(req.q.query);
1110         for (i = 0; i < (int)req.psz; i++)
1111                 free(req.p[i]);
1112         free(req.p);
1113         return(EXIT_SUCCESS);
1114 }
1115
1116 /*
1117  * Scan for indexable paths.
1118  */
1119 static void
1120 pathgen(struct req *req)
1121 {
1122         FILE    *fp;
1123         char    *dp;
1124         size_t   dpsz;
1125
1126         if (NULL == (fp = fopen("manpath.conf", "r"))) {
1127                 fprintf(stderr, "%s/manpath.conf: %s\n",
1128                         MAN_DIR, strerror(errno));
1129                 pg_error_internal();
1130                 exit(EXIT_FAILURE);
1131         }
1132
1133         while (NULL != (dp = fgetln(fp, &dpsz))) {
1134                 if ('\n' == dp[dpsz - 1])
1135                         dpsz--;
1136                 req->p = mandoc_realloc(req->p,
1137                     (req->psz + 1) * sizeof(char *));
1138                 dp = mandoc_strndup(dp, dpsz);
1139                 if ( ! validate_urifrag(dp)) {
1140                         fprintf(stderr, "%s/manpath.conf contains "
1141                             "unsafe path \"%s\"\n", MAN_DIR, dp);
1142                         pg_error_internal();
1143                         exit(EXIT_FAILURE);
1144                 }
1145                 if (NULL != strchr(dp, '/')) {
1146                         fprintf(stderr, "%s/manpath.conf contains "
1147                             "path with slash \"%s\"\n", MAN_DIR, dp);
1148                         pg_error_internal();
1149                         exit(EXIT_FAILURE);
1150                 }
1151                 req->p[req->psz++] = dp;
1152         }
1153
1154         if ( req->p == NULL ) {
1155                 fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1156                 pg_error_internal();
1157                 exit(EXIT_FAILURE);
1158         }
1159 }