1*f5938fa6Sschwarze /* $OpenBSD: cgi.c,v 1.99 2018/10/19 21:10:00 schwarze Exp $ */ 2c6c22f12Sschwarze /* 3c6c22f12Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 432f0ba5fSschwarze * Copyright (c) 2014, 2015, 2016, 2017, 2018 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 *); 68f7a12365Sschwarze static void http_encode(const char *p); 69941df026Sschwarze static void parse_manpath_conf(struct req *); 70941df026Sschwarze static void parse_path_info(struct req *req, const char *path); 71941df026Sschwarze static void parse_query_string(struct req *, const char *); 72facea411Sschwarze static void pg_error_badrequest(const char *); 73facea411Sschwarze static void pg_error_internal(void); 74facea411Sschwarze static void pg_index(const struct req *); 75facea411Sschwarze static void pg_noresult(const struct req *, const char *); 76e1beff2aSschwarze static void pg_redirect(const struct req *, const char *); 7757482ef4Sschwarze static void pg_search(const struct req *); 78facea411Sschwarze static void pg_searchres(const struct req *, 79facea411Sschwarze struct manpage *, size_t); 8081060b1aSschwarze static void pg_show(struct req *, const char *); 81fdef72b0Sschwarze static void resp_begin_html(int, const char *, const char *); 82c6c22f12Sschwarze static void resp_begin_http(int, const char *); 83941df026Sschwarze static void resp_catman(const struct req *, const char *); 84711661c7Sschwarze static void resp_copy(const char *); 85c6c22f12Sschwarze static void resp_end_html(void); 86941df026Sschwarze static void resp_format(const struct req *, const char *); 8784f05c93Sschwarze static void resp_searchform(const struct req *, enum focus); 8846723f19Sschwarze static void resp_show(const struct req *, const char *); 89e89321abSschwarze static void set_query_attr(char **, char **); 90f7a12365Sschwarze static int validate_arch(const char *); 91e89321abSschwarze static int validate_filename(const char *); 92e89321abSschwarze static int validate_manpath(const struct req *, const char *); 93e89321abSschwarze static int validate_urifrag(const char *); 94c6c22f12Sschwarze 953b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 96c6c22f12Sschwarze 9746723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 9828e449d6Sschwarze static const char *const sec_numbers[] = { 9928e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 10028e449d6Sschwarze }; 10128e449d6Sschwarze static const char *const sec_names[] = { 10228e449d6Sschwarze "All Sections", 10328e449d6Sschwarze "1 - General Commands", 10428e449d6Sschwarze "2 - System Calls", 1054e6618fdSschwarze "3 - Library Functions", 1064e6618fdSschwarze "3p - Perl Library", 1074e6618fdSschwarze "4 - Device Drivers", 10828e449d6Sschwarze "5 - File Formats", 10928e449d6Sschwarze "6 - Games", 1104e6618fdSschwarze "7 - Miscellaneous Information", 1114e6618fdSschwarze "8 - System Manager\'s Manual", 1124e6618fdSschwarze "9 - Kernel Developer\'s Manual" 11328e449d6Sschwarze }; 11428e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 11528e449d6Sschwarze 11628e449d6Sschwarze static const char *const arch_names[] = { 11793d3e4e1Sderaadt "amd64", "alpha", "armv7", "arm64", 118766ef059Sschwarze "hppa", "i386", "landisk", 1193bcd4815Sschwarze "loongson", "luna88k", "macppc", "mips64", 1206250a715Sschwarze "octeon", "sgi", "socppc", "sparc64", 121766ef059Sschwarze "amiga", "arc", "armish", "arm32", 122766ef059Sschwarze "atari", "aviion", "beagle", "cats", 123766ef059Sschwarze "hppa64", "hp300", 1243bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k", 1253bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos", 1260c245db5Sschwarze "pmax", "powerpc", "solbourne", "sparc", 1276250a715Sschwarze "sun3", "vax", "wgrisc", "x68k", 1286250a715Sschwarze "zaurus" 12928e449d6Sschwarze }; 13028e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 13128e449d6Sschwarze 132c6c22f12Sschwarze /* 133c6c22f12Sschwarze * Print a character, escaping HTML along the way. 134c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 135c6c22f12Sschwarze */ 136c6c22f12Sschwarze static void 137c6c22f12Sschwarze html_putchar(char c) 138c6c22f12Sschwarze { 139c6c22f12Sschwarze 140c6c22f12Sschwarze switch (c) { 141e74fa2aeSschwarze case '"': 142f765a656Sbentley printf("""); 143c6c22f12Sschwarze break; 144e74fa2aeSschwarze case '&': 145c6c22f12Sschwarze printf("&"); 146c6c22f12Sschwarze break; 147e74fa2aeSschwarze case '>': 148c6c22f12Sschwarze printf(">"); 149c6c22f12Sschwarze break; 150e74fa2aeSschwarze case '<': 151c6c22f12Sschwarze printf("<"); 152c6c22f12Sschwarze break; 153c6c22f12Sschwarze default: 154c6c22f12Sschwarze putchar((unsigned char)c); 155c6c22f12Sschwarze break; 156c6c22f12Sschwarze } 157c6c22f12Sschwarze } 158c6c22f12Sschwarze 159c6c22f12Sschwarze /* 160c6c22f12Sschwarze * Call through to html_putchar(). 161c6c22f12Sschwarze * Accepts NULL strings. 162c6c22f12Sschwarze */ 163c6c22f12Sschwarze static void 164c6c22f12Sschwarze html_print(const char *p) 165c6c22f12Sschwarze { 166c6c22f12Sschwarze 167c6c22f12Sschwarze if (NULL == p) 168c6c22f12Sschwarze return; 169c6c22f12Sschwarze while ('\0' != *p) 170c6c22f12Sschwarze html_putchar(*p++); 171c6c22f12Sschwarze } 172c6c22f12Sschwarze 173c6c22f12Sschwarze /* 17431e689c3Sschwarze * Transfer the responsibility for the allocated string *val 17531e689c3Sschwarze * to the query structure. 176c6c22f12Sschwarze */ 177c6c22f12Sschwarze static void 17831e689c3Sschwarze set_query_attr(char **attr, char **val) 17931e689c3Sschwarze { 18031e689c3Sschwarze 18131e689c3Sschwarze free(*attr); 18231e689c3Sschwarze if (**val == '\0') { 18331e689c3Sschwarze *attr = NULL; 18431e689c3Sschwarze free(*val); 18531e689c3Sschwarze } else 18631e689c3Sschwarze *attr = *val; 18731e689c3Sschwarze *val = NULL; 18831e689c3Sschwarze } 18931e689c3Sschwarze 19031e689c3Sschwarze /* 19131e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 19231e689c3Sschwarze * and store the values into the query structure. 19331e689c3Sschwarze */ 19431e689c3Sschwarze static void 195941df026Sschwarze parse_query_string(struct req *req, const char *qs) 196c6c22f12Sschwarze { 197c6c22f12Sschwarze char *key, *val; 19831e689c3Sschwarze size_t keysz, valsz; 199c6c22f12Sschwarze 20052b413c1Sschwarze req->isquery = 1; 20131e689c3Sschwarze req->q.manpath = NULL; 20231e689c3Sschwarze req->q.arch = NULL; 20331e689c3Sschwarze req->q.sec = NULL; 204e89321abSschwarze req->q.query = NULL; 2054477fbfaSschwarze req->q.equal = 1; 206c6c22f12Sschwarze 20731e689c3Sschwarze key = val = NULL; 20831e689c3Sschwarze while (*qs != '\0') { 209c6c22f12Sschwarze 21031e689c3Sschwarze /* Parse one key. */ 211c6c22f12Sschwarze 21231e689c3Sschwarze keysz = strcspn(qs, "=;&"); 21331e689c3Sschwarze key = mandoc_strndup(qs, keysz); 21431e689c3Sschwarze qs += keysz; 21531e689c3Sschwarze if (*qs != '=') 21631e689c3Sschwarze goto next; 217c6c22f12Sschwarze 21831e689c3Sschwarze /* Parse one value. */ 219c6c22f12Sschwarze 22031e689c3Sschwarze valsz = strcspn(++qs, ";&"); 22131e689c3Sschwarze val = mandoc_strndup(qs, valsz); 22231e689c3Sschwarze qs += valsz; 223c6c22f12Sschwarze 22431e689c3Sschwarze /* Decode and catch encoding errors. */ 22531e689c3Sschwarze 22631e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 22731e689c3Sschwarze goto next; 22831e689c3Sschwarze 22931e689c3Sschwarze /* Handle key-value pairs. */ 23031e689c3Sschwarze 23131e689c3Sschwarze if ( ! strcmp(key, "query")) 232e89321abSschwarze set_query_attr(&req->q.query, &val); 23331e689c3Sschwarze 23431e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 23531e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 23631e689c3Sschwarze 23731e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 238aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 23931e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 240aabcc9a1Sschwarze val[7] = '-'; 241aabcc9a1Sschwarze if ('C' == val[8]) 242aabcc9a1Sschwarze val[8] = 'c'; 243aabcc9a1Sschwarze } 244aabcc9a1Sschwarze #endif 24531e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 24631e689c3Sschwarze } 24731e689c3Sschwarze 24831e689c3Sschwarze else if ( ! (strcmp(key, "sec") 249aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 25031e689c3Sschwarze && strcmp(key, "sektion") 251aabcc9a1Sschwarze #endif 25231e689c3Sschwarze )) { 25331e689c3Sschwarze if ( ! strcmp(val, "0")) 25431e689c3Sschwarze *val = '\0'; 25531e689c3Sschwarze set_query_attr(&req->q.sec, &val); 256c6c22f12Sschwarze } 25731e689c3Sschwarze 25831e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 25931e689c3Sschwarze if ( ! strcmp(val, "default")) 26031e689c3Sschwarze *val = '\0'; 26131e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2624477fbfaSschwarze } 26331e689c3Sschwarze 26431e689c3Sschwarze /* 26531e689c3Sschwarze * The key must be freed in any case. 26631e689c3Sschwarze * The val may have been handed over to the query 26731e689c3Sschwarze * structure, in which case it is now NULL. 26831e689c3Sschwarze */ 26931e689c3Sschwarze next: 27031e689c3Sschwarze free(key); 27131e689c3Sschwarze key = NULL; 27231e689c3Sschwarze free(val); 27331e689c3Sschwarze val = NULL; 27431e689c3Sschwarze 27531e689c3Sschwarze if (*qs != '\0') 27631e689c3Sschwarze qs++; 27731e689c3Sschwarze } 278c6c22f12Sschwarze } 279c6c22f12Sschwarze 280c6c22f12Sschwarze /* 281c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 282c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 283c6c22f12Sschwarze * over the allocated string. 284c6c22f12Sschwarze */ 285c6c22f12Sschwarze static int 286c6c22f12Sschwarze http_decode(char *p) 287c6c22f12Sschwarze { 288c6c22f12Sschwarze char hex[3]; 2891f69f32bStedu char *q; 290c6c22f12Sschwarze int c; 291c6c22f12Sschwarze 292c6c22f12Sschwarze hex[2] = '\0'; 293c6c22f12Sschwarze 2941f69f32bStedu q = p; 2951f69f32bStedu for ( ; '\0' != *p; p++, q++) { 296c6c22f12Sschwarze if ('%' == *p) { 297c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 298526e306bSschwarze return 0; 299c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 300526e306bSschwarze return 0; 301c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 302526e306bSschwarze return 0; 303c6c22f12Sschwarze if ('\0' == c) 304526e306bSschwarze return 0; 305c6c22f12Sschwarze 3061f69f32bStedu *q = (char)c; 3071f69f32bStedu p += 2; 308c6c22f12Sschwarze } else 3091f69f32bStedu *q = '+' == *p ? ' ' : *p; 310c6c22f12Sschwarze } 311c6c22f12Sschwarze 3121f69f32bStedu *q = '\0'; 313526e306bSschwarze return 1; 314c6c22f12Sschwarze } 315c6c22f12Sschwarze 316c6c22f12Sschwarze static void 317f7a12365Sschwarze http_encode(const char *p) 318f7a12365Sschwarze { 319f7a12365Sschwarze for (; *p != '\0'; p++) { 320f7a12365Sschwarze if (isalnum((unsigned char)*p) == 0 && 321f7a12365Sschwarze strchr("-._~", *p) == NULL) 322f7a12365Sschwarze printf("%%%02.2X", (unsigned char)*p); 323f7a12365Sschwarze else 324f7a12365Sschwarze putchar(*p); 325f7a12365Sschwarze } 326f7a12365Sschwarze } 327f7a12365Sschwarze 328f7a12365Sschwarze static void 329c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 330c6c22f12Sschwarze { 331c6c22f12Sschwarze 332c6c22f12Sschwarze if (200 != code) 333fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 334c6c22f12Sschwarze 335fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 336fa9c540aStedu "Cache-Control: no-cache\r\n" 337fa9c540aStedu "Pragma: no-cache\r\n" 338fa9c540aStedu "\r\n"); 339c6c22f12Sschwarze 340c6c22f12Sschwarze fflush(stdout); 341c6c22f12Sschwarze } 342c6c22f12Sschwarze 343c6c22f12Sschwarze static void 344711661c7Sschwarze resp_copy(const char *filename) 345711661c7Sschwarze { 346711661c7Sschwarze char buf[4096]; 347711661c7Sschwarze ssize_t sz; 348711661c7Sschwarze int fd; 349711661c7Sschwarze 350711661c7Sschwarze if ((fd = open(filename, O_RDONLY)) != -1) { 351711661c7Sschwarze fflush(stdout); 352711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 353711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 354fd3cdd86Sjsg close(fd); 355711661c7Sschwarze } 356711661c7Sschwarze } 357711661c7Sschwarze 358711661c7Sschwarze static void 359fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file) 360c6c22f12Sschwarze { 361fdef72b0Sschwarze char *cp; 362c6c22f12Sschwarze 363c6c22f12Sschwarze resp_begin_http(code, msg); 364c6c22f12Sschwarze 365d649d931Sschwarze printf("<!DOCTYPE html>\n" 366735516bdSschwarze "<html>\n" 367735516bdSschwarze "<head>\n" 368735516bdSschwarze " <meta charset=\"UTF-8\"/>\n" 369661a42d8Sschwarze " <meta name=\"viewport\"" 370661a42d8Sschwarze " content=\"width=device-width, initial-scale=1.0\">\n" 371735516bdSschwarze " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 372735516bdSschwarze " type=\"text/css\" media=\"all\">\n" 373fdef72b0Sschwarze " <title>", 374fdef72b0Sschwarze CSS_DIR); 375fdef72b0Sschwarze if (file != NULL) { 376fdef72b0Sschwarze if ((cp = strrchr(file, '/')) != NULL) 377fdef72b0Sschwarze file = cp + 1; 378fdef72b0Sschwarze if ((cp = strrchr(file, '.')) != NULL) { 379fdef72b0Sschwarze printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1); 380fdef72b0Sschwarze } else 381fdef72b0Sschwarze printf("%s - ", file); 382fdef72b0Sschwarze } 383fdef72b0Sschwarze printf("%s</title>\n" 384735516bdSschwarze "</head>\n" 385ce781f36Sschwarze "<body>\n", 386fdef72b0Sschwarze CUSTOMIZE_TITLE); 387711661c7Sschwarze 388711661c7Sschwarze resp_copy(MAN_DIR "/header.html"); 389c6c22f12Sschwarze } 390c6c22f12Sschwarze 391c6c22f12Sschwarze static void 392c6c22f12Sschwarze resp_end_html(void) 393c6c22f12Sschwarze { 394c6c22f12Sschwarze 395711661c7Sschwarze resp_copy(MAN_DIR "/footer.html"); 396711661c7Sschwarze 397735516bdSschwarze puts("</body>\n" 398735516bdSschwarze "</html>"); 399c6c22f12Sschwarze } 400c6c22f12Sschwarze 401c6c22f12Sschwarze static void 40284f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus) 403c6c22f12Sschwarze { 404c6c22f12Sschwarze int i; 405c6c22f12Sschwarze 406ce781f36Sschwarze printf("<form action=\"/%s\" method=\"get\">\n" 407735516bdSschwarze " <fieldset>\n" 408735516bdSschwarze " <legend>Manual Page Search Parameters</legend>\n", 409c6c22f12Sschwarze scriptname); 41028e449d6Sschwarze 41128e449d6Sschwarze /* Write query input box. */ 41228e449d6Sschwarze 4136b824e3bSschwarze printf(" <input type=\"search\" name=\"query\" value=\""); 41484f05c93Sschwarze if (req->q.query != NULL) 415e89321abSschwarze html_print(req->q.query); 41684f05c93Sschwarze printf( "\" size=\"40\""); 41784f05c93Sschwarze if (focus == FOCUS_QUERY) 41884f05c93Sschwarze printf(" autofocus"); 41984f05c93Sschwarze puts(">"); 42028e449d6Sschwarze 421784a63d6Sschwarze /* Write submission buttons. */ 42228e449d6Sschwarze 423784a63d6Sschwarze printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">" 424784a63d6Sschwarze "man</button>\n" 425784a63d6Sschwarze " <button type=\"submit\" name=\"apropos\" value=\"1\">" 426542ee4bfSschwarze "apropos</button>\n" 427542ee4bfSschwarze " <br/>\n"); 42828e449d6Sschwarze 42928e449d6Sschwarze /* Write section selector. */ 43028e449d6Sschwarze 431784a63d6Sschwarze puts(" <select name=\"sec\">"); 43228e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 433735516bdSschwarze printf(" <option value=\"%s\"", sec_numbers[i]); 43428e449d6Sschwarze if (NULL != req->q.sec && 43528e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 436735516bdSschwarze printf(" selected=\"selected\""); 437735516bdSschwarze printf(">%s</option>\n", sec_names[i]); 43828e449d6Sschwarze } 439735516bdSschwarze puts(" </select>"); 44028e449d6Sschwarze 44128e449d6Sschwarze /* Write architecture selector. */ 44228e449d6Sschwarze 443735516bdSschwarze printf( " <select name=\"arch\">\n" 444735516bdSschwarze " <option value=\"default\""); 445be14a32aSschwarze if (NULL == req->q.arch) 446735516bdSschwarze printf(" selected=\"selected\""); 447735516bdSschwarze puts(">All Architectures</option>"); 44828e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 4496b824e3bSschwarze printf(" <option"); 45028e449d6Sschwarze if (NULL != req->q.arch && 45128e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 452735516bdSschwarze printf(" selected=\"selected\""); 453735516bdSschwarze printf(">%s</option>\n", arch_names[i]); 45428e449d6Sschwarze } 455735516bdSschwarze puts(" </select>"); 45628e449d6Sschwarze 45728e449d6Sschwarze /* Write manpath selector. */ 45828e449d6Sschwarze 459c6c22f12Sschwarze if (req->psz > 1) { 460735516bdSschwarze puts(" <select name=\"manpath\">"); 461c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 462735516bdSschwarze printf(" <option"); 46350eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 464735516bdSschwarze printf(" selected=\"selected\""); 4656b824e3bSschwarze printf(">"); 466c6c22f12Sschwarze html_print(req->p[i]); 467735516bdSschwarze puts("</option>"); 468c6c22f12Sschwarze } 469735516bdSschwarze puts(" </select>"); 470c6c22f12Sschwarze } 47128e449d6Sschwarze 472784a63d6Sschwarze puts(" </fieldset>\n" 473ce781f36Sschwarze "</form>"); 474c6c22f12Sschwarze } 475c6c22f12Sschwarze 47681475784Sschwarze static int 477cf3a545cSschwarze validate_urifrag(const char *frag) 478cf3a545cSschwarze { 479cf3a545cSschwarze 480cf3a545cSschwarze while ('\0' != *frag) { 481cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 482cf3a545cSschwarze '-' == *frag || '.' == *frag || 483cf3a545cSschwarze '/' == *frag || '_' == *frag)) 484526e306bSschwarze return 0; 485cf3a545cSschwarze frag++; 486cf3a545cSschwarze } 487526e306bSschwarze return 1; 488cf3a545cSschwarze } 489cf3a545cSschwarze 490cf3a545cSschwarze static int 491631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 492631ce2c6Sschwarze { 493631ce2c6Sschwarze size_t i; 494631ce2c6Sschwarze 495631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 496631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 497526e306bSschwarze return 1; 498631ce2c6Sschwarze 499526e306bSschwarze return 0; 500631ce2c6Sschwarze } 501631ce2c6Sschwarze 502631ce2c6Sschwarze static int 503f7a12365Sschwarze validate_arch(const char *arch) 504f7a12365Sschwarze { 505f7a12365Sschwarze int i; 506f7a12365Sschwarze 507f7a12365Sschwarze for (i = 0; i < arch_MAX; i++) 508f7a12365Sschwarze if (strcmp(arch, arch_names[i]) == 0) 509f7a12365Sschwarze return 1; 510f7a12365Sschwarze 511f7a12365Sschwarze return 0; 512f7a12365Sschwarze } 513f7a12365Sschwarze 514f7a12365Sschwarze static int 51581475784Sschwarze validate_filename(const char *file) 51681475784Sschwarze { 51781475784Sschwarze 51881475784Sschwarze if ('.' == file[0] && '/' == file[1]) 51981475784Sschwarze file += 2; 52081475784Sschwarze 521526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 522526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 52381475784Sschwarze } 52481475784Sschwarze 525c6c22f12Sschwarze static void 526facea411Sschwarze pg_index(const struct req *req) 527c6c22f12Sschwarze { 528c6c22f12Sschwarze 529fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 53084f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 531735516bdSschwarze printf("<p>\n" 532d56ca219Sschwarze "This web interface is documented in the\n" 5330a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n" 534d56ca219Sschwarze "manual, and the\n" 5350a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n" 5362a43838fSschwarze "manual explains the query syntax.\n" 537735516bdSschwarze "</p>\n", 5383b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5393b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 540c6c22f12Sschwarze resp_end_html(); 541c6c22f12Sschwarze } 542c6c22f12Sschwarze 543c6c22f12Sschwarze static void 544facea411Sschwarze pg_noresult(const struct req *req, const char *msg) 545c6c22f12Sschwarze { 546fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 54784f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 548735516bdSschwarze puts("<p>"); 549c6c22f12Sschwarze puts(msg); 550735516bdSschwarze puts("</p>"); 551c6c22f12Sschwarze resp_end_html(); 552c6c22f12Sschwarze } 553c6c22f12Sschwarze 554c6c22f12Sschwarze static void 555facea411Sschwarze pg_error_badrequest(const char *msg) 556c6c22f12Sschwarze { 557c6c22f12Sschwarze 558fdef72b0Sschwarze resp_begin_html(400, "Bad Request", NULL); 559735516bdSschwarze puts("<h1>Bad Request</h1>\n" 560735516bdSschwarze "<p>\n"); 561c6c22f12Sschwarze puts(msg); 562c6c22f12Sschwarze printf("Try again from the\n" 563735516bdSschwarze "<a href=\"/%s\">main page</a>.\n" 564735516bdSschwarze "</p>", scriptname); 565c6c22f12Sschwarze resp_end_html(); 566c6c22f12Sschwarze } 567c6c22f12Sschwarze 568c6c22f12Sschwarze static void 569facea411Sschwarze pg_error_internal(void) 570c6c22f12Sschwarze { 571fdef72b0Sschwarze resp_begin_html(500, "Internal Server Error", NULL); 572735516bdSschwarze puts("<p>Internal Server Error</p>"); 573c6c22f12Sschwarze resp_end_html(); 574c6c22f12Sschwarze } 575c6c22f12Sschwarze 576c6c22f12Sschwarze static void 577e1beff2aSschwarze pg_redirect(const struct req *req, const char *name) 578e1beff2aSschwarze { 579e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 580e0d9a108Sschwarze "Location: /"); 581e1beff2aSschwarze if (*scriptname != '\0') 582e1beff2aSschwarze printf("%s/", scriptname); 583e1beff2aSschwarze if (strcmp(req->q.manpath, req->p[0])) 584e1beff2aSschwarze printf("%s/", req->q.manpath); 585e1beff2aSschwarze if (req->q.arch != NULL) 586e1beff2aSschwarze printf("%s/", req->q.arch); 587f7a12365Sschwarze http_encode(name); 588f7a12365Sschwarze if (req->q.sec != NULL) { 589f7a12365Sschwarze putchar('.'); 590f7a12365Sschwarze http_encode(req->q.sec); 591f7a12365Sschwarze } 592e1beff2aSschwarze printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"); 593e1beff2aSschwarze } 594e1beff2aSschwarze 595e1beff2aSschwarze static void 596facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 597c6c22f12Sschwarze { 598be14a32aSschwarze char *arch, *archend; 599db26164eSschwarze const char *sec; 600db26164eSschwarze size_t i, iuse; 601be14a32aSschwarze int archprio, archpriouse; 60246723f19Sschwarze int prio, priouse; 603c6c22f12Sschwarze 60481475784Sschwarze for (i = 0; i < sz; i++) { 60581475784Sschwarze if (validate_filename(r[i].file)) 60681475784Sschwarze continue; 607c976f0e2Sschwarze warnx("invalid filename %s in %s database", 60881475784Sschwarze r[i].file, req->q.manpath); 60981475784Sschwarze pg_error_internal(); 61081475784Sschwarze return; 61181475784Sschwarze } 61281475784Sschwarze 61352b413c1Sschwarze if (req->isquery && sz == 1) { 614c6c22f12Sschwarze /* 615c6c22f12Sschwarze * If we have just one result, then jump there now 616c6c22f12Sschwarze * without any delay. 617c6c22f12Sschwarze */ 618e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 619e0d9a108Sschwarze "Location: /"); 620e0d9a108Sschwarze if (*scriptname != '\0') 621e0d9a108Sschwarze printf("%s/", scriptname); 622e0d9a108Sschwarze if (strcmp(req->q.manpath, req->p[0])) 623e0d9a108Sschwarze printf("%s/", req->q.manpath); 624e0d9a108Sschwarze printf("%s\r\n" 625e0d9a108Sschwarze "Content-Type: text/html; charset=utf-8\r\n\r\n", 626e0d9a108Sschwarze r[0].file); 627c6c22f12Sschwarze return; 628c6c22f12Sschwarze } 629c6c22f12Sschwarze 63046723f19Sschwarze /* 63146723f19Sschwarze * In man(1) mode, show one of the pages 63246723f19Sschwarze * even if more than one is found. 63346723f19Sschwarze */ 63446723f19Sschwarze 63546723f19Sschwarze iuse = 0; 636fdef72b0Sschwarze if (req->q.equal || sz == 1) { 637db26164eSschwarze priouse = 20; 638be14a32aSschwarze archpriouse = 3; 63946723f19Sschwarze for (i = 0; i < sz; i++) { 640db26164eSschwarze sec = r[i].file; 641db26164eSschwarze sec += strcspn(sec, "123456789"); 642db26164eSschwarze if (sec[0] == '\0') 64346723f19Sschwarze continue; 644db26164eSschwarze prio = sec_prios[sec[0] - '1']; 645db26164eSschwarze if (sec[1] != '/') 646db26164eSschwarze prio += 10; 647db26164eSschwarze if (req->q.arch == NULL) { 648be14a32aSschwarze archprio = 649db26164eSschwarze ((arch = strchr(sec + 1, '/')) 650db26164eSschwarze == NULL) ? 3 : 651db26164eSschwarze ((archend = strchr(arch + 1, '/')) 652db26164eSschwarze == NULL) ? 0 : 653be14a32aSschwarze strncmp(arch, "amd64/", 654be14a32aSschwarze archend - arch) ? 2 : 1; 655be14a32aSschwarze if (archprio < archpriouse) { 656be14a32aSschwarze archpriouse = archprio; 657be14a32aSschwarze priouse = prio; 658be14a32aSschwarze iuse = i; 659be14a32aSschwarze continue; 660be14a32aSschwarze } 661be14a32aSschwarze if (archprio > archpriouse) 662be14a32aSschwarze continue; 663be14a32aSschwarze } 66446723f19Sschwarze if (prio >= priouse) 66546723f19Sschwarze continue; 66646723f19Sschwarze priouse = prio; 66746723f19Sschwarze iuse = i; 66846723f19Sschwarze } 669fdef72b0Sschwarze resp_begin_html(200, NULL, r[iuse].file); 670fdef72b0Sschwarze } else 671fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 672fdef72b0Sschwarze 673fdef72b0Sschwarze resp_searchform(req, 674fdef72b0Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 675fdef72b0Sschwarze 676fdef72b0Sschwarze if (sz > 1) { 677fdef72b0Sschwarze puts("<table class=\"results\">"); 678fdef72b0Sschwarze for (i = 0; i < sz; i++) { 679fdef72b0Sschwarze printf(" <tr>\n" 680fdef72b0Sschwarze " <td>" 6817b3dbe16Sschwarze "<a class=\"Xr\" href=\"/"); 6827b3dbe16Sschwarze if (*scriptname != '\0') 6837b3dbe16Sschwarze printf("%s/", scriptname); 6847b3dbe16Sschwarze if (strcmp(req->q.manpath, req->p[0])) 6857b3dbe16Sschwarze printf("%s/", req->q.manpath); 6867b3dbe16Sschwarze printf("%s\">", r[i].file); 687fdef72b0Sschwarze html_print(r[i].names); 688fdef72b0Sschwarze printf("</a></td>\n" 689fdef72b0Sschwarze " <td><span class=\"Nd\">"); 690fdef72b0Sschwarze html_print(r[i].output); 691fdef72b0Sschwarze puts("</span></td>\n" 692fdef72b0Sschwarze " </tr>"); 693fdef72b0Sschwarze } 694fdef72b0Sschwarze puts("</table>"); 695fdef72b0Sschwarze } 696fdef72b0Sschwarze 697fdef72b0Sschwarze if (req->q.equal || sz == 1) { 698fdef72b0Sschwarze puts("<hr>"); 69946723f19Sschwarze resp_show(req, r[iuse].file); 70046723f19Sschwarze } 70146723f19Sschwarze 702c6c22f12Sschwarze resp_end_html(); 703c6c22f12Sschwarze } 704c6c22f12Sschwarze 705c6c22f12Sschwarze static void 706941df026Sschwarze resp_catman(const struct req *req, const char *file) 707c6c22f12Sschwarze { 708c6c22f12Sschwarze FILE *f; 709c6c22f12Sschwarze char *p; 71031f93c25Sschwarze size_t sz; 71131f93c25Sschwarze ssize_t len; 71231f93c25Sschwarze int i; 713c6c22f12Sschwarze int italic, bold; 714c6c22f12Sschwarze 71531f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 716735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 717c6c22f12Sschwarze return; 718c6c22f12Sschwarze } 719c6c22f12Sschwarze 720735516bdSschwarze puts("<div class=\"catman\">\n" 721735516bdSschwarze "<pre>"); 722c6c22f12Sschwarze 72331f93c25Sschwarze p = NULL; 72431f93c25Sschwarze sz = 0; 72531f93c25Sschwarze 72631f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 727c6c22f12Sschwarze bold = italic = 0; 72831f93c25Sschwarze for (i = 0; i < len - 1; i++) { 729c6c22f12Sschwarze /* 730c6c22f12Sschwarze * This means that the catpage is out of state. 731c6c22f12Sschwarze * Ignore it and keep going (although the 732c6c22f12Sschwarze * catpage is bogus). 733c6c22f12Sschwarze */ 734c6c22f12Sschwarze 735c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 736c6c22f12Sschwarze continue; 737c6c22f12Sschwarze 738c6c22f12Sschwarze /* 739c6c22f12Sschwarze * Print a regular character. 740c6c22f12Sschwarze * Close out any bold/italic scopes. 741c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 742c6c22f12Sschwarze * have something to enter when we backspace. 743c6c22f12Sschwarze */ 744c6c22f12Sschwarze 745c6c22f12Sschwarze if ('\b' != p[i + 1]) { 746c6c22f12Sschwarze if (italic) 747735516bdSschwarze printf("</i>"); 748c6c22f12Sschwarze if (bold) 749735516bdSschwarze printf("</b>"); 750c6c22f12Sschwarze italic = bold = 0; 751c6c22f12Sschwarze html_putchar(p[i]); 752c6c22f12Sschwarze continue; 75331f93c25Sschwarze } else if (i + 2 >= len) 754c6c22f12Sschwarze continue; 755c6c22f12Sschwarze 756c6c22f12Sschwarze /* Italic mode. */ 757c6c22f12Sschwarze 758c6c22f12Sschwarze if ('_' == p[i]) { 759c6c22f12Sschwarze if (bold) 760735516bdSschwarze printf("</b>"); 761c6c22f12Sschwarze if ( ! italic) 762735516bdSschwarze printf("<i>"); 763c6c22f12Sschwarze bold = 0; 764c6c22f12Sschwarze italic = 1; 765c6c22f12Sschwarze i += 2; 766c6c22f12Sschwarze html_putchar(p[i]); 767c6c22f12Sschwarze continue; 768c6c22f12Sschwarze } 769c6c22f12Sschwarze 770c6c22f12Sschwarze /* 771c6c22f12Sschwarze * Handle funny behaviour troff-isms. 772c6c22f12Sschwarze * These grok'd from the original man2html.c. 773c6c22f12Sschwarze */ 774c6c22f12Sschwarze 775c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 776c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) || 777c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) || 778c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) || 779c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) || 780c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) || 781c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) || 782c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) { 783c6c22f12Sschwarze if (italic) 784735516bdSschwarze printf("</i>"); 785c6c22f12Sschwarze if (bold) 786735516bdSschwarze printf("</b>"); 787c6c22f12Sschwarze italic = bold = 0; 788c6c22f12Sschwarze putchar('*'); 789c6c22f12Sschwarze i += 2; 790c6c22f12Sschwarze continue; 791c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 792c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) || 793c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) || 794c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) || 795c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) || 796c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) { 797c6c22f12Sschwarze if (italic) 798735516bdSschwarze printf("</i>"); 799c6c22f12Sschwarze if (bold) 800735516bdSschwarze printf("</b>"); 801c6c22f12Sschwarze italic = bold = 0; 802c6c22f12Sschwarze putchar('+'); 803c6c22f12Sschwarze i += 2; 804c6c22f12Sschwarze continue; 805c6c22f12Sschwarze } 806c6c22f12Sschwarze 807c6c22f12Sschwarze /* Bold mode. */ 808c6c22f12Sschwarze 809c6c22f12Sschwarze if (italic) 810735516bdSschwarze printf("</i>"); 811c6c22f12Sschwarze if ( ! bold) 812735516bdSschwarze printf("<b>"); 813c6c22f12Sschwarze bold = 1; 814c6c22f12Sschwarze italic = 0; 815c6c22f12Sschwarze i += 2; 816c6c22f12Sschwarze html_putchar(p[i]); 817c6c22f12Sschwarze } 818c6c22f12Sschwarze 819c6c22f12Sschwarze /* 820c6c22f12Sschwarze * Clean up the last character. 821c6c22f12Sschwarze * We can get to a newline; don't print that. 822c6c22f12Sschwarze */ 823c6c22f12Sschwarze 824c6c22f12Sschwarze if (italic) 825735516bdSschwarze printf("</i>"); 826c6c22f12Sschwarze if (bold) 827735516bdSschwarze printf("</b>"); 828c6c22f12Sschwarze 82931f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 830c6c22f12Sschwarze html_putchar(p[i]); 831c6c22f12Sschwarze 832c6c22f12Sschwarze putchar('\n'); 833c6c22f12Sschwarze } 83431f93c25Sschwarze free(p); 835c6c22f12Sschwarze 836735516bdSschwarze puts("</pre>\n" 837735516bdSschwarze "</div>"); 838c6c22f12Sschwarze 839c6c22f12Sschwarze fclose(f); 840c6c22f12Sschwarze } 841c6c22f12Sschwarze 842c6c22f12Sschwarze static void 843941df026Sschwarze resp_format(const struct req *req, const char *file) 844c6c22f12Sschwarze { 8452ccd0917Sschwarze struct manoutput conf; 846c6c22f12Sschwarze struct mparse *mp; 847ede1b9d0Sschwarze struct roff_man *man; 848c6c22f12Sschwarze void *vp; 849f74d674aSschwarze int fd; 850f74d674aSschwarze int usepath; 851c6c22f12Sschwarze 852c6c22f12Sschwarze if (-1 == (fd = open(file, O_RDONLY, 0))) { 853735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 854c6c22f12Sschwarze return; 855c6c22f12Sschwarze } 856c6c22f12Sschwarze 85716536faaSschwarze mchars_alloc(); 858f139d5f6Sschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1, 859f3476b07Sschwarze MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, req->q.manpath); 860df927bb6Sschwarze mparse_readfd(mp, fd, file); 861c6c22f12Sschwarze close(fd); 862c6c22f12Sschwarze 8632ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 8642ccd0917Sschwarze conf.fragment = 1; 8657c6e1b3aSschwarze conf.style = mandoc_strdup(CSS_DIR "/mandoc.css"); 86632f0ba5fSschwarze conf.toc = 1; 867f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 868ac2abdb8Sschwarze mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S", 869ac2abdb8Sschwarze scriptname, *scriptname == '\0' ? "" : "/", 870f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 871c6c22f12Sschwarze 872f2d5c709Sschwarze mparse_result(mp, &man, NULL); 873f2d5c709Sschwarze if (man == NULL) { 874c976f0e2Sschwarze warnx("fatal mandoc error: %s/%s", req->q.manpath, file); 875facea411Sschwarze pg_error_internal(); 876c6c22f12Sschwarze mparse_free(mp); 87716536faaSschwarze mchars_free(); 878c6c22f12Sschwarze return; 879c6c22f12Sschwarze } 880c6c22f12Sschwarze 88116536faaSschwarze vp = html_alloc(&conf); 882c6c22f12Sschwarze 883396853b5Sschwarze if (man->macroset == MACROSET_MDOC) { 884396853b5Sschwarze mdoc_validate(man); 885f2d5c709Sschwarze html_mdoc(vp, man); 886fec2846bSschwarze } else { 887fec2846bSschwarze man_validate(man); 888c6c22f12Sschwarze html_man(vp, man); 889fec2846bSschwarze } 890c6c22f12Sschwarze 891c6c22f12Sschwarze html_free(vp); 892c6c22f12Sschwarze mparse_free(mp); 89316536faaSschwarze mchars_free(); 8942ccd0917Sschwarze free(conf.man); 8957c6e1b3aSschwarze free(conf.style); 896c6c22f12Sschwarze } 897c6c22f12Sschwarze 898c6c22f12Sschwarze static void 89946723f19Sschwarze resp_show(const struct req *req, const char *file) 90046723f19Sschwarze { 90181475784Sschwarze 90281475784Sschwarze if ('.' == file[0] && '/' == file[1]) 9032f7bef27Sschwarze file += 2; 90446723f19Sschwarze 90546723f19Sschwarze if ('c' == *file) 906941df026Sschwarze resp_catman(req, file); 90746723f19Sschwarze else 908941df026Sschwarze resp_format(req, file); 90946723f19Sschwarze } 91046723f19Sschwarze 91146723f19Sschwarze static void 912b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 913c6c22f12Sschwarze { 914b53c14c3Sschwarze char *manpath; 915b53c14c3Sschwarze const char *file; 916c6c22f12Sschwarze 917b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 918facea411Sschwarze pg_error_badrequest( 919c6c22f12Sschwarze "You did not specify a page to show."); 920c6c22f12Sschwarze return; 921c6c22f12Sschwarze } 922b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 923b53c14c3Sschwarze file++; 924c6c22f12Sschwarze 925b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 926631ce2c6Sschwarze pg_error_badrequest( 927631ce2c6Sschwarze "You specified an invalid manpath."); 928b53c14c3Sschwarze free(manpath); 929631ce2c6Sschwarze return; 930631ce2c6Sschwarze } 931631ce2c6Sschwarze 932c6c22f12Sschwarze /* 933c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 934c6c22f12Sschwarze * This way we can pick up the database files, which are 935c6c22f12Sschwarze * relative to the manpath root. 936c6c22f12Sschwarze */ 937c6c22f12Sschwarze 938b53c14c3Sschwarze if (chdir(manpath) == -1) { 939c976f0e2Sschwarze warn("chdir %s", manpath); 940631ce2c6Sschwarze pg_error_internal(); 941b53c14c3Sschwarze free(manpath); 942c6c22f12Sschwarze return; 943c6c22f12Sschwarze } 944b53c14c3Sschwarze free(manpath); 945b53c14c3Sschwarze 946b53c14c3Sschwarze if ( ! validate_filename(file)) { 94781475784Sschwarze pg_error_badrequest( 94881475784Sschwarze "You specified an invalid manual file."); 94981475784Sschwarze return; 95081475784Sschwarze } 95181475784Sschwarze 952fdef72b0Sschwarze resp_begin_html(200, NULL, file); 95384f05c93Sschwarze resp_searchform(req, FOCUS_NONE); 954b53c14c3Sschwarze resp_show(req, file); 95546723f19Sschwarze resp_end_html(); 956c6c22f12Sschwarze } 957c6c22f12Sschwarze 958c6c22f12Sschwarze static void 95957482ef4Sschwarze pg_search(const struct req *req) 960c6c22f12Sschwarze { 961c6c22f12Sschwarze struct mansearch search; 962c6c22f12Sschwarze struct manpaths paths; 963c6c22f12Sschwarze struct manpage *res; 964fbeeb774Sschwarze char **argv; 965fbeeb774Sschwarze char *query, *rp, *wp; 966c6c22f12Sschwarze size_t ressz; 967fbeeb774Sschwarze int argc; 968c6c22f12Sschwarze 969c6c22f12Sschwarze /* 970c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 971c6c22f12Sschwarze * This way we can pick up the database files, which are 972c6c22f12Sschwarze * relative to the manpath root. 973c6c22f12Sschwarze */ 974c6c22f12Sschwarze 975c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) { 976c976f0e2Sschwarze warn("chdir %s", req->q.manpath); 977631ce2c6Sschwarze pg_error_internal(); 978c6c22f12Sschwarze return; 979c6c22f12Sschwarze } 980c6c22f12Sschwarze 981c6c22f12Sschwarze search.arch = req->q.arch; 982c6c22f12Sschwarze search.sec = req->q.sec; 9830f10154cSschwarze search.outkey = "Nd"; 9840f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 985fea71919Sschwarze search.firstmatch = 1; 986c6c22f12Sschwarze 987c6c22f12Sschwarze paths.sz = 1; 988c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 989c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 990c6c22f12Sschwarze 991c6c22f12Sschwarze /* 992fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 993c6c22f12Sschwarze */ 994c6c22f12Sschwarze 995fbeeb774Sschwarze argc = 0; 996fbeeb774Sschwarze argv = NULL; 997fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 998fbeeb774Sschwarze for (;;) { 999fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 1000fbeeb774Sschwarze rp++; 1001fbeeb774Sschwarze if (*rp == '\0') 1002fbeeb774Sschwarze break; 1003fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 1004fbeeb774Sschwarze argv[argc++] = wp = rp; 1005fbeeb774Sschwarze for (;;) { 1006fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 1007fbeeb774Sschwarze *wp = '\0'; 1008fbeeb774Sschwarze rp++; 1009fbeeb774Sschwarze break; 1010fbeeb774Sschwarze } 1011fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 1012fbeeb774Sschwarze rp++; 1013fbeeb774Sschwarze if (wp != rp) 1014fbeeb774Sschwarze *wp = *rp; 1015fbeeb774Sschwarze if (*rp == '\0') 1016fbeeb774Sschwarze break; 1017fbeeb774Sschwarze wp++; 1018fbeeb774Sschwarze rp++; 1019fbeeb774Sschwarze } 1020c6c22f12Sschwarze } 1021c6c22f12Sschwarze 1022e1beff2aSschwarze res = NULL; 1023e1beff2aSschwarze ressz = 0; 1024e1beff2aSschwarze if (req->isquery && req->q.equal && argc == 1) 1025e1beff2aSschwarze pg_redirect(req, argv[0]); 1026e1beff2aSschwarze else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0) 1027facea411Sschwarze pg_noresult(req, "You entered an invalid query."); 1028e1beff2aSschwarze else if (ressz == 0) 1029facea411Sschwarze pg_noresult(req, "No results found."); 1030c6c22f12Sschwarze else 1031facea411Sschwarze pg_searchres(req, res, ressz); 1032c6c22f12Sschwarze 1033fbeeb774Sschwarze free(query); 1034fbeeb774Sschwarze mansearch_free(res, ressz); 1035c6c22f12Sschwarze free(paths.paths[0]); 1036c6c22f12Sschwarze free(paths.paths); 1037c6c22f12Sschwarze } 1038c6c22f12Sschwarze 1039c6c22f12Sschwarze int 1040c6c22f12Sschwarze main(void) 1041c6c22f12Sschwarze { 1042c6c22f12Sschwarze struct req req; 1043136c26b8Sschwarze struct itimerval itimer; 104457482ef4Sschwarze const char *path; 104531e689c3Sschwarze const char *querystring; 104657482ef4Sschwarze int i; 1047c6c22f12Sschwarze 1048f80eb964Sschwarze /* 1049f80eb964Sschwarze * The "rpath" pledge could be revoked after mparse_readfd() 1050f80eb964Sschwarze * if the file desciptor to "/footer.html" would be opened 1051f80eb964Sschwarze * up front, but it's probably not worth the complication 1052f80eb964Sschwarze * of the code it would cause: it would require scattering 1053f80eb964Sschwarze * pledge() calls in multiple low-level resp_*() functions. 1054f80eb964Sschwarze */ 1055f80eb964Sschwarze 1056f80eb964Sschwarze if (pledge("stdio rpath", NULL) == -1) { 1057f80eb964Sschwarze warn("pledge"); 1058f80eb964Sschwarze pg_error_internal(); 1059f80eb964Sschwarze return EXIT_FAILURE; 1060f80eb964Sschwarze } 1061f80eb964Sschwarze 1062136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 1063136c26b8Sschwarze 10642935aafcSschwarze itimer.it_value.tv_sec = 2; 1065136c26b8Sschwarze itimer.it_value.tv_usec = 0; 10662935aafcSschwarze itimer.it_interval.tv_sec = 2; 1067136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 1068136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1069c976f0e2Sschwarze warn("setitimer"); 1070136c26b8Sschwarze pg_error_internal(); 1071526e306bSschwarze return EXIT_FAILURE; 1072136c26b8Sschwarze } 1073136c26b8Sschwarze 1074c6c22f12Sschwarze /* 10756fdade3eSschwarze * First we change directory into the MAN_DIR so that 1076c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 1077c6c22f12Sschwarze * relative to the same position. 1078c6c22f12Sschwarze */ 1079c6c22f12Sschwarze 1080c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) { 1081c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR); 1082facea411Sschwarze pg_error_internal(); 1083526e306bSschwarze return EXIT_FAILURE; 1084c6c22f12Sschwarze } 1085c6c22f12Sschwarze 1086c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1087abf19dc9Sschwarze req.q.equal = 1; 1088941df026Sschwarze parse_manpath_conf(&req); 1089c6c22f12Sschwarze 109002b1b494Sschwarze /* Parse the path info and the query string. */ 1091c6c22f12Sschwarze 109202b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 109302b1b494Sschwarze path = ""; 109402b1b494Sschwarze else if (*path == '/') 109502b1b494Sschwarze path++; 109602b1b494Sschwarze 1097aa16a3a8Sschwarze if (*path != '\0') { 1098941df026Sschwarze parse_path_info(&req, path); 1099a9941855Sschwarze if (req.q.manpath == NULL || req.q.sec == NULL || 1100a9941855Sschwarze *req.q.query == '\0' || access(path, F_OK) == -1) 110102b1b494Sschwarze path = ""; 110202b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1103941df026Sschwarze parse_query_string(&req, querystring); 1104c6c22f12Sschwarze 110502b1b494Sschwarze /* Validate parsed data and add defaults. */ 110602b1b494Sschwarze 110750eaed2bSschwarze if (req.q.manpath == NULL) 110850eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 110950eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1110631ce2c6Sschwarze pg_error_badrequest( 1111631ce2c6Sschwarze "You specified an invalid manpath."); 1112526e306bSschwarze return EXIT_FAILURE; 1113631ce2c6Sschwarze } 1114631ce2c6Sschwarze 1115f7a12365Sschwarze if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) { 1116cf3a545cSschwarze pg_error_badrequest( 1117cf3a545cSschwarze "You specified an invalid architecture."); 1118526e306bSschwarze return EXIT_FAILURE; 1119cf3a545cSschwarze } 1120cf3a545cSschwarze 112157482ef4Sschwarze /* Dispatch to the three different pages. */ 1122c6c22f12Sschwarze 112357482ef4Sschwarze if ('\0' != *path) 112457482ef4Sschwarze pg_show(&req, path); 1125e89321abSschwarze else if (NULL != req.q.query) 112657482ef4Sschwarze pg_search(&req); 112757482ef4Sschwarze else 1128facea411Sschwarze pg_index(&req); 1129c6c22f12Sschwarze 113031e689c3Sschwarze free(req.q.manpath); 113131e689c3Sschwarze free(req.q.arch); 113231e689c3Sschwarze free(req.q.sec); 1133e89321abSschwarze free(req.q.query); 1134c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1135c6c22f12Sschwarze free(req.p[i]); 1136c6c22f12Sschwarze free(req.p); 1137526e306bSschwarze return EXIT_SUCCESS; 1138c6c22f12Sschwarze } 1139c6c22f12Sschwarze 1140c6c22f12Sschwarze /* 1141*f5938fa6Sschwarze * Translate PATH_INFO to a query. 114202b1b494Sschwarze */ 114302b1b494Sschwarze static void 1144941df026Sschwarze parse_path_info(struct req *req, const char *path) 114502b1b494Sschwarze { 1146*f5938fa6Sschwarze const char *name, *sec, *end; 114702b1b494Sschwarze 114852b413c1Sschwarze req->isquery = 0; 114902b1b494Sschwarze req->q.equal = 1; 1150*f5938fa6Sschwarze req->q.manpath = NULL; 1151daf4c292Sschwarze req->q.arch = NULL; 115202b1b494Sschwarze 115302b1b494Sschwarze /* Mandatory manual page name. */ 1154*f5938fa6Sschwarze if ((name = strrchr(path, '/')) == NULL) 1155*f5938fa6Sschwarze name = path; 1156*f5938fa6Sschwarze else 1157*f5938fa6Sschwarze name++; 115802b1b494Sschwarze 115902b1b494Sschwarze /* Optional trailing section. */ 1160*f5938fa6Sschwarze sec = strrchr(name, '.'); 1161*f5938fa6Sschwarze if (sec != NULL && isdigit((unsigned char)*++sec)) { 1162*f5938fa6Sschwarze req->q.query = mandoc_strndup(name, sec - name - 1); 1163*f5938fa6Sschwarze req->q.sec = mandoc_strdup(sec); 1164*f5938fa6Sschwarze } else { 1165*f5938fa6Sschwarze req->q.query = mandoc_strdup(name); 116602b1b494Sschwarze req->q.sec = NULL; 116702b1b494Sschwarze } 116802b1b494Sschwarze 116902b1b494Sschwarze /* Handle the case of name[.section] only. */ 1170*f5938fa6Sschwarze if (name == path) 117102b1b494Sschwarze return; 117202b1b494Sschwarze 1173*f5938fa6Sschwarze /* Optional manpath. */ 1174*f5938fa6Sschwarze end = strchr(path, '/'); 1175*f5938fa6Sschwarze req->q.manpath = mandoc_strndup(path, end - path); 1176*f5938fa6Sschwarze if (validate_manpath(req, req->q.manpath)) { 1177*f5938fa6Sschwarze path = end + 1; 1178*f5938fa6Sschwarze if (name == path) 1179*f5938fa6Sschwarze return; 1180*f5938fa6Sschwarze } else { 1181*f5938fa6Sschwarze free(req->q.manpath); 1182*f5938fa6Sschwarze req->q.manpath = NULL; 1183*f5938fa6Sschwarze } 1184*f5938fa6Sschwarze 1185*f5938fa6Sschwarze /* Optional section. */ 1186*f5938fa6Sschwarze if (strncmp(path, "man", 3) == 0) { 1187*f5938fa6Sschwarze path += 3; 1188*f5938fa6Sschwarze end = strchr(path, '/'); 1189*f5938fa6Sschwarze free(req->q.sec); 1190*f5938fa6Sschwarze req->q.sec = mandoc_strndup(path, end - path); 1191*f5938fa6Sschwarze path = end + 1; 1192*f5938fa6Sschwarze if (name == path) 1193*f5938fa6Sschwarze return; 1194*f5938fa6Sschwarze } 1195*f5938fa6Sschwarze 1196*f5938fa6Sschwarze /* Optional architecture. */ 1197*f5938fa6Sschwarze end = strchr(path, '/'); 1198*f5938fa6Sschwarze if (end + 1 != name) { 1199daf4c292Sschwarze pg_error_badrequest( 1200daf4c292Sschwarze "You specified too many directory components."); 1201daf4c292Sschwarze exit(EXIT_FAILURE); 120202b1b494Sschwarze } 1203*f5938fa6Sschwarze req->q.arch = mandoc_strndup(path, end - path); 1204*f5938fa6Sschwarze if (validate_arch(req->q.arch) == 0) { 1205daf4c292Sschwarze pg_error_badrequest( 1206daf4c292Sschwarze "You specified an invalid directory component."); 1207daf4c292Sschwarze exit(EXIT_FAILURE); 1208daf4c292Sschwarze } 120902b1b494Sschwarze } 121002b1b494Sschwarze 121102b1b494Sschwarze /* 1212c6c22f12Sschwarze * Scan for indexable paths. 1213c6c22f12Sschwarze */ 1214c6c22f12Sschwarze static void 1215941df026Sschwarze parse_manpath_conf(struct req *req) 1216c6c22f12Sschwarze { 1217c6c22f12Sschwarze FILE *fp; 1218c6c22f12Sschwarze char *dp; 1219c6c22f12Sschwarze size_t dpsz; 122031f93c25Sschwarze ssize_t len; 1221c6c22f12Sschwarze 1222c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) { 1223c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR); 1224de651747Sschwarze pg_error_internal(); 1225de651747Sschwarze exit(EXIT_FAILURE); 1226de651747Sschwarze } 1227c6c22f12Sschwarze 122831f93c25Sschwarze dp = NULL; 122931f93c25Sschwarze dpsz = 0; 123031f93c25Sschwarze 123131f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 123231f93c25Sschwarze if (dp[len - 1] == '\n') 123331f93c25Sschwarze dp[--len] = '\0'; 1234c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1235c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1236cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1237c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1238c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp); 1239cf3a545cSschwarze pg_error_internal(); 1240cf3a545cSschwarze exit(EXIT_FAILURE); 1241cf3a545cSschwarze } 1242c976f0e2Sschwarze if (strchr(dp, '/') != NULL) { 1243c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1244c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp); 1245cf3a545cSschwarze pg_error_internal(); 1246cf3a545cSschwarze exit(EXIT_FAILURE); 1247cf3a545cSschwarze } 1248cf3a545cSschwarze req->p[req->psz++] = dp; 124931f93c25Sschwarze dp = NULL; 125031f93c25Sschwarze dpsz = 0; 1251c6c22f12Sschwarze } 125231f93c25Sschwarze free(dp); 1253de651747Sschwarze 1254de651747Sschwarze if (req->p == NULL) { 1255c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR); 1256de651747Sschwarze pg_error_internal(); 1257de651747Sschwarze exit(EXIT_FAILURE); 1258de651747Sschwarze } 1259c6c22f12Sschwarze } 1260