1322249Sbapt/* $Id: cgi.c,v 1.156 2017/06/24 14:38:32 schwarze Exp $ */ 2274880Sbapt/* 3274880Sbapt * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4316420Sbapt * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze <schwarze@usta.de> 5274880Sbapt * 6274880Sbapt * Permission to use, copy, modify, and distribute this software for any 7274880Sbapt * purpose with or without fee is hereby granted, provided that the above 8274880Sbapt * copyright notice and this permission notice appear in all copies. 9274880Sbapt * 10294113Sbapt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 11274880Sbapt * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12294113Sbapt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 13274880Sbapt * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14274880Sbapt * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15274880Sbapt * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16274880Sbapt * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17274880Sbapt */ 18274880Sbapt#include "config.h" 19274880Sbapt 20275432Sbapt#include <sys/types.h> 21275432Sbapt#include <sys/time.h> 22275432Sbapt 23274880Sbapt#include <ctype.h> 24322249Sbapt#if HAVE_ERR 25307795Sbapt#include <err.h> 26322249Sbapt#endif 27274880Sbapt#include <errno.h> 28274880Sbapt#include <fcntl.h> 29274880Sbapt#include <limits.h> 30274880Sbapt#include <stdint.h> 31274880Sbapt#include <stdio.h> 32274880Sbapt#include <stdlib.h> 33274880Sbapt#include <string.h> 34274880Sbapt#include <unistd.h> 35274880Sbapt 36294113Sbapt#include "mandoc_aux.h" 37274880Sbapt#include "mandoc.h" 38294113Sbapt#include "roff.h" 39294113Sbapt#include "mdoc.h" 40294113Sbapt#include "man.h" 41274880Sbapt#include "main.h" 42294113Sbapt#include "manconf.h" 43274880Sbapt#include "mansearch.h" 44274880Sbapt#include "cgi.h" 45274880Sbapt 46274880Sbapt/* 47274880Sbapt * A query as passed to the search function. 48274880Sbapt */ 49274880Sbaptstruct query { 50274880Sbapt char *manpath; /* desired manual directory */ 51274880Sbapt char *arch; /* architecture */ 52274880Sbapt char *sec; /* manual section */ 53274880Sbapt char *query; /* unparsed query expression */ 54274880Sbapt int equal; /* match whole names, not substrings */ 55274880Sbapt}; 56274880Sbapt 57274880Sbaptstruct req { 58274880Sbapt struct query q; 59274880Sbapt char **p; /* array of available manpaths */ 60274880Sbapt size_t psz; /* number of available manpaths */ 61307795Sbapt int isquery; /* QUERY_STRING used, not PATH_INFO */ 62274880Sbapt}; 63274880Sbapt 64307795Sbaptenum focus { 65307795Sbapt FOCUS_NONE = 0, 66307795Sbapt FOCUS_QUERY 67307795Sbapt}; 68307795Sbapt 69274880Sbaptstatic void html_print(const char *); 70274880Sbaptstatic void html_putchar(char); 71279527Sbaptstatic int http_decode(char *); 72307795Sbaptstatic void parse_manpath_conf(struct req *); 73307795Sbaptstatic void parse_path_info(struct req *req, const char *path); 74307795Sbaptstatic void parse_query_string(struct req *, const char *); 75274880Sbaptstatic void pg_error_badrequest(const char *); 76274880Sbaptstatic void pg_error_internal(void); 77274880Sbaptstatic void pg_index(const struct req *); 78274880Sbaptstatic void pg_noresult(const struct req *, const char *); 79322249Sbaptstatic void pg_redirect(const struct req *, const char *); 80274880Sbaptstatic void pg_search(const struct req *); 81274880Sbaptstatic void pg_searchres(const struct req *, 82274880Sbapt struct manpage *, size_t); 83274880Sbaptstatic void pg_show(struct req *, const char *); 84322249Sbaptstatic void resp_begin_html(int, const char *, const char *); 85274880Sbaptstatic void resp_begin_http(int, const char *); 86307795Sbaptstatic void resp_catman(const struct req *, const char *); 87294113Sbaptstatic void resp_copy(const char *); 88274880Sbaptstatic void resp_end_html(void); 89307795Sbaptstatic void resp_format(const struct req *, const char *); 90307795Sbaptstatic void resp_searchform(const struct req *, enum focus); 91274880Sbaptstatic void resp_show(const struct req *, const char *); 92274880Sbaptstatic void set_query_attr(char **, char **); 93274880Sbaptstatic int validate_filename(const char *); 94274880Sbaptstatic int validate_manpath(const struct req *, const char *); 95274880Sbaptstatic int validate_urifrag(const char *); 96274880Sbapt 97307795Sbaptstatic const char *scriptname = SCRIPT_NAME; 98274880Sbapt 99274880Sbaptstatic const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 100274880Sbaptstatic const char *const sec_numbers[] = { 101274880Sbapt "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 102274880Sbapt}; 103274880Sbaptstatic const char *const sec_names[] = { 104274880Sbapt "All Sections", 105274880Sbapt "1 - General Commands", 106274880Sbapt "2 - System Calls", 107275432Sbapt "3 - Library Functions", 108275432Sbapt "3p - Perl Library", 109275432Sbapt "4 - Device Drivers", 110274880Sbapt "5 - File Formats", 111274880Sbapt "6 - Games", 112275432Sbapt "7 - Miscellaneous Information", 113275432Sbapt "8 - System Manager\'s Manual", 114275432Sbapt "9 - Kernel Developer\'s Manual" 115274880Sbapt}; 116274880Sbaptstatic const int sec_MAX = sizeof(sec_names) / sizeof(char *); 117274880Sbapt 118274880Sbaptstatic const char *const arch_names[] = { 119322249Sbapt "amd64", "alpha", "armv7", "arm64", 120316420Sbapt "hppa", "i386", "landisk", 121307795Sbapt "loongson", "luna88k", "macppc", "mips64", 122316420Sbapt "octeon", "sgi", "socppc", "sparc64", 123316420Sbapt "amiga", "arc", "armish", "arm32", 124316420Sbapt "atari", "aviion", "beagle", "cats", 125316420Sbapt "hppa64", "hp300", 126307795Sbapt "ia64", "mac68k", "mvme68k", "mvme88k", 127307795Sbapt "mvmeppc", "palm", "pc532", "pegasos", 128316420Sbapt "pmax", "powerpc", "solbourne", "sparc", 129316420Sbapt "sun3", "vax", "wgrisc", "x68k", 130316420Sbapt "zaurus" 131274880Sbapt}; 132274880Sbaptstatic const int arch_MAX = sizeof(arch_names) / sizeof(char *); 133274880Sbapt 134274880Sbapt/* 135274880Sbapt * Print a character, escaping HTML along the way. 136274880Sbapt * This will pass non-ASCII straight to output: be warned! 137274880Sbapt */ 138274880Sbaptstatic void 139274880Sbapthtml_putchar(char c) 140274880Sbapt{ 141274880Sbapt 142274880Sbapt switch (c) { 143322249Sbapt case '"': 144316420Sbapt printf("""); 145274880Sbapt break; 146322249Sbapt case '&': 147274880Sbapt printf("&"); 148274880Sbapt break; 149322249Sbapt case '>': 150274880Sbapt printf(">"); 151274880Sbapt break; 152322249Sbapt case '<': 153274880Sbapt printf("<"); 154274880Sbapt break; 155274880Sbapt default: 156274880Sbapt putchar((unsigned char)c); 157274880Sbapt break; 158274880Sbapt } 159274880Sbapt} 160274880Sbapt 161274880Sbapt/* 162274880Sbapt * Call through to html_putchar(). 163274880Sbapt * Accepts NULL strings. 164274880Sbapt */ 165274880Sbaptstatic void 166274880Sbapthtml_print(const char *p) 167274880Sbapt{ 168279527Sbapt 169274880Sbapt if (NULL == p) 170274880Sbapt return; 171274880Sbapt while ('\0' != *p) 172274880Sbapt html_putchar(*p++); 173274880Sbapt} 174274880Sbapt 175274880Sbapt/* 176274880Sbapt * Transfer the responsibility for the allocated string *val 177274880Sbapt * to the query structure. 178274880Sbapt */ 179274880Sbaptstatic void 180274880Sbaptset_query_attr(char **attr, char **val) 181274880Sbapt{ 182274880Sbapt 183274880Sbapt free(*attr); 184274880Sbapt if (**val == '\0') { 185274880Sbapt *attr = NULL; 186274880Sbapt free(*val); 187274880Sbapt } else 188274880Sbapt *attr = *val; 189274880Sbapt *val = NULL; 190274880Sbapt} 191274880Sbapt 192274880Sbapt/* 193274880Sbapt * Parse the QUERY_STRING for key-value pairs 194274880Sbapt * and store the values into the query structure. 195274880Sbapt */ 196274880Sbaptstatic void 197307795Sbaptparse_query_string(struct req *req, const char *qs) 198274880Sbapt{ 199274880Sbapt char *key, *val; 200274880Sbapt size_t keysz, valsz; 201274880Sbapt 202307795Sbapt req->isquery = 1; 203274880Sbapt req->q.manpath = NULL; 204274880Sbapt req->q.arch = NULL; 205274880Sbapt req->q.sec = NULL; 206274880Sbapt req->q.query = NULL; 207274880Sbapt req->q.equal = 1; 208274880Sbapt 209274880Sbapt key = val = NULL; 210274880Sbapt while (*qs != '\0') { 211274880Sbapt 212274880Sbapt /* Parse one key. */ 213274880Sbapt 214274880Sbapt keysz = strcspn(qs, "=;&"); 215274880Sbapt key = mandoc_strndup(qs, keysz); 216274880Sbapt qs += keysz; 217274880Sbapt if (*qs != '=') 218274880Sbapt goto next; 219274880Sbapt 220274880Sbapt /* Parse one value. */ 221274880Sbapt 222274880Sbapt valsz = strcspn(++qs, ";&"); 223274880Sbapt val = mandoc_strndup(qs, valsz); 224274880Sbapt qs += valsz; 225274880Sbapt 226274880Sbapt /* Decode and catch encoding errors. */ 227274880Sbapt 228274880Sbapt if ( ! (http_decode(key) && http_decode(val))) 229274880Sbapt goto next; 230274880Sbapt 231274880Sbapt /* Handle key-value pairs. */ 232274880Sbapt 233274880Sbapt if ( ! strcmp(key, "query")) 234274880Sbapt set_query_attr(&req->q.query, &val); 235274880Sbapt 236274880Sbapt else if ( ! strcmp(key, "apropos")) 237274880Sbapt req->q.equal = !strcmp(val, "0"); 238274880Sbapt 239274880Sbapt else if ( ! strcmp(key, "manpath")) { 240274880Sbapt#ifdef COMPAT_OLDURI 241274880Sbapt if ( ! strncmp(val, "OpenBSD ", 8)) { 242274880Sbapt val[7] = '-'; 243274880Sbapt if ('C' == val[8]) 244274880Sbapt val[8] = 'c'; 245274880Sbapt } 246274880Sbapt#endif 247274880Sbapt set_query_attr(&req->q.manpath, &val); 248274880Sbapt } 249274880Sbapt 250274880Sbapt else if ( ! (strcmp(key, "sec") 251274880Sbapt#ifdef COMPAT_OLDURI 252274880Sbapt && strcmp(key, "sektion") 253274880Sbapt#endif 254274880Sbapt )) { 255274880Sbapt if ( ! strcmp(val, "0")) 256274880Sbapt *val = '\0'; 257274880Sbapt set_query_attr(&req->q.sec, &val); 258274880Sbapt } 259274880Sbapt 260274880Sbapt else if ( ! strcmp(key, "arch")) { 261274880Sbapt if ( ! strcmp(val, "default")) 262274880Sbapt *val = '\0'; 263274880Sbapt set_query_attr(&req->q.arch, &val); 264274880Sbapt } 265274880Sbapt 266274880Sbapt /* 267274880Sbapt * The key must be freed in any case. 268274880Sbapt * The val may have been handed over to the query 269274880Sbapt * structure, in which case it is now NULL. 270274880Sbapt */ 271274880Sbaptnext: 272274880Sbapt free(key); 273274880Sbapt key = NULL; 274274880Sbapt free(val); 275274880Sbapt val = NULL; 276274880Sbapt 277274880Sbapt if (*qs != '\0') 278274880Sbapt qs++; 279274880Sbapt } 280274880Sbapt} 281274880Sbapt 282274880Sbapt/* 283274880Sbapt * HTTP-decode a string. The standard explanation is that this turns 284274880Sbapt * "%4e+foo" into "n foo" in the regular way. This is done in-place 285274880Sbapt * over the allocated string. 286274880Sbapt */ 287274880Sbaptstatic int 288274880Sbapthttp_decode(char *p) 289274880Sbapt{ 290274880Sbapt char hex[3]; 291274880Sbapt char *q; 292274880Sbapt int c; 293274880Sbapt 294274880Sbapt hex[2] = '\0'; 295274880Sbapt 296274880Sbapt q = p; 297274880Sbapt for ( ; '\0' != *p; p++, q++) { 298274880Sbapt if ('%' == *p) { 299274880Sbapt if ('\0' == (hex[0] = *(p + 1))) 300294113Sbapt return 0; 301274880Sbapt if ('\0' == (hex[1] = *(p + 2))) 302294113Sbapt return 0; 303274880Sbapt if (1 != sscanf(hex, "%x", &c)) 304294113Sbapt return 0; 305274880Sbapt if ('\0' == c) 306294113Sbapt return 0; 307274880Sbapt 308274880Sbapt *q = (char)c; 309274880Sbapt p += 2; 310274880Sbapt } else 311274880Sbapt *q = '+' == *p ? ' ' : *p; 312274880Sbapt } 313274880Sbapt 314274880Sbapt *q = '\0'; 315294113Sbapt return 1; 316274880Sbapt} 317274880Sbapt 318274880Sbaptstatic void 319274880Sbaptresp_begin_http(int code, const char *msg) 320274880Sbapt{ 321274880Sbapt 322274880Sbapt if (200 != code) 323274880Sbapt printf("Status: %d %s\r\n", code, msg); 324274880Sbapt 325274880Sbapt printf("Content-Type: text/html; charset=utf-8\r\n" 326274880Sbapt "Cache-Control: no-cache\r\n" 327274880Sbapt "Pragma: no-cache\r\n" 328274880Sbapt "\r\n"); 329274880Sbapt 330274880Sbapt fflush(stdout); 331274880Sbapt} 332274880Sbapt 333274880Sbaptstatic void 334294113Sbaptresp_copy(const char *filename) 335294113Sbapt{ 336294113Sbapt char buf[4096]; 337294113Sbapt ssize_t sz; 338294113Sbapt int fd; 339294113Sbapt 340294113Sbapt if ((fd = open(filename, O_RDONLY)) != -1) { 341294113Sbapt fflush(stdout); 342294113Sbapt while ((sz = read(fd, buf, sizeof(buf))) > 0) 343294113Sbapt write(STDOUT_FILENO, buf, sz); 344316420Sbapt close(fd); 345294113Sbapt } 346294113Sbapt} 347294113Sbapt 348294113Sbaptstatic void 349322249Sbaptresp_begin_html(int code, const char *msg, const char *file) 350274880Sbapt{ 351322249Sbapt char *cp; 352274880Sbapt 353274880Sbapt resp_begin_http(code, msg); 354274880Sbapt 355275432Sbapt printf("<!DOCTYPE html>\n" 356307795Sbapt "<html>\n" 357307795Sbapt "<head>\n" 358316420Sbapt " <meta charset=\"UTF-8\"/>\n" 359316420Sbapt " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 360307795Sbapt " type=\"text/css\" media=\"all\">\n" 361322249Sbapt " <title>", 362322249Sbapt CSS_DIR); 363322249Sbapt if (file != NULL) { 364322249Sbapt if ((cp = strrchr(file, '/')) != NULL) 365322249Sbapt file = cp + 1; 366322249Sbapt if ((cp = strrchr(file, '.')) != NULL) { 367322249Sbapt printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1); 368322249Sbapt } else 369322249Sbapt printf("%s - ", file); 370322249Sbapt } 371322249Sbapt printf("%s</title>\n" 372307795Sbapt "</head>\n" 373316420Sbapt "<body>\n", 374322249Sbapt CUSTOMIZE_TITLE); 375294113Sbapt 376294113Sbapt resp_copy(MAN_DIR "/header.html"); 377274880Sbapt} 378274880Sbapt 379274880Sbaptstatic void 380274880Sbaptresp_end_html(void) 381274880Sbapt{ 382274880Sbapt 383294113Sbapt resp_copy(MAN_DIR "/footer.html"); 384294113Sbapt 385307795Sbapt puts("</body>\n" 386307795Sbapt "</html>"); 387274880Sbapt} 388274880Sbapt 389274880Sbaptstatic void 390307795Sbaptresp_searchform(const struct req *req, enum focus focus) 391274880Sbapt{ 392274880Sbapt int i; 393274880Sbapt 394316420Sbapt printf("<form action=\"/%s\" method=\"get\">\n" 395316420Sbapt " <fieldset>\n" 396316420Sbapt " <legend>Manual Page Search Parameters</legend>\n", 397274880Sbapt scriptname); 398274880Sbapt 399274880Sbapt /* Write query input box. */ 400274880Sbapt 401316420Sbapt printf(" <input type=\"text\" name=\"query\" value=\""); 402307795Sbapt if (req->q.query != NULL) 403274880Sbapt html_print(req->q.query); 404307795Sbapt printf( "\" size=\"40\""); 405307795Sbapt if (focus == FOCUS_QUERY) 406307795Sbapt printf(" autofocus"); 407307795Sbapt puts(">"); 408274880Sbapt 409307795Sbapt /* Write submission buttons. */ 410274880Sbapt 411316420Sbapt printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">" 412307795Sbapt "man</button>\n" 413316420Sbapt " <button type=\"submit\" name=\"apropos\" value=\"1\">" 414316420Sbapt "apropos</button>\n" 415316420Sbapt " <br/>\n"); 416274880Sbapt 417274880Sbapt /* Write section selector. */ 418274880Sbapt 419316420Sbapt puts(" <select name=\"sec\">"); 420274880Sbapt for (i = 0; i < sec_MAX; i++) { 421316420Sbapt printf(" <option value=\"%s\"", sec_numbers[i]); 422274880Sbapt if (NULL != req->q.sec && 423274880Sbapt 0 == strcmp(sec_numbers[i], req->q.sec)) 424307795Sbapt printf(" selected=\"selected\""); 425307795Sbapt printf(">%s</option>\n", sec_names[i]); 426274880Sbapt } 427316420Sbapt puts(" </select>"); 428274880Sbapt 429274880Sbapt /* Write architecture selector. */ 430274880Sbapt 431316420Sbapt printf( " <select name=\"arch\">\n" 432316420Sbapt " <option value=\"default\""); 433274880Sbapt if (NULL == req->q.arch) 434307795Sbapt printf(" selected=\"selected\""); 435307795Sbapt puts(">All Architectures</option>"); 436274880Sbapt for (i = 0; i < arch_MAX; i++) { 437316420Sbapt printf(" <option value=\"%s\"", arch_names[i]); 438274880Sbapt if (NULL != req->q.arch && 439274880Sbapt 0 == strcmp(arch_names[i], req->q.arch)) 440307795Sbapt printf(" selected=\"selected\""); 441307795Sbapt printf(">%s</option>\n", arch_names[i]); 442274880Sbapt } 443316420Sbapt puts(" </select>"); 444274880Sbapt 445274880Sbapt /* Write manpath selector. */ 446274880Sbapt 447274880Sbapt if (req->psz > 1) { 448316420Sbapt puts(" <select name=\"manpath\">"); 449274880Sbapt for (i = 0; i < (int)req->psz; i++) { 450316420Sbapt printf(" <option "); 451275432Sbapt if (strcmp(req->q.manpath, req->p[i]) == 0) 452307795Sbapt printf("selected=\"selected\" "); 453307795Sbapt printf("value=\""); 454274880Sbapt html_print(req->p[i]); 455274880Sbapt printf("\">"); 456274880Sbapt html_print(req->p[i]); 457307795Sbapt puts("</option>"); 458274880Sbapt } 459316420Sbapt puts(" </select>"); 460274880Sbapt } 461274880Sbapt 462316420Sbapt puts(" </fieldset>\n" 463316420Sbapt "</form>"); 464274880Sbapt} 465274880Sbapt 466274880Sbaptstatic int 467274880Sbaptvalidate_urifrag(const char *frag) 468274880Sbapt{ 469274880Sbapt 470274880Sbapt while ('\0' != *frag) { 471274880Sbapt if ( ! (isalnum((unsigned char)*frag) || 472274880Sbapt '-' == *frag || '.' == *frag || 473274880Sbapt '/' == *frag || '_' == *frag)) 474294113Sbapt return 0; 475274880Sbapt frag++; 476274880Sbapt } 477294113Sbapt return 1; 478274880Sbapt} 479274880Sbapt 480274880Sbaptstatic int 481274880Sbaptvalidate_manpath(const struct req *req, const char* manpath) 482274880Sbapt{ 483274880Sbapt size_t i; 484274880Sbapt 485274880Sbapt for (i = 0; i < req->psz; i++) 486274880Sbapt if ( ! strcmp(manpath, req->p[i])) 487294113Sbapt return 1; 488274880Sbapt 489294113Sbapt return 0; 490274880Sbapt} 491274880Sbapt 492274880Sbaptstatic int 493274880Sbaptvalidate_filename(const char *file) 494274880Sbapt{ 495274880Sbapt 496274880Sbapt if ('.' == file[0] && '/' == file[1]) 497274880Sbapt file += 2; 498274880Sbapt 499294113Sbapt return ! (strstr(file, "../") || strstr(file, "/..") || 500294113Sbapt (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 501274880Sbapt} 502274880Sbapt 503274880Sbaptstatic void 504274880Sbaptpg_index(const struct req *req) 505274880Sbapt{ 506274880Sbapt 507322249Sbapt resp_begin_html(200, NULL, NULL); 508307795Sbapt resp_searchform(req, FOCUS_QUERY); 509307795Sbapt printf("<p>\n" 510274880Sbapt "This web interface is documented in the\n" 511316420Sbapt "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n" 512274880Sbapt "manual, and the\n" 513316420Sbapt "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n" 514274880Sbapt "manual explains the query syntax.\n" 515307795Sbapt "</p>\n", 516307795Sbapt scriptname, *scriptname == '\0' ? "" : "/", 517307795Sbapt scriptname, *scriptname == '\0' ? "" : "/"); 518274880Sbapt resp_end_html(); 519274880Sbapt} 520274880Sbapt 521274880Sbaptstatic void 522274880Sbaptpg_noresult(const struct req *req, const char *msg) 523274880Sbapt{ 524322249Sbapt resp_begin_html(200, NULL, NULL); 525307795Sbapt resp_searchform(req, FOCUS_QUERY); 526307795Sbapt puts("<p>"); 527274880Sbapt puts(msg); 528307795Sbapt puts("</p>"); 529274880Sbapt resp_end_html(); 530274880Sbapt} 531274880Sbapt 532274880Sbaptstatic void 533274880Sbaptpg_error_badrequest(const char *msg) 534274880Sbapt{ 535274880Sbapt 536322249Sbapt resp_begin_html(400, "Bad Request", NULL); 537307795Sbapt puts("<h1>Bad Request</h1>\n" 538307795Sbapt "<p>\n"); 539274880Sbapt puts(msg); 540274880Sbapt printf("Try again from the\n" 541307795Sbapt "<a href=\"/%s\">main page</a>.\n" 542307795Sbapt "</p>", scriptname); 543274880Sbapt resp_end_html(); 544274880Sbapt} 545274880Sbapt 546274880Sbaptstatic void 547274880Sbaptpg_error_internal(void) 548274880Sbapt{ 549322249Sbapt resp_begin_html(500, "Internal Server Error", NULL); 550307795Sbapt puts("<p>Internal Server Error</p>"); 551274880Sbapt resp_end_html(); 552274880Sbapt} 553274880Sbapt 554274880Sbaptstatic void 555322249Sbaptpg_redirect(const struct req *req, const char *name) 556322249Sbapt{ 557322249Sbapt printf("Status: 303 See Other\r\n" 558322249Sbapt "Location: /"); 559322249Sbapt if (*scriptname != '\0') 560322249Sbapt printf("%s/", scriptname); 561322249Sbapt if (strcmp(req->q.manpath, req->p[0])) 562322249Sbapt printf("%s/", req->q.manpath); 563322249Sbapt if (req->q.arch != NULL) 564322249Sbapt printf("%s/", req->q.arch); 565322249Sbapt printf("%s", name); 566322249Sbapt if (req->q.sec != NULL) 567322249Sbapt printf(".%s", req->q.sec); 568322249Sbapt printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"); 569322249Sbapt} 570322249Sbapt 571322249Sbaptstatic void 572274880Sbaptpg_searchres(const struct req *req, struct manpage *r, size_t sz) 573274880Sbapt{ 574274880Sbapt char *arch, *archend; 575307795Sbapt const char *sec; 576307795Sbapt size_t i, iuse; 577274880Sbapt int archprio, archpriouse; 578274880Sbapt int prio, priouse; 579274880Sbapt 580274880Sbapt for (i = 0; i < sz; i++) { 581274880Sbapt if (validate_filename(r[i].file)) 582274880Sbapt continue; 583307795Sbapt warnx("invalid filename %s in %s database", 584274880Sbapt r[i].file, req->q.manpath); 585274880Sbapt pg_error_internal(); 586274880Sbapt return; 587274880Sbapt } 588274880Sbapt 589307795Sbapt if (req->isquery && sz == 1) { 590274880Sbapt /* 591274880Sbapt * If we have just one result, then jump there now 592274880Sbapt * without any delay. 593274880Sbapt */ 594322249Sbapt printf("Status: 303 See Other\r\n" 595322249Sbapt "Location: /"); 596322249Sbapt if (*scriptname != '\0') 597322249Sbapt printf("%s/", scriptname); 598322249Sbapt if (strcmp(req->q.manpath, req->p[0])) 599322249Sbapt printf("%s/", req->q.manpath); 600322249Sbapt printf("%s\r\n" 601322249Sbapt "Content-Type: text/html; charset=utf-8\r\n\r\n", 602322249Sbapt r[0].file); 603274880Sbapt return; 604274880Sbapt } 605274880Sbapt 606274880Sbapt /* 607274880Sbapt * In man(1) mode, show one of the pages 608274880Sbapt * even if more than one is found. 609274880Sbapt */ 610274880Sbapt 611322249Sbapt iuse = 0; 612307795Sbapt if (req->q.equal || sz == 1) { 613307795Sbapt priouse = 20; 614274880Sbapt archpriouse = 3; 615274880Sbapt for (i = 0; i < sz; i++) { 616307795Sbapt sec = r[i].file; 617307795Sbapt sec += strcspn(sec, "123456789"); 618307795Sbapt if (sec[0] == '\0') 619274880Sbapt continue; 620307795Sbapt prio = sec_prios[sec[0] - '1']; 621307795Sbapt if (sec[1] != '/') 622307795Sbapt prio += 10; 623307795Sbapt if (req->q.arch == NULL) { 624274880Sbapt archprio = 625307795Sbapt ((arch = strchr(sec + 1, '/')) 626307795Sbapt == NULL) ? 3 : 627307795Sbapt ((archend = strchr(arch + 1, '/')) 628307795Sbapt == NULL) ? 0 : 629274880Sbapt strncmp(arch, "amd64/", 630274880Sbapt archend - arch) ? 2 : 1; 631274880Sbapt if (archprio < archpriouse) { 632274880Sbapt archpriouse = archprio; 633274880Sbapt priouse = prio; 634274880Sbapt iuse = i; 635274880Sbapt continue; 636274880Sbapt } 637274880Sbapt if (archprio > archpriouse) 638274880Sbapt continue; 639274880Sbapt } 640274880Sbapt if (prio >= priouse) 641274880Sbapt continue; 642274880Sbapt priouse = prio; 643274880Sbapt iuse = i; 644274880Sbapt } 645322249Sbapt resp_begin_html(200, NULL, r[iuse].file); 646322249Sbapt } else 647322249Sbapt resp_begin_html(200, NULL, NULL); 648322249Sbapt 649322249Sbapt resp_searchform(req, 650322249Sbapt req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 651322249Sbapt 652322249Sbapt if (sz > 1) { 653322249Sbapt puts("<table class=\"results\">"); 654322249Sbapt for (i = 0; i < sz; i++) { 655322249Sbapt printf(" <tr>\n" 656322249Sbapt " <td>" 657322249Sbapt "<a class=\"Xr\" href=\"/"); 658322249Sbapt if (*scriptname != '\0') 659322249Sbapt printf("%s/", scriptname); 660322249Sbapt if (strcmp(req->q.manpath, req->p[0])) 661322249Sbapt printf("%s/", req->q.manpath); 662322249Sbapt printf("%s\">", r[i].file); 663322249Sbapt html_print(r[i].names); 664322249Sbapt printf("</a></td>\n" 665322249Sbapt " <td><span class=\"Nd\">"); 666322249Sbapt html_print(r[i].output); 667322249Sbapt puts("</span></td>\n" 668322249Sbapt " </tr>"); 669322249Sbapt } 670322249Sbapt puts("</table>"); 671322249Sbapt } 672322249Sbapt 673322249Sbapt if (req->q.equal || sz == 1) { 674322249Sbapt puts("<hr>"); 675274880Sbapt resp_show(req, r[iuse].file); 676274880Sbapt } 677274880Sbapt 678274880Sbapt resp_end_html(); 679274880Sbapt} 680274880Sbapt 681274880Sbaptstatic void 682307795Sbaptresp_catman(const struct req *req, const char *file) 683274880Sbapt{ 684274880Sbapt FILE *f; 685294113Sbapt char *p; 686294113Sbapt size_t sz; 687294113Sbapt ssize_t len; 688274880Sbapt int i; 689274880Sbapt int italic, bold; 690274880Sbapt 691294113Sbapt if ((f = fopen(file, "r")) == NULL) { 692307795Sbapt puts("<p>You specified an invalid manual file.</p>"); 693274880Sbapt return; 694274880Sbapt } 695274880Sbapt 696307795Sbapt puts("<div class=\"catman\">\n" 697307795Sbapt "<pre>"); 698274880Sbapt 699294113Sbapt p = NULL; 700294113Sbapt sz = 0; 701294113Sbapt 702294113Sbapt while ((len = getline(&p, &sz, f)) != -1) { 703274880Sbapt bold = italic = 0; 704294113Sbapt for (i = 0; i < len - 1; i++) { 705279527Sbapt /* 706274880Sbapt * This means that the catpage is out of state. 707274880Sbapt * Ignore it and keep going (although the 708274880Sbapt * catpage is bogus). 709274880Sbapt */ 710274880Sbapt 711274880Sbapt if ('\b' == p[i] || '\n' == p[i]) 712274880Sbapt continue; 713274880Sbapt 714274880Sbapt /* 715274880Sbapt * Print a regular character. 716274880Sbapt * Close out any bold/italic scopes. 717274880Sbapt * If we're in back-space mode, make sure we'll 718274880Sbapt * have something to enter when we backspace. 719274880Sbapt */ 720274880Sbapt 721274880Sbapt if ('\b' != p[i + 1]) { 722274880Sbapt if (italic) 723307795Sbapt printf("</i>"); 724274880Sbapt if (bold) 725307795Sbapt printf("</b>"); 726274880Sbapt italic = bold = 0; 727274880Sbapt html_putchar(p[i]); 728274880Sbapt continue; 729294113Sbapt } else if (i + 2 >= len) 730274880Sbapt continue; 731274880Sbapt 732274880Sbapt /* Italic mode. */ 733274880Sbapt 734274880Sbapt if ('_' == p[i]) { 735274880Sbapt if (bold) 736307795Sbapt printf("</b>"); 737274880Sbapt if ( ! italic) 738307795Sbapt printf("<i>"); 739274880Sbapt bold = 0; 740274880Sbapt italic = 1; 741274880Sbapt i += 2; 742274880Sbapt html_putchar(p[i]); 743274880Sbapt continue; 744274880Sbapt } 745274880Sbapt 746279527Sbapt /* 747274880Sbapt * Handle funny behaviour troff-isms. 748274880Sbapt * These grok'd from the original man2html.c. 749274880Sbapt */ 750274880Sbapt 751274880Sbapt if (('+' == p[i] && 'o' == p[i + 2]) || 752274880Sbapt ('o' == p[i] && '+' == p[i + 2]) || 753274880Sbapt ('|' == p[i] && '=' == p[i + 2]) || 754274880Sbapt ('=' == p[i] && '|' == p[i + 2]) || 755274880Sbapt ('*' == p[i] && '=' == p[i + 2]) || 756274880Sbapt ('=' == p[i] && '*' == p[i + 2]) || 757274880Sbapt ('*' == p[i] && '|' == p[i + 2]) || 758274880Sbapt ('|' == p[i] && '*' == p[i + 2])) { 759274880Sbapt if (italic) 760307795Sbapt printf("</i>"); 761274880Sbapt if (bold) 762307795Sbapt printf("</b>"); 763274880Sbapt italic = bold = 0; 764274880Sbapt putchar('*'); 765274880Sbapt i += 2; 766274880Sbapt continue; 767274880Sbapt } else if (('|' == p[i] && '-' == p[i + 2]) || 768274880Sbapt ('-' == p[i] && '|' == p[i + 1]) || 769274880Sbapt ('+' == p[i] && '-' == p[i + 1]) || 770274880Sbapt ('-' == p[i] && '+' == p[i + 1]) || 771274880Sbapt ('+' == p[i] && '|' == p[i + 1]) || 772274880Sbapt ('|' == p[i] && '+' == p[i + 1])) { 773274880Sbapt if (italic) 774307795Sbapt printf("</i>"); 775274880Sbapt if (bold) 776307795Sbapt printf("</b>"); 777274880Sbapt italic = bold = 0; 778274880Sbapt putchar('+'); 779274880Sbapt i += 2; 780274880Sbapt continue; 781274880Sbapt } 782274880Sbapt 783274880Sbapt /* Bold mode. */ 784279527Sbapt 785274880Sbapt if (italic) 786307795Sbapt printf("</i>"); 787274880Sbapt if ( ! bold) 788307795Sbapt printf("<b>"); 789274880Sbapt bold = 1; 790274880Sbapt italic = 0; 791274880Sbapt i += 2; 792274880Sbapt html_putchar(p[i]); 793274880Sbapt } 794274880Sbapt 795279527Sbapt /* 796274880Sbapt * Clean up the last character. 797279527Sbapt * We can get to a newline; don't print that. 798274880Sbapt */ 799274880Sbapt 800274880Sbapt if (italic) 801307795Sbapt printf("</i>"); 802274880Sbapt if (bold) 803307795Sbapt printf("</b>"); 804274880Sbapt 805294113Sbapt if (i == len - 1 && p[i] != '\n') 806274880Sbapt html_putchar(p[i]); 807274880Sbapt 808274880Sbapt putchar('\n'); 809274880Sbapt } 810294113Sbapt free(p); 811274880Sbapt 812307795Sbapt puts("</pre>\n" 813307795Sbapt "</div>"); 814274880Sbapt 815274880Sbapt fclose(f); 816274880Sbapt} 817274880Sbapt 818274880Sbaptstatic void 819307795Sbaptresp_format(const struct req *req, const char *file) 820274880Sbapt{ 821294113Sbapt struct manoutput conf; 822274880Sbapt struct mparse *mp; 823294113Sbapt struct roff_man *man; 824274880Sbapt void *vp; 825274880Sbapt int fd; 826274880Sbapt int usepath; 827274880Sbapt 828274880Sbapt if (-1 == (fd = open(file, O_RDONLY, 0))) { 829307795Sbapt puts("<p>You specified an invalid manual file.</p>"); 830274880Sbapt return; 831274880Sbapt } 832274880Sbapt 833294113Sbapt mchars_alloc(); 834316420Sbapt mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1, 835322249Sbapt MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, req->q.manpath); 836279527Sbapt mparse_readfd(mp, fd, file); 837274880Sbapt close(fd); 838274880Sbapt 839294113Sbapt memset(&conf, 0, sizeof(conf)); 840294113Sbapt conf.fragment = 1; 841322249Sbapt conf.style = mandoc_strdup(CSS_DIR "/mandoc.css"); 842274880Sbapt usepath = strcmp(req->q.manpath, req->p[0]); 843322249Sbapt mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S", 844322249Sbapt scriptname, *scriptname == '\0' ? "" : "/", 845307795Sbapt usepath ? req->q.manpath : "", usepath ? "/" : ""); 846274880Sbapt 847294113Sbapt mparse_result(mp, &man, NULL); 848294113Sbapt if (man == NULL) { 849307795Sbapt warnx("fatal mandoc error: %s/%s", req->q.manpath, file); 850274880Sbapt pg_error_internal(); 851274880Sbapt mparse_free(mp); 852294113Sbapt mchars_free(); 853274880Sbapt return; 854274880Sbapt } 855274880Sbapt 856294113Sbapt vp = html_alloc(&conf); 857274880Sbapt 858294113Sbapt if (man->macroset == MACROSET_MDOC) { 859294113Sbapt mdoc_validate(man); 860294113Sbapt html_mdoc(vp, man); 861294113Sbapt } else { 862294113Sbapt man_validate(man); 863274880Sbapt html_man(vp, man); 864294113Sbapt } 865274880Sbapt 866274880Sbapt html_free(vp); 867274880Sbapt mparse_free(mp); 868294113Sbapt mchars_free(); 869294113Sbapt free(conf.man); 870322249Sbapt free(conf.style); 871274880Sbapt} 872274880Sbapt 873274880Sbaptstatic void 874274880Sbaptresp_show(const struct req *req, const char *file) 875274880Sbapt{ 876274880Sbapt 877274880Sbapt if ('.' == file[0] && '/' == file[1]) 878274880Sbapt file += 2; 879274880Sbapt 880274880Sbapt if ('c' == *file) 881307795Sbapt resp_catman(req, file); 882274880Sbapt else 883307795Sbapt resp_format(req, file); 884274880Sbapt} 885274880Sbapt 886274880Sbaptstatic void 887274880Sbaptpg_show(struct req *req, const char *fullpath) 888274880Sbapt{ 889274880Sbapt char *manpath; 890274880Sbapt const char *file; 891274880Sbapt 892274880Sbapt if ((file = strchr(fullpath, '/')) == NULL) { 893274880Sbapt pg_error_badrequest( 894274880Sbapt "You did not specify a page to show."); 895274880Sbapt return; 896279527Sbapt } 897274880Sbapt manpath = mandoc_strndup(fullpath, file - fullpath); 898274880Sbapt file++; 899274880Sbapt 900274880Sbapt if ( ! validate_manpath(req, manpath)) { 901274880Sbapt pg_error_badrequest( 902274880Sbapt "You specified an invalid manpath."); 903274880Sbapt free(manpath); 904274880Sbapt return; 905274880Sbapt } 906274880Sbapt 907274880Sbapt /* 908274880Sbapt * Begin by chdir()ing into the manpath. 909274880Sbapt * This way we can pick up the database files, which are 910274880Sbapt * relative to the manpath root. 911274880Sbapt */ 912274880Sbapt 913274880Sbapt if (chdir(manpath) == -1) { 914307795Sbapt warn("chdir %s", manpath); 915274880Sbapt pg_error_internal(); 916274880Sbapt free(manpath); 917274880Sbapt return; 918274880Sbapt } 919307795Sbapt free(manpath); 920274880Sbapt 921274880Sbapt if ( ! validate_filename(file)) { 922274880Sbapt pg_error_badrequest( 923274880Sbapt "You specified an invalid manual file."); 924274880Sbapt return; 925274880Sbapt } 926274880Sbapt 927322249Sbapt resp_begin_html(200, NULL, file); 928307795Sbapt resp_searchform(req, FOCUS_NONE); 929274880Sbapt resp_show(req, file); 930274880Sbapt resp_end_html(); 931274880Sbapt} 932274880Sbapt 933274880Sbaptstatic void 934274880Sbaptpg_search(const struct req *req) 935274880Sbapt{ 936274880Sbapt struct mansearch search; 937274880Sbapt struct manpaths paths; 938274880Sbapt struct manpage *res; 939275432Sbapt char **argv; 940275432Sbapt char *query, *rp, *wp; 941274880Sbapt size_t ressz; 942275432Sbapt int argc; 943274880Sbapt 944274880Sbapt /* 945274880Sbapt * Begin by chdir()ing into the root of the manpath. 946274880Sbapt * This way we can pick up the database files, which are 947274880Sbapt * relative to the manpath root. 948274880Sbapt */ 949274880Sbapt 950307795Sbapt if (chdir(req->q.manpath) == -1) { 951307795Sbapt warn("chdir %s", req->q.manpath); 952274880Sbapt pg_error_internal(); 953274880Sbapt return; 954274880Sbapt } 955274880Sbapt 956274880Sbapt search.arch = req->q.arch; 957274880Sbapt search.sec = req->q.sec; 958275432Sbapt search.outkey = "Nd"; 959275432Sbapt search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 960275432Sbapt search.firstmatch = 1; 961274880Sbapt 962274880Sbapt paths.sz = 1; 963274880Sbapt paths.paths = mandoc_malloc(sizeof(char *)); 964274880Sbapt paths.paths[0] = mandoc_strdup("."); 965274880Sbapt 966274880Sbapt /* 967275432Sbapt * Break apart at spaces with backslash-escaping. 968274880Sbapt */ 969274880Sbapt 970275432Sbapt argc = 0; 971275432Sbapt argv = NULL; 972275432Sbapt rp = query = mandoc_strdup(req->q.query); 973275432Sbapt for (;;) { 974275432Sbapt while (isspace((unsigned char)*rp)) 975275432Sbapt rp++; 976275432Sbapt if (*rp == '\0') 977275432Sbapt break; 978275432Sbapt argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 979275432Sbapt argv[argc++] = wp = rp; 980275432Sbapt for (;;) { 981275432Sbapt if (isspace((unsigned char)*rp)) { 982275432Sbapt *wp = '\0'; 983275432Sbapt rp++; 984275432Sbapt break; 985275432Sbapt } 986275432Sbapt if (rp[0] == '\\' && rp[1] != '\0') 987275432Sbapt rp++; 988275432Sbapt if (wp != rp) 989275432Sbapt *wp = *rp; 990275432Sbapt if (*rp == '\0') 991275432Sbapt break; 992275432Sbapt wp++; 993275432Sbapt rp++; 994275432Sbapt } 995274880Sbapt } 996274880Sbapt 997322249Sbapt res = NULL; 998322249Sbapt ressz = 0; 999322249Sbapt if (req->isquery && req->q.equal && argc == 1) 1000322249Sbapt pg_redirect(req, argv[0]); 1001322249Sbapt else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0) 1002274880Sbapt pg_noresult(req, "You entered an invalid query."); 1003322249Sbapt else if (ressz == 0) 1004274880Sbapt pg_noresult(req, "No results found."); 1005274880Sbapt else 1006274880Sbapt pg_searchres(req, res, ressz); 1007274880Sbapt 1008275432Sbapt free(query); 1009275432Sbapt mansearch_free(res, ressz); 1010274880Sbapt free(paths.paths[0]); 1011274880Sbapt free(paths.paths); 1012274880Sbapt} 1013274880Sbapt 1014274880Sbaptint 1015274880Sbaptmain(void) 1016274880Sbapt{ 1017274880Sbapt struct req req; 1018275432Sbapt struct itimerval itimer; 1019274880Sbapt const char *path; 1020274880Sbapt const char *querystring; 1021274880Sbapt int i; 1022274880Sbapt 1023322249Sbapt#if HAVE_PLEDGE 1024322249Sbapt /* 1025322249Sbapt * The "rpath" pledge could be revoked after mparse_readfd() 1026322249Sbapt * if the file desciptor to "/footer.html" would be opened 1027322249Sbapt * up front, but it's probably not worth the complication 1028322249Sbapt * of the code it would cause: it would require scattering 1029322249Sbapt * pledge() calls in multiple low-level resp_*() functions. 1030322249Sbapt */ 1031322249Sbapt 1032322249Sbapt if (pledge("stdio rpath", NULL) == -1) { 1033322249Sbapt warn("pledge"); 1034322249Sbapt pg_error_internal(); 1035322249Sbapt return EXIT_FAILURE; 1036322249Sbapt } 1037322249Sbapt#endif 1038322249Sbapt 1039275432Sbapt /* Poor man's ReDoS mitigation. */ 1040275432Sbapt 1041275432Sbapt itimer.it_value.tv_sec = 2; 1042275432Sbapt itimer.it_value.tv_usec = 0; 1043275432Sbapt itimer.it_interval.tv_sec = 2; 1044275432Sbapt itimer.it_interval.tv_usec = 0; 1045275432Sbapt if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1046307795Sbapt warn("setitimer"); 1047275432Sbapt pg_error_internal(); 1048294113Sbapt return EXIT_FAILURE; 1049275432Sbapt } 1050275432Sbapt 1051274880Sbapt /* 1052274880Sbapt * First we change directory into the MAN_DIR so that 1053274880Sbapt * subsequent scanning for manpath directories is rooted 1054274880Sbapt * relative to the same position. 1055274880Sbapt */ 1056274880Sbapt 1057307795Sbapt if (chdir(MAN_DIR) == -1) { 1058307795Sbapt warn("MAN_DIR: %s", MAN_DIR); 1059274880Sbapt pg_error_internal(); 1060294113Sbapt return EXIT_FAILURE; 1061279527Sbapt } 1062274880Sbapt 1063274880Sbapt memset(&req, 0, sizeof(struct req)); 1064307795Sbapt req.q.equal = 1; 1065307795Sbapt parse_manpath_conf(&req); 1066274880Sbapt 1067307795Sbapt /* Parse the path info and the query string. */ 1068274880Sbapt 1069307795Sbapt if ((path = getenv("PATH_INFO")) == NULL) 1070307795Sbapt path = ""; 1071307795Sbapt else if (*path == '/') 1072307795Sbapt path++; 1073274880Sbapt 1074307795Sbapt if (*path != '\0') { 1075307795Sbapt parse_path_info(&req, path); 1076322249Sbapt if (req.q.manpath == NULL || req.q.sec == NULL || 1077322249Sbapt *req.q.query == '\0' || access(path, F_OK) == -1) 1078307795Sbapt path = ""; 1079307795Sbapt } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1080307795Sbapt parse_query_string(&req, querystring); 1081307795Sbapt 1082307795Sbapt /* Validate parsed data and add defaults. */ 1083307795Sbapt 1084275432Sbapt if (req.q.manpath == NULL) 1085275432Sbapt req.q.manpath = mandoc_strdup(req.p[0]); 1086275432Sbapt else if ( ! validate_manpath(&req, req.q.manpath)) { 1087274880Sbapt pg_error_badrequest( 1088274880Sbapt "You specified an invalid manpath."); 1089294113Sbapt return EXIT_FAILURE; 1090274880Sbapt } 1091274880Sbapt 1092274880Sbapt if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { 1093274880Sbapt pg_error_badrequest( 1094274880Sbapt "You specified an invalid architecture."); 1095294113Sbapt return EXIT_FAILURE; 1096274880Sbapt } 1097274880Sbapt 1098274880Sbapt /* Dispatch to the three different pages. */ 1099274880Sbapt 1100274880Sbapt if ('\0' != *path) 1101274880Sbapt pg_show(&req, path); 1102274880Sbapt else if (NULL != req.q.query) 1103274880Sbapt pg_search(&req); 1104274880Sbapt else 1105274880Sbapt pg_index(&req); 1106274880Sbapt 1107274880Sbapt free(req.q.manpath); 1108274880Sbapt free(req.q.arch); 1109274880Sbapt free(req.q.sec); 1110274880Sbapt free(req.q.query); 1111274880Sbapt for (i = 0; i < (int)req.psz; i++) 1112274880Sbapt free(req.p[i]); 1113274880Sbapt free(req.p); 1114294113Sbapt return EXIT_SUCCESS; 1115274880Sbapt} 1116274880Sbapt 1117274880Sbapt/* 1118307795Sbapt * If PATH_INFO is not a file name, translate it to a query. 1119307795Sbapt */ 1120307795Sbaptstatic void 1121307795Sbaptparse_path_info(struct req *req, const char *path) 1122307795Sbapt{ 1123307795Sbapt char *dir[4]; 1124307795Sbapt int i; 1125307795Sbapt 1126307795Sbapt req->isquery = 0; 1127307795Sbapt req->q.equal = 1; 1128307795Sbapt req->q.manpath = mandoc_strdup(path); 1129307795Sbapt req->q.arch = NULL; 1130307795Sbapt 1131307795Sbapt /* Mandatory manual page name. */ 1132307795Sbapt if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) { 1133307795Sbapt req->q.query = req->q.manpath; 1134307795Sbapt req->q.manpath = NULL; 1135307795Sbapt } else 1136307795Sbapt *req->q.query++ = '\0'; 1137307795Sbapt 1138307795Sbapt /* Optional trailing section. */ 1139307795Sbapt if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) { 1140307795Sbapt if(isdigit((unsigned char)req->q.sec[1])) { 1141307795Sbapt *req->q.sec++ = '\0'; 1142307795Sbapt req->q.sec = mandoc_strdup(req->q.sec); 1143307795Sbapt } else 1144307795Sbapt req->q.sec = NULL; 1145307795Sbapt } 1146307795Sbapt 1147307795Sbapt /* Handle the case of name[.section] only. */ 1148307795Sbapt if (req->q.manpath == NULL) 1149307795Sbapt return; 1150307795Sbapt req->q.query = mandoc_strdup(req->q.query); 1151307795Sbapt 1152307795Sbapt /* Split directory components. */ 1153307795Sbapt dir[i = 0] = req->q.manpath; 1154307795Sbapt while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) { 1155307795Sbapt if (++i == 3) { 1156307795Sbapt pg_error_badrequest( 1157307795Sbapt "You specified too many directory components."); 1158307795Sbapt exit(EXIT_FAILURE); 1159307795Sbapt } 1160307795Sbapt *dir[i]++ = '\0'; 1161307795Sbapt } 1162307795Sbapt 1163307795Sbapt /* Optional manpath. */ 1164307795Sbapt if ((i = validate_manpath(req, req->q.manpath)) == 0) 1165307795Sbapt req->q.manpath = NULL; 1166307795Sbapt else if (dir[1] == NULL) 1167307795Sbapt return; 1168307795Sbapt 1169307795Sbapt /* Optional section. */ 1170307795Sbapt if (strncmp(dir[i], "man", 3) == 0) { 1171307795Sbapt free(req->q.sec); 1172307795Sbapt req->q.sec = mandoc_strdup(dir[i++] + 3); 1173307795Sbapt } 1174307795Sbapt if (dir[i] == NULL) { 1175307795Sbapt if (req->q.manpath == NULL) 1176307795Sbapt free(dir[0]); 1177307795Sbapt return; 1178307795Sbapt } 1179307795Sbapt if (dir[i + 1] != NULL) { 1180307795Sbapt pg_error_badrequest( 1181307795Sbapt "You specified an invalid directory component."); 1182307795Sbapt exit(EXIT_FAILURE); 1183307795Sbapt } 1184307795Sbapt 1185307795Sbapt /* Optional architecture. */ 1186307795Sbapt if (i) { 1187307795Sbapt req->q.arch = mandoc_strdup(dir[i]); 1188307795Sbapt if (req->q.manpath == NULL) 1189307795Sbapt free(dir[0]); 1190307795Sbapt } else 1191307795Sbapt req->q.arch = dir[0]; 1192307795Sbapt} 1193307795Sbapt 1194307795Sbapt/* 1195274880Sbapt * Scan for indexable paths. 1196274880Sbapt */ 1197274880Sbaptstatic void 1198307795Sbaptparse_manpath_conf(struct req *req) 1199274880Sbapt{ 1200274880Sbapt FILE *fp; 1201274880Sbapt char *dp; 1202274880Sbapt size_t dpsz; 1203294113Sbapt ssize_t len; 1204274880Sbapt 1205307795Sbapt if ((fp = fopen("manpath.conf", "r")) == NULL) { 1206307795Sbapt warn("%s/manpath.conf", MAN_DIR); 1207274880Sbapt pg_error_internal(); 1208274880Sbapt exit(EXIT_FAILURE); 1209274880Sbapt } 1210274880Sbapt 1211294113Sbapt dp = NULL; 1212294113Sbapt dpsz = 0; 1213294113Sbapt 1214294113Sbapt while ((len = getline(&dp, &dpsz, fp)) != -1) { 1215294113Sbapt if (dp[len - 1] == '\n') 1216294113Sbapt dp[--len] = '\0'; 1217274880Sbapt req->p = mandoc_realloc(req->p, 1218274880Sbapt (req->psz + 1) * sizeof(char *)); 1219274880Sbapt if ( ! validate_urifrag(dp)) { 1220307795Sbapt warnx("%s/manpath.conf contains " 1221307795Sbapt "unsafe path \"%s\"", MAN_DIR, dp); 1222274880Sbapt pg_error_internal(); 1223274880Sbapt exit(EXIT_FAILURE); 1224274880Sbapt } 1225307795Sbapt if (strchr(dp, '/') != NULL) { 1226307795Sbapt warnx("%s/manpath.conf contains " 1227307795Sbapt "path with slash \"%s\"", MAN_DIR, dp); 1228274880Sbapt pg_error_internal(); 1229274880Sbapt exit(EXIT_FAILURE); 1230274880Sbapt } 1231274880Sbapt req->p[req->psz++] = dp; 1232294113Sbapt dp = NULL; 1233294113Sbapt dpsz = 0; 1234274880Sbapt } 1235294113Sbapt free(dp); 1236274880Sbapt 1237307795Sbapt if (req->p == NULL) { 1238307795Sbapt warnx("%s/manpath.conf is empty", MAN_DIR); 1239274880Sbapt pg_error_internal(); 1240274880Sbapt exit(EXIT_FAILURE); 1241274880Sbapt } 1242274880Sbapt} 1243