1 # Copyright (C) 2016-2020 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 # used for displaying help texts and other non-mail content
5 package PublicInbox::WwwText;
8 use bytes (); # only for bytes::length
9 use PublicInbox::Linkify;
10 use PublicInbox::WwwStream;
11 use PublicInbox::Hval qw(ascii_html);
12 use URI::Escape qw(uri_escape_utf8);
13 use PublicInbox::GzipFilter qw(gzf_maybe);
14 our $QP_URL = 'https://xapian.org/docs/queryparser.html';
15 our $WIKI_URL = 'https://en.wikipedia.org/wiki';
17 require PublicInbox::HlMod;
18 PublicInbox::HlMod->new
21 # /$INBOX/_/text/$KEY/ # KEY may contain slashes
22 # For now, "help" is the only supported $KEY
27 $key = 'help' if !defined $key; # this 302s to _/text/help/
29 # get the raw text the same way we get mboxrds
30 my $raw = ($key =~ s!/raw\z!!);
31 my $have_tslash = ($key =~ s!/\z!!) if !$raw;
34 my $hdr = [ 'Content-Type', 'text/plain', 'Content-Length', undef ];
35 if (!_default_text($ctx, $key, $hdr, \$txt)) {
37 $txt = "404 Not Found ($key)\n";
39 my $env = $ctx->{env};
42 my $gzf = gzf_maybe($hdr, $env);
43 $txt = $gzf->translate($txt);
46 $hdr->[3] = bytes::length($txt);
47 return [ $code, $hdr, [ $txt ] ]
50 # enforce trailing slash for "wget -r" compatibility
51 if (!$have_tslash && $code == 200) {
52 my $url = $ctx->{-inbox}->base_url($env);
53 $url .= "_/text/$key/";
55 return [ 302, [ 'Content-Type', 'text/plain',
57 [ "Redirecting to $url\n" ] ];
60 # Follow git commit message conventions,
61 # first line is the Subject/title
62 my ($title) = ($txt =~ /\A([^\n]*)/s);
63 $ctx->{-title_html} = ascii_html($title);
64 my $nslash = ($key =~ tr!/!/!);
65 $ctx->{-upfx} = '../../../' . ('../' x $nslash);
66 my $l = PublicInbox::Linkify->new;
69 $hl->do_hl_text(\$txt);
71 $txt = ascii_html($txt);
73 $txt = '<pre>' . $l->linkify_2($txt) . '</pre>';
74 PublicInbox::WwwStream::html_oneshot($ctx, $code, \$txt);
77 sub _srch_prefix ($$) {
78 my ($srch, $txt) = @_;
81 my $help = $srch->help;
83 for ($i = 0; $i < @$help; $i += 2) {
84 my $pfx = $help->[$i];
86 $pad = $n if $n > $pad;
88 $htxt .= $help->[$i + 1];
92 my $padding = ' ' x ($pad + 8);
93 $htxt =~ s/^/$padding/gms;
94 $htxt =~ s/^$padding(\S+)\0/" $1".
95 (' ' x ($pad - length($1)))/egms;
96 $htxt =~ s/\f\n/\n/gs;
101 sub _colors_help ($$) {
102 my ($ctx, $txt) = @_;
103 my $ibx = $ctx->{-inbox};
104 my $env = $ctx->{env};
105 my $base_url = $ibx->base_url($env);
106 $$txt .= "color customization for $base_url\n";
109 public-inbox provides a stable set of CSS classes for users to
110 customize colors for highlighting diffs and code.
112 Users of browsers such as dillo, Firefox, or some browser
113 extensions may start by downloading the following sample CSS file
114 to control the colors they see:
116 ${base_url}userContent.css
122 $$txt .= PublicInbox::UserContent::sample($ibx, $env) . "```\n";
125 # git-config section names are quoted in the config file, so escape them
128 $name =~ s/\\/\\\\/g;
133 sub URI_PATH () { '^A-Za-z0-9\-\._~/' }
135 # n.b. this is a perfect candidate for memoization
136 sub inbox_config ($$$) {
137 my ($ctx, $hdr, $txt) = @_;
138 my $ibx = $ctx->{-inbox};
139 push @$hdr, 'Content-Disposition', 'inline; filename=inbox.config';
140 my $name = dq_escape($ibx->{name});
141 my $inboxdir = '/path/to/top-level-inbox';
142 my $base_url = $ibx->base_url($ctx->{env});
144 ; Example public-inbox config snippet for a mirror of
146 ; See public-inbox-config(5) manpage for more details:
147 ; https://public-inbox.org/public-inbox-config.html
148 [publicinbox "$name"]
150 ; note: public-inbox before v1.2.0 used `mainrepo' instead of
151 ; `inboxdir', both remain supported after 1.2
153 url = https://example.com/$name/
154 url = http://example.onion/$name/
156 for my $k (qw(address listid infourl watchheader)) {
157 defined(my $v = $ibx->{$k}) or next;
158 $$txt .= "\t$k = $_\n" for @$v;
160 if (my $altid = $ibx->{altid}) {
161 my $altid_map = $ibx->altid_map;
163 ; altid DBs may be used to provide numeric article ID lookup from
164 ; old, pre-existing sources. You can recreate them via curl(1),
165 ; gzip(1), and sqlite3(1) as documented:
167 for (sort keys %$altid_map) {
168 $$txt .= "\t;\tcurl -XPOST $base_url$_.sql.gz | \\\n" .
169 "\t;\tgzip -dc | \\\n" .
170 "\t;\tsqlite3 $inboxdir/$_.sqlite3\n";
171 $$txt .= "\taltid = serial:$_:file=$_.sqlite3\n";
175 for my $k (qw(filter newsgroup obfuscate replyto)) {
176 defined(my $v = $ibx->{$k}) or next;
177 $$txt .= "\t$k = $v\n";
179 $$txt .= "\tnntpmirror = $_\n" for (@{$ibx->nntp_url});
181 # note: this doesn't preserve cgitrc layout, since we parse cgitrc
182 # and drop the original structure
183 if (defined(my $cr = $ibx->{coderepo})) {
184 $$txt .= "\tcoderepo = $_\n" for @$cr;
187 ; `coderepo' entries allows blob reconstruction via patch emails if
188 ; the inbox is indexed with Xapian. `@@ <from-range> <to-range> @@'
189 ; line number ranges in `[PATCH]' emails link to /$INBOX_NAME/$OID/s/,
190 ; an HTTP endpoint which reconstructs git blobs via git-apply(1).
192 my $pi_config = $ctx->{www}->{pi_config};
193 for my $cr_name (@$cr) {
194 my $urls = $pi_config->{"coderepo.$cr_name.cgiturl"};
195 my $path = "/path/to/$cr_name";
196 $cr_name = dq_escape($cr_name);
198 $$txt .= qq([coderepo "$cr_name"]\n);
199 if ($urls && scalar(@$urls)) {
201 $$txt .= join(" ||\n\t;\t", map {;
203 if ($path !~ m![a-z0-9_/\.\-]!i) {
204 $dst = '"'.dq_escape($dst).'"';
206 qq(git clone $_ $dst);
210 $$txt .= "\tdir = $path\n";
211 $$txt .= "\tcgiturl = https://example.com/";
212 $$txt .= uri_escape_utf8($cr_name, URI_PATH)."\n";
218 sub _default_text ($$$$) {
219 my ($ctx, $key, $hdr, $txt) = @_;
220 return _colors_help($ctx, $txt) if $key eq 'color';
221 return inbox_config($ctx, $hdr, $txt) if $key eq 'config';
222 return if $key ne 'help'; # TODO more keys?
224 my $ibx = $ctx->{-inbox};
225 my $base_url = $ibx->base_url($ctx->{env});
226 $$txt .= "public-inbox help for $base_url\n";
232 public-inbox uses Message-ID identifiers in URLs.
233 One may look up messages by substituting Message-IDs
234 (without the leading '<' or trailing '>') into the URL.
235 Forward slash ('/') characters in the Message-IDs
236 need to be escaped as "%2F" (without quotes).
238 Thus, it is possible to retrieve any message by its
239 Message-ID by going to:
241 $base_url<Message-ID>/
243 (without the '<' or '>')
245 Message-IDs are described at:
251 # n.b. we use the Xapian DB for any regeneratable,
252 # order-of-arrival-independent data.
253 my $srch = $ibx->search;
259 This public-inbox has search functionality provided by Xapian.
261 It supports typical AND, OR, NOT, '+', '-' queries present
262 in other search engines.
264 We also support search prefixes to limit the scope of the
265 search to certain fields.
267 Prefixes supported in this installation include:
270 _srch_prefix($srch, $txt);
274 Most prefixes are probabilistic, meaning they support stemming
275 and wildcards ('*'). Ranges (such as 'd:') and boolean prefixes
276 do not support stemming or wildcards.
277 The upstream Xapian query parser documentation fully explains
284 my $over = $ibx->over;
290 Message threading is enabled for this public-inbox,
291 additional endpoints for message threads are available:
293 * $base_url<Message-ID>/T/#u
295 Loads the thread belonging to the given <Message-ID>
296 in flat chronological order. The "#u" anchor
297 focuses the browser on the given <Message-ID>.
299 * $base_url<Message-ID>/t/#u
301 Loads the thread belonging to the given <Message-ID>
302 in threaded order with nesting. For deep threads,
303 this requires a wide display or horizontal scrolling.
305 Both of these HTML endpoints are suitable for offline reading
306 using the thread overview at the bottom of each page.
308 Users of feed readers may follow a particular thread using:
310 * $base_url<Message-ID>/t.atom
312 Which loads the thread in Atom Syndication Standard
313 described at Wikipedia and RFC4287:
315 $WIKI_URL/Atom_(standard)
316 https://tools.ietf.org/html/rfc4287
318 Atom Threading Extensions (RFC4685) is supported:
320 https://tools.ietf.org/html/rfc4685
322 Finally, the gzipped mbox for a thread is available for
323 downloading and importing into your favorite mail client:
325 * $base_url<Message-ID>/t.mbox.gz
327 We use the mboxrd variant of the mbox format described
339 This help text is maintained by public-inbox developers
340 reachable via plain-text email at: meta\@public-inbox.org
341 Their inbox is archived at: https://public-inbox.org/meta/
344 # TODO: support admin contact info in ~/.public-inbox/config