$Id: README,v 1.35 2023/10/26 04:17:55 rlr Exp $

Introduction
============

** Documentation is incomplete.
** "XXX" marks parts in need of further explanation.

fwobac is a modest articles-and-comments CGI program derived from the
software (formerly) used on http://undeadly.org/, and originally written
by Daniel Hartmeier.  It retains the flat file content storage of the
original, but extends it in various ways.

In contrast with the original software, fwobac:
- is fairly modern in coding standards (for example, it uses explicit_bzero(3),
  timingsafe_bcmp(3), and pledge(2), as well as HMAC-SHA256 rather than SHA1)
- is not specific to undeadly.org
- uses Kristaps Dzonsons' kcgi libraries
- logs to the standard error
- provides more flexible features, particularly those intended to be friendly
  to the content-maintainer(s)
- does not require that the server run with UTC timezone

The code has been written to run on OpenBSD [6.1], but should be
readily portable to other platforms.

| "FWOBAC" is an acronym from
|	For Want Of Better Articles and Comments
|
| (For those not fluent in English: "want" means "lack")
| "For Want Of Better" is (appropriate and) taken from the Australian poem
| "Clancy of the Overflow":
| <URL:https://ebooks.adelaide.edu.au/p/paterson/ab/man_from_snowy_river/
| chapter4.html>
|
| The name is intended to reflect the fact that the software was developed
| because no better solution has been offered for the needs of undeadly.org


Development
===========

See file: README.development (for which this file provides background reading)


Build Dependencies
==================

libkcgi
- http://kristaps.bsd.lv/kcgi/
	- under OpenBSD, port www/kcgi


Run-time Dependencies
=====================

- A Mail Transfer Agent (MTA) with a sendmail-compatible command line.  This is
  used for sending registration confirmation email messages.

  This defaults to /usr/sbin/sendmail, but is configurable (see MAIL_MTA below).

  Under OpenBSD, the femail-chroot package (port mail/femail) provides a
  suitable executable in the chroot(2) environment.


Compilation
===========

cpp(1) macro definitions may be used to override default values:

	CONF_FILE
		Location of run-time configuration file.
		[Default: /etc/fwobac.conf]

	MAIL_MTA
		Path of sendmail-compatible MTA.
		[Default: /usr/sbin/sendmail]

Optional features of may be enabled by defining cpp(1) macros:

	COMPAT_BRAINDEAD_FB
		Deal with broken fb percent-encoding of URL in the case of
		  fwobac?action=article;sid=YYYYmmddHHMMSS...
		by redirecting to the intended URL.

	COMPAT_LEGACY_ID
		Relax rules for user account IDs on _existing_ accounts.
		[Required for old Undeadly accounts.]

	COMPAT_LEGACY_PW
		If defined to 2, legacy format passwords (only) are used.
		If defined (1), both legacy and bcrypt password hash formats
		are used for validation, and new/updated passwords are stored
		in bcrypt format.
		If undefined (0), bcrypt format passwords (only) are used.

	COMPAT_LEGACY_R
		Support reading legacy metadata.
		[Required for old Undeadly data.]

	COMPAT_LEGACY_W
		Support writing legacy metadata.
		[Required for coexistence with legacy Undeadly CGI code.]

	DEBUG
		Include additional debugging code (principally, logging).
		In a debug build (i.e. DEBUG is defined), run-time debugging
		is enabled by including in the request a name-value pair
		"debug=B", where B is: "on", "t", "true", "y", "yes", or
		an integer other than zero.

	FEATURE_A_PREV_NEXT_LINKS
		Include code for generating "previous" and "next" links
		for articles and submissions.

	FEATURE_COMMENT_SUBJECT
		Support (editable) subjects for comments.  If this macro is
		undefined but macro COMPAT_LEGACY_W is defined, saved comments
		are given the subject of the article to which they are replies.

	FEATURE_MODERATE
		Support "action=moderate" (down-voting and up-voting comments.)
		The code is untested.

	FEATURE_QUOTE_PARENT_COMMENT
		Supply quoted parent comment as initial content for a new
		comments.

	FEATURE_SEARCH
		Support "action=search"

	FEATURE_SHOW_THREAD
		Support "action=article;pid=PPP".

	FEATURE_THRES
		Support "thres=TTT" for specifying a threshold for comment
		visibility.

	FEATURE_VOTE
		Include support for "action=vote" (for voting in polls).
		The code is untested.

	FIELDVAL_SEP_AMP
		In generated URLs, use '&' rather than ';' as the separator
		between name-value pairs.

	UNDEADLY_TRANSITION
		Include support for "action=errata".  This Undeadly-specific
		feature was required for compatibility with the legacy code.
		The HTTP response is a redirection ("301 Moved Permanently")
		to the URL for an externally-generated file.  (The legacy code
		dynamically generated an RSS feed of the OpenBSD errata.)

	UNUSED
		Include (sane but) unused code.

	XXX
		Include code which is unneeded, broken, or (at best) untested.
		It is not intended that this macro be defined under any
		circumstances; rather, it is used to mark and disable code
		which should be reviewed, and probably deleted.


