1*955967fcSschwarze /* $OpenBSD: cgi.c,v 1.103 2019/01/31 23:00:18 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" 3799acaf1eSschwarze #include "mandoc_parse.h" 38c6c22f12Sschwarze #include "main.h" 394de77decSschwarze #include "manconf.h" 40c6c22f12Sschwarze #include "mansearch.h" 416fdade3eSschwarze #include "cgi.h" 42c6c22f12Sschwarze 43c6c22f12Sschwarze /* 44c6c22f12Sschwarze * A query as passed to the search function. 45c6c22f12Sschwarze */ 46c6c22f12Sschwarze struct query { 4731e689c3Sschwarze char *manpath; /* desired manual directory */ 4831e689c3Sschwarze char *arch; /* architecture */ 4931e689c3Sschwarze char *sec; /* manual section */ 50e89321abSschwarze char *query; /* unparsed query expression */ 514477fbfaSschwarze int equal; /* match whole names, not substrings */ 52c6c22f12Sschwarze }; 53c6c22f12Sschwarze 54c6c22f12Sschwarze struct req { 55c6c22f12Sschwarze struct query q; 56c6c22f12Sschwarze char **p; /* array of available manpaths */ 57c6c22f12Sschwarze size_t psz; /* number of available manpaths */ 5852b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */ 59c6c22f12Sschwarze }; 60c6c22f12Sschwarze 6184f05c93Sschwarze enum focus { 6284f05c93Sschwarze FOCUS_NONE = 0, 6384f05c93Sschwarze FOCUS_QUERY 6484f05c93Sschwarze }; 6584f05c93Sschwarze 66c6c22f12Sschwarze static void html_print(const char *); 67c6c22f12Sschwarze static void html_putchar(char); 68c6c22f12Sschwarze static int http_decode(char *); 69f7a12365Sschwarze static void http_encode(const char *p); 70941df026Sschwarze static void parse_manpath_conf(struct req *); 71941df026Sschwarze static void parse_path_info(struct req *req, const char *path); 72941df026Sschwarze static void parse_query_string(struct req *, const char *); 73facea411Sschwarze static void pg_error_badrequest(const char *); 74facea411Sschwarze static void pg_error_internal(void); 75facea411Sschwarze static void pg_index(const struct req *); 76facea411Sschwarze static void pg_noresult(const struct req *, const char *); 77e1beff2aSschwarze static void pg_redirect(const struct req *, const char *); 7857482ef4Sschwarze static void pg_search(const struct req *); 79facea411Sschwarze static void pg_searchres(const struct req *, 80facea411Sschwarze struct manpage *, size_t); 8181060b1aSschwarze static void pg_show(struct req *, const char *); 82fdef72b0Sschwarze static void resp_begin_html(int, const char *, const char *); 83c6c22f12Sschwarze static void resp_begin_http(int, const char *); 84941df026Sschwarze static void resp_catman(const struct req *, const char *); 85711661c7Sschwarze static void resp_copy(const char *); 86c6c22f12Sschwarze static void resp_end_html(void); 87941df026Sschwarze static void resp_format(const struct req *, const char *); 8884f05c93Sschwarze static void resp_searchform(const struct req *, enum focus); 8946723f19Sschwarze static void resp_show(const struct req *, const char *); 90e89321abSschwarze static void set_query_attr(char **, char **); 91f7a12365Sschwarze static int validate_arch(const char *); 92e89321abSschwarze static int validate_filename(const char *); 93e89321abSschwarze static int validate_manpath(const struct req *, const char *); 94e89321abSschwarze static int validate_urifrag(const char *); 95c6c22f12Sschwarze 963b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 97c6c22f12Sschwarze 9846723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 9928e449d6Sschwarze static const char *const sec_numbers[] = { 10028e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 10128e449d6Sschwarze }; 10228e449d6Sschwarze static const char *const sec_names[] = { 10328e449d6Sschwarze "All Sections", 10428e449d6Sschwarze "1 - General Commands", 10528e449d6Sschwarze "2 - System Calls", 1064e6618fdSschwarze "3 - Library Functions", 1074e6618fdSschwarze "3p - Perl Library", 1084e6618fdSschwarze "4 - Device Drivers", 10928e449d6Sschwarze "5 - File Formats", 11028e449d6Sschwarze "6 - Games", 1114e6618fdSschwarze "7 - Miscellaneous Information", 1124e6618fdSschwarze "8 - System Manager\'s Manual", 1134e6618fdSschwarze "9 - Kernel Developer\'s Manual" 11428e449d6Sschwarze }; 11528e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 11628e449d6Sschwarze 11728e449d6Sschwarze static const char *const arch_names[] = { 11893d3e4e1Sderaadt "amd64", "alpha", "armv7", "arm64", 119766ef059Sschwarze "hppa", "i386", "landisk", 1203bcd4815Sschwarze "loongson", "luna88k", "macppc", "mips64", 1216250a715Sschwarze "octeon", "sgi", "socppc", "sparc64", 122766ef059Sschwarze "amiga", "arc", "armish", "arm32", 123766ef059Sschwarze "atari", "aviion", "beagle", "cats", 124766ef059Sschwarze "hppa64", "hp300", 1253bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k", 1263bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos", 1270c245db5Sschwarze "pmax", "powerpc", "solbourne", "sparc", 1286250a715Sschwarze "sun3", "vax", "wgrisc", "x68k", 1296250a715Sschwarze "zaurus" 13028e449d6Sschwarze }; 13128e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 13228e449d6Sschwarze 133c6c22f12Sschwarze /* 134c6c22f12Sschwarze * Print a character, escaping HTML along the way. 135c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 136c6c22f12Sschwarze */ 137c6c22f12Sschwarze static void 138c6c22f12Sschwarze html_putchar(char c) 139c6c22f12Sschwarze { 140c6c22f12Sschwarze 141c6c22f12Sschwarze switch (c) { 142e74fa2aeSschwarze case '"': 143f765a656Sbentley printf("""); 144c6c22f12Sschwarze break; 145e74fa2aeSschwarze case '&': 146c6c22f12Sschwarze printf("&"); 147c6c22f12Sschwarze break; 148e74fa2aeSschwarze case '>': 149c6c22f12Sschwarze printf(">"); 150c6c22f12Sschwarze break; 151e74fa2aeSschwarze case '<': 152c6c22f12Sschwarze printf("<"); 153c6c22f12Sschwarze break; 154c6c22f12Sschwarze default: 155c6c22f12Sschwarze putchar((unsigned char)c); 156c6c22f12Sschwarze break; 157c6c22f12Sschwarze } 158c6c22f12Sschwarze } 159c6c22f12Sschwarze 160c6c22f12Sschwarze /* 161c6c22f12Sschwarze * Call through to html_putchar(). 162c6c22f12Sschwarze * Accepts NULL strings. 163c6c22f12Sschwarze */ 164c6c22f12Sschwarze static void 165c6c22f12Sschwarze html_print(const char *p) 166c6c22f12Sschwarze { 167c6c22f12Sschwarze 168c6c22f12Sschwarze if (NULL == p) 169c6c22f12Sschwarze return; 170c6c22f12Sschwarze while ('\0' != *p) 171c6c22f12Sschwarze html_putchar(*p++); 172c6c22f12Sschwarze } 173c6c22f12Sschwarze 174c6c22f12Sschwarze /* 17531e689c3Sschwarze * Transfer the responsibility for the allocated string *val 17631e689c3Sschwarze * to the query structure. 177c6c22f12Sschwarze */ 178c6c22f12Sschwarze static void 17931e689c3Sschwarze set_query_attr(char **attr, char **val) 18031e689c3Sschwarze { 18131e689c3Sschwarze 18231e689c3Sschwarze free(*attr); 18331e689c3Sschwarze if (**val == '\0') { 18431e689c3Sschwarze *attr = NULL; 18531e689c3Sschwarze free(*val); 18631e689c3Sschwarze } else 18731e689c3Sschwarze *attr = *val; 18831e689c3Sschwarze *val = NULL; 18931e689c3Sschwarze } 19031e689c3Sschwarze 19131e689c3Sschwarze /* 19231e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 19331e689c3Sschwarze * and store the values into the query structure. 19431e689c3Sschwarze */ 19531e689c3Sschwarze static void 196941df026Sschwarze parse_query_string(struct req *req, const char *qs) 197c6c22f12Sschwarze { 198c6c22f12Sschwarze char *key, *val; 19931e689c3Sschwarze size_t keysz, valsz; 200c6c22f12Sschwarze 20152b413c1Sschwarze req->isquery = 1; 20231e689c3Sschwarze req->q.manpath = NULL; 20331e689c3Sschwarze req->q.arch = NULL; 20431e689c3Sschwarze req->q.sec = NULL; 205e89321abSschwarze req->q.query = NULL; 2064477fbfaSschwarze req->q.equal = 1; 207c6c22f12Sschwarze 20831e689c3Sschwarze key = val = NULL; 20931e689c3Sschwarze while (*qs != '\0') { 210c6c22f12Sschwarze 21131e689c3Sschwarze /* Parse one key. */ 212c6c22f12Sschwarze 21331e689c3Sschwarze keysz = strcspn(qs, "=;&"); 21431e689c3Sschwarze key = mandoc_strndup(qs, keysz); 21531e689c3Sschwarze qs += keysz; 21631e689c3Sschwarze if (*qs != '=') 21731e689c3Sschwarze goto next; 218c6c22f12Sschwarze 21931e689c3Sschwarze /* Parse one value. */ 220c6c22f12Sschwarze 22131e689c3Sschwarze valsz = strcspn(++qs, ";&"); 22231e689c3Sschwarze val = mandoc_strndup(qs, valsz); 22331e689c3Sschwarze qs += valsz; 224c6c22f12Sschwarze 22531e689c3Sschwarze /* Decode and catch encoding errors. */ 22631e689c3Sschwarze 22731e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 22831e689c3Sschwarze goto next; 22931e689c3Sschwarze 23031e689c3Sschwarze /* Handle key-value pairs. */ 23131e689c3Sschwarze 23231e689c3Sschwarze if ( ! strcmp(key, "query")) 233e89321abSschwarze set_query_attr(&req->q.query, &val); 23431e689c3Sschwarze 23531e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 23631e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 23731e689c3Sschwarze 23831e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 239aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 24031e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 241aabcc9a1Sschwarze val[7] = '-'; 242aabcc9a1Sschwarze if ('C' == val[8]) 243aabcc9a1Sschwarze val[8] = 'c'; 244aabcc9a1Sschwarze } 245aabcc9a1Sschwarze #endif 24631e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 24731e689c3Sschwarze } 24831e689c3Sschwarze 24931e689c3Sschwarze else if ( ! (strcmp(key, "sec") 250aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 25131e689c3Sschwarze && strcmp(key, "sektion") 252aabcc9a1Sschwarze #endif 25331e689c3Sschwarze )) { 25431e689c3Sschwarze if ( ! strcmp(val, "0")) 25531e689c3Sschwarze *val = '\0'; 25631e689c3Sschwarze set_query_attr(&req->q.sec, &val); 257c6c22f12Sschwarze } 25831e689c3Sschwarze 25931e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 26031e689c3Sschwarze if ( ! strcmp(val, "default")) 26131e689c3Sschwarze *val = '\0'; 26231e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2634477fbfaSschwarze } 26431e689c3Sschwarze 26531e689c3Sschwarze /* 26631e689c3Sschwarze * The key must be freed in any case. 26731e689c3Sschwarze * The val may have been handed over to the query 26831e689c3Sschwarze * structure, in which case it is now NULL. 26931e689c3Sschwarze */ 27031e689c3Sschwarze next: 27131e689c3Sschwarze free(key); 27231e689c3Sschwarze key = NULL; 27331e689c3Sschwarze free(val); 27431e689c3Sschwarze val = NULL; 27531e689c3Sschwarze 27631e689c3Sschwarze if (*qs != '\0') 27731e689c3Sschwarze qs++; 27831e689c3Sschwarze } 279c6c22f12Sschwarze } 280c6c22f12Sschwarze 281c6c22f12Sschwarze /* 282c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 283c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 284c6c22f12Sschwarze * over the allocated string. 285c6c22f12Sschwarze */ 286c6c22f12Sschwarze static int 287c6c22f12Sschwarze http_decode(char *p) 288c6c22f12Sschwarze { 289c6c22f12Sschwarze char hex[3]; 2901f69f32bStedu char *q; 291c6c22f12Sschwarze int c; 292c6c22f12Sschwarze 293c6c22f12Sschwarze hex[2] = '\0'; 294c6c22f12Sschwarze 2951f69f32bStedu q = p; 2961f69f32bStedu for ( ; '\0' != *p; p++, q++) { 297c6c22f12Sschwarze if ('%' == *p) { 298c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 299526e306bSschwarze return 0; 300c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 301526e306bSschwarze return 0; 302c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 303526e306bSschwarze return 0; 304c6c22f12Sschwarze if ('\0' == c) 305526e306bSschwarze return 0; 306c6c22f12Sschwarze 3071f69f32bStedu *q = (char)c; 3081f69f32bStedu p += 2; 309c6c22f12Sschwarze } else 3101f69f32bStedu *q = '+' == *p ? ' ' : *p; 311c6c22f12Sschwarze } 312c6c22f12Sschwarze 3131f69f32bStedu *q = '\0'; 314526e306bSschwarze return 1; 315c6c22f12Sschwarze } 316c6c22f12Sschwarze 317c6c22f12Sschwarze static void 318f7a12365Sschwarze http_encode(const char *p) 319f7a12365Sschwarze { 320f7a12365Sschwarze for (; *p != '\0'; p++) { 321f7a12365Sschwarze if (isalnum((unsigned char)*p) == 0 && 322f7a12365Sschwarze strchr("-._~", *p) == NULL) 323f7a12365Sschwarze printf("%%%02.2X", (unsigned char)*p); 324f7a12365Sschwarze else 325f7a12365Sschwarze putchar(*p); 326f7a12365Sschwarze } 327f7a12365Sschwarze } 328f7a12365Sschwarze 329f7a12365Sschwarze static void 330c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 331c6c22f12Sschwarze { 332c6c22f12Sschwarze 333c6c22f12Sschwarze if (200 != code) 334fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 335c6c22f12Sschwarze 336fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 337fa9c540aStedu "Cache-Control: no-cache\r\n" 338fa9c540aStedu "Pragma: no-cache\r\n" 339fa9c540aStedu "\r\n"); 340c6c22f12Sschwarze 341c6c22f12Sschwarze fflush(stdout); 342c6c22f12Sschwarze } 343c6c22f12Sschwarze 344c6c22f12Sschwarze static void 345711661c7Sschwarze resp_copy(const char *filename) 346711661c7Sschwarze { 347711661c7Sschwarze char buf[4096]; 348711661c7Sschwarze ssize_t sz; 349711661c7Sschwarze int fd; 350711661c7Sschwarze 351711661c7Sschwarze if ((fd = open(filename, O_RDONLY)) != -1) { 352711661c7Sschwarze fflush(stdout); 353711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 354711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 355fd3cdd86Sjsg close(fd); 356711661c7Sschwarze } 357711661c7Sschwarze } 358711661c7Sschwarze 359711661c7Sschwarze static void 360fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file) 361c6c22f12Sschwarze { 362fdef72b0Sschwarze char *cp; 363c6c22f12Sschwarze 364c6c22f12Sschwarze resp_begin_http(code, msg); 365c6c22f12Sschwarze 366d649d931Sschwarze printf("<!DOCTYPE html>\n" 367735516bdSschwarze "<html>\n" 368735516bdSschwarze "<head>\n" 369735516bdSschwarze " <meta charset=\"UTF-8\"/>\n" 370661a42d8Sschwarze " <meta name=\"viewport\"" 371661a42d8Sschwarze " content=\"width=device-width, initial-scale=1.0\">\n" 372735516bdSschwarze " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 373735516bdSschwarze " type=\"text/css\" media=\"all\">\n" 374fdef72b0Sschwarze " <title>", 375fdef72b0Sschwarze CSS_DIR); 376fdef72b0Sschwarze if (file != NULL) { 377fdef72b0Sschwarze if ((cp = strrchr(file, '/')) != NULL) 378fdef72b0Sschwarze file = cp + 1; 379fdef72b0Sschwarze if ((cp = strrchr(file, '.')) != NULL) { 380fdef72b0Sschwarze printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1); 381fdef72b0Sschwarze } else 382fdef72b0Sschwarze printf("%s - ", file); 383fdef72b0Sschwarze } 384fdef72b0Sschwarze printf("%s</title>\n" 385735516bdSschwarze "</head>\n" 386ce781f36Sschwarze "<body>\n", 387fdef72b0Sschwarze CUSTOMIZE_TITLE); 388711661c7Sschwarze 389711661c7Sschwarze resp_copy(MAN_DIR "/header.html"); 390c6c22f12Sschwarze } 391c6c22f12Sschwarze 392c6c22f12Sschwarze static void 393c6c22f12Sschwarze resp_end_html(void) 394c6c22f12Sschwarze { 395c6c22f12Sschwarze 396711661c7Sschwarze resp_copy(MAN_DIR "/footer.html"); 397711661c7Sschwarze 398735516bdSschwarze puts("</body>\n" 399735516bdSschwarze "</html>"); 400c6c22f12Sschwarze } 401c6c22f12Sschwarze 402c6c22f12Sschwarze static void 40384f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus) 404c6c22f12Sschwarze { 405c6c22f12Sschwarze int i; 406c6c22f12Sschwarze 407ce781f36Sschwarze printf("<form action=\"/%s\" method=\"get\">\n" 408735516bdSschwarze " <fieldset>\n" 409735516bdSschwarze " <legend>Manual Page Search Parameters</legend>\n", 410c6c22f12Sschwarze scriptname); 41128e449d6Sschwarze 41228e449d6Sschwarze /* Write query input box. */ 41328e449d6Sschwarze 4146b824e3bSschwarze printf(" <input type=\"search\" name=\"query\" value=\""); 41584f05c93Sschwarze if (req->q.query != NULL) 416e89321abSschwarze html_print(req->q.query); 41784f05c93Sschwarze printf( "\" size=\"40\""); 41884f05c93Sschwarze if (focus == FOCUS_QUERY) 41984f05c93Sschwarze printf(" autofocus"); 42084f05c93Sschwarze puts(">"); 42128e449d6Sschwarze 422784a63d6Sschwarze /* Write submission buttons. */ 42328e449d6Sschwarze 424784a63d6Sschwarze printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">" 425784a63d6Sschwarze "man</button>\n" 426784a63d6Sschwarze " <button type=\"submit\" name=\"apropos\" value=\"1\">" 427542ee4bfSschwarze "apropos</button>\n" 428542ee4bfSschwarze " <br/>\n"); 42928e449d6Sschwarze 43028e449d6Sschwarze /* Write section selector. */ 43128e449d6Sschwarze 432784a63d6Sschwarze puts(" <select name=\"sec\">"); 43328e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 434735516bdSschwarze printf(" <option value=\"%s\"", sec_numbers[i]); 43528e449d6Sschwarze if (NULL != req->q.sec && 43628e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 437735516bdSschwarze printf(" selected=\"selected\""); 438735516bdSschwarze printf(">%s</option>\n", sec_names[i]); 43928e449d6Sschwarze } 440735516bdSschwarze puts(" </select>"); 44128e449d6Sschwarze 44228e449d6Sschwarze /* Write architecture selector. */ 44328e449d6Sschwarze 444735516bdSschwarze printf( " <select name=\"arch\">\n" 445735516bdSschwarze " <option value=\"default\""); 446be14a32aSschwarze if (NULL == req->q.arch) 447735516bdSschwarze printf(" selected=\"selected\""); 448735516bdSschwarze puts(">All Architectures</option>"); 44928e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 4506b824e3bSschwarze printf(" <option"); 45128e449d6Sschwarze if (NULL != req->q.arch && 45228e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 453735516bdSschwarze printf(" selected=\"selected\""); 454735516bdSschwarze printf(">%s</option>\n", arch_names[i]); 45528e449d6Sschwarze } 456735516bdSschwarze puts(" </select>"); 45728e449d6Sschwarze 45828e449d6Sschwarze /* Write manpath selector. */ 45928e449d6Sschwarze 460c6c22f12Sschwarze if (req->psz > 1) { 461735516bdSschwarze puts(" <select name=\"manpath\">"); 462c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 463735516bdSschwarze printf(" <option"); 46450eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 465735516bdSschwarze printf(" selected=\"selected\""); 4666b824e3bSschwarze printf(">"); 467c6c22f12Sschwarze html_print(req->p[i]); 468735516bdSschwarze puts("</option>"); 469c6c22f12Sschwarze } 470735516bdSschwarze puts(" </select>"); 471c6c22f12Sschwarze } 47228e449d6Sschwarze 473784a63d6Sschwarze puts(" </fieldset>\n" 474ce781f36Sschwarze "</form>"); 475c6c22f12Sschwarze } 476c6c22f12Sschwarze 47781475784Sschwarze static int 478cf3a545cSschwarze validate_urifrag(const char *frag) 479cf3a545cSschwarze { 480cf3a545cSschwarze 481cf3a545cSschwarze while ('\0' != *frag) { 482cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 483cf3a545cSschwarze '-' == *frag || '.' == *frag || 484cf3a545cSschwarze '/' == *frag || '_' == *frag)) 485526e306bSschwarze return 0; 486cf3a545cSschwarze frag++; 487cf3a545cSschwarze } 488526e306bSschwarze return 1; 489cf3a545cSschwarze } 490cf3a545cSschwarze 491cf3a545cSschwarze static int 492631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 493631ce2c6Sschwarze { 494631ce2c6Sschwarze size_t i; 495631ce2c6Sschwarze 496631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 497631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 498526e306bSschwarze return 1; 499631ce2c6Sschwarze 500526e306bSschwarze return 0; 501631ce2c6Sschwarze } 502631ce2c6Sschwarze 503631ce2c6Sschwarze static int 504f7a12365Sschwarze validate_arch(const char *arch) 505f7a12365Sschwarze { 506f7a12365Sschwarze int i; 507f7a12365Sschwarze 508f7a12365Sschwarze for (i = 0; i < arch_MAX; i++) 509f7a12365Sschwarze if (strcmp(arch, arch_names[i]) == 0) 510f7a12365Sschwarze return 1; 511f7a12365Sschwarze 512f7a12365Sschwarze return 0; 513f7a12365Sschwarze } 514f7a12365Sschwarze 515f7a12365Sschwarze static int 51681475784Sschwarze validate_filename(const char *file) 51781475784Sschwarze { 51881475784Sschwarze 51981475784Sschwarze if ('.' == file[0] && '/' == file[1]) 52081475784Sschwarze file += 2; 52181475784Sschwarze 522526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 523526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 52481475784Sschwarze } 52581475784Sschwarze 526c6c22f12Sschwarze static void 527facea411Sschwarze pg_index(const struct req *req) 528c6c22f12Sschwarze { 529c6c22f12Sschwarze 530fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 53184f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 532735516bdSschwarze printf("<p>\n" 533d56ca219Sschwarze "This web interface is documented in the\n" 5340a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n" 535d56ca219Sschwarze "manual, and the\n" 5360a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n" 5372a43838fSschwarze "manual explains the query syntax.\n" 538735516bdSschwarze "</p>\n", 5393b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5403b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 541c6c22f12Sschwarze resp_end_html(); 542c6c22f12Sschwarze } 543c6c22f12Sschwarze 544c6c22f12Sschwarze static void 545facea411Sschwarze pg_noresult(const struct req *req, const char *msg) 546c6c22f12Sschwarze { 547fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 54884f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 549735516bdSschwarze puts("<p>"); 550c6c22f12Sschwarze puts(msg); 551735516bdSschwarze puts("</p>"); 552c6c22f12Sschwarze resp_end_html(); 553c6c22f12Sschwarze } 554c6c22f12Sschwarze 555c6c22f12Sschwarze static void 556facea411Sschwarze pg_error_badrequest(const char *msg) 557c6c22f12Sschwarze { 558c6c22f12Sschwarze 559fdef72b0Sschwarze resp_begin_html(400, "Bad Request", NULL); 560735516bdSschwarze puts("<h1>Bad Request</h1>\n" 561735516bdSschwarze "<p>\n"); 562c6c22f12Sschwarze puts(msg); 563c6c22f12Sschwarze printf("Try again from the\n" 564735516bdSschwarze "<a href=\"/%s\">main page</a>.\n" 565735516bdSschwarze "</p>", scriptname); 566c6c22f12Sschwarze resp_end_html(); 567c6c22f12Sschwarze } 568c6c22f12Sschwarze 569c6c22f12Sschwarze static void 570facea411Sschwarze pg_error_internal(void) 571c6c22f12Sschwarze { 572fdef72b0Sschwarze resp_begin_html(500, "Internal Server Error", NULL); 573735516bdSschwarze puts("<p>Internal Server Error</p>"); 574c6c22f12Sschwarze resp_end_html(); 575c6c22f12Sschwarze } 576c6c22f12Sschwarze 577c6c22f12Sschwarze static void 578e1beff2aSschwarze pg_redirect(const struct req *req, const char *name) 579e1beff2aSschwarze { 580e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 581e0d9a108Sschwarze "Location: /"); 582e1beff2aSschwarze if (*scriptname != '\0') 583e1beff2aSschwarze printf("%s/", scriptname); 584e1beff2aSschwarze if (strcmp(req->q.manpath, req->p[0])) 585e1beff2aSschwarze printf("%s/", req->q.manpath); 586e1beff2aSschwarze if (req->q.arch != NULL) 587e1beff2aSschwarze printf("%s/", req->q.arch); 588f7a12365Sschwarze http_encode(name); 589f7a12365Sschwarze if (req->q.sec != NULL) { 590f7a12365Sschwarze putchar('.'); 591f7a12365Sschwarze http_encode(req->q.sec); 592f7a12365Sschwarze } 593e1beff2aSschwarze printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"); 594e1beff2aSschwarze } 595e1beff2aSschwarze 596e1beff2aSschwarze static void 597facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 598c6c22f12Sschwarze { 599be14a32aSschwarze char *arch, *archend; 600db26164eSschwarze const char *sec; 601db26164eSschwarze size_t i, iuse; 602be14a32aSschwarze int archprio, archpriouse; 60346723f19Sschwarze int prio, priouse; 604c6c22f12Sschwarze 60581475784Sschwarze for (i = 0; i < sz; i++) { 60681475784Sschwarze if (validate_filename(r[i].file)) 60781475784Sschwarze continue; 608c976f0e2Sschwarze warnx("invalid filename %s in %s database", 60981475784Sschwarze r[i].file, req->q.manpath); 61081475784Sschwarze pg_error_internal(); 61181475784Sschwarze return; 61281475784Sschwarze } 61381475784Sschwarze 61452b413c1Sschwarze if (req->isquery && sz == 1) { 615c6c22f12Sschwarze /* 616c6c22f12Sschwarze * If we have just one result, then jump there now 617c6c22f12Sschwarze * without any delay. 618c6c22f12Sschwarze */ 619e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 620e0d9a108Sschwarze "Location: /"); 621e0d9a108Sschwarze if (*scriptname != '\0') 622e0d9a108Sschwarze printf("%s/", scriptname); 623e0d9a108Sschwarze if (strcmp(req->q.manpath, req->p[0])) 624e0d9a108Sschwarze printf("%s/", req->q.manpath); 625e0d9a108Sschwarze printf("%s\r\n" 626e0d9a108Sschwarze "Content-Type: text/html; charset=utf-8\r\n\r\n", 627e0d9a108Sschwarze r[0].file); 628c6c22f12Sschwarze return; 629c6c22f12Sschwarze } 630c6c22f12Sschwarze 63146723f19Sschwarze /* 63246723f19Sschwarze * In man(1) mode, show one of the pages 63346723f19Sschwarze * even if more than one is found. 63446723f19Sschwarze */ 63546723f19Sschwarze 63646723f19Sschwarze iuse = 0; 637fdef72b0Sschwarze if (req->q.equal || sz == 1) { 638db26164eSschwarze priouse = 20; 639be14a32aSschwarze archpriouse = 3; 64046723f19Sschwarze for (i = 0; i < sz; i++) { 641db26164eSschwarze sec = r[i].file; 642db26164eSschwarze sec += strcspn(sec, "123456789"); 643db26164eSschwarze if (sec[0] == '\0') 64446723f19Sschwarze continue; 645db26164eSschwarze prio = sec_prios[sec[0] - '1']; 646db26164eSschwarze if (sec[1] != '/') 647db26164eSschwarze prio += 10; 648db26164eSschwarze if (req->q.arch == NULL) { 649be14a32aSschwarze archprio = 650db26164eSschwarze ((arch = strchr(sec + 1, '/')) 651db26164eSschwarze == NULL) ? 3 : 652db26164eSschwarze ((archend = strchr(arch + 1, '/')) 653db26164eSschwarze == NULL) ? 0 : 654be14a32aSschwarze strncmp(arch, "amd64/", 655be14a32aSschwarze archend - arch) ? 2 : 1; 656be14a32aSschwarze if (archprio < archpriouse) { 657be14a32aSschwarze archpriouse = archprio; 658be14a32aSschwarze priouse = prio; 659be14a32aSschwarze iuse = i; 660be14a32aSschwarze continue; 661be14a32aSschwarze } 662be14a32aSschwarze if (archprio > archpriouse) 663be14a32aSschwarze continue; 664be14a32aSschwarze } 66546723f19Sschwarze if (prio >= priouse) 66646723f19Sschwarze continue; 66746723f19Sschwarze priouse = prio; 66846723f19Sschwarze iuse = i; 66946723f19Sschwarze } 670fdef72b0Sschwarze resp_begin_html(200, NULL, r[iuse].file); 671fdef72b0Sschwarze } else 672fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 673fdef72b0Sschwarze 674fdef72b0Sschwarze resp_searchform(req, 675fdef72b0Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 676fdef72b0Sschwarze 677fdef72b0Sschwarze if (sz > 1) { 678fdef72b0Sschwarze puts("<table class=\"results\">"); 679fdef72b0Sschwarze for (i = 0; i < sz; i++) { 680fdef72b0Sschwarze printf(" <tr>\n" 681fdef72b0Sschwarze " <td>" 6827b3dbe16Sschwarze "<a class=\"Xr\" href=\"/"); 6837b3dbe16Sschwarze if (*scriptname != '\0') 6847b3dbe16Sschwarze printf("%s/", scriptname); 6857b3dbe16Sschwarze if (strcmp(req->q.manpath, req->p[0])) 6867b3dbe16Sschwarze printf("%s/", req->q.manpath); 6877b3dbe16Sschwarze printf("%s\">", r[i].file); 688fdef72b0Sschwarze html_print(r[i].names); 689fdef72b0Sschwarze printf("</a></td>\n" 690fdef72b0Sschwarze " <td><span class=\"Nd\">"); 691fdef72b0Sschwarze html_print(r[i].output); 692fdef72b0Sschwarze puts("</span></td>\n" 693fdef72b0Sschwarze " </tr>"); 694fdef72b0Sschwarze } 695fdef72b0Sschwarze puts("</table>"); 696fdef72b0Sschwarze } 697fdef72b0Sschwarze 698fdef72b0Sschwarze if (req->q.equal || sz == 1) { 699fdef72b0Sschwarze puts("<hr>"); 70046723f19Sschwarze resp_show(req, r[iuse].file); 70146723f19Sschwarze } 70246723f19Sschwarze 703c6c22f12Sschwarze resp_end_html(); 704c6c22f12Sschwarze } 705c6c22f12Sschwarze 706c6c22f12Sschwarze static void 707941df026Sschwarze resp_catman(const struct req *req, const char *file) 708c6c22f12Sschwarze { 709c6c22f12Sschwarze FILE *f; 710c6c22f12Sschwarze char *p; 71131f93c25Sschwarze size_t sz; 71231f93c25Sschwarze ssize_t len; 71331f93c25Sschwarze int i; 714c6c22f12Sschwarze int italic, bold; 715c6c22f12Sschwarze 71631f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 717735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 718c6c22f12Sschwarze return; 719c6c22f12Sschwarze } 720c6c22f12Sschwarze 721735516bdSschwarze puts("<div class=\"catman\">\n" 722735516bdSschwarze "<pre>"); 723c6c22f12Sschwarze 72431f93c25Sschwarze p = NULL; 72531f93c25Sschwarze sz = 0; 72631f93c25Sschwarze 72731f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 728c6c22f12Sschwarze bold = italic = 0; 72931f93c25Sschwarze for (i = 0; i < len - 1; i++) { 730c6c22f12Sschwarze /* 731c6c22f12Sschwarze * This means that the catpage is out of state. 732c6c22f12Sschwarze * Ignore it and keep going (although the 733c6c22f12Sschwarze * catpage is bogus). 734c6c22f12Sschwarze */ 735c6c22f12Sschwarze 736c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 737c6c22f12Sschwarze continue; 738c6c22f12Sschwarze 739c6c22f12Sschwarze /* 740c6c22f12Sschwarze * Print a regular character. 741c6c22f12Sschwarze * Close out any bold/italic scopes. 742c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 743c6c22f12Sschwarze * have something to enter when we backspace. 744c6c22f12Sschwarze */ 745c6c22f12Sschwarze 746c6c22f12Sschwarze if ('\b' != p[i + 1]) { 747c6c22f12Sschwarze if (italic) 748735516bdSschwarze printf("</i>"); 749c6c22f12Sschwarze if (bold) 750735516bdSschwarze printf("</b>"); 751c6c22f12Sschwarze italic = bold = 0; 752c6c22f12Sschwarze html_putchar(p[i]); 753c6c22f12Sschwarze continue; 75431f93c25Sschwarze } else if (i + 2 >= len) 755c6c22f12Sschwarze continue; 756c6c22f12Sschwarze 757c6c22f12Sschwarze /* Italic mode. */ 758c6c22f12Sschwarze 759c6c22f12Sschwarze if ('_' == p[i]) { 760c6c22f12Sschwarze if (bold) 761735516bdSschwarze printf("</b>"); 762c6c22f12Sschwarze if ( ! italic) 763735516bdSschwarze printf("<i>"); 764c6c22f12Sschwarze bold = 0; 765c6c22f12Sschwarze italic = 1; 766c6c22f12Sschwarze i += 2; 767c6c22f12Sschwarze html_putchar(p[i]); 768c6c22f12Sschwarze continue; 769c6c22f12Sschwarze } 770c6c22f12Sschwarze 771c6c22f12Sschwarze /* 772c6c22f12Sschwarze * Handle funny behaviour troff-isms. 773c6c22f12Sschwarze * These grok'd from the original man2html.c. 774c6c22f12Sschwarze */ 775c6c22f12Sschwarze 776c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 777c6c22f12Sschwarze ('o' == 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 ('|' == p[i] && '*' == p[i + 2])) { 784c6c22f12Sschwarze if (italic) 785735516bdSschwarze printf("</i>"); 786c6c22f12Sschwarze if (bold) 787735516bdSschwarze printf("</b>"); 788c6c22f12Sschwarze italic = bold = 0; 789c6c22f12Sschwarze putchar('*'); 790c6c22f12Sschwarze i += 2; 791c6c22f12Sschwarze continue; 792c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 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 ('|' == p[i] && '+' == p[i + 1])) { 798c6c22f12Sschwarze if (italic) 799735516bdSschwarze printf("</i>"); 800c6c22f12Sschwarze if (bold) 801735516bdSschwarze printf("</b>"); 802c6c22f12Sschwarze italic = bold = 0; 803c6c22f12Sschwarze putchar('+'); 804c6c22f12Sschwarze i += 2; 805c6c22f12Sschwarze continue; 806c6c22f12Sschwarze } 807c6c22f12Sschwarze 808c6c22f12Sschwarze /* Bold mode. */ 809c6c22f12Sschwarze 810c6c22f12Sschwarze if (italic) 811735516bdSschwarze printf("</i>"); 812c6c22f12Sschwarze if ( ! bold) 813735516bdSschwarze printf("<b>"); 814c6c22f12Sschwarze bold = 1; 815c6c22f12Sschwarze italic = 0; 816c6c22f12Sschwarze i += 2; 817c6c22f12Sschwarze html_putchar(p[i]); 818c6c22f12Sschwarze } 819c6c22f12Sschwarze 820c6c22f12Sschwarze /* 821c6c22f12Sschwarze * Clean up the last character. 822c6c22f12Sschwarze * We can get to a newline; don't print that. 823c6c22f12Sschwarze */ 824c6c22f12Sschwarze 825c6c22f12Sschwarze if (italic) 826735516bdSschwarze printf("</i>"); 827c6c22f12Sschwarze if (bold) 828735516bdSschwarze printf("</b>"); 829c6c22f12Sschwarze 83031f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 831c6c22f12Sschwarze html_putchar(p[i]); 832c6c22f12Sschwarze 833c6c22f12Sschwarze putchar('\n'); 834c6c22f12Sschwarze } 83531f93c25Sschwarze free(p); 836c6c22f12Sschwarze 837735516bdSschwarze puts("</pre>\n" 838735516bdSschwarze "</div>"); 839c6c22f12Sschwarze 840c6c22f12Sschwarze fclose(f); 841c6c22f12Sschwarze } 842c6c22f12Sschwarze 843c6c22f12Sschwarze static void 844941df026Sschwarze resp_format(const struct req *req, const char *file) 845c6c22f12Sschwarze { 8462ccd0917Sschwarze struct manoutput conf; 847c6c22f12Sschwarze struct mparse *mp; 8486b86842eSschwarze struct roff_meta *meta; 849c6c22f12Sschwarze void *vp; 850f74d674aSschwarze int fd; 851f74d674aSschwarze int usepath; 852c6c22f12Sschwarze 853c6c22f12Sschwarze if (-1 == (fd = open(file, O_RDONLY, 0))) { 854735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 855c6c22f12Sschwarze return; 856c6c22f12Sschwarze } 857c6c22f12Sschwarze 85816536faaSschwarze mchars_alloc(); 8596b86842eSschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 | 8606b86842eSschwarze MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath); 861df927bb6Sschwarze mparse_readfd(mp, fd, file); 862c6c22f12Sschwarze close(fd); 8636b86842eSschwarze meta = mparse_result(mp); 864c6c22f12Sschwarze 8652ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 8662ccd0917Sschwarze conf.fragment = 1; 8677c6e1b3aSschwarze conf.style = mandoc_strdup(CSS_DIR "/mandoc.css"); 86832f0ba5fSschwarze conf.toc = 1; 869f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 870ac2abdb8Sschwarze mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S", 871ac2abdb8Sschwarze scriptname, *scriptname == '\0' ? "" : "/", 872f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 873c6c22f12Sschwarze 87416536faaSschwarze vp = html_alloc(&conf); 8756b86842eSschwarze if (meta->macroset == MACROSET_MDOC) 8766b86842eSschwarze html_mdoc(vp, meta); 8776b86842eSschwarze else 8786b86842eSschwarze html_man(vp, meta); 879c6c22f12Sschwarze 880c6c22f12Sschwarze html_free(vp); 881c6c22f12Sschwarze mparse_free(mp); 88216536faaSschwarze mchars_free(); 8832ccd0917Sschwarze free(conf.man); 8847c6e1b3aSschwarze free(conf.style); 885c6c22f12Sschwarze } 886c6c22f12Sschwarze 887c6c22f12Sschwarze static void 88846723f19Sschwarze resp_show(const struct req *req, const char *file) 88946723f19Sschwarze { 89081475784Sschwarze 89181475784Sschwarze if ('.' == file[0] && '/' == file[1]) 8922f7bef27Sschwarze file += 2; 89346723f19Sschwarze 89446723f19Sschwarze if ('c' == *file) 895941df026Sschwarze resp_catman(req, file); 89646723f19Sschwarze else 897941df026Sschwarze resp_format(req, file); 89846723f19Sschwarze } 89946723f19Sschwarze 90046723f19Sschwarze static void 901b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 902c6c22f12Sschwarze { 903b53c14c3Sschwarze char *manpath; 904b53c14c3Sschwarze const char *file; 905c6c22f12Sschwarze 906b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 907facea411Sschwarze pg_error_badrequest( 908c6c22f12Sschwarze "You did not specify a page to show."); 909c6c22f12Sschwarze return; 910c6c22f12Sschwarze } 911b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 912b53c14c3Sschwarze file++; 913c6c22f12Sschwarze 914b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 915631ce2c6Sschwarze pg_error_badrequest( 916631ce2c6Sschwarze "You specified an invalid manpath."); 917b53c14c3Sschwarze free(manpath); 918631ce2c6Sschwarze return; 919631ce2c6Sschwarze } 920631ce2c6Sschwarze 921c6c22f12Sschwarze /* 922c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 923c6c22f12Sschwarze * This way we can pick up the database files, which are 924c6c22f12Sschwarze * relative to the manpath root. 925c6c22f12Sschwarze */ 926c6c22f12Sschwarze 927b53c14c3Sschwarze if (chdir(manpath) == -1) { 928c976f0e2Sschwarze warn("chdir %s", manpath); 929631ce2c6Sschwarze pg_error_internal(); 930b53c14c3Sschwarze free(manpath); 931c6c22f12Sschwarze return; 932c6c22f12Sschwarze } 933b53c14c3Sschwarze free(manpath); 934b53c14c3Sschwarze 935b53c14c3Sschwarze if ( ! validate_filename(file)) { 93681475784Sschwarze pg_error_badrequest( 93781475784Sschwarze "You specified an invalid manual file."); 93881475784Sschwarze return; 93981475784Sschwarze } 94081475784Sschwarze 941fdef72b0Sschwarze resp_begin_html(200, NULL, file); 94284f05c93Sschwarze resp_searchform(req, FOCUS_NONE); 943b53c14c3Sschwarze resp_show(req, file); 94446723f19Sschwarze resp_end_html(); 945c6c22f12Sschwarze } 946c6c22f12Sschwarze 947c6c22f12Sschwarze static void 94857482ef4Sschwarze pg_search(const struct req *req) 949c6c22f12Sschwarze { 950c6c22f12Sschwarze struct mansearch search; 951c6c22f12Sschwarze struct manpaths paths; 952c6c22f12Sschwarze struct manpage *res; 953fbeeb774Sschwarze char **argv; 954fbeeb774Sschwarze char *query, *rp, *wp; 955c6c22f12Sschwarze size_t ressz; 956fbeeb774Sschwarze int argc; 957c6c22f12Sschwarze 958c6c22f12Sschwarze /* 959c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 960c6c22f12Sschwarze * This way we can pick up the database files, which are 961c6c22f12Sschwarze * relative to the manpath root. 962c6c22f12Sschwarze */ 963c6c22f12Sschwarze 964c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) { 965c976f0e2Sschwarze warn("chdir %s", req->q.manpath); 966631ce2c6Sschwarze pg_error_internal(); 967c6c22f12Sschwarze return; 968c6c22f12Sschwarze } 969c6c22f12Sschwarze 970c6c22f12Sschwarze search.arch = req->q.arch; 971c6c22f12Sschwarze search.sec = req->q.sec; 9720f10154cSschwarze search.outkey = "Nd"; 9730f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 974fea71919Sschwarze search.firstmatch = 1; 975c6c22f12Sschwarze 976c6c22f12Sschwarze paths.sz = 1; 977c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 978c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 979c6c22f12Sschwarze 980c6c22f12Sschwarze /* 981fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 982c6c22f12Sschwarze */ 983c6c22f12Sschwarze 984fbeeb774Sschwarze argc = 0; 985fbeeb774Sschwarze argv = NULL; 986fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 987fbeeb774Sschwarze for (;;) { 988fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 989fbeeb774Sschwarze rp++; 990fbeeb774Sschwarze if (*rp == '\0') 991fbeeb774Sschwarze break; 992fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 993fbeeb774Sschwarze argv[argc++] = wp = rp; 994fbeeb774Sschwarze for (;;) { 995fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 996fbeeb774Sschwarze *wp = '\0'; 997fbeeb774Sschwarze rp++; 998fbeeb774Sschwarze break; 999fbeeb774Sschwarze } 1000fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 1001fbeeb774Sschwarze rp++; 1002fbeeb774Sschwarze if (wp != rp) 1003fbeeb774Sschwarze *wp = *rp; 1004fbeeb774Sschwarze if (*rp == '\0') 1005fbeeb774Sschwarze break; 1006fbeeb774Sschwarze wp++; 1007fbeeb774Sschwarze rp++; 1008fbeeb774Sschwarze } 1009c6c22f12Sschwarze } 1010c6c22f12Sschwarze 1011e1beff2aSschwarze res = NULL; 1012e1beff2aSschwarze ressz = 0; 1013e1beff2aSschwarze if (req->isquery && req->q.equal && argc == 1) 1014e1beff2aSschwarze pg_redirect(req, argv[0]); 1015e1beff2aSschwarze else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0) 1016facea411Sschwarze pg_noresult(req, "You entered an invalid query."); 1017e1beff2aSschwarze else if (ressz == 0) 1018facea411Sschwarze pg_noresult(req, "No results found."); 1019c6c22f12Sschwarze else 1020facea411Sschwarze pg_searchres(req, res, ressz); 1021c6c22f12Sschwarze 1022fbeeb774Sschwarze free(query); 1023fbeeb774Sschwarze mansearch_free(res, ressz); 1024c6c22f12Sschwarze free(paths.paths[0]); 1025c6c22f12Sschwarze free(paths.paths); 1026c6c22f12Sschwarze } 1027c6c22f12Sschwarze 1028c6c22f12Sschwarze int 1029c6c22f12Sschwarze main(void) 1030c6c22f12Sschwarze { 1031c6c22f12Sschwarze struct req req; 1032136c26b8Sschwarze struct itimerval itimer; 103357482ef4Sschwarze const char *path; 103431e689c3Sschwarze const char *querystring; 103557482ef4Sschwarze int i; 1036c6c22f12Sschwarze 1037f80eb964Sschwarze /* 1038f80eb964Sschwarze * The "rpath" pledge could be revoked after mparse_readfd() 1039f80eb964Sschwarze * if the file desciptor to "/footer.html" would be opened 1040f80eb964Sschwarze * up front, but it's probably not worth the complication 1041f80eb964Sschwarze * of the code it would cause: it would require scattering 1042f80eb964Sschwarze * pledge() calls in multiple low-level resp_*() functions. 1043f80eb964Sschwarze */ 1044f80eb964Sschwarze 1045f80eb964Sschwarze if (pledge("stdio rpath", NULL) == -1) { 1046f80eb964Sschwarze warn("pledge"); 1047f80eb964Sschwarze pg_error_internal(); 1048f80eb964Sschwarze return EXIT_FAILURE; 1049f80eb964Sschwarze } 1050f80eb964Sschwarze 1051136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 1052136c26b8Sschwarze 10532935aafcSschwarze itimer.it_value.tv_sec = 2; 1054136c26b8Sschwarze itimer.it_value.tv_usec = 0; 10552935aafcSschwarze itimer.it_interval.tv_sec = 2; 1056136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 1057136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1058c976f0e2Sschwarze warn("setitimer"); 1059136c26b8Sschwarze pg_error_internal(); 1060526e306bSschwarze return EXIT_FAILURE; 1061136c26b8Sschwarze } 1062136c26b8Sschwarze 1063c6c22f12Sschwarze /* 10646fdade3eSschwarze * First we change directory into the MAN_DIR so that 1065c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 1066c6c22f12Sschwarze * relative to the same position. 1067c6c22f12Sschwarze */ 1068c6c22f12Sschwarze 1069c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) { 1070c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR); 1071facea411Sschwarze pg_error_internal(); 1072526e306bSschwarze return EXIT_FAILURE; 1073c6c22f12Sschwarze } 1074c6c22f12Sschwarze 1075c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1076abf19dc9Sschwarze req.q.equal = 1; 1077941df026Sschwarze parse_manpath_conf(&req); 1078c6c22f12Sschwarze 107902b1b494Sschwarze /* Parse the path info and the query string. */ 1080c6c22f12Sschwarze 108102b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 108202b1b494Sschwarze path = ""; 108302b1b494Sschwarze else if (*path == '/') 108402b1b494Sschwarze path++; 108502b1b494Sschwarze 1086aa16a3a8Sschwarze if (*path != '\0') { 1087941df026Sschwarze parse_path_info(&req, path); 1088a9941855Sschwarze if (req.q.manpath == NULL || req.q.sec == NULL || 1089a9941855Sschwarze *req.q.query == '\0' || access(path, F_OK) == -1) 109002b1b494Sschwarze path = ""; 109102b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1092941df026Sschwarze parse_query_string(&req, querystring); 1093c6c22f12Sschwarze 109402b1b494Sschwarze /* Validate parsed data and add defaults. */ 109502b1b494Sschwarze 109650eaed2bSschwarze if (req.q.manpath == NULL) 109750eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 109850eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1099631ce2c6Sschwarze pg_error_badrequest( 1100631ce2c6Sschwarze "You specified an invalid manpath."); 1101526e306bSschwarze return EXIT_FAILURE; 1102631ce2c6Sschwarze } 1103631ce2c6Sschwarze 1104f7a12365Sschwarze if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) { 1105cf3a545cSschwarze pg_error_badrequest( 1106cf3a545cSschwarze "You specified an invalid architecture."); 1107526e306bSschwarze return EXIT_FAILURE; 1108cf3a545cSschwarze } 1109cf3a545cSschwarze 111057482ef4Sschwarze /* Dispatch to the three different pages. */ 1111c6c22f12Sschwarze 111257482ef4Sschwarze if ('\0' != *path) 111357482ef4Sschwarze pg_show(&req, path); 1114e89321abSschwarze else if (NULL != req.q.query) 111557482ef4Sschwarze pg_search(&req); 111657482ef4Sschwarze else 1117facea411Sschwarze pg_index(&req); 1118c6c22f12Sschwarze 111931e689c3Sschwarze free(req.q.manpath); 112031e689c3Sschwarze free(req.q.arch); 112131e689c3Sschwarze free(req.q.sec); 1122e89321abSschwarze free(req.q.query); 1123c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1124c6c22f12Sschwarze free(req.p[i]); 1125c6c22f12Sschwarze free(req.p); 1126526e306bSschwarze return EXIT_SUCCESS; 1127c6c22f12Sschwarze } 1128c6c22f12Sschwarze 1129c6c22f12Sschwarze /* 1130f5938fa6Sschwarze * Translate PATH_INFO to a query. 113102b1b494Sschwarze */ 113202b1b494Sschwarze static void 1133941df026Sschwarze parse_path_info(struct req *req, const char *path) 113402b1b494Sschwarze { 1135f5938fa6Sschwarze const char *name, *sec, *end; 113602b1b494Sschwarze 113752b413c1Sschwarze req->isquery = 0; 113802b1b494Sschwarze req->q.equal = 1; 1139f5938fa6Sschwarze req->q.manpath = NULL; 1140daf4c292Sschwarze req->q.arch = NULL; 114102b1b494Sschwarze 114202b1b494Sschwarze /* Mandatory manual page name. */ 1143f5938fa6Sschwarze if ((name = strrchr(path, '/')) == NULL) 1144f5938fa6Sschwarze name = path; 1145f5938fa6Sschwarze else 1146f5938fa6Sschwarze name++; 114702b1b494Sschwarze 114802b1b494Sschwarze /* Optional trailing section. */ 1149f5938fa6Sschwarze sec = strrchr(name, '.'); 1150f5938fa6Sschwarze if (sec != NULL && isdigit((unsigned char)*++sec)) { 1151f5938fa6Sschwarze req->q.query = mandoc_strndup(name, sec - name - 1); 1152f5938fa6Sschwarze req->q.sec = mandoc_strdup(sec); 1153f5938fa6Sschwarze } else { 1154f5938fa6Sschwarze req->q.query = mandoc_strdup(name); 115502b1b494Sschwarze req->q.sec = NULL; 115602b1b494Sschwarze } 115702b1b494Sschwarze 115802b1b494Sschwarze /* Handle the case of name[.section] only. */ 1159f5938fa6Sschwarze if (name == path) 116002b1b494Sschwarze return; 116102b1b494Sschwarze 1162f5938fa6Sschwarze /* Optional manpath. */ 1163f5938fa6Sschwarze end = strchr(path, '/'); 1164f5938fa6Sschwarze req->q.manpath = mandoc_strndup(path, end - path); 1165f5938fa6Sschwarze if (validate_manpath(req, req->q.manpath)) { 1166f5938fa6Sschwarze path = end + 1; 1167f5938fa6Sschwarze if (name == path) 1168f5938fa6Sschwarze return; 1169f5938fa6Sschwarze } else { 1170f5938fa6Sschwarze free(req->q.manpath); 1171f5938fa6Sschwarze req->q.manpath = NULL; 1172f5938fa6Sschwarze } 1173f5938fa6Sschwarze 1174f5938fa6Sschwarze /* Optional section. */ 1175*955967fcSschwarze if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) { 1176f5938fa6Sschwarze path += 3; 1177f5938fa6Sschwarze end = strchr(path, '/'); 1178f5938fa6Sschwarze free(req->q.sec); 1179f5938fa6Sschwarze req->q.sec = mandoc_strndup(path, end - path); 1180f5938fa6Sschwarze path = end + 1; 1181f5938fa6Sschwarze if (name == path) 1182f5938fa6Sschwarze return; 1183f5938fa6Sschwarze } 1184f5938fa6Sschwarze 1185f5938fa6Sschwarze /* Optional architecture. */ 1186f5938fa6Sschwarze end = strchr(path, '/'); 1187f5938fa6Sschwarze if (end + 1 != name) { 1188daf4c292Sschwarze pg_error_badrequest( 1189daf4c292Sschwarze "You specified too many directory components."); 1190daf4c292Sschwarze exit(EXIT_FAILURE); 119102b1b494Sschwarze } 1192f5938fa6Sschwarze req->q.arch = mandoc_strndup(path, end - path); 1193f5938fa6Sschwarze if (validate_arch(req->q.arch) == 0) { 1194daf4c292Sschwarze pg_error_badrequest( 1195daf4c292Sschwarze "You specified an invalid directory component."); 1196daf4c292Sschwarze exit(EXIT_FAILURE); 1197daf4c292Sschwarze } 119802b1b494Sschwarze } 119902b1b494Sschwarze 120002b1b494Sschwarze /* 1201c6c22f12Sschwarze * Scan for indexable paths. 1202c6c22f12Sschwarze */ 1203c6c22f12Sschwarze static void 1204941df026Sschwarze parse_manpath_conf(struct req *req) 1205c6c22f12Sschwarze { 1206c6c22f12Sschwarze FILE *fp; 1207c6c22f12Sschwarze char *dp; 1208c6c22f12Sschwarze size_t dpsz; 120931f93c25Sschwarze ssize_t len; 1210c6c22f12Sschwarze 1211c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) { 1212c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR); 1213de651747Sschwarze pg_error_internal(); 1214de651747Sschwarze exit(EXIT_FAILURE); 1215de651747Sschwarze } 1216c6c22f12Sschwarze 121731f93c25Sschwarze dp = NULL; 121831f93c25Sschwarze dpsz = 0; 121931f93c25Sschwarze 122031f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 122131f93c25Sschwarze if (dp[len - 1] == '\n') 122231f93c25Sschwarze dp[--len] = '\0'; 1223c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1224c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1225cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1226c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1227c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp); 1228cf3a545cSschwarze pg_error_internal(); 1229cf3a545cSschwarze exit(EXIT_FAILURE); 1230cf3a545cSschwarze } 1231c976f0e2Sschwarze if (strchr(dp, '/') != NULL) { 1232c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1233c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp); 1234cf3a545cSschwarze pg_error_internal(); 1235cf3a545cSschwarze exit(EXIT_FAILURE); 1236cf3a545cSschwarze } 1237cf3a545cSschwarze req->p[req->psz++] = dp; 123831f93c25Sschwarze dp = NULL; 123931f93c25Sschwarze dpsz = 0; 1240c6c22f12Sschwarze } 124131f93c25Sschwarze free(dp); 1242de651747Sschwarze 1243de651747Sschwarze if (req->p == NULL) { 1244c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR); 1245de651747Sschwarze pg_error_internal(); 1246de651747Sschwarze exit(EXIT_FAILURE); 1247de651747Sschwarze } 1248c6c22f12Sschwarze } 1249