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