Data Files and Directory Layout
===============================

All data is stored in a hierarchy of flat files.

Each article is identified by a "sid", which is a timestamp of the format
	yyyymmddHHMMSS
and is represented in the file system as a directory (or directory
hierarchy, taking into account comments).  All times are UTC.

Published articles are known as "articles", and unpublished articles as
"submissions".  (Submissions have no comments.)

Comments are identified by the sid of the relevant article, and a "pid",
which is a positive integer.  pid values are allocated sequentially, starting
with 1.  In the file system, each comment is represented as a directory, with
the file hierarchy corresponding to the logical hierarchy of comments.

Files and directories found in an article directory are:
- "article"
	contains the article metadata and initial content - a blank line
	separates the headers from the content material
- "article.more"
	contains additional content not displayed on the front page
- "comments_open"
	file indicating that comments will be accepted for the article
- "lastread"
	file storing last-read information for users
- comment directories
	for replies to the article

Files and directories found in a comment directory are:
- "comment"
	contains the comment metadata and initial content - format is the
	same as that of an "article" file
- "report"
	file indicating that the comment has been reported as inappropriate
- comment directories
	for replies to the comment


Example Layout of Relevant Files and Directories
================================================

Assuming the web server runs chroot(2) /var/www, an example layout is:

/var/www/etc/
		hosts  # containing localhost entries for femail(8)
		localtime@ -> /usr/share/zoneinfo/<region>/<city>
		fwobac.conf  # configuration file for fwobac

/var/www/usr/share/zoneinfo/
			<region>/
				<city>
			posixrules
			GMT

/var/www/example.com/
			cache/  # see below
				archive_index.2017
				last_art_ch.2017
				rss_log/
					111.222.33.44
			data/   # (example) see below
				articles/
					...
				submission/
					...
			etc/private/
				captcha.key
				cookie.key
				registration.key
				example.com.passwd

			htocs/
				fwobac
				favicon.ico
				images/
				robots.txt
				fwobac.css
				...
			templates/
				...

/var/www/usr/sbin/
		sendmail*	# femail MTA


Web Server Configuration
========================

Any web server supporting CGI should be sufficient.  The following is a
sample configuration (snippet) for OpenBSD's httpd(8) (which runs
chroot(2) /var/www):

server "example.com" {
        alias "www.example.com"

        listen on $ext_addr_ipv4 port 80
        listen on $ext_addr_ipv6 port 80

        listen on $ext_addr_ipv4 tls port 443
        listen on $ext_addr_ipv6 tls port 443
        tls certificate "/etc/ssl/example.com.pem"
        tls key "/etc/ssl/private/example.com.key"

        hsts preload
        hsts subdomains

        root "/example.com/htdocs"

        location "/" {
                block return 301 "/fwobac?action=front"
                directory no index
        }

        location "/index.html" {
                block return 301 "/fwobac?action=front"
                directory no index
        }

        location "/fwobac" {
                fastcgi
        }
}


Configuration File
==================

fwobac reads a configuration file (when invoked in a CGI environment).  The
default path to the file is "/etc/fwobac.conf" (within any chroot(2)
environment).

Blank lines and those starting with '#' are ignored.  Other lines must be of
the form:
	<configuration_item> <one or more space and tab characters> <value>
All values are simple strings which extend to the end of the line.

** When invoked (outside a CGI-environment) with flag "-c", fwobac writes a
** dummy configuration file to the standard output.

