1*766ef059Sschwarze /* $OpenBSD: cgi.c,v 1.76 2016/08/10 18:39:04 schwarze Exp $ */ 2c6c22f12Sschwarze /* 3c6c22f12Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4abf19dc9Sschwarze * Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@usta.de> 5c6c22f12Sschwarze * 6c6c22f12Sschwarze * Permission to use, copy, modify, and distribute this software for any 7c6c22f12Sschwarze * purpose with or without fee is hereby granted, provided that the above 8c6c22f12Sschwarze * copyright notice and this permission notice appear in all copies. 9c6c22f12Sschwarze * 104de77decSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 11c6c22f12Sschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 124de77decSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 13c6c22f12Sschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14c6c22f12Sschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15c6c22f12Sschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16c6c22f12Sschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17c6c22f12Sschwarze */ 18136c26b8Sschwarze #include <sys/types.h> 19136c26b8Sschwarze #include <sys/time.h> 20136c26b8Sschwarze 21c6c22f12Sschwarze #include <ctype.h> 22c976f0e2Sschwarze #include <err.h> 23c6c22f12Sschwarze #include <errno.h> 24c6c22f12Sschwarze #include <fcntl.h> 25c6c22f12Sschwarze #include <limits.h> 26c2235d37Sschwarze #include <stdint.h> 27c6c22f12Sschwarze #include <stdio.h> 28c6c22f12Sschwarze #include <stdlib.h> 29c6c22f12Sschwarze #include <string.h> 30c6c22f12Sschwarze #include <unistd.h> 31c6c22f12Sschwarze 32c6c22f12Sschwarze #include "mandoc_aux.h" 33f2d5c709Sschwarze #include "mandoc.h" 34f2d5c709Sschwarze #include "roff.h" 35396853b5Sschwarze #include "mdoc.h" 36fec2846bSschwarze #include "man.h" 37c6c22f12Sschwarze #include "main.h" 384de77decSschwarze #include "manconf.h" 39c6c22f12Sschwarze #include "mansearch.h" 406fdade3eSschwarze #include "cgi.h" 41c6c22f12Sschwarze 42c6c22f12Sschwarze /* 43c6c22f12Sschwarze * A query as passed to the search function. 44c6c22f12Sschwarze */ 45c6c22f12Sschwarze struct query { 4631e689c3Sschwarze char *manpath; /* desired manual directory */ 4731e689c3Sschwarze char *arch; /* architecture */ 4831e689c3Sschwarze char *sec; /* manual section */ 49e89321abSschwarze char *query; /* unparsed query expression */ 504477fbfaSschwarze int equal; /* match whole names, not substrings */ 51c6c22f12Sschwarze }; 52c6c22f12Sschwarze 53c6c22f12Sschwarze struct req { 54c6c22f12Sschwarze struct query q; 55c6c22f12Sschwarze char **p; /* array of available manpaths */ 56c6c22f12Sschwarze size_t psz; /* number of available manpaths */ 5752b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */ 58c6c22f12Sschwarze }; 59c6c22f12Sschwarze 6084f05c93Sschwarze enum focus { 6184f05c93Sschwarze FOCUS_NONE = 0, 6284f05c93Sschwarze FOCUS_QUERY 6384f05c93Sschwarze }; 6484f05c93Sschwarze 65c6c22f12Sschwarze static void html_print(const char *); 66c6c22f12Sschwarze static void html_putchar(char); 67c6c22f12Sschwarze static int http_decode(char *); 68941df026Sschwarze static void parse_manpath_conf(struct req *); 69941df026Sschwarze static void parse_path_info(struct req *req, const char *path); 70941df026Sschwarze static void parse_query_string(struct req *, const char *); 71facea411Sschwarze static void pg_error_badrequest(const char *); 72facea411Sschwarze static void pg_error_internal(void); 73facea411Sschwarze static void pg_index(const struct req *); 74facea411Sschwarze static void pg_noresult(const struct req *, const char *); 7557482ef4Sschwarze static void pg_search(const struct req *); 76facea411Sschwarze static void pg_searchres(const struct req *, 77facea411Sschwarze struct manpage *, size_t); 7881060b1aSschwarze static void pg_show(struct req *, const char *); 79c6c22f12Sschwarze static void resp_begin_html(int, const char *); 80c6c22f12Sschwarze static void resp_begin_http(int, const char *); 81941df026Sschwarze static void resp_catman(const struct req *, const char *); 82711661c7Sschwarze static void resp_copy(const char *); 83c6c22f12Sschwarze static void resp_end_html(void); 84941df026Sschwarze static void resp_format(const struct req *, const char *); 8584f05c93Sschwarze static void resp_searchform(const struct req *, enum focus); 8646723f19Sschwarze static void resp_show(const struct req *, const char *); 87e89321abSschwarze static void set_query_attr(char **, char **); 88e89321abSschwarze static int validate_filename(const char *); 89e89321abSschwarze static int validate_manpath(const struct req *, const char *); 90e89321abSschwarze static int validate_urifrag(const char *); 91c6c22f12Sschwarze 923b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 93c6c22f12Sschwarze 9446723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 9528e449d6Sschwarze static const char *const sec_numbers[] = { 9628e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 9728e449d6Sschwarze }; 9828e449d6Sschwarze static const char *const sec_names[] = { 9928e449d6Sschwarze "All Sections", 10028e449d6Sschwarze "1 - General Commands", 10128e449d6Sschwarze "2 - System Calls", 1024e6618fdSschwarze "3 - Library Functions", 1034e6618fdSschwarze "3p - Perl Library", 1044e6618fdSschwarze "4 - Device Drivers", 10528e449d6Sschwarze "5 - File Formats", 10628e449d6Sschwarze "6 - Games", 1074e6618fdSschwarze "7 - Miscellaneous Information", 1084e6618fdSschwarze "8 - System Manager\'s Manual", 1094e6618fdSschwarze "9 - Kernel Developer\'s Manual" 11028e449d6Sschwarze }; 11128e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 11228e449d6Sschwarze 11328e449d6Sschwarze static const char *const arch_names[] = { 114*766ef059Sschwarze "amd64", "alpha", "armv7", 115*766ef059Sschwarze "hppa", "i386", "landisk", 1163bcd4815Sschwarze "loongson", "luna88k", "macppc", "mips64", 1173bcd4815Sschwarze "octeon", "sgi", "socppc", "sparc", 1183bcd4815Sschwarze "sparc64", "zaurus", 119*766ef059Sschwarze "amiga", "arc", "armish", "arm32", 120*766ef059Sschwarze "atari", "aviion", "beagle", "cats", 121*766ef059Sschwarze "hppa64", "hp300", 1223bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k", 1233bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos", 1243bcd4815Sschwarze "pmax", "powerpc", "solbourne", "sun3", 1253bcd4815Sschwarze "vax", "wgrisc", "x68k" 12628e449d6Sschwarze }; 12728e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 12828e449d6Sschwarze 129c6c22f12Sschwarze /* 130c6c22f12Sschwarze * Print a character, escaping HTML along the way. 131c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 132c6c22f12Sschwarze */ 133c6c22f12Sschwarze static void 134c6c22f12Sschwarze html_putchar(char c) 135c6c22f12Sschwarze { 136c6c22f12Sschwarze 137c6c22f12Sschwarze switch (c) { 138c6c22f12Sschwarze case ('"'): 139c6c22f12Sschwarze printf(""e;"); 140c6c22f12Sschwarze break; 141c6c22f12Sschwarze case ('&'): 142c6c22f12Sschwarze printf("&"); 143c6c22f12Sschwarze break; 144c6c22f12Sschwarze case ('>'): 145c6c22f12Sschwarze printf(">"); 146c6c22f12Sschwarze break; 147c6c22f12Sschwarze case ('<'): 148c6c22f12Sschwarze printf("<"); 149c6c22f12Sschwarze break; 150c6c22f12Sschwarze default: 151c6c22f12Sschwarze putchar((unsigned char)c); 152c6c22f12Sschwarze break; 153c6c22f12Sschwarze } 154c6c22f12Sschwarze } 155c6c22f12Sschwarze 156c6c22f12Sschwarze /* 157c6c22f12Sschwarze * Call through to html_putchar(). 158c6c22f12Sschwarze * Accepts NULL strings. 159c6c22f12Sschwarze */ 160c6c22f12Sschwarze static void 161c6c22f12Sschwarze html_print(const char *p) 162c6c22f12Sschwarze { 163c6c22f12Sschwarze 164c6c22f12Sschwarze if (NULL == p) 165c6c22f12Sschwarze return; 166c6c22f12Sschwarze while ('\0' != *p) 167c6c22f12Sschwarze html_putchar(*p++); 168c6c22f12Sschwarze } 169c6c22f12Sschwarze 170c6c22f12Sschwarze /* 17131e689c3Sschwarze * Transfer the responsibility for the allocated string *val 17231e689c3Sschwarze * to the query structure. 173c6c22f12Sschwarze */ 174c6c22f12Sschwarze static void 17531e689c3Sschwarze set_query_attr(char **attr, char **val) 17631e689c3Sschwarze { 17731e689c3Sschwarze 17831e689c3Sschwarze free(*attr); 17931e689c3Sschwarze if (**val == '\0') { 18031e689c3Sschwarze *attr = NULL; 18131e689c3Sschwarze free(*val); 18231e689c3Sschwarze } else 18331e689c3Sschwarze *attr = *val; 18431e689c3Sschwarze *val = NULL; 18531e689c3Sschwarze } 18631e689c3Sschwarze 18731e689c3Sschwarze /* 18831e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 18931e689c3Sschwarze * and store the values into the query structure. 19031e689c3Sschwarze */ 19131e689c3Sschwarze static void 192941df026Sschwarze parse_query_string(struct req *req, const char *qs) 193c6c22f12Sschwarze { 194c6c22f12Sschwarze char *key, *val; 19531e689c3Sschwarze size_t keysz, valsz; 196c6c22f12Sschwarze 19752b413c1Sschwarze req->isquery = 1; 19831e689c3Sschwarze req->q.manpath = NULL; 19931e689c3Sschwarze req->q.arch = NULL; 20031e689c3Sschwarze req->q.sec = NULL; 201e89321abSschwarze req->q.query = NULL; 2024477fbfaSschwarze req->q.equal = 1; 203c6c22f12Sschwarze 20431e689c3Sschwarze key = val = NULL; 20531e689c3Sschwarze while (*qs != '\0') { 206c6c22f12Sschwarze 20731e689c3Sschwarze /* Parse one key. */ 208c6c22f12Sschwarze 20931e689c3Sschwarze keysz = strcspn(qs, "=;&"); 21031e689c3Sschwarze key = mandoc_strndup(qs, keysz); 21131e689c3Sschwarze qs += keysz; 21231e689c3Sschwarze if (*qs != '=') 21331e689c3Sschwarze goto next; 214c6c22f12Sschwarze 21531e689c3Sschwarze /* Parse one value. */ 216c6c22f12Sschwarze 21731e689c3Sschwarze valsz = strcspn(++qs, ";&"); 21831e689c3Sschwarze val = mandoc_strndup(qs, valsz); 21931e689c3Sschwarze qs += valsz; 220c6c22f12Sschwarze 22131e689c3Sschwarze /* Decode and catch encoding errors. */ 22231e689c3Sschwarze 22331e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 22431e689c3Sschwarze goto next; 22531e689c3Sschwarze 22631e689c3Sschwarze /* Handle key-value pairs. */ 22731e689c3Sschwarze 22831e689c3Sschwarze if ( ! strcmp(key, "query")) 229e89321abSschwarze set_query_attr(&req->q.query, &val); 23031e689c3Sschwarze 23131e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 23231e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 23331e689c3Sschwarze 23431e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 235aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 23631e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 237aabcc9a1Sschwarze val[7] = '-'; 238aabcc9a1Sschwarze if ('C' == val[8]) 239aabcc9a1Sschwarze val[8] = 'c'; 240aabcc9a1Sschwarze } 241aabcc9a1Sschwarze #endif 24231e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 24331e689c3Sschwarze } 24431e689c3Sschwarze 24531e689c3Sschwarze else if ( ! (strcmp(key, "sec") 246aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 24731e689c3Sschwarze && strcmp(key, "sektion") 248aabcc9a1Sschwarze #endif 24931e689c3Sschwarze )) { 25031e689c3Sschwarze if ( ! strcmp(val, "0")) 25131e689c3Sschwarze *val = '\0'; 25231e689c3Sschwarze set_query_attr(&req->q.sec, &val); 253c6c22f12Sschwarze } 25431e689c3Sschwarze 25531e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 25631e689c3Sschwarze if ( ! strcmp(val, "default")) 25731e689c3Sschwarze *val = '\0'; 25831e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2594477fbfaSschwarze } 26031e689c3Sschwarze 26131e689c3Sschwarze /* 26231e689c3Sschwarze * The key must be freed in any case. 26331e689c3Sschwarze * The val may have been handed over to the query 26431e689c3Sschwarze * structure, in which case it is now NULL. 26531e689c3Sschwarze */ 26631e689c3Sschwarze next: 26731e689c3Sschwarze free(key); 26831e689c3Sschwarze key = NULL; 26931e689c3Sschwarze free(val); 27031e689c3Sschwarze val = NULL; 27131e689c3Sschwarze 27231e689c3Sschwarze if (*qs != '\0') 27331e689c3Sschwarze qs++; 27431e689c3Sschwarze } 275c6c22f12Sschwarze } 276c6c22f12Sschwarze 277c6c22f12Sschwarze /* 278c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 279c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 280c6c22f12Sschwarze * over the allocated string. 281c6c22f12Sschwarze */ 282c6c22f12Sschwarze static int 283c6c22f12Sschwarze http_decode(char *p) 284c6c22f12Sschwarze { 285c6c22f12Sschwarze char hex[3]; 2861f69f32bStedu char *q; 287c6c22f12Sschwarze int c; 288c6c22f12Sschwarze 289c6c22f12Sschwarze hex[2] = '\0'; 290c6c22f12Sschwarze 2911f69f32bStedu q = p; 2921f69f32bStedu for ( ; '\0' != *p; p++, q++) { 293c6c22f12Sschwarze if ('%' == *p) { 294c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 295526e306bSschwarze return 0; 296c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 297526e306bSschwarze return 0; 298c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 299526e306bSschwarze return 0; 300c6c22f12Sschwarze if ('\0' == c) 301526e306bSschwarze return 0; 302c6c22f12Sschwarze 3031f69f32bStedu *q = (char)c; 3041f69f32bStedu p += 2; 305c6c22f12Sschwarze } else 3061f69f32bStedu *q = '+' == *p ? ' ' : *p; 307c6c22f12Sschwarze } 308c6c22f12Sschwarze 3091f69f32bStedu *q = '\0'; 310526e306bSschwarze return 1; 311c6c22f12Sschwarze } 312c6c22f12Sschwarze 313c6c22f12Sschwarze static void 314c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 315c6c22f12Sschwarze { 316c6c22f12Sschwarze 317c6c22f12Sschwarze if (200 != code) 318fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 319c6c22f12Sschwarze 320fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 321fa9c540aStedu "Cache-Control: no-cache\r\n" 322fa9c540aStedu "Pragma: no-cache\r\n" 323fa9c540aStedu "\r\n"); 324c6c22f12Sschwarze 325c6c22f12Sschwarze fflush(stdout); 326c6c22f12Sschwarze } 327c6c22f12Sschwarze 328c6c22f12Sschwarze static void 329711661c7Sschwarze resp_copy(const char *filename) 330711661c7Sschwarze { 331711661c7Sschwarze char buf[4096]; 332711661c7Sschwarze ssize_t sz; 333711661c7Sschwarze int fd; 334711661c7Sschwarze 335711661c7Sschwarze if ((fd = open(filename, O_RDONLY)) != -1) { 336711661c7Sschwarze fflush(stdout); 337711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 338711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 339711661c7Sschwarze } 340711661c7Sschwarze } 341711661c7Sschwarze 342711661c7Sschwarze static void 343c6c22f12Sschwarze resp_begin_html(int code, const char *msg) 344c6c22f12Sschwarze { 345c6c22f12Sschwarze 346c6c22f12Sschwarze resp_begin_http(code, msg); 347c6c22f12Sschwarze 348d649d931Sschwarze printf("<!DOCTYPE html>\n" 349735516bdSschwarze "<html>\n" 350735516bdSschwarze "<head>\n" 351735516bdSschwarze "<meta charset=\"UTF-8\"/>\n" 352735516bdSschwarze "<link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 353735516bdSschwarze " type=\"text/css\" media=\"all\">\n" 354735516bdSschwarze "<title>%s</title>\n" 355735516bdSschwarze "</head>\n" 356735516bdSschwarze "<body>\n" 357c6c22f12Sschwarze "<!-- Begin page content. //-->\n", 35806bcd913Sschwarze CSS_DIR, CUSTOMIZE_TITLE); 359711661c7Sschwarze 360711661c7Sschwarze resp_copy(MAN_DIR "/header.html"); 361c6c22f12Sschwarze } 362c6c22f12Sschwarze 363c6c22f12Sschwarze static void 364c6c22f12Sschwarze resp_end_html(void) 365c6c22f12Sschwarze { 366c6c22f12Sschwarze 367711661c7Sschwarze resp_copy(MAN_DIR "/footer.html"); 368711661c7Sschwarze 369735516bdSschwarze puts("</body>\n" 370735516bdSschwarze "</html>"); 371c6c22f12Sschwarze } 372c6c22f12Sschwarze 373c6c22f12Sschwarze static void 37484f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus) 375c6c22f12Sschwarze { 376c6c22f12Sschwarze int i; 377c6c22f12Sschwarze 378c6c22f12Sschwarze puts("<!-- Begin search form. //-->"); 379735516bdSschwarze printf("<div id=\"mancgi\">\n" 380735516bdSschwarze "<form action=\"/%s\" method=\"get\">\n" 381735516bdSschwarze "<fieldset>\n" 382735516bdSschwarze "<legend>Manual Page Search Parameters</legend>\n", 383c6c22f12Sschwarze scriptname); 38428e449d6Sschwarze 38528e449d6Sschwarze /* Write query input box. */ 38628e449d6Sschwarze 387784a63d6Sschwarze printf("<input type=\"text\" name=\"query\" value=\""); 38884f05c93Sschwarze if (req->q.query != NULL) 389e89321abSschwarze html_print(req->q.query); 39084f05c93Sschwarze printf( "\" size=\"40\""); 39184f05c93Sschwarze if (focus == FOCUS_QUERY) 39284f05c93Sschwarze printf(" autofocus"); 39384f05c93Sschwarze puts(">"); 39428e449d6Sschwarze 395784a63d6Sschwarze /* Write submission buttons. */ 39628e449d6Sschwarze 397784a63d6Sschwarze printf( "<button type=\"submit\" name=\"apropos\" value=\"0\">" 398784a63d6Sschwarze "man</button>\n" 399784a63d6Sschwarze "<button type=\"submit\" name=\"apropos\" value=\"1\">" 400784a63d6Sschwarze "apropos</button>\n<br/>\n"); 40128e449d6Sschwarze 40228e449d6Sschwarze /* Write section selector. */ 40328e449d6Sschwarze 404784a63d6Sschwarze puts("<select name=\"sec\">"); 40528e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 406735516bdSschwarze printf("<option value=\"%s\"", sec_numbers[i]); 40728e449d6Sschwarze if (NULL != req->q.sec && 40828e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 409735516bdSschwarze printf(" selected=\"selected\""); 410735516bdSschwarze printf(">%s</option>\n", sec_names[i]); 41128e449d6Sschwarze } 412735516bdSschwarze puts("</select>"); 41328e449d6Sschwarze 41428e449d6Sschwarze /* Write architecture selector. */ 41528e449d6Sschwarze 416735516bdSschwarze printf( "<select name=\"arch\">\n" 417735516bdSschwarze "<option value=\"default\""); 418be14a32aSschwarze if (NULL == req->q.arch) 419735516bdSschwarze printf(" selected=\"selected\""); 420735516bdSschwarze puts(">All Architectures</option>"); 42128e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 422735516bdSschwarze printf("<option value=\"%s\"", arch_names[i]); 42328e449d6Sschwarze if (NULL != req->q.arch && 42428e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 425735516bdSschwarze printf(" selected=\"selected\""); 426735516bdSschwarze printf(">%s</option>\n", arch_names[i]); 42728e449d6Sschwarze } 428735516bdSschwarze puts("</select>"); 42928e449d6Sschwarze 43028e449d6Sschwarze /* Write manpath selector. */ 43128e449d6Sschwarze 432c6c22f12Sschwarze if (req->psz > 1) { 433735516bdSschwarze puts("<select name=\"manpath\">"); 434c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 435735516bdSschwarze printf("<option "); 43650eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 437735516bdSschwarze printf("selected=\"selected\" "); 438735516bdSschwarze printf("value=\""); 439c6c22f12Sschwarze html_print(req->p[i]); 440c6c22f12Sschwarze printf("\">"); 441c6c22f12Sschwarze html_print(req->p[i]); 442735516bdSschwarze puts("</option>"); 443c6c22f12Sschwarze } 444735516bdSschwarze puts("</select>"); 445c6c22f12Sschwarze } 44628e449d6Sschwarze 447784a63d6Sschwarze puts("</fieldset>\n" 448735516bdSschwarze "</form>\n" 449735516bdSschwarze "</div>"); 450c6c22f12Sschwarze puts("<!-- End search form. //-->"); 451c6c22f12Sschwarze } 452c6c22f12Sschwarze 45381475784Sschwarze static int 454cf3a545cSschwarze validate_urifrag(const char *frag) 455cf3a545cSschwarze { 456cf3a545cSschwarze 457cf3a545cSschwarze while ('\0' != *frag) { 458cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 459cf3a545cSschwarze '-' == *frag || '.' == *frag || 460cf3a545cSschwarze '/' == *frag || '_' == *frag)) 461526e306bSschwarze return 0; 462cf3a545cSschwarze frag++; 463cf3a545cSschwarze } 464526e306bSschwarze return 1; 465cf3a545cSschwarze } 466cf3a545cSschwarze 467cf3a545cSschwarze static int 468631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 469631ce2c6Sschwarze { 470631ce2c6Sschwarze size_t i; 471631ce2c6Sschwarze 472631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 473631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 474526e306bSschwarze return 1; 475631ce2c6Sschwarze 476526e306bSschwarze return 0; 477631ce2c6Sschwarze } 478631ce2c6Sschwarze 479631ce2c6Sschwarze static int 48081475784Sschwarze validate_filename(const char *file) 48181475784Sschwarze { 48281475784Sschwarze 48381475784Sschwarze if ('.' == file[0] && '/' == file[1]) 48481475784Sschwarze file += 2; 48581475784Sschwarze 486526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 487526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 48881475784Sschwarze } 48981475784Sschwarze 490c6c22f12Sschwarze static void 491facea411Sschwarze pg_index(const struct req *req) 492c6c22f12Sschwarze { 493c6c22f12Sschwarze 494c6c22f12Sschwarze resp_begin_html(200, NULL); 49584f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 496735516bdSschwarze printf("<p>\n" 497d56ca219Sschwarze "This web interface is documented in the\n" 4988361bf54Sschwarze "<a href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n" 499d56ca219Sschwarze "manual, and the\n" 5008361bf54Sschwarze "<a href=\"/%s%sapropos.1\">apropos(1)</a>\n" 5012a43838fSschwarze "manual explains the query syntax.\n" 502735516bdSschwarze "</p>\n", 5033b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5043b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 505c6c22f12Sschwarze resp_end_html(); 506c6c22f12Sschwarze } 507c6c22f12Sschwarze 508c6c22f12Sschwarze static void 509facea411Sschwarze pg_noresult(const struct req *req, const char *msg) 510c6c22f12Sschwarze { 511c6c22f12Sschwarze resp_begin_html(200, NULL); 51284f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 513735516bdSschwarze puts("<p>"); 514c6c22f12Sschwarze puts(msg); 515735516bdSschwarze puts("</p>"); 516c6c22f12Sschwarze resp_end_html(); 517c6c22f12Sschwarze } 518c6c22f12Sschwarze 519c6c22f12Sschwarze static void 520facea411Sschwarze pg_error_badrequest(const char *msg) 521c6c22f12Sschwarze { 522c6c22f12Sschwarze 523c6c22f12Sschwarze resp_begin_html(400, "Bad Request"); 524735516bdSschwarze puts("<h1>Bad Request</h1>\n" 525735516bdSschwarze "<p>\n"); 526c6c22f12Sschwarze puts(msg); 527c6c22f12Sschwarze printf("Try again from the\n" 528735516bdSschwarze "<a href=\"/%s\">main page</a>.\n" 529735516bdSschwarze "</p>", scriptname); 530c6c22f12Sschwarze resp_end_html(); 531c6c22f12Sschwarze } 532c6c22f12Sschwarze 533c6c22f12Sschwarze static void 534facea411Sschwarze pg_error_internal(void) 535c6c22f12Sschwarze { 536c6c22f12Sschwarze resp_begin_html(500, "Internal Server Error"); 537735516bdSschwarze puts("<p>Internal Server Error</p>"); 538c6c22f12Sschwarze resp_end_html(); 539c6c22f12Sschwarze } 540c6c22f12Sschwarze 541c6c22f12Sschwarze static void 542facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 543c6c22f12Sschwarze { 544be14a32aSschwarze char *arch, *archend; 545db26164eSschwarze const char *sec; 546db26164eSschwarze size_t i, iuse; 547be14a32aSschwarze int archprio, archpriouse; 54846723f19Sschwarze int prio, priouse; 549c6c22f12Sschwarze 55081475784Sschwarze for (i = 0; i < sz; i++) { 55181475784Sschwarze if (validate_filename(r[i].file)) 55281475784Sschwarze continue; 553c976f0e2Sschwarze warnx("invalid filename %s in %s database", 55481475784Sschwarze r[i].file, req->q.manpath); 55581475784Sschwarze pg_error_internal(); 55681475784Sschwarze return; 55781475784Sschwarze } 55881475784Sschwarze 55952b413c1Sschwarze if (req->isquery && sz == 1) { 560c6c22f12Sschwarze /* 561c6c22f12Sschwarze * If we have just one result, then jump there now 562c6c22f12Sschwarze * without any delay. 563c6c22f12Sschwarze */ 564fa9c540aStedu printf("Status: 303 See Other\r\n"); 5653b9cfc6fSschwarze printf("Location: http://%s/%s%s%s/%s", 5663b9cfc6fSschwarze HTTP_HOST, scriptname, 5673b9cfc6fSschwarze *scriptname == '\0' ? "" : "/", 5683b9cfc6fSschwarze req->q.manpath, r[0].file); 569fa9c540aStedu printf("\r\n" 570fa9c540aStedu "Content-Type: text/html; charset=utf-8\r\n" 571fa9c540aStedu "\r\n"); 572c6c22f12Sschwarze return; 573c6c22f12Sschwarze } 574c6c22f12Sschwarze 575c6c22f12Sschwarze resp_begin_html(200, NULL); 57684f05c93Sschwarze resp_searchform(req, 57784f05c93Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 5784592dbf1Sschwarze 5794592dbf1Sschwarze if (sz > 1) { 580735516bdSschwarze puts("<div class=\"results\">"); 581735516bdSschwarze puts("<table>"); 582c6c22f12Sschwarze 583c6c22f12Sschwarze for (i = 0; i < sz; i++) { 584735516bdSschwarze printf("<tr>\n" 585735516bdSschwarze "<td class=\"title\">\n" 586735516bdSschwarze "<a href=\"/%s%s%s/%s", 5873b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5883b9cfc6fSschwarze req->q.manpath, r[i].file); 589c6c22f12Sschwarze printf("\">"); 590c6c22f12Sschwarze html_print(r[i].names); 591735516bdSschwarze printf("</a>\n" 592735516bdSschwarze "</td>\n" 593735516bdSschwarze "<td class=\"desc\">"); 594c6c22f12Sschwarze html_print(r[i].output); 595735516bdSschwarze puts("</td>\n" 596735516bdSschwarze "</tr>"); 597c6c22f12Sschwarze } 598c6c22f12Sschwarze 599735516bdSschwarze puts("</table>\n" 600735516bdSschwarze "</div>"); 6014592dbf1Sschwarze } 60246723f19Sschwarze 60346723f19Sschwarze /* 60446723f19Sschwarze * In man(1) mode, show one of the pages 60546723f19Sschwarze * even if more than one is found. 60646723f19Sschwarze */ 60746723f19Sschwarze 6084592dbf1Sschwarze if (req->q.equal || sz == 1) { 609735516bdSschwarze puts("<hr>"); 61046723f19Sschwarze iuse = 0; 611db26164eSschwarze priouse = 20; 612be14a32aSschwarze archpriouse = 3; 61346723f19Sschwarze for (i = 0; i < sz; i++) { 614db26164eSschwarze sec = r[i].file; 615db26164eSschwarze sec += strcspn(sec, "123456789"); 616db26164eSschwarze if (sec[0] == '\0') 61746723f19Sschwarze continue; 618db26164eSschwarze prio = sec_prios[sec[0] - '1']; 619db26164eSschwarze if (sec[1] != '/') 620db26164eSschwarze prio += 10; 621db26164eSschwarze if (req->q.arch == NULL) { 622be14a32aSschwarze archprio = 623db26164eSschwarze ((arch = strchr(sec + 1, '/')) 624db26164eSschwarze == NULL) ? 3 : 625db26164eSschwarze ((archend = strchr(arch + 1, '/')) 626db26164eSschwarze == NULL) ? 0 : 627be14a32aSschwarze strncmp(arch, "amd64/", 628be14a32aSschwarze archend - arch) ? 2 : 1; 629be14a32aSschwarze if (archprio < archpriouse) { 630be14a32aSschwarze archpriouse = archprio; 631be14a32aSschwarze priouse = prio; 632be14a32aSschwarze iuse = i; 633be14a32aSschwarze continue; 634be14a32aSschwarze } 635be14a32aSschwarze if (archprio > archpriouse) 636be14a32aSschwarze continue; 637be14a32aSschwarze } 63846723f19Sschwarze if (prio >= priouse) 63946723f19Sschwarze continue; 64046723f19Sschwarze priouse = prio; 64146723f19Sschwarze iuse = i; 64246723f19Sschwarze } 64346723f19Sschwarze resp_show(req, r[iuse].file); 64446723f19Sschwarze } 64546723f19Sschwarze 646c6c22f12Sschwarze resp_end_html(); 647c6c22f12Sschwarze } 648c6c22f12Sschwarze 649c6c22f12Sschwarze static void 650941df026Sschwarze resp_catman(const struct req *req, const char *file) 651c6c22f12Sschwarze { 652c6c22f12Sschwarze FILE *f; 653c6c22f12Sschwarze char *p; 65431f93c25Sschwarze size_t sz; 65531f93c25Sschwarze ssize_t len; 65631f93c25Sschwarze int i; 657c6c22f12Sschwarze int italic, bold; 658c6c22f12Sschwarze 65931f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 660735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 661c6c22f12Sschwarze return; 662c6c22f12Sschwarze } 663c6c22f12Sschwarze 664735516bdSschwarze puts("<div class=\"catman\">\n" 665735516bdSschwarze "<pre>"); 666c6c22f12Sschwarze 66731f93c25Sschwarze p = NULL; 66831f93c25Sschwarze sz = 0; 66931f93c25Sschwarze 67031f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 671c6c22f12Sschwarze bold = italic = 0; 67231f93c25Sschwarze for (i = 0; i < len - 1; i++) { 673c6c22f12Sschwarze /* 674c6c22f12Sschwarze * This means that the catpage is out of state. 675c6c22f12Sschwarze * Ignore it and keep going (although the 676c6c22f12Sschwarze * catpage is bogus). 677c6c22f12Sschwarze */ 678c6c22f12Sschwarze 679c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 680c6c22f12Sschwarze continue; 681c6c22f12Sschwarze 682c6c22f12Sschwarze /* 683c6c22f12Sschwarze * Print a regular character. 684c6c22f12Sschwarze * Close out any bold/italic scopes. 685c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 686c6c22f12Sschwarze * have something to enter when we backspace. 687c6c22f12Sschwarze */ 688c6c22f12Sschwarze 689c6c22f12Sschwarze if ('\b' != p[i + 1]) { 690c6c22f12Sschwarze if (italic) 691735516bdSschwarze printf("</i>"); 692c6c22f12Sschwarze if (bold) 693735516bdSschwarze printf("</b>"); 694c6c22f12Sschwarze italic = bold = 0; 695c6c22f12Sschwarze html_putchar(p[i]); 696c6c22f12Sschwarze continue; 69731f93c25Sschwarze } else if (i + 2 >= len) 698c6c22f12Sschwarze continue; 699c6c22f12Sschwarze 700c6c22f12Sschwarze /* Italic mode. */ 701c6c22f12Sschwarze 702c6c22f12Sschwarze if ('_' == p[i]) { 703c6c22f12Sschwarze if (bold) 704735516bdSschwarze printf("</b>"); 705c6c22f12Sschwarze if ( ! italic) 706735516bdSschwarze printf("<i>"); 707c6c22f12Sschwarze bold = 0; 708c6c22f12Sschwarze italic = 1; 709c6c22f12Sschwarze i += 2; 710c6c22f12Sschwarze html_putchar(p[i]); 711c6c22f12Sschwarze continue; 712c6c22f12Sschwarze } 713c6c22f12Sschwarze 714c6c22f12Sschwarze /* 715c6c22f12Sschwarze * Handle funny behaviour troff-isms. 716c6c22f12Sschwarze * These grok'd from the original man2html.c. 717c6c22f12Sschwarze */ 718c6c22f12Sschwarze 719c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 720c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) || 721c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) || 722c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) || 723c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) || 724c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) || 725c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) || 726c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) { 727c6c22f12Sschwarze if (italic) 728735516bdSschwarze printf("</i>"); 729c6c22f12Sschwarze if (bold) 730735516bdSschwarze printf("</b>"); 731c6c22f12Sschwarze italic = bold = 0; 732c6c22f12Sschwarze putchar('*'); 733c6c22f12Sschwarze i += 2; 734c6c22f12Sschwarze continue; 735c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 736c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) || 737c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) || 738c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) || 739c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) || 740c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) { 741c6c22f12Sschwarze if (italic) 742735516bdSschwarze printf("</i>"); 743c6c22f12Sschwarze if (bold) 744735516bdSschwarze printf("</b>"); 745c6c22f12Sschwarze italic = bold = 0; 746c6c22f12Sschwarze putchar('+'); 747c6c22f12Sschwarze i += 2; 748c6c22f12Sschwarze continue; 749c6c22f12Sschwarze } 750c6c22f12Sschwarze 751c6c22f12Sschwarze /* Bold mode. */ 752c6c22f12Sschwarze 753c6c22f12Sschwarze if (italic) 754735516bdSschwarze printf("</i>"); 755c6c22f12Sschwarze if ( ! bold) 756735516bdSschwarze printf("<b>"); 757c6c22f12Sschwarze bold = 1; 758c6c22f12Sschwarze italic = 0; 759c6c22f12Sschwarze i += 2; 760c6c22f12Sschwarze html_putchar(p[i]); 761c6c22f12Sschwarze } 762c6c22f12Sschwarze 763c6c22f12Sschwarze /* 764c6c22f12Sschwarze * Clean up the last character. 765c6c22f12Sschwarze * We can get to a newline; don't print that. 766c6c22f12Sschwarze */ 767c6c22f12Sschwarze 768c6c22f12Sschwarze if (italic) 769735516bdSschwarze printf("</i>"); 770c6c22f12Sschwarze if (bold) 771735516bdSschwarze printf("</b>"); 772c6c22f12Sschwarze 77331f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 774c6c22f12Sschwarze html_putchar(p[i]); 775c6c22f12Sschwarze 776c6c22f12Sschwarze putchar('\n'); 777c6c22f12Sschwarze } 77831f93c25Sschwarze free(p); 779c6c22f12Sschwarze 780735516bdSschwarze puts("</pre>\n" 781735516bdSschwarze "</div>"); 782c6c22f12Sschwarze 783c6c22f12Sschwarze fclose(f); 784c6c22f12Sschwarze } 785c6c22f12Sschwarze 786c6c22f12Sschwarze static void 787941df026Sschwarze resp_format(const struct req *req, const char *file) 788c6c22f12Sschwarze { 7892ccd0917Sschwarze struct manoutput conf; 790c6c22f12Sschwarze struct mparse *mp; 791ede1b9d0Sschwarze struct roff_man *man; 792c6c22f12Sschwarze void *vp; 793f74d674aSschwarze int fd; 794f74d674aSschwarze int usepath; 795c6c22f12Sschwarze 796c6c22f12Sschwarze if (-1 == (fd = open(file, O_RDONLY, 0))) { 797735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 798c6c22f12Sschwarze return; 799c6c22f12Sschwarze } 800c6c22f12Sschwarze 80116536faaSschwarze mchars_alloc(); 802f139d5f6Sschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1, 803f139d5f6Sschwarze MANDOCLEVEL_BADARG, NULL, req->q.manpath); 804df927bb6Sschwarze mparse_readfd(mp, fd, file); 805c6c22f12Sschwarze close(fd); 806c6c22f12Sschwarze 8072ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 8082ccd0917Sschwarze conf.fragment = 1; 809f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 810f211c215Sschwarze mandoc_asprintf(&conf.man, "/%s%s%%N.%%S", 811f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 812c6c22f12Sschwarze 813f2d5c709Sschwarze mparse_result(mp, &man, NULL); 814f2d5c709Sschwarze if (man == NULL) { 815c976f0e2Sschwarze warnx("fatal mandoc error: %s/%s", req->q.manpath, file); 816facea411Sschwarze pg_error_internal(); 817c6c22f12Sschwarze mparse_free(mp); 81816536faaSschwarze mchars_free(); 819c6c22f12Sschwarze return; 820c6c22f12Sschwarze } 821c6c22f12Sschwarze 82216536faaSschwarze vp = html_alloc(&conf); 823c6c22f12Sschwarze 824396853b5Sschwarze if (man->macroset == MACROSET_MDOC) { 825396853b5Sschwarze mdoc_validate(man); 826f2d5c709Sschwarze html_mdoc(vp, man); 827fec2846bSschwarze } else { 828fec2846bSschwarze man_validate(man); 829c6c22f12Sschwarze html_man(vp, man); 830fec2846bSschwarze } 831c6c22f12Sschwarze 832c6c22f12Sschwarze html_free(vp); 833c6c22f12Sschwarze mparse_free(mp); 83416536faaSschwarze mchars_free(); 8352ccd0917Sschwarze free(conf.man); 836c6c22f12Sschwarze } 837c6c22f12Sschwarze 838c6c22f12Sschwarze static void 83946723f19Sschwarze resp_show(const struct req *req, const char *file) 84046723f19Sschwarze { 84181475784Sschwarze 84281475784Sschwarze if ('.' == file[0] && '/' == file[1]) 8432f7bef27Sschwarze file += 2; 84446723f19Sschwarze 84546723f19Sschwarze if ('c' == *file) 846941df026Sschwarze resp_catman(req, file); 84746723f19Sschwarze else 848941df026Sschwarze resp_format(req, file); 84946723f19Sschwarze } 85046723f19Sschwarze 85146723f19Sschwarze static void 852b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 853c6c22f12Sschwarze { 854b53c14c3Sschwarze char *manpath; 855b53c14c3Sschwarze const char *file; 856c6c22f12Sschwarze 857b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 858facea411Sschwarze pg_error_badrequest( 859c6c22f12Sschwarze "You did not specify a page to show."); 860c6c22f12Sschwarze return; 861c6c22f12Sschwarze } 862b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 863b53c14c3Sschwarze file++; 864c6c22f12Sschwarze 865b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 866631ce2c6Sschwarze pg_error_badrequest( 867631ce2c6Sschwarze "You specified an invalid manpath."); 868b53c14c3Sschwarze free(manpath); 869631ce2c6Sschwarze return; 870631ce2c6Sschwarze } 871631ce2c6Sschwarze 872c6c22f12Sschwarze /* 873c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 874c6c22f12Sschwarze * This way we can pick up the database files, which are 875c6c22f12Sschwarze * relative to the manpath root. 876c6c22f12Sschwarze */ 877c6c22f12Sschwarze 878b53c14c3Sschwarze if (chdir(manpath) == -1) { 879c976f0e2Sschwarze warn("chdir %s", manpath); 880631ce2c6Sschwarze pg_error_internal(); 881b53c14c3Sschwarze free(manpath); 882c6c22f12Sschwarze return; 883c6c22f12Sschwarze } 884b53c14c3Sschwarze free(manpath); 885b53c14c3Sschwarze 886b53c14c3Sschwarze if ( ! validate_filename(file)) { 88781475784Sschwarze pg_error_badrequest( 88881475784Sschwarze "You specified an invalid manual file."); 88981475784Sschwarze return; 89081475784Sschwarze } 89181475784Sschwarze 89246723f19Sschwarze resp_begin_html(200, NULL); 89384f05c93Sschwarze resp_searchform(req, FOCUS_NONE); 894b53c14c3Sschwarze resp_show(req, file); 89546723f19Sschwarze resp_end_html(); 896c6c22f12Sschwarze } 897c6c22f12Sschwarze 898c6c22f12Sschwarze static void 89957482ef4Sschwarze pg_search(const struct req *req) 900c6c22f12Sschwarze { 901c6c22f12Sschwarze struct mansearch search; 902c6c22f12Sschwarze struct manpaths paths; 903c6c22f12Sschwarze struct manpage *res; 904fbeeb774Sschwarze char **argv; 905fbeeb774Sschwarze char *query, *rp, *wp; 906c6c22f12Sschwarze size_t ressz; 907fbeeb774Sschwarze int argc; 908c6c22f12Sschwarze 909c6c22f12Sschwarze /* 910c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 911c6c22f12Sschwarze * This way we can pick up the database files, which are 912c6c22f12Sschwarze * relative to the manpath root. 913c6c22f12Sschwarze */ 914c6c22f12Sschwarze 915c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) { 916c976f0e2Sschwarze warn("chdir %s", req->q.manpath); 917631ce2c6Sschwarze pg_error_internal(); 918c6c22f12Sschwarze return; 919c6c22f12Sschwarze } 920c6c22f12Sschwarze 921c6c22f12Sschwarze search.arch = req->q.arch; 922c6c22f12Sschwarze search.sec = req->q.sec; 9230f10154cSschwarze search.outkey = "Nd"; 9240f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 925fea71919Sschwarze search.firstmatch = 1; 926c6c22f12Sschwarze 927c6c22f12Sschwarze paths.sz = 1; 928c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 929c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 930c6c22f12Sschwarze 931c6c22f12Sschwarze /* 932fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 933c6c22f12Sschwarze */ 934c6c22f12Sschwarze 935fbeeb774Sschwarze argc = 0; 936fbeeb774Sschwarze argv = NULL; 937fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 938fbeeb774Sschwarze for (;;) { 939fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 940fbeeb774Sschwarze rp++; 941fbeeb774Sschwarze if (*rp == '\0') 942fbeeb774Sschwarze break; 943fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 944fbeeb774Sschwarze argv[argc++] = wp = rp; 945fbeeb774Sschwarze for (;;) { 946fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 947fbeeb774Sschwarze *wp = '\0'; 948fbeeb774Sschwarze rp++; 949fbeeb774Sschwarze break; 950fbeeb774Sschwarze } 951fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 952fbeeb774Sschwarze rp++; 953fbeeb774Sschwarze if (wp != rp) 954fbeeb774Sschwarze *wp = *rp; 955fbeeb774Sschwarze if (*rp == '\0') 956fbeeb774Sschwarze break; 957fbeeb774Sschwarze wp++; 958fbeeb774Sschwarze rp++; 959fbeeb774Sschwarze } 960c6c22f12Sschwarze } 961c6c22f12Sschwarze 962fbeeb774Sschwarze if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz)) 963facea411Sschwarze pg_noresult(req, "You entered an invalid query."); 964c6c22f12Sschwarze else if (0 == ressz) 965facea411Sschwarze pg_noresult(req, "No results found."); 966c6c22f12Sschwarze else 967facea411Sschwarze pg_searchres(req, res, ressz); 968c6c22f12Sschwarze 969fbeeb774Sschwarze free(query); 970fbeeb774Sschwarze mansearch_free(res, ressz); 971c6c22f12Sschwarze free(paths.paths[0]); 972c6c22f12Sschwarze free(paths.paths); 973c6c22f12Sschwarze } 974c6c22f12Sschwarze 975c6c22f12Sschwarze int 976c6c22f12Sschwarze main(void) 977c6c22f12Sschwarze { 978c6c22f12Sschwarze struct req req; 979136c26b8Sschwarze struct itimerval itimer; 98057482ef4Sschwarze const char *path; 98131e689c3Sschwarze const char *querystring; 98257482ef4Sschwarze int i; 983c6c22f12Sschwarze 984136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 985136c26b8Sschwarze 9862935aafcSschwarze itimer.it_value.tv_sec = 2; 987136c26b8Sschwarze itimer.it_value.tv_usec = 0; 9882935aafcSschwarze itimer.it_interval.tv_sec = 2; 989136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 990136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 991c976f0e2Sschwarze warn("setitimer"); 992136c26b8Sschwarze pg_error_internal(); 993526e306bSschwarze return EXIT_FAILURE; 994136c26b8Sschwarze } 995136c26b8Sschwarze 996c6c22f12Sschwarze /* 9976fdade3eSschwarze * First we change directory into the MAN_DIR so that 998c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 999c6c22f12Sschwarze * relative to the same position. 1000c6c22f12Sschwarze */ 1001c6c22f12Sschwarze 1002c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) { 1003c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR); 1004facea411Sschwarze pg_error_internal(); 1005526e306bSschwarze return EXIT_FAILURE; 1006c6c22f12Sschwarze } 1007c6c22f12Sschwarze 1008c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1009abf19dc9Sschwarze req.q.equal = 1; 1010941df026Sschwarze parse_manpath_conf(&req); 1011c6c22f12Sschwarze 101202b1b494Sschwarze /* Parse the path info and the query string. */ 1013c6c22f12Sschwarze 101402b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 101502b1b494Sschwarze path = ""; 101602b1b494Sschwarze else if (*path == '/') 101702b1b494Sschwarze path++; 101802b1b494Sschwarze 1019aa16a3a8Sschwarze if (*path != '\0') { 1020941df026Sschwarze parse_path_info(&req, path); 102186f4328bSschwarze if (req.q.manpath == NULL || access(path, F_OK) == -1) 102202b1b494Sschwarze path = ""; 102302b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1024941df026Sschwarze parse_query_string(&req, querystring); 1025c6c22f12Sschwarze 102602b1b494Sschwarze /* Validate parsed data and add defaults. */ 102702b1b494Sschwarze 102850eaed2bSschwarze if (req.q.manpath == NULL) 102950eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 103050eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1031631ce2c6Sschwarze pg_error_badrequest( 1032631ce2c6Sschwarze "You specified an invalid manpath."); 1033526e306bSschwarze return EXIT_FAILURE; 1034631ce2c6Sschwarze } 1035631ce2c6Sschwarze 1036cf3a545cSschwarze if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { 1037cf3a545cSschwarze pg_error_badrequest( 1038cf3a545cSschwarze "You specified an invalid architecture."); 1039526e306bSschwarze return EXIT_FAILURE; 1040cf3a545cSschwarze } 1041cf3a545cSschwarze 104257482ef4Sschwarze /* Dispatch to the three different pages. */ 1043c6c22f12Sschwarze 104457482ef4Sschwarze if ('\0' != *path) 104557482ef4Sschwarze pg_show(&req, path); 1046e89321abSschwarze else if (NULL != req.q.query) 104757482ef4Sschwarze pg_search(&req); 104857482ef4Sschwarze else 1049facea411Sschwarze pg_index(&req); 1050c6c22f12Sschwarze 105131e689c3Sschwarze free(req.q.manpath); 105231e689c3Sschwarze free(req.q.arch); 105331e689c3Sschwarze free(req.q.sec); 1054e89321abSschwarze free(req.q.query); 1055c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1056c6c22f12Sschwarze free(req.p[i]); 1057c6c22f12Sschwarze free(req.p); 1058526e306bSschwarze return EXIT_SUCCESS; 1059c6c22f12Sschwarze } 1060c6c22f12Sschwarze 1061c6c22f12Sschwarze /* 106202b1b494Sschwarze * If PATH_INFO is not a file name, translate it to a query. 106302b1b494Sschwarze */ 106402b1b494Sschwarze static void 1065941df026Sschwarze parse_path_info(struct req *req, const char *path) 106602b1b494Sschwarze { 1067daf4c292Sschwarze char *dir[4]; 1068daf4c292Sschwarze int i; 106902b1b494Sschwarze 107052b413c1Sschwarze req->isquery = 0; 107102b1b494Sschwarze req->q.equal = 1; 107202b1b494Sschwarze req->q.manpath = mandoc_strdup(path); 1073daf4c292Sschwarze req->q.arch = NULL; 107402b1b494Sschwarze 107502b1b494Sschwarze /* Mandatory manual page name. */ 107602b1b494Sschwarze if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) { 107702b1b494Sschwarze req->q.query = req->q.manpath; 107802b1b494Sschwarze req->q.manpath = NULL; 107902b1b494Sschwarze } else 108002b1b494Sschwarze *req->q.query++ = '\0'; 108102b1b494Sschwarze 108202b1b494Sschwarze /* Optional trailing section. */ 108302b1b494Sschwarze if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) { 108402b1b494Sschwarze if(isdigit((unsigned char)req->q.sec[1])) { 108502b1b494Sschwarze *req->q.sec++ = '\0'; 108602b1b494Sschwarze req->q.sec = mandoc_strdup(req->q.sec); 108702b1b494Sschwarze } else 108802b1b494Sschwarze req->q.sec = NULL; 108902b1b494Sschwarze } 109002b1b494Sschwarze 109102b1b494Sschwarze /* Handle the case of name[.section] only. */ 1092daf4c292Sschwarze if (req->q.manpath == NULL) 109302b1b494Sschwarze return; 109402b1b494Sschwarze req->q.query = mandoc_strdup(req->q.query); 109502b1b494Sschwarze 1096daf4c292Sschwarze /* Split directory components. */ 1097daf4c292Sschwarze dir[i = 0] = req->q.manpath; 1098daf4c292Sschwarze while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) { 1099daf4c292Sschwarze if (++i == 3) { 1100daf4c292Sschwarze pg_error_badrequest( 1101daf4c292Sschwarze "You specified too many directory components."); 1102daf4c292Sschwarze exit(EXIT_FAILURE); 110302b1b494Sschwarze } 1104daf4c292Sschwarze *dir[i]++ = '\0'; 1105daf4c292Sschwarze } 1106daf4c292Sschwarze 1107daf4c292Sschwarze /* Optional manpath. */ 1108daf4c292Sschwarze if ((i = validate_manpath(req, req->q.manpath)) == 0) 1109daf4c292Sschwarze req->q.manpath = NULL; 1110daf4c292Sschwarze else if (dir[1] == NULL) 1111daf4c292Sschwarze return; 1112daf4c292Sschwarze 1113daf4c292Sschwarze /* Optional section. */ 1114daf4c292Sschwarze if (strncmp(dir[i], "man", 3) == 0) { 1115daf4c292Sschwarze free(req->q.sec); 1116daf4c292Sschwarze req->q.sec = mandoc_strdup(dir[i++] + 3); 1117daf4c292Sschwarze } 1118daf4c292Sschwarze if (dir[i] == NULL) { 1119daf4c292Sschwarze if (req->q.manpath == NULL) 1120daf4c292Sschwarze free(dir[0]); 1121daf4c292Sschwarze return; 1122daf4c292Sschwarze } 1123daf4c292Sschwarze if (dir[i + 1] != NULL) { 1124daf4c292Sschwarze pg_error_badrequest( 1125daf4c292Sschwarze "You specified an invalid directory component."); 1126daf4c292Sschwarze exit(EXIT_FAILURE); 1127daf4c292Sschwarze } 1128daf4c292Sschwarze 1129daf4c292Sschwarze /* Optional architecture. */ 1130daf4c292Sschwarze if (i) { 1131daf4c292Sschwarze req->q.arch = mandoc_strdup(dir[i]); 1132daf4c292Sschwarze if (req->q.manpath == NULL) 1133daf4c292Sschwarze free(dir[0]); 1134daf4c292Sschwarze } else 1135daf4c292Sschwarze req->q.arch = dir[0]; 113602b1b494Sschwarze } 113702b1b494Sschwarze 113802b1b494Sschwarze /* 1139c6c22f12Sschwarze * Scan for indexable paths. 1140c6c22f12Sschwarze */ 1141c6c22f12Sschwarze static void 1142941df026Sschwarze parse_manpath_conf(struct req *req) 1143c6c22f12Sschwarze { 1144c6c22f12Sschwarze FILE *fp; 1145c6c22f12Sschwarze char *dp; 1146c6c22f12Sschwarze size_t dpsz; 114731f93c25Sschwarze ssize_t len; 1148c6c22f12Sschwarze 1149c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) { 1150c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR); 1151de651747Sschwarze pg_error_internal(); 1152de651747Sschwarze exit(EXIT_FAILURE); 1153de651747Sschwarze } 1154c6c22f12Sschwarze 115531f93c25Sschwarze dp = NULL; 115631f93c25Sschwarze dpsz = 0; 115731f93c25Sschwarze 115831f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 115931f93c25Sschwarze if (dp[len - 1] == '\n') 116031f93c25Sschwarze dp[--len] = '\0'; 1161c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1162c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1163cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1164c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1165c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp); 1166cf3a545cSschwarze pg_error_internal(); 1167cf3a545cSschwarze exit(EXIT_FAILURE); 1168cf3a545cSschwarze } 1169c976f0e2Sschwarze if (strchr(dp, '/') != NULL) { 1170c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1171c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp); 1172cf3a545cSschwarze pg_error_internal(); 1173cf3a545cSschwarze exit(EXIT_FAILURE); 1174cf3a545cSschwarze } 1175cf3a545cSschwarze req->p[req->psz++] = dp; 117631f93c25Sschwarze dp = NULL; 117731f93c25Sschwarze dpsz = 0; 1178c6c22f12Sschwarze } 117931f93c25Sschwarze free(dp); 1180de651747Sschwarze 1181de651747Sschwarze if (req->p == NULL) { 1182c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR); 1183de651747Sschwarze pg_error_internal(); 1184de651747Sschwarze exit(EXIT_FAILURE); 1185de651747Sschwarze } 1186c6c22f12Sschwarze } 1187