]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/WwwText.pm
wwwtext: switch to html_oneshot
[public-inbox.git] / lib / PublicInbox / WwwText.pm
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>
3
4 # used for displaying help texts and other non-mail content
5 package PublicInbox::WwwText;
6 use strict;
7 use warnings;
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 use Compress::Raw::Zlib qw(Z_FINISH Z_OK);
15 our $QP_URL = 'https://xapian.org/docs/queryparser.html';
16 our $WIKI_URL = 'https://en.wikipedia.org/wiki';
17 my $hl = eval {
18         require PublicInbox::HlMod;
19         PublicInbox::HlMod->new
20 };
21
22 # /$INBOX/_/text/$KEY/ # KEY may contain slashes
23 # For now, "help" is the only supported $KEY
24 sub get_text {
25         my ($ctx, $key) = @_;
26         my $code = 200;
27
28         $key = 'help' if !defined $key; # this 302s to _/text/help/
29
30         # get the raw text the same way we get mboxrds
31         my $raw = ($key =~ s!/raw\z!!);
32         my $have_tslash = ($key =~ s!/\z!!) if !$raw;
33
34         my $txt = '';
35         my $hdr = [ 'Content-Type', 'text/plain', 'Content-Length', undef ];
36         if (!_default_text($ctx, $key, $hdr, \$txt)) {
37                 $code = 404;
38                 $txt = "404 Not Found ($key)\n";
39         }
40         my $env = $ctx->{env};
41         if ($raw) {
42                 my $body;
43                 if (my $gzf = $code == 200 ? gzf_maybe($hdr, $env) : undef) {
44                         my $zbuf = $gzf->translate($txt);
45                         undef $txt;
46                         $body = [ $zbuf .= $gzf->translate(undef) ];
47                 } else {
48                         $body = [ $txt ];
49                 }
50                 $hdr->[3] = bytes::length($body->[0]);
51                 return [ $code, $hdr, $body ]
52         }
53
54         # enforce trailing slash for "wget -r" compatibility
55         if (!$have_tslash && $code == 200) {
56                 my $url = $ctx->{-inbox}->base_url($env);
57                 $url .= "_/text/$key/";
58
59                 return [ 302, [ 'Content-Type', 'text/plain',
60                                 'Location', $url ],
61                         [ "Redirecting to $url\n" ] ];
62         }
63
64         # Follow git commit message conventions,
65         # first line is the Subject/title
66         my ($title) = ($txt =~ /\A([^\n]*)/s);
67         $ctx->{-title_html} = ascii_html($title);
68         my $nslash = ($key =~ tr!/!/!);
69         $ctx->{-upfx} = '../../../' . ('../' x $nslash);
70         my $l = PublicInbox::Linkify->new;
71         $l->linkify_1($txt);
72         if ($hl) {
73                 $hl->do_hl_text(\$txt);
74         } else {
75                 $txt = ascii_html($txt);
76         }
77         $txt = '<pre>' . $l->linkify_2($txt) . '</pre>';
78         PublicInbox::WwwStream::html_oneshot($ctx, $code, \$txt);
79 }
80
81 sub _srch_prefix ($$) {
82         my ($srch, $txt) = @_;
83         my $pad = 0;
84         my $htxt = '';
85         my $help = $srch->help;
86         my $i;
87         for ($i = 0; $i < @$help; $i += 2) {
88                 my $pfx = $help->[$i];
89                 my $n = length($pfx);
90                 $pad = $n if $n > $pad;
91                 $htxt .= $pfx . "\0";
92                 $htxt .= $help->[$i + 1];
93                 $htxt .= "\f\n";
94         }
95         $pad += 2;
96         my $padding = ' ' x ($pad + 8);
97         $htxt =~ s/^/$padding/gms;
98         $htxt =~ s/^$padding(\S+)\0/"        $1".
99                                 (' ' x ($pad - length($1)))/egms;
100         $htxt =~ s/\f\n/\n/gs;
101         $$txt .= $htxt;
102         1;
103 }
104
105 sub _colors_help ($$) {
106         my ($ctx, $txt) = @_;
107         my $ibx = $ctx->{-inbox};
108         my $env = $ctx->{env};
109         my $base_url = $ibx->base_url($env);
110         $$txt .= "color customization for $base_url\n";
111         $$txt .= <<EOF;
112
113 public-inbox provides a stable set of CSS classes for users to
114 customize colors for highlighting diffs and code.
115
116 Users of browsers such as dillo, Firefox, or some browser
117 extensions may start by downloading the following sample CSS file
118 to control the colors they see:
119
120         ${base_url}userContent.css
121
122 CSS sample
123 ----------
124 ```css
125 EOF
126         $$txt .= PublicInbox::UserContent::sample($ibx, $env) . "```\n";
127 }
128
129 # git-config section names are quoted in the config file, so escape them
130 sub dq_escape ($) {
131         my ($name) = @_;
132         $name =~ s/\\/\\\\/g;
133         $name =~ s/"/\\"/g;
134         $name;
135 }
136
137 sub URI_PATH () { '^A-Za-z0-9\-\._~/' }
138
139 # n.b. this is a perfect candidate for memoization
140 sub inbox_config ($$$) {
141         my ($ctx, $hdr, $txt) = @_;
142         my $ibx = $ctx->{-inbox};
143         push @$hdr, 'Content-Disposition', 'inline; filename=inbox.config';
144         my $name = dq_escape($ibx->{name});
145         my $inboxdir = '/path/to/top-level-inbox';
146         $$txt .= <<EOS;
147 ; example public-inbox config snippet for "$name"
148 ; see public-inbox-config(5) manpage for more details:
149 ; https://public-inbox.org/public-inbox-config.html
150 [publicinbox "$name"]
151         inboxdir = $inboxdir
152         ; note: public-inbox before v1.2.0 used "mainrepo"
153         ; instead of "inboxdir", both remain supported after 1.2
154         mainrepo = $inboxdir
155         url = https://example.com/$name/
156         url = http://example.onion/$name/
157 EOS
158         for my $k (qw(address listid infourl watchheader)) {
159                 defined(my $v = $ibx->{$k}) or next;
160                 $$txt .= "\t$k = $_\n" for @$v;
161         }
162         if (my $altid = $ibx->{altid}) {
163                 my $base_url = $ibx->base_url($ctx->{env});
164                 my $altid_map = $ibx->altid_map;
165                 $$txt .= <<EOF;
166         ; altid DBs may be used to provide numeric article ID lookup from
167         ; old, pre-existing sources.  You can recreate them via curl(1),
168         ; gzip(1), and sqlite3(1) as documented:
169 EOF
170                 for (sort keys %$altid_map) {
171                         $$txt .= "\t;\tcurl -XPOST $base_url$_.sql.gz | \\\n" .
172                                 "\t;\tgzip -dc | \\\n" .
173                                 "\t;\tsqlite3 $inboxdir/$_.sqlite3\n";
174                         $$txt .= "\taltid = serial:$_:file=$_.sqlite3\n";
175                 }
176         }
177
178         for my $k (qw(filter newsgroup obfuscate replyto)) {
179                 defined(my $v = $ibx->{$k}) or next;
180                 $$txt .= "\t$k = $v\n";
181         }
182         $$txt .= "\tnntpmirror = $_\n" for (@{$ibx->nntp_url});
183
184         # note: this doesn't preserve cgitrc layout, since we parse cgitrc
185         # and drop the original structure
186         if (defined(my $cr = $ibx->{coderepo})) {
187                 $$txt .= "\tcoderepo = $_\n" for @$cr;
188
189                 my $pi_config = $ctx->{www}->{pi_config};
190                 for my $cr_name (@$cr) {
191                         my $urls = $pi_config->{"coderepo.$cr_name.cgiturl"};
192                         my $path = "/path/to/$cr_name";
193                         $cr_name = dq_escape($cr_name);
194
195                         $$txt .= qq([coderepo "$cr_name"]\n);
196                         if ($urls && scalar(@$urls)) {
197                                 $$txt .= "\t; ";
198                                 $$txt .= join(" ||\n\t;\t", map {;
199                                         my $cpath = $path;
200                                         if ($path !~ m![a-z0-9_/\.\-]!i) {
201                                                 $cpath = dq_escape($cpath);
202                                         }
203                                         qq(git clone $_ "$cpath");
204                                 } @$urls);
205                                 $$txt .= "\n";
206                         }
207                         $$txt .= "\tdir = $path\n";
208                         $$txt .= "\tcgiturl = https://example.com/";
209                         $$txt .= uri_escape_utf8($cr_name, URI_PATH)."\n";
210                 }
211         }
212         1;
213 }
214
215 sub _default_text ($$$$) {
216         my ($ctx, $key, $hdr, $txt) = @_;
217         return _colors_help($ctx, $txt) if $key eq 'color';
218         return inbox_config($ctx, $hdr, $txt) if $key eq 'config';
219         return if $key ne 'help'; # TODO more keys?
220
221         my $ibx = $ctx->{-inbox};
222         my $base_url = $ibx->base_url($ctx->{env});
223         $$txt .= "public-inbox help for $base_url\n";
224         $$txt .= <<EOF;
225
226 overview
227 --------
228
229     public-inbox uses Message-ID identifiers in URLs.
230     One may look up messages by substituting Message-IDs
231     (without the leading '<' or trailing '>') into the URL.
232     Forward slash ('/') characters in the Message-IDs
233     need to be escaped as "%2F" (without quotes).
234
235     Thus, it is possible to retrieve any message by its
236     Message-ID by going to:
237
238         $base_url<Message-ID>/
239
240         (without the '<' or '>')
241
242     Message-IDs are described at:
243
244         $WIKI_URL/Message-ID
245
246 EOF
247
248         # n.b. we use the Xapian DB for any regeneratable,
249         # order-of-arrival-independent data.
250         my $srch = $ibx->search;
251         if ($srch) {
252                 $$txt .= <<EOF;
253 search
254 ------
255
256     This public-inbox has search functionality provided by Xapian.
257
258     It supports typical AND, OR, NOT, '+', '-' queries present
259     in other search engines.
260
261     We also support search prefixes to limit the scope of the
262     search to certain fields.
263
264     Prefixes supported in this installation include:
265
266 EOF
267                 _srch_prefix($srch, $txt);
268
269                 $$txt .= <<EOF;
270
271     Most prefixes are probabilistic, meaning they support stemming
272     and wildcards ('*').  Ranges (such as 'd:') and boolean prefixes
273     do not support stemming or wildcards.
274     The upstream Xapian query parser documentation fully explains
275     the query syntax:
276
277         $QP_URL
278
279 EOF
280         } # $srch
281         my $over = $ibx->over;
282         if ($over) {
283                 $$txt .= <<EOF;
284 message threading
285 -----------------
286
287     Message threading is enabled for this public-inbox,
288     additional endpoints for message threads are available:
289
290     * $base_url<Message-ID>/T/#u
291
292       Loads the thread belonging to the given <Message-ID>
293       in flat chronological order.  The "#u" anchor
294       focuses the browser on the given <Message-ID>.
295
296     * $base_url<Message-ID>/t/#u
297
298       Loads the thread belonging to the given <Message-ID>
299       in threaded order with nesting.  For deep threads,
300       this requires a wide display or horizontal scrolling.
301
302     Both of these HTML endpoints are suitable for offline reading
303     using the thread overview at the bottom of each page.
304
305     Users of feed readers may follow a particular thread using:
306
307     * $base_url<Message-ID>/t.atom
308
309       Which loads the thread in Atom Syndication Standard
310       described at Wikipedia and RFC4287:
311
312         $WIKI_URL/Atom_(standard)
313         https://tools.ietf.org/html/rfc4287
314
315       Atom Threading Extensions (RFC4685) is supported:
316
317         https://tools.ietf.org/html/rfc4685
318
319     Finally, the gzipped mbox for a thread is available for
320     downloading and importing into your favorite mail client:
321
322     * $base_url<Message-ID>/t.mbox.gz
323
324     We use the mboxrd variant of the mbox format described
325     at:
326
327         $WIKI_URL/Mbox
328
329 EOF
330         } # $over
331
332         $$txt .= <<EOF;
333 contact
334 -------
335
336     This help text is maintained by public-inbox developers
337     reachable via plain-text email at: meta\@public-inbox.org
338
339 EOF
340         # TODO: support admin contact info in ~/.public-inbox/config
341         1;
342 }
343
344 1;