The configuration items and their meanings are:

	domain
		Value to use for cookies, etc.

	https_port
		Port number to use for HTTPS.

	default_topic
		Default topic for submissions/articles.

	articles_dir
		Directory containing articles directories.

	submissions_dir
		Directory containing submission directories.

	template_dir
		Directory containing HTML (and other) templates.

	template_macro_chars
		Template macro-delimiting characters.  See below.

	themes (if compiled with FEATURE_THEMES)
		Comma-separated list of valid theme names.  See below.

	cache_dir
		Directory containing cache files.  See below.

	captcha_key
		Key file for CAPTCHA HMACs.

	cookie_key
		Key file for cookie HMACs.

	registration_key
		Key file for registration HMACs.

	user_db_file
		The user password/database file.

	group_rights
		User groups and their entitlements.  See below.


Some subdirectories of the directory specified by "template_dir" are special:

	"doc"
		Contains document templates used for action "show_doc".
		See below.

	"groups"
		Contains document templates used with macro
		"INCLUDE_BY_GROUP:".  See below.

	"themes" (if compiled with FEATURE_THEMES)
		Contains document templates used with macro
		"INCLUDE_BY_THEME:".  See below.

Group rights are expressed as a semicolon-separated sequence of
	<group name> '=0x' <entitlements>

Group names are arbitrary, with the exception of
	"anon"
		Pseudo-group for unauthenticated clients.  In normal use, an
		entitlements entry should exist for this group.
	"none"
		Reserved name for partially-registered users.  No entitlements
		entry should exist for this group.
	"user"
		Group to which newly-confirmed accounts are assigned.

The entitlements are expressed as a (hexadecimal) set of bits corresponding to
the actions ("action=AAA" in requests) to which group members are entitled.
The output of "fwobac -c" includes comments giving the lists of actions and
values, and useful combinations thereof.  See below.

** WARNING - Recompiling the software with different features enabled may
**           change the actions and bits, and thus require recalculation of
**           group entitlements.

A configuration corresponding the sample layout above is:
	domain	example.com
	https_port	443
	default_topic	atopic
	articles_dir	/example.com/data/articles
	submissions_dir	/example.com/data/submissions
	template_dir	/example.com/templates
	template_macro_chars	%@&^
	cache_dir	/example.com/cache
	captcha_key	/example.com/etc/private/captcha.key
	cookie_key	/example.com/etc/private/cookie.key
	registration_key	/example.com/etc/private/registration.key
	user_db_file	/example.com/etc/private/example.com.passwd
	group_rights	anon=0x03c616010f;user=0x1bf8c60327;editor=0x1ff9e7fbe7


The Cache Directory
===================

The directory specified by configuration item "cache_dir" contains:
	"rss_log/"
		Directory containing files for rate-limiting RSS requests.
		It is safe to remove the contents of this directory.

	archive_index.YYYY
	last_art_ch.YYYY
		Files used for efficiency in handling requests for archive
		index pages for various years.  Rather than dynamically
		generating archive index pages on each request, fwobac creates
		and caches _templates_ (named "archive_index.<YYYY>").
		Various actions which might render a template out-of-date
		result in the modification time of the corresponding
		"last_art_ch.<YYYY>" file being updated.  fwobac compares
		the modification time of the template and last-chage files
		before using the former, and generates a new template if the
		existing one is (missing or) out-of-date.
		It is generally safe to remove these files, but strongly
		recommended that the files for any year are removed together.

Actions
=======

