1*f0f927fcSschwarze /* $OpenBSD: cgi.c,v 1.118 2022/07/06 16:02:52 schwarze Exp $ */ 2c6c22f12Sschwarze /* 3a43df5a0Sschwarze * Copyright (c) 2014-2019, 2021, 2022 Ingo Schwarze <schwarze@usta.de> 46a6803e4Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 5b2d1ada6Sschwarze * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in> 6c6c22f12Sschwarze * 7c6c22f12Sschwarze * Permission to use, copy, modify, and distribute this software for any 8c6c22f12Sschwarze * purpose with or without fee is hereby granted, provided that the above 9c6c22f12Sschwarze * copyright notice and this permission notice appear in all copies. 10c6c22f12Sschwarze * 114de77decSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 12c6c22f12Sschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 134de77decSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 14c6c22f12Sschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15c6c22f12Sschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16c6c22f12Sschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17c6c22f12Sschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 186a6803e4Sschwarze * 196a6803e4Sschwarze * Implementation of the man.cgi(8) program. 20c6c22f12Sschwarze */ 21136c26b8Sschwarze #include <sys/types.h> 22136c26b8Sschwarze #include <sys/time.h> 23136c26b8Sschwarze 24c6c22f12Sschwarze #include <ctype.h> 25c976f0e2Sschwarze #include <err.h> 26c6c22f12Sschwarze #include <errno.h> 27c6c22f12Sschwarze #include <fcntl.h> 28c6c22f12Sschwarze #include <limits.h> 29c2235d37Sschwarze #include <stdint.h> 30c6c22f12Sschwarze #include <stdio.h> 31c6c22f12Sschwarze #include <stdlib.h> 32c6c22f12Sschwarze #include <string.h> 33c6c22f12Sschwarze #include <unistd.h> 34c6c22f12Sschwarze 35c6c22f12Sschwarze #include "mandoc_aux.h" 36f2d5c709Sschwarze #include "mandoc.h" 37f2d5c709Sschwarze #include "roff.h" 38396853b5Sschwarze #include "mdoc.h" 39fec2846bSschwarze #include "man.h" 4099acaf1eSschwarze #include "mandoc_parse.h" 41c6c22f12Sschwarze #include "main.h" 424de77decSschwarze #include "manconf.h" 43c6c22f12Sschwarze #include "mansearch.h" 446fdade3eSschwarze #include "cgi.h" 45c6c22f12Sschwarze 46c6c22f12Sschwarze /* 47c6c22f12Sschwarze * A query as passed to the search function. 48c6c22f12Sschwarze */ 49c6c22f12Sschwarze struct query { 5031e689c3Sschwarze char *manpath; /* desired manual directory */ 5131e689c3Sschwarze char *arch; /* architecture */ 5231e689c3Sschwarze char *sec; /* manual section */ 53e89321abSschwarze char *query; /* unparsed query expression */ 544477fbfaSschwarze int equal; /* match whole names, not substrings */ 55c6c22f12Sschwarze }; 56c6c22f12Sschwarze 57c6c22f12Sschwarze struct req { 58c6c22f12Sschwarze struct query q; 59c6c22f12Sschwarze char **p; /* array of available manpaths */ 60c6c22f12Sschwarze size_t psz; /* number of available manpaths */ 6152b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */ 62c6c22f12Sschwarze }; 63c6c22f12Sschwarze 6484f05c93Sschwarze enum focus { 6584f05c93Sschwarze FOCUS_NONE = 0, 6684f05c93Sschwarze FOCUS_QUERY 6784f05c93Sschwarze }; 6884f05c93Sschwarze 69c6c22f12Sschwarze static void html_print(const char *); 70c6c22f12Sschwarze static void html_putchar(char); 71c6c22f12Sschwarze static int http_decode(char *); 726a6803e4Sschwarze static void http_encode(const char *); 73941df026Sschwarze static void parse_manpath_conf(struct req *); 746a6803e4Sschwarze static void parse_path_info(struct req *, const char *); 75941df026Sschwarze static void parse_query_string(struct req *, const char *); 76facea411Sschwarze static void pg_error_badrequest(const char *); 77facea411Sschwarze static void pg_error_internal(void); 78facea411Sschwarze static void pg_index(const struct req *); 7918ccf011Sschwarze static void pg_noresult(const struct req *, int, const char *, 8018ccf011Sschwarze const char *); 81e1beff2aSschwarze static void pg_redirect(const struct req *, const char *); 8257482ef4Sschwarze static void pg_search(const struct req *); 83facea411Sschwarze static void pg_searchres(const struct req *, 84facea411Sschwarze struct manpage *, size_t); 8581060b1aSschwarze static void pg_show(struct req *, const char *); 86a43df5a0Sschwarze static int resp_begin_html(int, const char *, const char *); 87c6c22f12Sschwarze static void resp_begin_http(int, const char *); 88941df026Sschwarze static void resp_catman(const struct req *, const char *); 89a43df5a0Sschwarze static int resp_copy(const char *, const char *); 90c6c22f12Sschwarze static void resp_end_html(void); 91941df026Sschwarze static void resp_format(const struct req *, const char *); 9284f05c93Sschwarze static void resp_searchform(const struct req *, enum focus); 9346723f19Sschwarze static void resp_show(const struct req *, const char *); 94e89321abSschwarze static void set_query_attr(char **, char **); 95f7a12365Sschwarze static int validate_arch(const char *); 96e89321abSschwarze static int validate_filename(const char *); 97e89321abSschwarze static int validate_manpath(const struct req *, const char *); 98e89321abSschwarze static int validate_urifrag(const char *); 99c6c22f12Sschwarze 1003b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 101c6c22f12Sschwarze 10246723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 10328e449d6Sschwarze static const char *const sec_numbers[] = { 10428e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 10528e449d6Sschwarze }; 10628e449d6Sschwarze static const char *const sec_names[] = { 10728e449d6Sschwarze "All Sections", 10828e449d6Sschwarze "1 - General Commands", 10928e449d6Sschwarze "2 - System Calls", 1104e6618fdSschwarze "3 - Library Functions", 1114e6618fdSschwarze "3p - Perl Library", 1124e6618fdSschwarze "4 - Device Drivers", 11328e449d6Sschwarze "5 - File Formats", 11428e449d6Sschwarze "6 - Games", 1154e6618fdSschwarze "7 - Miscellaneous Information", 1164e6618fdSschwarze "8 - System Manager\'s Manual", 1174e6618fdSschwarze "9 - Kernel Developer\'s Manual" 11828e449d6Sschwarze }; 11928e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 12028e449d6Sschwarze 12128e449d6Sschwarze static const char *const arch_names[] = { 12293d3e4e1Sderaadt "amd64", "alpha", "armv7", "arm64", 123ab2c7ff4Sschwarze "hppa", "i386", "landisk", "loongson", 124ab2c7ff4Sschwarze "luna88k", "macppc", "mips64", "octeon", 125bfc185c1Svisa "powerpc64", "riscv64", "sparc64", 126ab2c7ff4Sschwarze 127766ef059Sschwarze "amiga", "arc", "armish", "arm32", 128766ef059Sschwarze "atari", "aviion", "beagle", "cats", 129766ef059Sschwarze "hppa64", "hp300", 1303bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k", 1313bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos", 132bfc185c1Svisa "pmax", "powerpc", "sgi", "socppc", 133bfc185c1Svisa "solbourne", "sparc", 1346250a715Sschwarze "sun3", "vax", "wgrisc", "x68k", 1356250a715Sschwarze "zaurus" 13628e449d6Sschwarze }; 13728e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 13828e449d6Sschwarze 139c6c22f12Sschwarze /* 140c6c22f12Sschwarze * Print a character, escaping HTML along the way. 141c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 142c6c22f12Sschwarze */ 143c6c22f12Sschwarze static void 144c6c22f12Sschwarze html_putchar(char c) 145c6c22f12Sschwarze { 146c6c22f12Sschwarze 147c6c22f12Sschwarze switch (c) { 148e74fa2aeSschwarze case '"': 149f765a656Sbentley printf("""); 150c6c22f12Sschwarze break; 151e74fa2aeSschwarze case '&': 152c6c22f12Sschwarze printf("&"); 153c6c22f12Sschwarze break; 154e74fa2aeSschwarze case '>': 155c6c22f12Sschwarze printf(">"); 156c6c22f12Sschwarze break; 157e74fa2aeSschwarze case '<': 158c6c22f12Sschwarze printf("<"); 159c6c22f12Sschwarze break; 160c6c22f12Sschwarze default: 161c6c22f12Sschwarze putchar((unsigned char)c); 162c6c22f12Sschwarze break; 163c6c22f12Sschwarze } 164c6c22f12Sschwarze } 165c6c22f12Sschwarze 166c6c22f12Sschwarze /* 167c6c22f12Sschwarze * Call through to html_putchar(). 168c6c22f12Sschwarze * Accepts NULL strings. 169c6c22f12Sschwarze */ 170c6c22f12Sschwarze static void 171c6c22f12Sschwarze html_print(const char *p) 172c6c22f12Sschwarze { 173c6c22f12Sschwarze 174c6c22f12Sschwarze if (NULL == p) 175c6c22f12Sschwarze return; 176c6c22f12Sschwarze while ('\0' != *p) 177c6c22f12Sschwarze html_putchar(*p++); 178c6c22f12Sschwarze } 179c6c22f12Sschwarze 180c6c22f12Sschwarze /* 18131e689c3Sschwarze * Transfer the responsibility for the allocated string *val 18231e689c3Sschwarze * to the query structure. 183c6c22f12Sschwarze */ 184c6c22f12Sschwarze static void 18531e689c3Sschwarze set_query_attr(char **attr, char **val) 18631e689c3Sschwarze { 18731e689c3Sschwarze 18831e689c3Sschwarze free(*attr); 18931e689c3Sschwarze if (**val == '\0') { 19031e689c3Sschwarze *attr = NULL; 19131e689c3Sschwarze free(*val); 19231e689c3Sschwarze } else 19331e689c3Sschwarze *attr = *val; 19431e689c3Sschwarze *val = NULL; 19531e689c3Sschwarze } 19631e689c3Sschwarze 19731e689c3Sschwarze /* 19831e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 19931e689c3Sschwarze * and store the values into the query structure. 20031e689c3Sschwarze */ 20131e689c3Sschwarze static void 202941df026Sschwarze parse_query_string(struct req *req, const char *qs) 203c6c22f12Sschwarze { 204c6c22f12Sschwarze char *key, *val; 20531e689c3Sschwarze size_t keysz, valsz; 206c6c22f12Sschwarze 20752b413c1Sschwarze req->isquery = 1; 20831e689c3Sschwarze req->q.manpath = NULL; 20931e689c3Sschwarze req->q.arch = NULL; 21031e689c3Sschwarze req->q.sec = NULL; 211e89321abSschwarze req->q.query = NULL; 2124477fbfaSschwarze req->q.equal = 1; 213c6c22f12Sschwarze 21431e689c3Sschwarze key = val = NULL; 21531e689c3Sschwarze while (*qs != '\0') { 216c6c22f12Sschwarze 21731e689c3Sschwarze /* Parse one key. */ 218c6c22f12Sschwarze 21931e689c3Sschwarze keysz = strcspn(qs, "=;&"); 22031e689c3Sschwarze key = mandoc_strndup(qs, keysz); 22131e689c3Sschwarze qs += keysz; 22231e689c3Sschwarze if (*qs != '=') 22331e689c3Sschwarze goto next; 224c6c22f12Sschwarze 22531e689c3Sschwarze /* Parse one value. */ 226c6c22f12Sschwarze 22731e689c3Sschwarze valsz = strcspn(++qs, ";&"); 22831e689c3Sschwarze val = mandoc_strndup(qs, valsz); 22931e689c3Sschwarze qs += valsz; 230c6c22f12Sschwarze 23131e689c3Sschwarze /* Decode and catch encoding errors. */ 23231e689c3Sschwarze 23331e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 23431e689c3Sschwarze goto next; 23531e689c3Sschwarze 23631e689c3Sschwarze /* Handle key-value pairs. */ 23731e689c3Sschwarze 23831e689c3Sschwarze if ( ! strcmp(key, "query")) 239e89321abSschwarze set_query_attr(&req->q.query, &val); 24031e689c3Sschwarze 24131e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 24231e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 24331e689c3Sschwarze 24431e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 245aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 24631e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 247aabcc9a1Sschwarze val[7] = '-'; 248aabcc9a1Sschwarze if ('C' == val[8]) 249aabcc9a1Sschwarze val[8] = 'c'; 250aabcc9a1Sschwarze } 251aabcc9a1Sschwarze #endif 25231e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 25331e689c3Sschwarze } 25431e689c3Sschwarze 25531e689c3Sschwarze else if ( ! (strcmp(key, "sec") 256aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 25731e689c3Sschwarze && strcmp(key, "sektion") 258aabcc9a1Sschwarze #endif 25931e689c3Sschwarze )) { 26031e689c3Sschwarze if ( ! strcmp(val, "0")) 26131e689c3Sschwarze *val = '\0'; 26231e689c3Sschwarze set_query_attr(&req->q.sec, &val); 263c6c22f12Sschwarze } 26431e689c3Sschwarze 26531e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 26631e689c3Sschwarze if ( ! strcmp(val, "default")) 26731e689c3Sschwarze *val = '\0'; 26831e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2694477fbfaSschwarze } 27031e689c3Sschwarze 27131e689c3Sschwarze /* 27231e689c3Sschwarze * The key must be freed in any case. 27331e689c3Sschwarze * The val may have been handed over to the query 27431e689c3Sschwarze * structure, in which case it is now NULL. 27531e689c3Sschwarze */ 27631e689c3Sschwarze next: 27731e689c3Sschwarze free(key); 27831e689c3Sschwarze key = NULL; 27931e689c3Sschwarze free(val); 28031e689c3Sschwarze val = NULL; 28131e689c3Sschwarze 28231e689c3Sschwarze if (*qs != '\0') 28331e689c3Sschwarze qs++; 28431e689c3Sschwarze } 285c6c22f12Sschwarze } 286c6c22f12Sschwarze 287c6c22f12Sschwarze /* 288c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 289c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 290c6c22f12Sschwarze * over the allocated string. 291c6c22f12Sschwarze */ 292c6c22f12Sschwarze static int 293c6c22f12Sschwarze http_decode(char *p) 294c6c22f12Sschwarze { 295c6c22f12Sschwarze char hex[3]; 2961f69f32bStedu char *q; 297c6c22f12Sschwarze int c; 298c6c22f12Sschwarze 299c6c22f12Sschwarze hex[2] = '\0'; 300c6c22f12Sschwarze 3011f69f32bStedu q = p; 3021f69f32bStedu for ( ; '\0' != *p; p++, q++) { 303c6c22f12Sschwarze if ('%' == *p) { 304c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 305526e306bSschwarze return 0; 306c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 307526e306bSschwarze return 0; 308c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 309526e306bSschwarze return 0; 310c6c22f12Sschwarze if ('\0' == c) 311526e306bSschwarze return 0; 312c6c22f12Sschwarze 3131f69f32bStedu *q = (char)c; 3141f69f32bStedu p += 2; 315c6c22f12Sschwarze } else 3161f69f32bStedu *q = '+' == *p ? ' ' : *p; 317c6c22f12Sschwarze } 318c6c22f12Sschwarze 3191f69f32bStedu *q = '\0'; 320526e306bSschwarze return 1; 321c6c22f12Sschwarze } 322c6c22f12Sschwarze 323c6c22f12Sschwarze static void 324f7a12365Sschwarze http_encode(const char *p) 325f7a12365Sschwarze { 326f7a12365Sschwarze for (; *p != '\0'; p++) { 327f7a12365Sschwarze if (isalnum((unsigned char)*p) == 0 && 328f7a12365Sschwarze strchr("-._~", *p) == NULL) 3291ecf8c4fSschwarze printf("%%%2.2X", (unsigned char)*p); 330f7a12365Sschwarze else 331f7a12365Sschwarze putchar(*p); 332f7a12365Sschwarze } 333f7a12365Sschwarze } 334f7a12365Sschwarze 335f7a12365Sschwarze static void 336c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 337c6c22f12Sschwarze { 338c6c22f12Sschwarze 339c6c22f12Sschwarze if (200 != code) 340fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 341c6c22f12Sschwarze 342fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 343fa9c540aStedu "Cache-Control: no-cache\r\n" 34453459fdfSbentley "Content-Security-Policy: default-src 'none'; " 34553459fdfSbentley "style-src 'self' 'unsafe-inline'\r\n" 346fa9c540aStedu "Pragma: no-cache\r\n" 347fa9c540aStedu "\r\n"); 348c6c22f12Sschwarze 349c6c22f12Sschwarze fflush(stdout); 350c6c22f12Sschwarze } 351c6c22f12Sschwarze 352a43df5a0Sschwarze static int 353a43df5a0Sschwarze resp_copy(const char *element, const char *filename) 354711661c7Sschwarze { 355711661c7Sschwarze char buf[4096]; 356711661c7Sschwarze ssize_t sz; 357711661c7Sschwarze int fd; 358711661c7Sschwarze 359a43df5a0Sschwarze if ((fd = open(filename, O_RDONLY)) == -1) 360a43df5a0Sschwarze return 0; 361a43df5a0Sschwarze 362a43df5a0Sschwarze if (element != NULL) 363a43df5a0Sschwarze printf("<%s>\n", element); 364711661c7Sschwarze fflush(stdout); 365711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 366711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 367fd3cdd86Sjsg close(fd); 368a43df5a0Sschwarze return 1; 369711661c7Sschwarze } 370711661c7Sschwarze 371a43df5a0Sschwarze static int 372fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file) 373c6c22f12Sschwarze { 374e621f1d2Sschwarze const char *name, *sec, *cp; 375e621f1d2Sschwarze int namesz, secsz; 376c6c22f12Sschwarze 377c6c22f12Sschwarze resp_begin_http(code, msg); 378c6c22f12Sschwarze 379d649d931Sschwarze printf("<!DOCTYPE html>\n" 380735516bdSschwarze "<html>\n" 381735516bdSschwarze "<head>\n" 382735516bdSschwarze " <meta charset=\"UTF-8\"/>\n" 383661a42d8Sschwarze " <meta name=\"viewport\"" 384661a42d8Sschwarze " content=\"width=device-width, initial-scale=1.0\">\n" 385735516bdSschwarze " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 386735516bdSschwarze " type=\"text/css\" media=\"all\">\n" 387fdef72b0Sschwarze " <title>", 388fdef72b0Sschwarze CSS_DIR); 389fdef72b0Sschwarze if (file != NULL) { 390e621f1d2Sschwarze cp = strrchr(file, '/'); 391e621f1d2Sschwarze name = cp == NULL ? file : cp + 1; 392e621f1d2Sschwarze cp = strrchr(name, '.'); 393e621f1d2Sschwarze namesz = cp == NULL ? strlen(name) : cp - name; 394e621f1d2Sschwarze sec = NULL; 395e621f1d2Sschwarze if (cp != NULL && cp[1] != '0') { 396e621f1d2Sschwarze sec = cp + 1; 397e621f1d2Sschwarze secsz = strlen(sec); 398e621f1d2Sschwarze } else if (name - file > 1) { 399e621f1d2Sschwarze for (cp = name - 2; cp >= file; cp--) { 400e621f1d2Sschwarze if (*cp < '1' || *cp > '9') 401e621f1d2Sschwarze continue; 402e621f1d2Sschwarze sec = cp; 403e621f1d2Sschwarze secsz = name - cp - 1; 404e621f1d2Sschwarze break; 405e621f1d2Sschwarze } 406e621f1d2Sschwarze } 407e621f1d2Sschwarze printf("%.*s", namesz, name); 408e621f1d2Sschwarze if (sec != NULL) 409e621f1d2Sschwarze printf("(%.*s)", secsz, sec); 410e621f1d2Sschwarze fputs(" - ", stdout); 411fdef72b0Sschwarze } 412fdef72b0Sschwarze printf("%s</title>\n" 413735516bdSschwarze "</head>\n" 414ce781f36Sschwarze "<body>\n", 415fdef72b0Sschwarze CUSTOMIZE_TITLE); 416711661c7Sschwarze 417a43df5a0Sschwarze return resp_copy("header", MAN_DIR "/header.html"); 418c6c22f12Sschwarze } 419c6c22f12Sschwarze 420c6c22f12Sschwarze static void 421c6c22f12Sschwarze resp_end_html(void) 422c6c22f12Sschwarze { 423a43df5a0Sschwarze if (resp_copy("footer", MAN_DIR "/footer.html")) 424a43df5a0Sschwarze puts("</footer>"); 425711661c7Sschwarze 426735516bdSschwarze puts("</body>\n" 427735516bdSschwarze "</html>"); 428c6c22f12Sschwarze } 429c6c22f12Sschwarze 430c6c22f12Sschwarze static void 43184f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus) 432c6c22f12Sschwarze { 433c6c22f12Sschwarze int i; 434c6c22f12Sschwarze 435a43df5a0Sschwarze printf("<form role=\"search\" action=\"/%s\" method=\"get\" " 4367411869fSschwarze "autocomplete=\"off\" autocapitalize=\"none\">\n" 437735516bdSschwarze " <fieldset>\n" 438735516bdSschwarze " <legend>Manual Page Search Parameters</legend>\n", 439c6c22f12Sschwarze scriptname); 44028e449d6Sschwarze 44128e449d6Sschwarze /* Write query input box. */ 44228e449d6Sschwarze 4436b824e3bSschwarze printf(" <input type=\"search\" name=\"query\" value=\""); 44484f05c93Sschwarze if (req->q.query != NULL) 445e89321abSschwarze html_print(req->q.query); 44684f05c93Sschwarze printf( "\" size=\"40\""); 44784f05c93Sschwarze if (focus == FOCUS_QUERY) 44884f05c93Sschwarze printf(" autofocus"); 44984f05c93Sschwarze puts(">"); 45028e449d6Sschwarze 451784a63d6Sschwarze /* Write submission buttons. */ 45228e449d6Sschwarze 453784a63d6Sschwarze printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">" 454784a63d6Sschwarze "man</button>\n" 455784a63d6Sschwarze " <button type=\"submit\" name=\"apropos\" value=\"1\">" 456542ee4bfSschwarze "apropos</button>\n" 457542ee4bfSschwarze " <br/>\n"); 45828e449d6Sschwarze 45928e449d6Sschwarze /* Write section selector. */ 46028e449d6Sschwarze 461*f0f927fcSschwarze puts(" <select name=\"sec\" aria-label=\"Manual section\">"); 46228e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 463735516bdSschwarze printf(" <option value=\"%s\"", sec_numbers[i]); 46428e449d6Sschwarze if (NULL != req->q.sec && 46528e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 466735516bdSschwarze printf(" selected=\"selected\""); 467735516bdSschwarze printf(">%s</option>\n", sec_names[i]); 46828e449d6Sschwarze } 469735516bdSschwarze puts(" </select>"); 47028e449d6Sschwarze 47128e449d6Sschwarze /* Write architecture selector. */ 47228e449d6Sschwarze 473b2d1ada6Sschwarze printf( " <select name=\"arch\" aria-label=\"CPU architecture\">\n" 474735516bdSschwarze " <option value=\"default\""); 475be14a32aSschwarze if (NULL == req->q.arch) 476735516bdSschwarze printf(" selected=\"selected\""); 477735516bdSschwarze puts(">All Architectures</option>"); 47828e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 4796b824e3bSschwarze printf(" <option"); 48028e449d6Sschwarze if (NULL != req->q.arch && 48128e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 482735516bdSschwarze printf(" selected=\"selected\""); 483735516bdSschwarze printf(">%s</option>\n", arch_names[i]); 48428e449d6Sschwarze } 485735516bdSschwarze puts(" </select>"); 48628e449d6Sschwarze 48728e449d6Sschwarze /* Write manpath selector. */ 48828e449d6Sschwarze 489c6c22f12Sschwarze if (req->psz > 1) { 490735516bdSschwarze puts(" <select name=\"manpath\">"); 491c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 492735516bdSschwarze printf(" <option"); 49350eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 494735516bdSschwarze printf(" selected=\"selected\""); 4956b824e3bSschwarze printf(">"); 496c6c22f12Sschwarze html_print(req->p[i]); 497735516bdSschwarze puts("</option>"); 498c6c22f12Sschwarze } 499735516bdSschwarze puts(" </select>"); 500c6c22f12Sschwarze } 50128e449d6Sschwarze 502784a63d6Sschwarze puts(" </fieldset>\n" 503a43df5a0Sschwarze "</form>"); 504c6c22f12Sschwarze } 505c6c22f12Sschwarze 50681475784Sschwarze static int 507cf3a545cSschwarze validate_urifrag(const char *frag) 508cf3a545cSschwarze { 509cf3a545cSschwarze 510cf3a545cSschwarze while ('\0' != *frag) { 511cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 512cf3a545cSschwarze '-' == *frag || '.' == *frag || 513cf3a545cSschwarze '/' == *frag || '_' == *frag)) 514526e306bSschwarze return 0; 515cf3a545cSschwarze frag++; 516cf3a545cSschwarze } 517526e306bSschwarze return 1; 518cf3a545cSschwarze } 519cf3a545cSschwarze 520cf3a545cSschwarze static int 521631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 522631ce2c6Sschwarze { 523631ce2c6Sschwarze size_t i; 524631ce2c6Sschwarze 525631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 526631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 527526e306bSschwarze return 1; 528631ce2c6Sschwarze 529526e306bSschwarze return 0; 530631ce2c6Sschwarze } 531631ce2c6Sschwarze 532631ce2c6Sschwarze static int 533f7a12365Sschwarze validate_arch(const char *arch) 534f7a12365Sschwarze { 535f7a12365Sschwarze int i; 536f7a12365Sschwarze 537f7a12365Sschwarze for (i = 0; i < arch_MAX; i++) 538f7a12365Sschwarze if (strcmp(arch, arch_names[i]) == 0) 539f7a12365Sschwarze return 1; 540f7a12365Sschwarze 541f7a12365Sschwarze return 0; 542f7a12365Sschwarze } 543f7a12365Sschwarze 544f7a12365Sschwarze static int 54581475784Sschwarze validate_filename(const char *file) 54681475784Sschwarze { 54781475784Sschwarze 54881475784Sschwarze if ('.' == file[0] && '/' == file[1]) 54981475784Sschwarze file += 2; 55081475784Sschwarze 551526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 552526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 55381475784Sschwarze } 55481475784Sschwarze 555c6c22f12Sschwarze static void 556facea411Sschwarze pg_index(const struct req *req) 557c6c22f12Sschwarze { 558a43df5a0Sschwarze if (resp_begin_html(200, NULL, NULL) == 0) 559a43df5a0Sschwarze puts("<header>"); 56084f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 561a43df5a0Sschwarze printf("</header>\n" 562a43df5a0Sschwarze "<main>\n" 563*f0f927fcSschwarze "<p role=\"doc-notice\" aria-label=\"Usage\">\n" 564d56ca219Sschwarze "This web interface is documented in the\n" 565b2d1ada6Sschwarze "<a class=\"Xr\" href=\"/%s%sman.cgi.8\"" 566b2d1ada6Sschwarze " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n" 567d56ca219Sschwarze "manual, and the\n" 568b2d1ada6Sschwarze "<a class=\"Xr\" href=\"/%s%sapropos.1\"" 569b2d1ada6Sschwarze " aria-label=\"apropos, section 1\">apropos(1)</a>\n" 5702a43838fSschwarze "manual explains the query syntax.\n" 571b2d1ada6Sschwarze "</p>\n" 572b2d1ada6Sschwarze "</main>\n", 5733b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5743b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 575c6c22f12Sschwarze resp_end_html(); 576c6c22f12Sschwarze } 577c6c22f12Sschwarze 578c6c22f12Sschwarze static void 57918ccf011Sschwarze pg_noresult(const struct req *req, int code, const char *http_msg, 58018ccf011Sschwarze const char *user_msg) 581c6c22f12Sschwarze { 582a43df5a0Sschwarze if (resp_begin_html(code, http_msg, NULL) == 0) 583a43df5a0Sschwarze puts("<header>"); 58484f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 585a43df5a0Sschwarze puts("</header>"); 586b2d1ada6Sschwarze puts("<main>"); 587*f0f927fcSschwarze puts("<p role=\"doc-notice\" aria-label=\"No result\">"); 58818ccf011Sschwarze puts(user_msg); 589735516bdSschwarze puts("</p>"); 590b2d1ada6Sschwarze puts("</main>"); 591c6c22f12Sschwarze resp_end_html(); 592c6c22f12Sschwarze } 593c6c22f12Sschwarze 594c6c22f12Sschwarze static void 595facea411Sschwarze pg_error_badrequest(const char *msg) 596c6c22f12Sschwarze { 597a43df5a0Sschwarze if (resp_begin_html(400, "Bad Request", NULL)) 598a43df5a0Sschwarze puts("</header>"); 599b2d1ada6Sschwarze puts("<main>\n" 600b2d1ada6Sschwarze "<h1>Bad Request</h1>\n" 601b2d1ada6Sschwarze "<p role=\"doc-notice\" aria-label=\"Bad Request\">"); 602c6c22f12Sschwarze puts(msg); 603c6c22f12Sschwarze printf("Try again from the\n" 604735516bdSschwarze "<a href=\"/%s\">main page</a>.\n" 605b2d1ada6Sschwarze "</p>\n" 606a43df5a0Sschwarze "</main>\n", scriptname); 607c6c22f12Sschwarze resp_end_html(); 608c6c22f12Sschwarze } 609c6c22f12Sschwarze 610c6c22f12Sschwarze static void 611facea411Sschwarze pg_error_internal(void) 612c6c22f12Sschwarze { 613a43df5a0Sschwarze if (resp_begin_html(500, "Internal Server Error", NULL)) 614a43df5a0Sschwarze puts("</header>"); 615b2d1ada6Sschwarze puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>"); 616c6c22f12Sschwarze resp_end_html(); 617c6c22f12Sschwarze } 618c6c22f12Sschwarze 619c6c22f12Sschwarze static void 620e1beff2aSschwarze pg_redirect(const struct req *req, const char *name) 621e1beff2aSschwarze { 622e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 623e0d9a108Sschwarze "Location: /"); 624e1beff2aSschwarze if (*scriptname != '\0') 625e1beff2aSschwarze printf("%s/", scriptname); 626e1beff2aSschwarze if (strcmp(req->q.manpath, req->p[0])) 627e1beff2aSschwarze printf("%s/", req->q.manpath); 628e1beff2aSschwarze if (req->q.arch != NULL) 629e1beff2aSschwarze printf("%s/", req->q.arch); 630f7a12365Sschwarze http_encode(name); 631f7a12365Sschwarze if (req->q.sec != NULL) { 632f7a12365Sschwarze putchar('.'); 633f7a12365Sschwarze http_encode(req->q.sec); 634f7a12365Sschwarze } 635e1beff2aSschwarze printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"); 636e1beff2aSschwarze } 637e1beff2aSschwarze 638e1beff2aSschwarze static void 639facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 640c6c22f12Sschwarze { 641be14a32aSschwarze char *arch, *archend; 642db26164eSschwarze const char *sec; 643db26164eSschwarze size_t i, iuse; 644be14a32aSschwarze int archprio, archpriouse; 64546723f19Sschwarze int prio, priouse; 646a43df5a0Sschwarze int have_header; 647c6c22f12Sschwarze 64881475784Sschwarze for (i = 0; i < sz; i++) { 64981475784Sschwarze if (validate_filename(r[i].file)) 65081475784Sschwarze continue; 651c976f0e2Sschwarze warnx("invalid filename %s in %s database", 65281475784Sschwarze r[i].file, req->q.manpath); 65381475784Sschwarze pg_error_internal(); 65481475784Sschwarze return; 65581475784Sschwarze } 65681475784Sschwarze 65752b413c1Sschwarze if (req->isquery && sz == 1) { 658c6c22f12Sschwarze /* 659c6c22f12Sschwarze * If we have just one result, then jump there now 660c6c22f12Sschwarze * without any delay. 661c6c22f12Sschwarze */ 662e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 663e0d9a108Sschwarze "Location: /"); 664e0d9a108Sschwarze if (*scriptname != '\0') 665e0d9a108Sschwarze printf("%s/", scriptname); 666e0d9a108Sschwarze if (strcmp(req->q.manpath, req->p[0])) 667e0d9a108Sschwarze printf("%s/", req->q.manpath); 668e0d9a108Sschwarze printf("%s\r\n" 669e0d9a108Sschwarze "Content-Type: text/html; charset=utf-8\r\n\r\n", 670e0d9a108Sschwarze r[0].file); 671c6c22f12Sschwarze return; 672c6c22f12Sschwarze } 673c6c22f12Sschwarze 67446723f19Sschwarze /* 67546723f19Sschwarze * In man(1) mode, show one of the pages 67646723f19Sschwarze * even if more than one is found. 67746723f19Sschwarze */ 67846723f19Sschwarze 67946723f19Sschwarze iuse = 0; 680fdef72b0Sschwarze if (req->q.equal || sz == 1) { 681db26164eSschwarze priouse = 20; 682be14a32aSschwarze archpriouse = 3; 68346723f19Sschwarze for (i = 0; i < sz; i++) { 684db26164eSschwarze sec = r[i].file; 685db26164eSschwarze sec += strcspn(sec, "123456789"); 686db26164eSschwarze if (sec[0] == '\0') 68746723f19Sschwarze continue; 688db26164eSschwarze prio = sec_prios[sec[0] - '1']; 689db26164eSschwarze if (sec[1] != '/') 690db26164eSschwarze prio += 10; 691db26164eSschwarze if (req->q.arch == NULL) { 692be14a32aSschwarze archprio = 693db26164eSschwarze ((arch = strchr(sec + 1, '/')) 694db26164eSschwarze == NULL) ? 3 : 695db26164eSschwarze ((archend = strchr(arch + 1, '/')) 696db26164eSschwarze == NULL) ? 0 : 697be14a32aSschwarze strncmp(arch, "amd64/", 698be14a32aSschwarze archend - arch) ? 2 : 1; 699be14a32aSschwarze if (archprio < archpriouse) { 700be14a32aSschwarze archpriouse = archprio; 701be14a32aSschwarze priouse = prio; 702be14a32aSschwarze iuse = i; 703be14a32aSschwarze continue; 704be14a32aSschwarze } 705be14a32aSschwarze if (archprio > archpriouse) 706be14a32aSschwarze continue; 707be14a32aSschwarze } 70846723f19Sschwarze if (prio >= priouse) 70946723f19Sschwarze continue; 71046723f19Sschwarze priouse = prio; 71146723f19Sschwarze iuse = i; 71246723f19Sschwarze } 713a43df5a0Sschwarze have_header = resp_begin_html(200, NULL, r[iuse].file); 714fdef72b0Sschwarze } else 715a43df5a0Sschwarze have_header = resp_begin_html(200, NULL, NULL); 716fdef72b0Sschwarze 717a43df5a0Sschwarze if (have_header == 0) 718a43df5a0Sschwarze puts("<header>"); 719fdef72b0Sschwarze resp_searchform(req, 720fdef72b0Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 721a43df5a0Sschwarze puts("</header>"); 722fdef72b0Sschwarze 723fdef72b0Sschwarze if (sz > 1) { 724b2d1ada6Sschwarze puts("<nav>"); 725fdef72b0Sschwarze puts("<table class=\"results\">"); 726fdef72b0Sschwarze for (i = 0; i < sz; i++) { 727fdef72b0Sschwarze printf(" <tr>\n" 728fdef72b0Sschwarze " <td>" 7297b3dbe16Sschwarze "<a class=\"Xr\" href=\"/"); 7307b3dbe16Sschwarze if (*scriptname != '\0') 7317b3dbe16Sschwarze printf("%s/", scriptname); 7327b3dbe16Sschwarze if (strcmp(req->q.manpath, req->p[0])) 7337b3dbe16Sschwarze printf("%s/", req->q.manpath); 7347b3dbe16Sschwarze printf("%s\">", r[i].file); 735fdef72b0Sschwarze html_print(r[i].names); 736fdef72b0Sschwarze printf("</a></td>\n" 737fdef72b0Sschwarze " <td><span class=\"Nd\">"); 738fdef72b0Sschwarze html_print(r[i].output); 739fdef72b0Sschwarze puts("</span></td>\n" 740fdef72b0Sschwarze " </tr>"); 741fdef72b0Sschwarze } 742fdef72b0Sschwarze puts("</table>"); 743b2d1ada6Sschwarze puts("</nav>"); 744fdef72b0Sschwarze } 745fdef72b0Sschwarze 746fdef72b0Sschwarze if (req->q.equal || sz == 1) { 747fdef72b0Sschwarze puts("<hr>"); 74846723f19Sschwarze resp_show(req, r[iuse].file); 74946723f19Sschwarze } 75046723f19Sschwarze 751c6c22f12Sschwarze resp_end_html(); 752c6c22f12Sschwarze } 753c6c22f12Sschwarze 754c6c22f12Sschwarze static void 755941df026Sschwarze resp_catman(const struct req *req, const char *file) 756c6c22f12Sschwarze { 757c6c22f12Sschwarze FILE *f; 758c6c22f12Sschwarze char *p; 75931f93c25Sschwarze size_t sz; 76031f93c25Sschwarze ssize_t len; 76131f93c25Sschwarze int i; 762c6c22f12Sschwarze int italic, bold; 763c6c22f12Sschwarze 76431f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 765b2d1ada6Sschwarze puts("<p role=\"doc-notice\">\n" 766b2d1ada6Sschwarze " You specified an invalid manual file.\n" 767b2d1ada6Sschwarze "</p>"); 768c6c22f12Sschwarze return; 769c6c22f12Sschwarze } 770c6c22f12Sschwarze 771735516bdSschwarze puts("<div class=\"catman\">\n" 772735516bdSschwarze "<pre>"); 773c6c22f12Sschwarze 77431f93c25Sschwarze p = NULL; 77531f93c25Sschwarze sz = 0; 77631f93c25Sschwarze 77731f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 778c6c22f12Sschwarze bold = italic = 0; 77931f93c25Sschwarze for (i = 0; i < len - 1; i++) { 780c6c22f12Sschwarze /* 781c6c22f12Sschwarze * This means that the catpage is out of state. 782c6c22f12Sschwarze * Ignore it and keep going (although the 783c6c22f12Sschwarze * catpage is bogus). 784c6c22f12Sschwarze */ 785c6c22f12Sschwarze 786c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 787c6c22f12Sschwarze continue; 788c6c22f12Sschwarze 789c6c22f12Sschwarze /* 790c6c22f12Sschwarze * Print a regular character. 791c6c22f12Sschwarze * Close out any bold/italic scopes. 792c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 793c6c22f12Sschwarze * have something to enter when we backspace. 794c6c22f12Sschwarze */ 795c6c22f12Sschwarze 796c6c22f12Sschwarze if ('\b' != p[i + 1]) { 797c6c22f12Sschwarze if (italic) 798735516bdSschwarze printf("</i>"); 799c6c22f12Sschwarze if (bold) 800735516bdSschwarze printf("</b>"); 801c6c22f12Sschwarze italic = bold = 0; 802c6c22f12Sschwarze html_putchar(p[i]); 803c6c22f12Sschwarze continue; 80431f93c25Sschwarze } else if (i + 2 >= len) 805c6c22f12Sschwarze continue; 806c6c22f12Sschwarze 807c6c22f12Sschwarze /* Italic mode. */ 808c6c22f12Sschwarze 809c6c22f12Sschwarze if ('_' == p[i]) { 810c6c22f12Sschwarze if (bold) 811735516bdSschwarze printf("</b>"); 812c6c22f12Sschwarze if ( ! italic) 813735516bdSschwarze printf("<i>"); 814c6c22f12Sschwarze bold = 0; 815c6c22f12Sschwarze italic = 1; 816c6c22f12Sschwarze i += 2; 817c6c22f12Sschwarze html_putchar(p[i]); 818c6c22f12Sschwarze continue; 819c6c22f12Sschwarze } 820c6c22f12Sschwarze 821c6c22f12Sschwarze /* 822c6c22f12Sschwarze * Handle funny behaviour troff-isms. 823c6c22f12Sschwarze * These grok'd from the original man2html.c. 824c6c22f12Sschwarze */ 825c6c22f12Sschwarze 826c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 827c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) || 828c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) || 829c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) || 830c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) || 831c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) || 832c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) || 833c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) { 834c6c22f12Sschwarze if (italic) 835735516bdSschwarze printf("</i>"); 836c6c22f12Sschwarze if (bold) 837735516bdSschwarze printf("</b>"); 838c6c22f12Sschwarze italic = bold = 0; 839c6c22f12Sschwarze putchar('*'); 840c6c22f12Sschwarze i += 2; 841c6c22f12Sschwarze continue; 842c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 843c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) || 844c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) || 845c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) || 846c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) || 847c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) { 848c6c22f12Sschwarze if (italic) 849735516bdSschwarze printf("</i>"); 850c6c22f12Sschwarze if (bold) 851735516bdSschwarze printf("</b>"); 852c6c22f12Sschwarze italic = bold = 0; 853c6c22f12Sschwarze putchar('+'); 854c6c22f12Sschwarze i += 2; 855c6c22f12Sschwarze continue; 856c6c22f12Sschwarze } 857c6c22f12Sschwarze 858c6c22f12Sschwarze /* Bold mode. */ 859c6c22f12Sschwarze 860c6c22f12Sschwarze if (italic) 861735516bdSschwarze printf("</i>"); 862c6c22f12Sschwarze if ( ! bold) 863735516bdSschwarze printf("<b>"); 864c6c22f12Sschwarze bold = 1; 865c6c22f12Sschwarze italic = 0; 866c6c22f12Sschwarze i += 2; 867c6c22f12Sschwarze html_putchar(p[i]); 868c6c22f12Sschwarze } 869c6c22f12Sschwarze 870c6c22f12Sschwarze /* 871c6c22f12Sschwarze * Clean up the last character. 872c6c22f12Sschwarze * We can get to a newline; don't print that. 873c6c22f12Sschwarze */ 874c6c22f12Sschwarze 875c6c22f12Sschwarze if (italic) 876735516bdSschwarze printf("</i>"); 877c6c22f12Sschwarze if (bold) 878735516bdSschwarze printf("</b>"); 879c6c22f12Sschwarze 88031f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 881c6c22f12Sschwarze html_putchar(p[i]); 882c6c22f12Sschwarze 883c6c22f12Sschwarze putchar('\n'); 884c6c22f12Sschwarze } 88531f93c25Sschwarze free(p); 886c6c22f12Sschwarze 887735516bdSschwarze puts("</pre>\n" 888735516bdSschwarze "</div>"); 889c6c22f12Sschwarze 890c6c22f12Sschwarze fclose(f); 891c6c22f12Sschwarze } 892c6c22f12Sschwarze 893c6c22f12Sschwarze static void 894941df026Sschwarze resp_format(const struct req *req, const char *file) 895c6c22f12Sschwarze { 8962ccd0917Sschwarze struct manoutput conf; 897c6c22f12Sschwarze struct mparse *mp; 8986b86842eSschwarze struct roff_meta *meta; 899c6c22f12Sschwarze void *vp; 900f74d674aSschwarze int fd; 901f74d674aSschwarze int usepath; 902c6c22f12Sschwarze 903b7041c07Sderaadt if (-1 == (fd = open(file, O_RDONLY))) { 904b2d1ada6Sschwarze puts("<p role=\"doc-notice\">\n" 905b2d1ada6Sschwarze " You specified an invalid manual file.\n" 906b2d1ada6Sschwarze "</p>"); 907c6c22f12Sschwarze return; 908c6c22f12Sschwarze } 909c6c22f12Sschwarze 91016536faaSschwarze mchars_alloc(); 9116b86842eSschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 | 9126b86842eSschwarze MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath); 913df927bb6Sschwarze mparse_readfd(mp, fd, file); 914c6c22f12Sschwarze close(fd); 9156b86842eSschwarze meta = mparse_result(mp); 916c6c22f12Sschwarze 9172ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 9182ccd0917Sschwarze conf.fragment = 1; 9197c6e1b3aSschwarze conf.style = mandoc_strdup(CSS_DIR "/mandoc.css"); 920f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 921ac2abdb8Sschwarze mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S", 922ac2abdb8Sschwarze scriptname, *scriptname == '\0' ? "" : "/", 923f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 924c6c22f12Sschwarze 92516536faaSschwarze vp = html_alloc(&conf); 9266b86842eSschwarze if (meta->macroset == MACROSET_MDOC) 9276b86842eSschwarze html_mdoc(vp, meta); 9286b86842eSschwarze else 9296b86842eSschwarze html_man(vp, meta); 930c6c22f12Sschwarze 931c6c22f12Sschwarze html_free(vp); 932c6c22f12Sschwarze mparse_free(mp); 93316536faaSschwarze mchars_free(); 9342ccd0917Sschwarze free(conf.man); 9357c6e1b3aSschwarze free(conf.style); 936c6c22f12Sschwarze } 937c6c22f12Sschwarze 938c6c22f12Sschwarze static void 93946723f19Sschwarze resp_show(const struct req *req, const char *file) 94046723f19Sschwarze { 94181475784Sschwarze 94281475784Sschwarze if ('.' == file[0] && '/' == file[1]) 9432f7bef27Sschwarze file += 2; 94446723f19Sschwarze 94546723f19Sschwarze if ('c' == *file) 946941df026Sschwarze resp_catman(req, file); 94746723f19Sschwarze else 948941df026Sschwarze resp_format(req, file); 94946723f19Sschwarze } 95046723f19Sschwarze 95146723f19Sschwarze static void 952b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 953c6c22f12Sschwarze { 954b53c14c3Sschwarze char *manpath; 955b53c14c3Sschwarze const char *file; 956c6c22f12Sschwarze 957b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 958facea411Sschwarze pg_error_badrequest( 959c6c22f12Sschwarze "You did not specify a page to show."); 960c6c22f12Sschwarze return; 961c6c22f12Sschwarze } 962b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 963b53c14c3Sschwarze file++; 964c6c22f12Sschwarze 965b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 966631ce2c6Sschwarze pg_error_badrequest( 967631ce2c6Sschwarze "You specified an invalid manpath."); 968b53c14c3Sschwarze free(manpath); 969631ce2c6Sschwarze return; 970631ce2c6Sschwarze } 971631ce2c6Sschwarze 972c6c22f12Sschwarze /* 973c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 974c6c22f12Sschwarze * This way we can pick up the database files, which are 975c6c22f12Sschwarze * relative to the manpath root. 976c6c22f12Sschwarze */ 977c6c22f12Sschwarze 978b53c14c3Sschwarze if (chdir(manpath) == -1) { 979c976f0e2Sschwarze warn("chdir %s", manpath); 980631ce2c6Sschwarze pg_error_internal(); 981b53c14c3Sschwarze free(manpath); 982c6c22f12Sschwarze return; 983c6c22f12Sschwarze } 984b53c14c3Sschwarze free(manpath); 985b53c14c3Sschwarze 986b53c14c3Sschwarze if ( ! validate_filename(file)) { 98781475784Sschwarze pg_error_badrequest( 98881475784Sschwarze "You specified an invalid manual file."); 98981475784Sschwarze return; 99081475784Sschwarze } 99181475784Sschwarze 992a43df5a0Sschwarze if (resp_begin_html(200, NULL, file) == 0) 993a43df5a0Sschwarze puts("<header>"); 99484f05c93Sschwarze resp_searchform(req, FOCUS_NONE); 995a43df5a0Sschwarze puts("</header>"); 996b53c14c3Sschwarze resp_show(req, file); 99746723f19Sschwarze resp_end_html(); 998c6c22f12Sschwarze } 999c6c22f12Sschwarze 1000c6c22f12Sschwarze static void 100157482ef4Sschwarze pg_search(const struct req *req) 1002c6c22f12Sschwarze { 1003c6c22f12Sschwarze struct mansearch search; 1004c6c22f12Sschwarze struct manpaths paths; 1005c6c22f12Sschwarze struct manpage *res; 1006fbeeb774Sschwarze char **argv; 1007fbeeb774Sschwarze char *query, *rp, *wp; 1008c6c22f12Sschwarze size_t ressz; 1009fbeeb774Sschwarze int argc; 1010c6c22f12Sschwarze 1011c6c22f12Sschwarze /* 1012c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 1013c6c22f12Sschwarze * This way we can pick up the database files, which are 1014c6c22f12Sschwarze * relative to the manpath root. 1015c6c22f12Sschwarze */ 1016c6c22f12Sschwarze 1017c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) { 1018c976f0e2Sschwarze warn("chdir %s", req->q.manpath); 1019631ce2c6Sschwarze pg_error_internal(); 1020c6c22f12Sschwarze return; 1021c6c22f12Sschwarze } 1022c6c22f12Sschwarze 1023c6c22f12Sschwarze search.arch = req->q.arch; 1024c6c22f12Sschwarze search.sec = req->q.sec; 10250f10154cSschwarze search.outkey = "Nd"; 10260f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 1027fea71919Sschwarze search.firstmatch = 1; 1028c6c22f12Sschwarze 1029c6c22f12Sschwarze paths.sz = 1; 1030c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 1031c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 1032c6c22f12Sschwarze 1033c6c22f12Sschwarze /* 1034fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 1035c6c22f12Sschwarze */ 1036c6c22f12Sschwarze 1037fbeeb774Sschwarze argc = 0; 1038fbeeb774Sschwarze argv = NULL; 1039fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 1040fbeeb774Sschwarze for (;;) { 1041fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 1042fbeeb774Sschwarze rp++; 1043fbeeb774Sschwarze if (*rp == '\0') 1044fbeeb774Sschwarze break; 1045fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 1046fbeeb774Sschwarze argv[argc++] = wp = rp; 1047fbeeb774Sschwarze for (;;) { 1048fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 1049fbeeb774Sschwarze *wp = '\0'; 1050fbeeb774Sschwarze rp++; 1051fbeeb774Sschwarze break; 1052fbeeb774Sschwarze } 1053fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 1054fbeeb774Sschwarze rp++; 1055fbeeb774Sschwarze if (wp != rp) 1056fbeeb774Sschwarze *wp = *rp; 1057fbeeb774Sschwarze if (*rp == '\0') 1058fbeeb774Sschwarze break; 1059fbeeb774Sschwarze wp++; 1060fbeeb774Sschwarze rp++; 1061fbeeb774Sschwarze } 1062c6c22f12Sschwarze } 1063c6c22f12Sschwarze 1064e1beff2aSschwarze res = NULL; 1065e1beff2aSschwarze ressz = 0; 1066e1beff2aSschwarze if (req->isquery && req->q.equal && argc == 1) 1067e1beff2aSschwarze pg_redirect(req, argv[0]); 1068e1beff2aSschwarze else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0) 106918ccf011Sschwarze pg_noresult(req, 400, "Bad Request", 107018ccf011Sschwarze "You entered an invalid query."); 1071e1beff2aSschwarze else if (ressz == 0) 107218ccf011Sschwarze pg_noresult(req, 404, "Not Found", "No results found."); 1073c6c22f12Sschwarze else 1074facea411Sschwarze pg_searchres(req, res, ressz); 1075c6c22f12Sschwarze 1076fbeeb774Sschwarze free(query); 1077fbeeb774Sschwarze mansearch_free(res, ressz); 1078c6c22f12Sschwarze free(paths.paths[0]); 1079c6c22f12Sschwarze free(paths.paths); 1080c6c22f12Sschwarze } 1081c6c22f12Sschwarze 1082c6c22f12Sschwarze int 1083c6c22f12Sschwarze main(void) 1084c6c22f12Sschwarze { 1085c6c22f12Sschwarze struct req req; 1086136c26b8Sschwarze struct itimerval itimer; 108757482ef4Sschwarze const char *path; 108831e689c3Sschwarze const char *querystring; 108957482ef4Sschwarze int i; 1090c6c22f12Sschwarze 1091f80eb964Sschwarze /* 1092f80eb964Sschwarze * The "rpath" pledge could be revoked after mparse_readfd() 1093f80eb964Sschwarze * if the file desciptor to "/footer.html" would be opened 1094f80eb964Sschwarze * up front, but it's probably not worth the complication 1095f80eb964Sschwarze * of the code it would cause: it would require scattering 1096f80eb964Sschwarze * pledge() calls in multiple low-level resp_*() functions. 1097f80eb964Sschwarze */ 1098f80eb964Sschwarze 1099f80eb964Sschwarze if (pledge("stdio rpath", NULL) == -1) { 1100f80eb964Sschwarze warn("pledge"); 1101f80eb964Sschwarze pg_error_internal(); 1102f80eb964Sschwarze return EXIT_FAILURE; 1103f80eb964Sschwarze } 1104f80eb964Sschwarze 1105136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 1106136c26b8Sschwarze 11072935aafcSschwarze itimer.it_value.tv_sec = 2; 1108136c26b8Sschwarze itimer.it_value.tv_usec = 0; 11092935aafcSschwarze itimer.it_interval.tv_sec = 2; 1110136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 1111136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1112c976f0e2Sschwarze warn("setitimer"); 1113136c26b8Sschwarze pg_error_internal(); 1114526e306bSschwarze return EXIT_FAILURE; 1115136c26b8Sschwarze } 1116136c26b8Sschwarze 1117c6c22f12Sschwarze /* 11186fdade3eSschwarze * First we change directory into the MAN_DIR so that 1119c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 1120c6c22f12Sschwarze * relative to the same position. 1121c6c22f12Sschwarze */ 1122c6c22f12Sschwarze 1123c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) { 1124c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR); 1125facea411Sschwarze pg_error_internal(); 1126526e306bSschwarze return EXIT_FAILURE; 1127c6c22f12Sschwarze } 1128c6c22f12Sschwarze 1129c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1130abf19dc9Sschwarze req.q.equal = 1; 1131941df026Sschwarze parse_manpath_conf(&req); 1132c6c22f12Sschwarze 113302b1b494Sschwarze /* Parse the path info and the query string. */ 1134c6c22f12Sschwarze 113502b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 113602b1b494Sschwarze path = ""; 113702b1b494Sschwarze else if (*path == '/') 113802b1b494Sschwarze path++; 113902b1b494Sschwarze 1140aa16a3a8Sschwarze if (*path != '\0') { 1141941df026Sschwarze parse_path_info(&req, path); 1142a9941855Sschwarze if (req.q.manpath == NULL || req.q.sec == NULL || 1143a9941855Sschwarze *req.q.query == '\0' || access(path, F_OK) == -1) 114402b1b494Sschwarze path = ""; 114502b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1146941df026Sschwarze parse_query_string(&req, querystring); 1147c6c22f12Sschwarze 114802b1b494Sschwarze /* Validate parsed data and add defaults. */ 114902b1b494Sschwarze 115050eaed2bSschwarze if (req.q.manpath == NULL) 115150eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 115250eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1153631ce2c6Sschwarze pg_error_badrequest( 1154631ce2c6Sschwarze "You specified an invalid manpath."); 1155526e306bSschwarze return EXIT_FAILURE; 1156631ce2c6Sschwarze } 1157631ce2c6Sschwarze 1158f7a12365Sschwarze if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) { 1159cf3a545cSschwarze pg_error_badrequest( 1160cf3a545cSschwarze "You specified an invalid architecture."); 1161526e306bSschwarze return EXIT_FAILURE; 1162cf3a545cSschwarze } 1163cf3a545cSschwarze 116457482ef4Sschwarze /* Dispatch to the three different pages. */ 1165c6c22f12Sschwarze 116657482ef4Sschwarze if ('\0' != *path) 116757482ef4Sschwarze pg_show(&req, path); 1168e89321abSschwarze else if (NULL != req.q.query) 116957482ef4Sschwarze pg_search(&req); 117057482ef4Sschwarze else 1171facea411Sschwarze pg_index(&req); 1172c6c22f12Sschwarze 117331e689c3Sschwarze free(req.q.manpath); 117431e689c3Sschwarze free(req.q.arch); 117531e689c3Sschwarze free(req.q.sec); 1176e89321abSschwarze free(req.q.query); 1177c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1178c6c22f12Sschwarze free(req.p[i]); 1179c6c22f12Sschwarze free(req.p); 1180526e306bSschwarze return EXIT_SUCCESS; 1181c6c22f12Sschwarze } 1182c6c22f12Sschwarze 1183c6c22f12Sschwarze /* 1184f5938fa6Sschwarze * Translate PATH_INFO to a query. 118502b1b494Sschwarze */ 118602b1b494Sschwarze static void 1187941df026Sschwarze parse_path_info(struct req *req, const char *path) 118802b1b494Sschwarze { 1189f5938fa6Sschwarze const char *name, *sec, *end; 119002b1b494Sschwarze 119152b413c1Sschwarze req->isquery = 0; 119202b1b494Sschwarze req->q.equal = 1; 1193f5938fa6Sschwarze req->q.manpath = NULL; 1194daf4c292Sschwarze req->q.arch = NULL; 119502b1b494Sschwarze 119602b1b494Sschwarze /* Mandatory manual page name. */ 1197f5938fa6Sschwarze if ((name = strrchr(path, '/')) == NULL) 1198f5938fa6Sschwarze name = path; 1199f5938fa6Sschwarze else 1200f5938fa6Sschwarze name++; 120102b1b494Sschwarze 120202b1b494Sschwarze /* Optional trailing section. */ 1203f5938fa6Sschwarze sec = strrchr(name, '.'); 1204f5938fa6Sschwarze if (sec != NULL && isdigit((unsigned char)*++sec)) { 1205f5938fa6Sschwarze req->q.query = mandoc_strndup(name, sec - name - 1); 1206f5938fa6Sschwarze req->q.sec = mandoc_strdup(sec); 1207f5938fa6Sschwarze } else { 1208f5938fa6Sschwarze req->q.query = mandoc_strdup(name); 120902b1b494Sschwarze req->q.sec = NULL; 121002b1b494Sschwarze } 121102b1b494Sschwarze 121202b1b494Sschwarze /* Handle the case of name[.section] only. */ 1213f5938fa6Sschwarze if (name == path) 121402b1b494Sschwarze return; 121502b1b494Sschwarze 1216f5938fa6Sschwarze /* Optional manpath. */ 1217f5938fa6Sschwarze end = strchr(path, '/'); 1218f5938fa6Sschwarze req->q.manpath = mandoc_strndup(path, end - path); 1219f5938fa6Sschwarze if (validate_manpath(req, req->q.manpath)) { 1220f5938fa6Sschwarze path = end + 1; 1221f5938fa6Sschwarze if (name == path) 1222f5938fa6Sschwarze return; 1223f5938fa6Sschwarze } else { 1224f5938fa6Sschwarze free(req->q.manpath); 1225f5938fa6Sschwarze req->q.manpath = NULL; 1226f5938fa6Sschwarze } 1227f5938fa6Sschwarze 1228f5938fa6Sschwarze /* Optional section. */ 1229955967fcSschwarze if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) { 1230f5938fa6Sschwarze path += 3; 1231f5938fa6Sschwarze end = strchr(path, '/'); 1232f5938fa6Sschwarze free(req->q.sec); 1233f5938fa6Sschwarze req->q.sec = mandoc_strndup(path, end - path); 1234f5938fa6Sschwarze path = end + 1; 1235f5938fa6Sschwarze if (name == path) 1236f5938fa6Sschwarze return; 1237f5938fa6Sschwarze } 1238f5938fa6Sschwarze 1239f5938fa6Sschwarze /* Optional architecture. */ 1240f5938fa6Sschwarze end = strchr(path, '/'); 1241f5938fa6Sschwarze if (end + 1 != name) { 1242daf4c292Sschwarze pg_error_badrequest( 1243daf4c292Sschwarze "You specified too many directory components."); 1244daf4c292Sschwarze exit(EXIT_FAILURE); 124502b1b494Sschwarze } 1246f5938fa6Sschwarze req->q.arch = mandoc_strndup(path, end - path); 1247f5938fa6Sschwarze if (validate_arch(req->q.arch) == 0) { 1248daf4c292Sschwarze pg_error_badrequest( 1249daf4c292Sschwarze "You specified an invalid directory component."); 1250daf4c292Sschwarze exit(EXIT_FAILURE); 1251daf4c292Sschwarze } 125202b1b494Sschwarze } 125302b1b494Sschwarze 125402b1b494Sschwarze /* 1255c6c22f12Sschwarze * Scan for indexable paths. 1256c6c22f12Sschwarze */ 1257c6c22f12Sschwarze static void 1258941df026Sschwarze parse_manpath_conf(struct req *req) 1259c6c22f12Sschwarze { 1260c6c22f12Sschwarze FILE *fp; 1261c6c22f12Sschwarze char *dp; 1262c6c22f12Sschwarze size_t dpsz; 126331f93c25Sschwarze ssize_t len; 1264c6c22f12Sschwarze 1265c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) { 1266c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR); 1267de651747Sschwarze pg_error_internal(); 1268de651747Sschwarze exit(EXIT_FAILURE); 1269de651747Sschwarze } 1270c6c22f12Sschwarze 127131f93c25Sschwarze dp = NULL; 127231f93c25Sschwarze dpsz = 0; 127331f93c25Sschwarze 127431f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 127531f93c25Sschwarze if (dp[len - 1] == '\n') 127631f93c25Sschwarze dp[--len] = '\0'; 1277c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1278c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1279cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1280c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1281c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp); 1282cf3a545cSschwarze pg_error_internal(); 1283cf3a545cSschwarze exit(EXIT_FAILURE); 1284cf3a545cSschwarze } 1285c976f0e2Sschwarze if (strchr(dp, '/') != NULL) { 1286c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1287c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp); 1288cf3a545cSschwarze pg_error_internal(); 1289cf3a545cSschwarze exit(EXIT_FAILURE); 1290cf3a545cSschwarze } 1291cf3a545cSschwarze req->p[req->psz++] = dp; 129231f93c25Sschwarze dp = NULL; 129331f93c25Sschwarze dpsz = 0; 1294c6c22f12Sschwarze } 129531f93c25Sschwarze free(dp); 1296de651747Sschwarze 1297de651747Sschwarze if (req->p == NULL) { 1298c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR); 1299de651747Sschwarze pg_error_internal(); 1300de651747Sschwarze exit(EXIT_FAILURE); 1301de651747Sschwarze } 1302c6c22f12Sschwarze } 1303