Here's a proposal to add unique HTML titles to man-pages served using man.cgi. The name of the man-page is used as a title prefix (inspired by NetBSD's adoption of mandoc).
There might be a more elegant way to produce the title given the filename. Index: cgi.c =================================================================== RCS file: /cvs/src/usr.bin/mandoc/cgi.c,v retrieving revision 1.85 diff -u -p -r1.85 cgi.c --- cgi.c 25 Jan 2017 03:19:56 -0000 1.85 +++ cgi.c 5 Feb 2017 09:20:03 -0000 @@ -68,6 +68,7 @@ static int http_decode(char *); static void parse_manpath_conf(struct req *); static void parse_path_info(struct req *req, const char *path); static void parse_query_string(struct req *, const char *); +static char *path_to_title(const char *); static void pg_error_badrequest(const char *); static void pg_error_internal(void); static void pg_index(const struct req *); @@ -76,7 +77,7 @@ static void pg_search(const struct req static void pg_searchres(const struct req *, struct manpage *, size_t); static void pg_show(struct req *, const char *); -static void resp_begin_html(int, const char *); +static void resp_begin_html(int, const char *, const char *); static void resp_begin_http(int, const char *); static void resp_catman(const struct req *, const char *); static void resp_copy(const char *); @@ -127,6 +128,36 @@ static const char *const arch_names[] = static const int arch_MAX = sizeof(arch_names) / sizeof(char *); /* + * Returns path transformed to a representation suitable for use as a HTML + * title. + * Example: "man1/man.1" -> "man(1)" + */ +static char * +path_to_title(const char *path) +{ + char *e, *s, *title; + ssize_t len; + + if ((s = strrchr(path, '/')) == NULL) + return NULL; + s++; + + if ((e = strrchr(s, '.')) == NULL) + return NULL; + e++; + + len = strlen(s) + 2; /* right parens and nul */ + title = mandoc_malloc(len); + if (snprintf(title, len, "%.*s(%s)", (int)(e - s - 1), s, e) >= len) { + warnx("title buffer too small"); + free(title); + return NULL; + } + + return title; +} + +/* * Print a character, escaping HTML along the way. * This will pass non-ASCII straight to output: be warned! */ @@ -341,8 +372,21 @@ resp_copy(const char *filename) } static void -resp_begin_html(int code, const char *msg) +resp_begin_html(int code, const char *msg, const char *title) { + const char *delim = " - "; + char *prefix = NULL; + ssize_t len; + + if (title != NULL) { + len = strlen(title) + strlen(delim) + 1; + prefix = mandoc_malloc(len); + if (snprintf(prefix, len, "%s%s", title, delim) >= len) { + warnx("prefix buffer too small"); + free(prefix); + prefix = NULL; + } + } resp_begin_http(code, msg); @@ -352,12 +396,15 @@ resp_begin_html(int code, const char *ms " <meta charset=\"UTF-8\"/>\n" " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" " type=\"text/css\" media=\"all\">\n" - " <title>%s</title>\n" + " <title>%s%s</title>\n" "</head>\n" "<body>\n", - CSS_DIR, CUSTOMIZE_TITLE); + CSS_DIR, prefix != NULL ? prefix : "", CUSTOMIZE_TITLE); resp_copy(MAN_DIR "/header.html"); + + if (prefix != NULL) + free(prefix); } static void @@ -488,7 +535,7 @@ static void pg_index(const struct req *req) { - resp_begin_html(200, NULL); + resp_begin_html(200, NULL, NULL); resp_searchform(req, FOCUS_QUERY); printf("<p>\n" "This web interface is documented in the\n" @@ -505,7 +552,7 @@ pg_index(const struct req *req) static void pg_noresult(const struct req *req, const char *msg) { - resp_begin_html(200, NULL); + resp_begin_html(200, NULL, NULL); resp_searchform(req, FOCUS_QUERY); puts("<p>"); puts(msg); @@ -517,7 +564,7 @@ static void pg_error_badrequest(const char *msg) { - resp_begin_html(400, "Bad Request"); + resp_begin_html(400, "Bad Request", NULL); puts("<h1>Bad Request</h1>\n" "<p>\n"); puts(msg); @@ -530,7 +577,7 @@ pg_error_badrequest(const char *msg) static void pg_error_internal(void) { - resp_begin_html(500, "Internal Server Error"); + resp_begin_html(500, "Internal Server Error", NULL); puts("<p>Internal Server Error</p>"); resp_end_html(); } @@ -569,7 +616,7 @@ pg_searchres(const struct req *req, stru return; } - resp_begin_html(200, NULL); + resp_begin_html(200, NULL, NULL); resp_searchform(req, req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); @@ -844,7 +891,7 @@ resp_show(const struct req *req, const c static void pg_show(struct req *req, const char *fullpath) { - char *manpath; + char *manpath, *title; const char *file; if ((file = strchr(fullpath, '/')) == NULL) { @@ -882,10 +929,14 @@ pg_show(struct req *req, const char *ful return; } - resp_begin_html(200, NULL); + title = path_to_title(file); + resp_begin_html(200, NULL, title); resp_searchform(req, FOCUS_NONE); resp_show(req, file); resp_end_html(); + + if (title != NULL) + free(title); } static void