The behaviour of fwobac (in CGI context) is determined primarily by the
"name=AAA" name/value pair in the request.  Supported actions depend on
the compilation options (-DMMM) used to build fwobac.  The full list of
actions is:

	about
		Display the "About" page.
	acct_lock
		Lock an account.
	acct_unlock
		Unlock an account.
	addr_block
		Block an IP address.  [Not Yet Implemented]
	addr_unblock
		Unblock an IP address.  [Not Yet Implemented]
	archive
		Display the article archive.
	article
		Display the specified article.
	auth
		Deal with submitted authentication form.
	changepw
		Deal with submitted password change form.
	close_comments
		Close the comments for an article.
	close_report
		Close a report regarding an inappropriate comment.
	confirm
		Confirm the creation of a new account.
	deauth
		Logout user.
	delete_a
		Delete an article.
	delete_c
		Delete a comment.
	delete_s
		Delete a submission.
	edit_a
		Edit an article.
	edit_s
		Edit a submission.
	editpost_a
		Deal with submitted article editing form.
	editpost_s
		Deal with submitted submission editing form.
	errata
		[Undeadly-specific - for the transition from legacy system]
		Redirect client to the RSS document of OpenBSD errata.
	front
		Display the front page.
	login
		Display the login form.
	moderate
		Down-vote or up-vote a comment.
	open_comments
		Open comments for an article.
	prefs
		Display the preferences form.
	prefspost
		Deal with submitted preferences form.
	publish
		Publish a submission (to become an article).
	register
		Display the user registration form.
	registerpost
		Deal with submitted user registration form.
	reply
		Display the comment submission form.
	replypost
		Deal with submitted comment form.
	report
		Report a comment as inappropriate.
	rss
		Display RSS document of recent articles.
	search
		Redirect to search engine.
	show_doc
		Display document specified by "topic" key.  That is, a request
		containing "action=show_doc;topic=TTT" returns the document
		based on template <template_dir>/doc/<TTT>.html
	submission
		Display a submission.
	submissions
		Display available submissions.
	submit
		Display form for submitting new article.
	submitpost
		Deal with submitted article form.
	vote
		Vote in a poll.

In normal use:
- creation of a new account involves the sequence:
	register, registerpost, confirm
- login involves the sequence:
	login, auth
- changing preferences involves the sequence:
	prefs, prefspost
- changing password involves the sequence:
	prefs, changepw
- submission of an article involves the sequence:
	submit, submitpost
- submission of an comment involves the sequence:
	reply, replypost


Templates and Macros
====================

Most of the output (HTML, RSS, email) of fwobac is created from templates using
macro substitutions.  With run-time debugging enabled (...;debug=true... in a
DEBUG build), HTML-style comments are included before and after each template
in the output.  Any '<' and '>' in the template name are escaped by inserting
'_' before and after, and the template name may be truncated.  For example:
	<!-- BEGIN template.html -->
	...
	<!-- END template.html -->

The templates are located in the directory specified by configuation item
"template_dir".  Templates may contain macros of the form:
	<cc> <macro name> [ <optional additional information> ] <cc>
where c is the macro-delimiting character.  The (sequence of) macro-delimiting
characters are specified by configuration item "template_macro_chars".  The
primary such character in the sample configuration is '%'.

