]> git.cameronkatri.com Git - mandoc.git/blob - cgi.c
Tweak to make man.cgi's search results validate properly.
[mandoc.git] / cgi.c
1 /* $Id: cgi.c,v 1.16 2011/12/07 16:18:52 kristaps Exp $ */
2 /*
3 * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/param.h>
22 #include <sys/wait.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <limits.h>
29 #include <regex.h>
30 #include <stdio.h>
31 #include <stdarg.h>
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36
37 #include "apropos_db.h"
38 #include "mandoc.h"
39 #include "mdoc.h"
40 #include "man.h"
41 #include "main.h"
42 #include "manpath.h"
43
44 #ifdef __linux__
45 # include <db_185.h>
46 #else
47 # include <db.h>
48 #endif
49
50 enum page {
51 PAGE_INDEX,
52 PAGE_SEARCH,
53 PAGE_SHOW,
54 PAGE__MAX
55 };
56
57 struct kval {
58 char *key;
59 char *val;
60 };
61
62 struct req {
63 struct kval *fields;
64 size_t fieldsz;
65 enum page page;
66 };
67
68 static int atou(const char *, unsigned *);
69 static void catman(const char *);
70 static int cmp(const void *, const void *);
71 static void format(const char *);
72 static void html_print(const char *);
73 static void html_putchar(char);
74 static int kval_decode(char *);
75 static void kval_parse(struct kval **, size_t *, char *);
76 static void kval_free(struct kval *, size_t);
77 static void pg_index(const struct manpaths *,
78 const struct req *, char *);
79 static void pg_search(const struct manpaths *,
80 const struct req *, char *);
81 static void pg_show(const struct manpaths *,
82 const struct req *, char *);
83 static void resp_bad(void);
84 static void resp_baddb(void);
85 static void resp_error400(void);
86 static void resp_error404(const char *);
87 static void resp_begin_html(int, const char *);
88 static void resp_begin_http(int, const char *);
89 static void resp_end_html(void);
90 static void resp_index(const struct req *);
91 static void resp_search(struct res *, size_t, void *);
92 static void resp_searchform(const struct req *);
93
94 static const char *progname;
95 static const char *cache;
96 static const char *host;
97
98 static const char * const pages[PAGE__MAX] = {
99 "index", /* PAGE_INDEX */
100 "search", /* PAGE_SEARCH */
101 "show", /* PAGE_SHOW */
102 };
103
104 /*
105 * This is just OpenBSD's strtol(3) suggestion.
106 * I use it instead of strtonum(3) for portability's sake.
107 */
108 static int
109 atou(const char *buf, unsigned *v)
110 {
111 char *ep;
112 long lval;
113
114 errno = 0;
115 lval = strtol(buf, &ep, 10);
116 if (buf[0] == '\0' || *ep != '\0')
117 return(0);
118 if ((errno == ERANGE && (lval == LONG_MAX ||
119 lval == LONG_MIN)) ||
120 (lval > UINT_MAX || lval < 0))
121 return(0);
122
123 *v = (unsigned int)lval;
124 return(1);
125 }
126
127 static void
128 html_putchar(char c)
129 {
130
131 switch (c) {
132 case ('"'):
133 printf("&quote;");
134 break;
135 case ('&'):
136 printf("&amp;");
137 break;
138 case ('>'):
139 printf("&gt;");
140 break;
141 case ('<'):
142 printf("&lt;");
143 break;
144 default:
145 putchar((unsigned char)c);
146 break;
147 }
148 }
149
150 /*
151 * Print a word, escaping HTML along the way.
152 * This will pass non-ASCII straight to output: be warned!
153 */
154 static void
155 html_print(const char *p)
156 {
157
158 if (NULL == p)
159 return;
160 while ('\0' != *p)
161 html_putchar(*p++);
162 }
163
164 static void
165 kval_free(struct kval *p, size_t sz)
166 {
167 int i;
168
169 for (i = 0; i < (int)sz; i++) {
170 free(p[i].key);
171 free(p[i].val);
172 }
173 free(p);
174 }
175
176 /*
177 * Parse out key-value pairs from an HTTP request variable.
178 * This can be either a cookie or a POST/GET string, although man.cgi
179 * uses only GET for simplicity.
180 */
181 static void
182 kval_parse(struct kval **kv, size_t *kvsz, char *p)
183 {
184 char *key, *val;
185 size_t sz, cur;
186
187 cur = 0;
188
189 while (p && '\0' != *p) {
190 while (' ' == *p)
191 p++;
192
193 key = p;
194 val = NULL;
195
196 if (NULL != (p = strchr(p, '='))) {
197 *p++ = '\0';
198 val = p;
199
200 sz = strcspn(p, ";&");
201 /* LINTED */
202 p += sz;
203
204 if ('\0' != *p)
205 *p++ = '\0';
206 } else {
207 p = key;
208 sz = strcspn(p, ";&");
209 /* LINTED */
210 p += sz;
211
212 if ('\0' != *p)
213 p++;
214 continue;
215 }
216
217 if ('\0' == *key || '\0' == *val)
218 continue;
219
220 /* Just abort handling. */
221
222 if ( ! kval_decode(key))
223 return;
224 if ( ! kval_decode(val))
225 return;
226
227 if (*kvsz + 1 >= cur) {
228 cur++;
229 *kv = mandoc_realloc
230 (*kv, cur * sizeof(struct kval));
231 }
232
233 (*kv)[(int)*kvsz].key = mandoc_strdup(key);
234 (*kv)[(int)*kvsz].val = mandoc_strdup(val);
235 (*kvsz)++;
236 }
237 }
238
239 /*
240 * HTTP-decode a string. The standard explanation is that this turns
241 * "%4e+foo" into "n foo" in the regular way. This is done in-place
242 * over the allocated string.
243 */
244 static int
245 kval_decode(char *p)
246 {
247 char hex[3];
248 int c;
249
250 hex[2] = '\0';
251
252 for ( ; '\0' != *p; p++) {
253 if ('%' == *p) {
254 if ('\0' == (hex[0] = *(p + 1)))
255 return(0);
256 if ('\0' == (hex[1] = *(p + 2)))
257 return(0);
258 if (1 != sscanf(hex, "%x", &c))
259 return(0);
260 if ('\0' == c)
261 return(0);
262
263 *p = (char)c;
264 memmove(p + 1, p + 3, strlen(p + 3) + 1);
265 } else
266 *p = '+' == *p ? ' ' : *p;
267 }
268
269 *p = '\0';
270 return(1);
271 }
272
273 static void
274 resp_begin_http(int code, const char *msg)
275 {
276
277 if (200 != code)
278 printf("Status: %d %s\n", code, msg);
279
280 puts("Content-Type: text/html; charset=utf-8" "\n"
281 "Cache-Control: no-cache" "\n"
282 "Pragma: no-cache" "\n"
283 "");
284
285 fflush(stdout);
286 }
287
288 static void
289 resp_begin_html(int code, const char *msg)
290 {
291
292 resp_begin_http(code, msg);
293
294 puts("<!DOCTYPE HTML PUBLIC " "\n"
295 " \"-//W3C//DTD HTML 4.01//EN\"" "\n"
296 " \"http://www.w3.org/TR/html4/strict.dtd\">" "\n"
297 "<HTML>" "\n"
298 " <HEAD>" "\n"
299 " <META HTTP-EQUIV=\"Content-Type\" " "\n"
300 " CONTENT=\"text/html; charset=utf-8\">" "\n"
301 " <LINK REL=\"stylesheet\" HREF=\"/man.cgi.css\"" "\n"
302 " TYPE=\"text/css\" media=\"all\">" "\n"
303 " <TITLE>System Manpage Reference</TITLE>" "\n"
304 " </HEAD>" "\n"
305 " <BODY>" "\n"
306 "<!-- Begin page content. //-->");
307 }
308
309 static void
310 resp_end_html(void)
311 {
312
313 puts(" </BODY>\n</HTML>");
314 }
315
316 static void
317 resp_searchform(const struct req *req)
318 {
319 int i;
320 const char *expr, *sec, *arch;
321
322 expr = sec = arch = "";
323
324 for (i = 0; i < (int)req->fieldsz; i++)
325 if (0 == strcmp(req->fields[i].key, "expr"))
326 expr = req->fields[i].val;
327 else if (0 == strcmp(req->fields[i].key, "query"))
328 expr = req->fields[i].val;
329 else if (0 == strcmp(req->fields[i].key, "sec"))
330 sec = req->fields[i].val;
331 else if (0 == strcmp(req->fields[i].key, "sektion"))
332 sec = req->fields[i].val;
333 else if (0 == strcmp(req->fields[i].key, "arch"))
334 arch = req->fields[i].val;
335
336 if (NULL != sec && 0 == strcmp(sec, "0"))
337 sec = NULL;
338
339 puts("<!-- Begin search form. //-->");
340 printf("<FORM ACTION=\"");
341 html_print(progname);
342 printf("/search.html\" METHOD=\"get\">\n");
343 printf("<FIELDSET>\n"
344 "<LEGEND>Search Parameters</LEGEND>\n"
345 "<INPUT TYPE=\"submit\" NAME=\"op\" "
346 "VALUE=\"Whatis\"> or \n"
347 "<INPUT TYPE=\"submit\" NAME=\"op\" "
348 "VALUE=\"apropos\"> for manuals satisfying \n"
349 "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"");
350 html_print(expr);
351 printf("\">, section "
352 "<INPUT TYPE=\"text\" "
353 "SIZE=\"4\" NAME=\"sec\" VALUE=\"");
354 html_print(sec);
355 printf("\">, arch "
356 "<INPUT TYPE=\"text\" "
357 "SIZE=\"8\" NAME=\"arch\" VALUE=\"");
358 html_print(arch);
359 puts("\">.\n"
360 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
361 "</FIELDSET>\n"
362 "</FORM>\n"
363 "<!-- End search form. //-->");
364 }
365
366 static void
367 resp_index(const struct req *req)
368 {
369
370 resp_begin_html(200, NULL);
371 resp_searchform(req);
372 resp_end_html();
373 }
374
375 static void
376 resp_error400(void)
377 {
378
379 resp_begin_html(400, "Query Malformed");
380 printf("<H1>Malformed Query</H1>\n"
381 "<P>\n"
382 " The query your entered was malformed.\n"
383 " Try again from the\n"
384 " <A HREF=\"%s/index.html\">main page</A>\n"
385 "</P>", progname);
386 resp_end_html();
387 }
388
389 static void
390 resp_error404(const char *page)
391 {
392
393 resp_begin_html(404, "Not Found");
394 puts("<H1>Page Not Found</H1>\n"
395 "<P>\n"
396 " The page you're looking for, ");
397 printf(" <B>");
398 html_print(page);
399 printf("</B>,\n"
400 " could not be found.\n"
401 " Try searching from the\n"
402 " <A HREF=\"%s/index.html\">main page</A>\n"
403 "</P>", progname);
404 resp_end_html();
405 }
406
407 static void
408 resp_bad(void)
409 {
410 resp_begin_html(500, "Internal Server Error");
411 puts("<P>Generic badness happened.</P>");
412 resp_end_html();
413 }
414
415 static void
416 resp_baddb(void)
417 {
418
419 resp_begin_html(500, "Internal Server Error");
420 puts("<P>Your database is broken.</P>");
421 resp_end_html();
422 }
423
424 static void
425 resp_search(struct res *r, size_t sz, void *arg)
426 {
427 int i;
428
429 if (1 == sz) {
430 /*
431 * If we have just one result, then jump there now
432 * without any delay.
433 */
434 puts("Status: 303 See Other");
435 printf("Location: http://%s%s/show/%u/%u.html\n",
436 host, progname,
437 r[0].volume, r[0].rec);
438 puts("Content-Type: text/html; charset=utf-8\n");
439 return;
440 }
441
442 qsort(r, sz, sizeof(struct res), cmp);
443
444 resp_begin_html(200, NULL);
445 resp_searchform((const struct req *)arg);
446
447 if (0 == sz) {
448 puts("<P>No results found.</P>");
449 resp_end_html();
450 return;
451 }
452
453 puts("<P></P>\n"
454 "<TABLE>");
455
456 for (i = 0; i < (int)sz; i++) {
457 printf("<TR><TD CLASS=\"title\"><A HREF=\"");
458 html_print(progname);
459 printf("/show/%u/%u.html\">", r[i].volume, r[i].rec);
460 html_print(r[i].title);
461 putchar('(');
462 html_print(r[i].cat);
463 if (r[i].arch && '\0' != *r[i].arch) {
464 putchar('/');
465 html_print(r[i].arch);
466 }
467 printf(")</A></TD><TD CLASS=\"desc\">");
468 html_print(r[i].desc);
469 puts("</TD></TR>");
470 }
471
472 puts("</TABLE>");
473
474 resp_end_html();
475 }
476
477 /* ARGSUSED */
478 static void
479 pg_index(const struct manpaths *ps, const struct req *req, char *path)
480 {
481
482 resp_index(req);
483 }
484
485 static void
486 catman(const char *file)
487 {
488 FILE *f;
489 size_t len;
490 int i;
491 char *p;
492 int italic, bold;
493
494 if (NULL == (f = fopen(file, "r"))) {
495 resp_baddb();
496 return;
497 }
498
499 resp_begin_http(200, NULL);
500 puts("<!DOCTYPE HTML PUBLIC " "\n"
501 " \"-//W3C//DTD HTML 4.01//EN\"" "\n"
502 " \"http://www.w3.org/TR/html4/strict.dtd\">" "\n"
503 "<HTML>" "\n"
504 " <HEAD>" "\n"
505 " <META HTTP-EQUIV=\"Content-Type\" " "\n"
506 " CONTENT=\"text/html; charset=utf-8\">" "\n"
507 " <LINK REL=\"stylesheet\" HREF=\"/catman.css\"" "\n"
508 " TYPE=\"text/css\" media=\"all\">" "\n"
509 " <TITLE>System Manpage Reference</TITLE>" "\n"
510 " </HEAD>" "\n"
511 " <BODY>" "\n"
512 "<!-- Begin page content. //-->");
513
514 puts("<PRE>");
515 while (NULL != (p = fgetln(f, &len))) {
516 bold = italic = 0;
517 for (i = 0; i < (int)len - 1; i++) {
518 /*
519 * This means that the catpage is out of state.
520 * Ignore it and keep going (although the
521 * catpage is bogus).
522 */
523
524 if ('\b' == p[i] || '\n' == p[i])
525 continue;
526
527 /*
528 * Print a regular character.
529 * Close out any bold/italic scopes.
530 * If we're in back-space mode, make sure we'll
531 * have something to enter when we backspace.
532 */
533
534 if ('\b' != p[i + 1]) {
535 if (italic)
536 printf("</I>");
537 if (bold)
538 printf("</B>");
539 italic = bold = 0;
540 html_putchar(p[i]);
541 continue;
542 } else if (i + 2 >= (int)len)
543 continue;
544
545 /* Italic mode. */
546
547 if ('_' == p[i]) {
548 if (bold)
549 printf("</B>");
550 if ( ! italic)
551 printf("<I>");
552 bold = 0;
553 italic = 1;
554 i += 2;
555 html_putchar(p[i]);
556 continue;
557 }
558
559 /*
560 * Handle funny behaviour troff-isms.
561 * These grok'd from the original man2html.c.
562 */
563
564 if (('+' == p[i] && 'o' == p[i + 2]) ||
565 ('o' == p[i] && '+' == p[i + 2]) ||
566 ('|' == p[i] && '=' == p[i + 2]) ||
567 ('=' == p[i] && '|' == p[i + 2]) ||
568 ('*' == p[i] && '=' == p[i + 2]) ||
569 ('=' == p[i] && '*' == p[i + 2]) ||
570 ('*' == p[i] && '|' == p[i + 2]) ||
571 ('|' == p[i] && '*' == p[i + 2])) {
572 if (italic)
573 printf("</I>");
574 if (bold)
575 printf("</B>");
576 italic = bold = 0;
577 putchar('*');
578 i += 2;
579 continue;
580 } else if (('|' == p[i] && '-' == p[i + 2]) ||
581 ('-' == p[i] && '|' == p[i + 1]) ||
582 ('+' == p[i] && '-' == p[i + 1]) ||
583 ('-' == p[i] && '+' == p[i + 1]) ||
584 ('+' == p[i] && '|' == p[i + 1]) ||
585 ('|' == p[i] && '+' == p[i + 1])) {
586 if (italic)
587 printf("</I>");
588 if (bold)
589 printf("</B>");
590 italic = bold = 0;
591 putchar('+');
592 i += 2;
593 continue;
594 }
595
596 /* Bold mode. */
597
598 if (italic)
599 printf("</I>");
600 if ( ! bold)
601 printf("<B>");
602 bold = 1;
603 italic = 0;
604 i += 2;
605 html_putchar(p[i]);
606 }
607
608 /*
609 * Clean up the last character.
610 * We can get to a newline; don't print that.
611 */
612
613 if (italic)
614 printf("</I>");
615 if (bold)
616 printf("</B>");
617
618 if (i == (int)len - 1 && '\n' != p[i])
619 html_putchar(p[i]);
620
621 putchar('\n');
622 }
623
624 puts("</PRE>\n"
625 "</BODY>\n"
626 "</HTML>");
627
628 fclose(f);
629 }
630
631 static void
632 format(const char *file)
633 {
634 struct mparse *mp;
635 int fd;
636 struct mdoc *mdoc;
637 struct man *man;
638 void *vp;
639 enum mandoclevel rc;
640 char opts[MAXPATHLEN + 128];
641
642 if (-1 == (fd = open(file, O_RDONLY, 0))) {
643 resp_baddb();
644 return;
645 }
646
647 mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
648 rc = mparse_readfd(mp, fd, file);
649 close(fd);
650
651 if (rc >= MANDOCLEVEL_FATAL) {
652 resp_baddb();
653 return;
654 }
655
656 snprintf(opts, sizeof(opts), "style=/man.css,"
657 "man=%s/search.html?sec=%%S&expr=%%N,"
658 /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
659 progname);
660
661 mparse_result(mp, &mdoc, &man);
662 vp = html_alloc(opts);
663
664 if (NULL != mdoc) {
665 resp_begin_http(200, NULL);
666 html_mdoc(vp, mdoc);
667 } else if (NULL != man) {
668 resp_begin_http(200, NULL);
669 html_man(vp, man);
670 } else
671 resp_baddb();
672
673 html_free(vp);
674 mparse_free(mp);
675 }
676
677 static void
678 pg_show(const struct manpaths *ps, const struct req *req, char *path)
679 {
680 char *sub;
681 char file[MAXPATHLEN];
682 const char *fn, *cp;
683 int rc;
684 unsigned int vol, rec;
685 DB *idx;
686 DBT key, val;
687
688 if (NULL == path) {
689 resp_error400();
690 return;
691 } else if (NULL == (sub = strrchr(path, '/'))) {
692 resp_error400();
693 return;
694 } else
695 *sub++ = '\0';
696
697 if ( ! (atou(path, &vol) && atou(sub, &rec))) {
698 resp_error400();
699 return;
700 } else if (vol >= (unsigned int)ps->sz) {
701 resp_error400();
702 return;
703 }
704
705 strlcpy(file, ps->paths[vol], MAXPATHLEN);
706 strlcat(file, "/mandoc.index", MAXPATHLEN);
707
708 /* Open the index recno(3) database. */
709
710 idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
711 if (NULL == idx) {
712 resp_baddb();
713 return;
714 }
715
716 key.data = &rec;
717 key.size = 4;
718
719 if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
720 rc < 0 ? resp_baddb() : resp_error400();
721 goto out;
722 }
723
724 cp = (char *)val.data;
725
726 if (NULL == (fn = memchr(cp, '\0', val.size)))
727 resp_baddb();
728 else if (++fn - cp >= (int)val.size)
729 resp_baddb();
730 else if (NULL == memchr(fn, '\0', val.size - (fn - cp)))
731 resp_baddb();
732 else {
733 strlcpy(file, ps->paths[vol], MAXPATHLEN);
734 strlcat(file, "/", MAXPATHLEN);
735 strlcat(file, fn, MAXPATHLEN);
736 if (0 == strcmp(cp, "cat"))
737 catman(file);
738 else
739 format(file);
740 }
741 out:
742 (*idx->close)(idx);
743 }
744
745 static void
746 pg_search(const struct manpaths *ps, const struct req *req, char *path)
747 {
748 size_t tt;
749 int i, sz, rc, whatis;
750 const char *ep, *start;
751 char **cp;
752 struct opts opt;
753 struct expr *expr;
754
755 expr = NULL;
756 cp = NULL;
757 ep = NULL;
758 sz = 0;
759 whatis = 1;
760
761 memset(&opt, 0, sizeof(struct opts));
762
763 for (sz = i = 0; i < (int)req->fieldsz; i++)
764 if (0 == strcmp(req->fields[i].key, "expr"))
765 ep = req->fields[i].val;
766 else if (0 == strcmp(req->fields[i].key, "query"))
767 ep = req->fields[i].val;
768 else if (0 == strcmp(req->fields[i].key, "sec"))
769 opt.cat = req->fields[i].val;
770 else if (0 == strcmp(req->fields[i].key, "sektion"))
771 opt.cat = req->fields[i].val;
772 else if (0 == strcmp(req->fields[i].key, "arch"))
773 opt.arch = req->fields[i].val;
774 else if (0 == strcmp(req->fields[i].key, "apropos"))
775 whatis = 0 == strcmp
776 (req->fields[i].val, "0");
777 else if (0 == strcmp(req->fields[i].key, "op"))
778 whatis = 0 == strcasecmp
779 (req->fields[i].val, "whatis");
780
781 if (NULL != opt.cat && 0 == strcmp(opt.cat, "0"))
782 opt.cat = NULL;
783
784 /*
785 * Poor man's tokenisation.
786 * Just break apart by spaces.
787 * Yes, this is half-ass. But it works for now.
788 */
789
790 while (ep && isspace((unsigned char)*ep))
791 ep++;
792
793 while (ep && '\0' != *ep) {
794 cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
795 start = ep;
796 while ('\0' != *ep && ! isspace((unsigned char)*ep))
797 ep++;
798 cp[sz] = mandoc_malloc((ep - start) + 1);
799 memcpy(cp[sz], start, ep - start);
800 cp[sz++][ep - start] = '\0';
801 while (isspace((unsigned char)*ep))
802 ep++;
803 }
804
805 rc = -1;
806
807 /*
808 * Pump down into apropos backend.
809 * The resp_search() function is called with the results.
810 */
811
812 expr = whatis ? termcomp(sz, cp, &tt) :
813 exprcomp(sz, cp, &tt);
814
815 if (NULL != expr)
816 rc = apropos_search
817 (ps->sz, ps->paths, &opt,
818 expr, tt, (void *)req, resp_search);
819
820 /* ...unless errors occured. */
821
822 if (0 == rc)
823 resp_baddb();
824 else if (-1 == rc)
825 resp_search(NULL, 0, (void *)req);
826
827 for (i = 0; i < sz; i++)
828 free(cp[i]);
829
830 free(cp);
831 exprfree(expr);
832 }
833
834 int
835 main(void)
836 {
837 int i;
838 struct req req;
839 char *p, *path, *subpath;
840 struct manpaths paths;
841
842 /* HTTP init: read and parse the query string. */
843
844 progname = getenv("SCRIPT_NAME");
845 if (NULL == progname)
846 progname = "";
847
848 cache = getenv("CACHE_DIR");
849 if (NULL == cache)
850 cache = "/cache/man.cgi";
851
852 if (-1 == chdir(cache)) {
853 resp_bad();
854 return(EXIT_FAILURE);
855 }
856
857 host = getenv("HTTP_HOST");
858 if (NULL == host)
859 host = "localhost";
860
861 memset(&req, 0, sizeof(struct req));
862
863 if (NULL != (p = getenv("QUERY_STRING")))
864 kval_parse(&req.fields, &req.fieldsz, p);
865
866 /* Resolve leading subpath component. */
867
868 subpath = path = NULL;
869 req.page = PAGE__MAX;
870
871 if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
872 req.page = PAGE_INDEX;
873
874 if (NULL != path && '/' == *path && '\0' == *++path)
875 req.page = PAGE_INDEX;
876
877 /* Strip file suffix. */
878
879 if (NULL != path && NULL != (p = strrchr(path, '.')))
880 if (NULL != p && NULL == strchr(p, '/'))
881 *p++ = '\0';
882
883 /* Resolve subpath component. */
884
885 if (NULL != path && NULL != (subpath = strchr(path, '/')))
886 *subpath++ = '\0';
887
888 /* Map path into one we recognise. */
889
890 if (NULL != path && '\0' != *path)
891 for (i = 0; i < (int)PAGE__MAX; i++)
892 if (0 == strcmp(pages[i], path)) {
893 req.page = (enum page)i;
894 break;
895 }
896
897 /* Initialise MANPATH. */
898
899 memset(&paths, 0, sizeof(struct manpaths));
900 manpath_manconf("etc/catman.conf", &paths);
901
902 /* Route pages. */
903
904 switch (req.page) {
905 case (PAGE_INDEX):
906 pg_index(&paths, &req, subpath);
907 break;
908 case (PAGE_SEARCH):
909 pg_search(&paths, &req, subpath);
910 break;
911 case (PAGE_SHOW):
912 pg_show(&paths, &req, subpath);
913 break;
914 default:
915 resp_error404(path);
916 break;
917 }
918
919 manpath_free(&paths);
920 kval_free(req.fields, req.fieldsz);
921
922 return(EXIT_SUCCESS);
923 }
924
925 static int
926 cmp(const void *p1, const void *p2)
927 {
928
929 return(strcasecmp(((const struct res *)p1)->title,
930 ((const struct res *)p2)->title));
931 }
932