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