There are three classes of macros in fwobac:

	Simple Macros

		These consist of (just) the macro name.

		The macros are:

			"" (the empty string)
				As a special case, this is substituted by
				consecutive macro-delimiting characters.
				e.g. %%%% ==> %%
			ACTION
				The value specified for name "action" in the
				request (if valid).
			ADDR_AUTHOR
				IP address used by the author of an article or
				comment.
			ADDR_EDITOR
				IP address used by the most recent editor of an
				article or comment.
			AI_EACH_YEAR
				XXX
			AI_MONTH_ARTICLES
				XXX
			AI_MONTH_LIST
				XXX
			AI_YEAR_INDEX
				XXX
			AL_DAY_ARTICLE_LIST
				XXX
			AL_DAY_LIST
				XXX
			ARTICLE
				XXX
			ARTICLES
				XXX
			AUTHOR_INFO
				XXX
			BODY
				XXX
			CAPTCHA
				XXX
			CAPTCHA_CHK
				XXX
			CAPTCHA_TXT
				XXX
			CHECKED_POLL
				XXX
			CLIENT
				XXX
			COMMENTS
				XXX
			COUNT
				XXX
			CONTENT0
				XXX
			CONTENT1
				XXX
			DEPT
				"Department" of article.
			EDITOR_INFO
				XXX
			EMAIL
				XXX
			EXTRA
				XXX
			FORM_X
				XXX
			FORM_ERR_INVALID_LIST
				XXX
			FORM_ERR_MISSING_LIST
				XXX
			FORM_ERR_MSG
				XXX
			GROUP
				XXX
			HMAC
				XXX
			HREF
				XXX
			ID
				XXX
			ID_AUTHOR
				User id of the (original) author of an article
				or comment.
			ID_EDITOR
				User id of the most recent editor of an article
				or comment.
			INDEX
				XXX
			INTRO
				XXX
			INTRO_PASSWORD
				XXX
			INTRO_PREFS
				XXX
			ITEMS
				XXX
			LATEST_LIST
				XXX
			MODCOUNT
				XXX
			MODSUM
				XXX
			MORE
				XXX
			NAME
				XXX
			NAVIGATION
				XXX
			OLDER_LIST
				XXX
			PID
				Comment pid.
			POLL
				XXX
			PREVIEW
				XXX
			PUBDATE
				XXX
			PW
				XXX
			PWCONFIRM
				XXX
			REG_ADDR
				Corresponds with CGI variable of same name.
			REG_REF
				XXX
			REG_SNOM
				XXX
			REG_SRVR
				XXX
			REG_UA
				XXX
			REMOTE_ADDR
				XXX
			SCHEME
				CGI scheme in use - "http" or "https".
			SCRIPT_NAME
				Corresponds with CGI variable of same name.
			SERVER_NAME
				Corresponds with CGI variable of same name.
			SERVER_PORT
				Corresponds with CGI variable of same name.
			SID
				Article or comment sid.
			STATUS
				XXX
			SUBJECT
				Article or comment subject.
			SUBJECT_PLAIN
				Subject stripped of HTML markup and with
				character references converted to UTF-8
				sequences.
			SUBJECT_PLAIN_CREF
				Subject stripped of HTML markup, with
				character references preserved.
			SUBJECT_RAW
				Subject in HTML-escaped format.
			THEME (if compiled with FEATURE_THEMES)
				Name of current theme.  See below.
			THRES
				XXX
			TOPIC
				Topic of an article.
			URL_X
				XXX
			VERSION
				fwobac version string.
			YEAR
				XXX

	Macros Accepting or Requiring Additional Information

		These consist of a name, followed by a ':', followed by
		additional information.

		The macros are:

			INCLUDE:<path>
				Causes the specified file (relative to the
				templates directory) to be included inline.
				Note that this may be used to include
				externally-generated content.
			INCLUDE_BY_GROUP:<path>
				Causes the specified file (relative to the
				"groups/<group_name>" subdirectory of the
				templates directory) to be included inline,
				where "group_name" is "anon" for a user who
				is not logged in, or the name of the group
				of the user (taken from the user database).
			INCLUDE_BY_THEME:<path> (if compiled with
				    FEATURE_THEMES)
				Causes the specified file (relative to the
				"themes/<theme_name>" subdirectory of the
				templates directory) to be included inline.
				See below.
			TIME_A_FMT:<format>
				This is replaced by the time of authoring of
				the article or comment, in the strftime(3)
				format supplied, or "%Y-%m-%d %H:%M:%S %Z"
				if the format is omitted.
			TIME_E_FMT:<format>
				This is replaced by the time of last editing of
				the article or comment, in the strftime(3)
				format supplied, or "%Y-%m-%d %H:%M:%S %Z"
				if the format is omitted.
			TIME_FMT:<format>
				This is replaced by the current time, in the
				strftime(3) format supplied, or
				"%Y-%m-%d %H:%M:%S %Z" if the format is
				omitted.
			TIME_I_FMT:<format>
				In archive index generation, this is replaced
				by the relevant year +/- month, in the
				strftime(3) format supplied, or
				"%Y-%m-%d %H:%M:%S %Z" if the format is
				omitted.
			TIME_S_FMT:<format>
				This is replaced by the time of last editing of
				the article or comment, in the strftime(3)
				format supplied, or "%Y-%m-%d %H:%M:%S %Z"
				if the format is omitted.
			SELECTED_TOPIC:
				XXX
			CHECKED_TYPE:
				XXX

	Conditional Macros

		These consist of a name, followed by a '?', followed by
		optional alternative expansions for true and false cases
		separated by ':' c ':', where c is the current
		macro-delimiting character.

		The macros are:

			IF_ARTICLES_FOUND?
				XXX
			IF_AUTH?
				True if and only if user is logged in.
			IF_REPORTED?
				True if and only if the comment has been
				reported as inappropriate.
			IF_CMTS_OPEN?
				True if and only if the comment submission
				is open for the article.
			IF_DEBUG?
				True if and only if run-time debugging is
				enabled.
			IF_DEBUG_BUILD?
				True if and only if fwobac was built with
				compilation macro -DDEBUG.
			IF_DELAYED?
				XXX
			IF_HREF_IS_URL?
			IF_HTTPS?
				True if and only if the HTTPS schema is in use.
			IF_MORE?
				True if and only if an article (or submission)
				has a "more" component.
			IF_NEW?
				If user is not logged in no output is produced
				(if this is significant, nest within IF_AUTH?),
				otherwise:
				- in article context true if and only if the
				  user has not viewed the article
				- in comment context true if and only if the
				  the comment is newer than the user's most
				  recent viewing
			IF_NEW_CMTS?
				If user is not logged in no output is produced
				(if this is significant, nest within IF_AUTH?),
				otherwise, in article context, true if and only
				if the there exist comments more recent that
				the user's most recent viewing.
			IF_NON_EMTPY_NAME?
				XXX
			IF_NON_EMTPY_HREF?
				XXX
			IF_PREVIEW_BODY?
				XXX
			IF_SUBMISSION?
				True if and only if an article is a submission
				(i.e. unpublished).


