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