]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/WwwText.pm
ds: simplify EventLoop implementation
[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 our $QP_URL = 'https://xapian.org/docs/queryparser.html';
15 our $WIKI_URL = 'https://en.wikipedia.org/wiki';
16 my $hl = eval {
17         require PublicInbox::HlMod;
18         PublicInbox::HlMod->new
19 };
20
21 # /$INBOX/_/text/$KEY/ # KEY may contain slashes
22 # For now, "help" is the only supported $KEY
23 sub get_text {
24         my ($ctx, $key) = @_;
25         my $code = 200;
26
27         $key = 'help' if !defined $key; # this 302s to _/text/help/
28
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;
32
33         my $txt = '';
34         my $hdr = [ 'Content-Type', 'text/plain', 'Content-Length', undef ];
35         if (!_default_text($ctx, $key, $hdr, \$txt)) {
36                 $code = 404;
37                 $txt = "404 Not Found ($key)\n";
38         }
39         my $env = $ctx->{env};
40         if ($raw) {
41                 if ($code == 200) {
42                         my $gzf = gzf_maybe($hdr, $env);
43                         $txt = $gzf->translate($txt);
44                         $txt .= $gzf->zflush;
45                 }
46                 $hdr->[3] = bytes::length($txt);
47                 return [ $code, $hdr, [ $txt ] ]
48         }
49
50         # enforce trailing slash for "wget -r" compatibility
51         if (!$have_tslash && $code == 200) {
52                 my $url = $ctx->{ibx}->base_url($env);
53                 $url .= "_/text/$key/";
54
55                 return [ 302, [ 'Content-Type', 'text/plain',
56                                 'Location', $url ],
57                         [ "Redirecting to $url\n" ] ];
58         }
59
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;
67         $l->linkify_1($txt);
68         if ($hl) {
69                 $hl->do_hl_text(\$txt);
70         } else {
71                 $txt = ascii_html($txt);
72         }
73         $txt = '<pre>' . $l->linkify_2($txt) . '</pre>';
74         PublicInbox::WwwStream::html_oneshot($ctx, $code, \$txt);
75 }
76
77 sub _srch_prefix ($$) {
78         my ($srch, $txt) = @_;
79         my $pad = 0;
80         my $htxt = '';
81         my $help = $srch->help;
82         my $i;
83         for ($i = 0; $i < @$help; $i += 2) {
84                 my $pfx = $help->[$i];
85                 my $n = length($pfx);
86                 $pad = $n if $n > $pad;
87                 $htxt .= $pfx . "\0";
88                 $htxt .= $help->[$i + 1];
89                 $htxt .= "\f\n";
90         }
91         $pad += 2;
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;
97         $$txt .= $htxt;
98         1;
99 }
100
101 sub _colors_help ($$) {
102         my ($ctx, $txt) = @_;
103         my $ibx = $ctx->{ibx};
104         my $env = $ctx->{env};
105         my $base_url = $ibx->base_url($env);
106         $$txt .= "color customization for $base_url\n";
107         $$txt .= <<EOF;
108
109 public-inbox provides a stable set of CSS classes for users to
110 customize colors for highlighting diffs and code.
111
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:
115
116         ${base_url}userContent.css
117
118 CSS sample
119 ----------
120 ```css
121 EOF
122         $$txt .= PublicInbox::UserContent::sample($ibx, $env) . "```\n";
123 }
124
125 # git-config section names are quoted in the config file, so escape them
126 sub dq_escape ($) {
127         my ($name) = @_;
128         $name =~ s/\\/\\\\/g;
129         $name =~ s/"/\\"/g;
130         $name;
131 }
132
133 sub URI_PATH () { '^A-Za-z0-9\-\._~/' }
134
135 # n.b. this is a perfect candidate for memoization
136 sub inbox_config ($$$) {
137         my ($ctx, $hdr, $txt) = @_;
138         my $ibx = $ctx->{ibx};
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});
143         $$txt .= <<EOS;
144 ; Example public-inbox config snippet for a mirror of
145 ; $base_url
146 ; See public-inbox-config(5) manpage for more details:
147 ; https://public-inbox.org/public-inbox-config.html
148 [publicinbox "$name"]
149         inboxdir = $inboxdir
150         ; note: public-inbox before v1.2.0 used `mainrepo' instead of
151         ; `inboxdir', both remain supported after 1.2
152         mainrepo = $inboxdir
153         url = https://example.com/$name/
154         url = http://example.onion/$name/
155 EOS
156         for my $k (qw(address listid infourl watchheader)) {
157                 defined(my $v = $ibx->{$k}) or next;
158                 $$txt .= "\t$k = $_\n" for @$v;
159         }
160         if (my $altid = $ibx->{altid}) {
161                 my $altid_map = $ibx->altid_map;
162                 $$txt .= <<EOF;
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:
166 EOF
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";
172                 }
173         }
174
175         for my $k (qw(filter newsgroup obfuscate replyto)) {
176                 defined(my $v = $ibx->{$k}) or next;
177                 $$txt .= "\t$k = $v\n";
178         }
179         $$txt .= "\tnntpmirror = $_\n" for (@{$ibx->nntp_url});
180
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;
185                 $$txt .= <<'EOF';
186
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).
191 EOF
192                 my $pi_cfg = $ctx->{www}->{pi_cfg};
193                 for my $cr_name (@$cr) {
194                         my $urls = $pi_cfg->{"coderepo.$cr_name.cgiturl"};
195                         my $path = "/path/to/$cr_name";
196                         $cr_name = dq_escape($cr_name);
197
198                         $$txt .= qq([coderepo "$cr_name"]\n);
199                         if ($urls && scalar(@$urls)) {
200                                 $$txt .= "\t; ";
201                                 $$txt .= join(" ||\n\t;\t", map {;
202                                         my $dst = $path;
203                                         if ($path !~ m![a-z0-9_/\.\-]!i) {
204                                                 $dst = '"'.dq_escape($dst).'"';
205                                         }
206                                         qq(git clone $_ $dst);
207                                 } @$urls);
208                                 $$txt .= "\n";
209                         }
210                         $$txt .= "\tdir = $path\n";
211                         $$txt .= "\tcgiturl = https://example.com/";
212                         $$txt .= uri_escape_utf8($cr_name, URI_PATH)."\n";
213                 }
214         }
215         1;
216 }
217
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?
223
224         my $ibx = $ctx->{ibx};
225         my $base_url = $ibx->base_url($ctx->{env});
226         $$txt .= "public-inbox help for $base_url\n";
227         $$txt .= <<EOF;
228
229 overview
230 --------
231
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).
237
238     Thus, it is possible to retrieve any message by its
239     Message-ID by going to:
240
241         $base_url<Message-ID>/
242
243         (without the '<' or '>')
244
245     Message-IDs are described at:
246
247         $WIKI_URL/Message-ID
248
249 EOF
250
251         # n.b. we use the Xapian DB for any regeneratable,
252         # order-of-arrival-independent data.
253         my $srch = $ibx->isrch;
254         if ($srch) {
255                 $$txt .= <<EOF;
256 search
257 ------
258
259     This public-inbox has search functionality provided by Xapian.
260
261     It supports typical AND, OR, NOT, '+', '-' queries present
262     in other search engines.
263
264     We also support search prefixes to limit the scope of the
265     search to certain fields.
266
267     Prefixes supported in this installation include:
268
269 EOF
270                 _srch_prefix($srch, $txt);
271
272                 $$txt .= <<EOF;
273
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
278     the query syntax:
279
280         $QP_URL
281
282 EOF
283         } # $srch
284         my $over = $ibx->over;
285         if ($over) {
286                 $$txt .= <<EOF;
287 message threading
288 -----------------
289
290     Message threading is enabled for this public-inbox,
291     additional endpoints for message threads are available:
292
293     * $base_url<Message-ID>/T/#u
294
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>.
298
299     * $base_url<Message-ID>/t/#u
300
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.
304
305     Both of these HTML endpoints are suitable for offline reading
306     using the thread overview at the bottom of each page.
307
308     Users of feed readers may follow a particular thread using:
309
310     * $base_url<Message-ID>/t.atom
311
312       Which loads the thread in Atom Syndication Standard
313       described at Wikipedia and RFC4287:
314
315         $WIKI_URL/Atom_(standard)
316         https://tools.ietf.org/html/rfc4287
317
318       Atom Threading Extensions (RFC4685) is supported:
319
320         https://tools.ietf.org/html/rfc4685
321
322     Finally, the gzipped mbox for a thread is available for
323     downloading and importing into your favorite mail client:
324
325     * $base_url<Message-ID>/t.mbox.gz
326
327     We use the mboxrd variant of the mbox format described
328     at:
329
330         $WIKI_URL/Mbox
331
332 EOF
333         } # $over
334
335         $$txt .= <<EOF;
336 contact
337 -------
338
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/
342
343 EOF
344         # TODO: support admin contact info in ~/.public-inbox/config
345         1;
346 }
347
348 1;