The alternatives of a conditional macro may contain further macros delimited by
the next character in the sequence of macro-delimiting characters given in the
configuration file.  The nesting may be arbitrarily deep.  For example, with
macro-delimiting characters "%@&", we might have:
	%%IF_NON_EMTPY_NAME?@@IF_HREF_IS_URL?<a href="&&HREF&&">&&NAME&&</a>
	:@:&&NAME&&@@:%:[error]%%

Macros may have different values depending on the context in which they are
evaluated (e.g. article context, comment context, or global context).

It is recommended that the time format macros ("TIME_A_FMT:" and friends) are
used with attention to the possibility of failure.
If a template contains
  ...at <time datetime="%%TIME_A_FMT:%Y-%m-%dT%H:%M:%SZ">%Y-%m-%d%%</time>...
and substitution fails for some reason, the result will be
  ...at <time datetime="</time>...
which is invalid HTML (and may have undesirable functional consequences).
A preferable approach is to use
  ...%%TIME_A_FMT:at <time datetime="%Y-%m-%dT%H:%M:%SZ">%Y-%m-%d</time>%%...
which, on failure, results in the entire sequence being elided
  ......
with the HTML remaining valid, and a better chance of the meaning of the
output being preserved.


Key Files
=========

Hashes of the files specified by the configuration file entries "captcha_key",
"cookie_key", and "registration_key" are used as the secret keys for the HMACs
used for CAPTCHAs, cookies, and registration confirmation (respectively).
The files, therefore, should have restricted file modes; the user account as
which fwobac is executed must have read permissions.  Each file may be created
with a command such as:

	# dd if=/dev/random of=/var/www/example.com/etc/private/cookie.key \
	    bs=2048 count=1


Themes
======

If compiled with option FEATURE_THEMES, simple theme support is included.
The following features are added:

	configuration (file) entry "themes"
		This is a comma-separated list of all valid theme names.
		The list must be non-empty, and the first item becomes the
		default theme.

	a URL key/name "theme"
		This is used in a request to specify a theme - ";theme=<tn>".
		The default theme is used if the specified name is invalid
		(that is, not in the configured list).

	template macro "THEME"
		This is replaced by the theme name specified in the request if
		valid, or the default theme name otherwise.

	template macro (prefix) "INCLUDE_BY_THEME:<name>"
		This causes the inclusion of the file
			<templates_dir>/themes/<theme_name>/<name>

Many approaches to implementing themes are possible, but simple examples are:

- The simplest approach is to include the following in a document template:
	<link rel="stylesheet" href="%%THEME%%.css">

- A more sophisticated approach is to include in a document template:
	%%INCLUDE_BY_THEME:stylesheet.html%%
  The various "stylesheet.html" templates then have:
	<link rel="stylesheet" href="default.css" title="default theme">
	<link rel="alternate stylesheet" href="t0.css" title="theme 0">
	<link rel="alternate stylesheet" href="t1.css" title="theme 1">
	...
	<link rel="alternate stylesheet" href="tN.css" title="theme N">
  or:
	<link rel="stylesheet" href="t1.css" title="theme 1">
	<link rel="alternate stylesheet" href="default.css"
	    title="default theme">
	<link rel="alternate stylesheet" href="t0.css" title="theme 0">
	...
	<link rel="alternate stylesheet" href="tN.css" title="theme N">
  It is also possible to have themes _not_ listed amongst the standard
  "alternate" stylesheets.

