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