]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/WwwText.pm
2008ba0928decaba609dadbd8e67515cea2678fa
[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 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         if ($raw) {
39                 $hdr->[3] = bytes::length($txt);
40                 return [ $code, $hdr, [ $txt ] ]
41         }
42
43         # enforce trailing slash for "wget -r" compatibility
44         if (!$have_tslash && $code == 200) {
45                 my $url = $ctx->{-inbox}->base_url($ctx->{env});
46                 $url .= "_/text/$key/";
47
48                 return [ 302, [ 'Content-Type', 'text/plain',
49                                 'Location', $url ],
50                         [ "Redirecting to $url\n" ] ];
51         }
52
53         # Follow git commit message conventions,
54         # first line is the Subject/title
55         my ($title) = ($txt =~ /\A([^\n]*)/s);
56         $ctx->{txt} = \$txt;
57         $ctx->{-title_html} = ascii_html($title);
58         my $nslash = ($key =~ tr!/!/!);
59         $ctx->{-upfx} = '../../../' . ('../' x $nslash);
60         PublicInbox::WwwStream->response($ctx, $code, \&_do_linkify);
61 }
62
63 sub _do_linkify {
64         my ($nr, $ctx) = @_;
65         return unless $nr == 1;
66         my $l = PublicInbox::Linkify->new;
67         my $txt = delete $ctx->{txt};
68         $l->linkify_1($$txt);
69         if ($hl) {
70                 $hl->do_hl_text($txt);
71         } else {
72                 $$txt = ascii_html($$txt);
73         }
74         '<pre>' . $l->linkify_2($$txt) . '</pre>';
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->{-inbox};
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->{-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         $$txt .= <<EOS;
143 ; example public-inbox config snippet for "$name"
144 ; see public-inbox-config(5) manpage for more details:
145 ; https://public-inbox.org/public-inbox-config.html
146 [publicinbox "$name"]
147         inboxdir = $inboxdir
148         ; note: public-inbox before v1.2.0 used "mainrepo"
149         ; instead of "inboxdir", both remain supported after 1.2
150         mainrepo = $inboxdir
151         url = https://example.com/$name/
152         url = http://example.onion/$name/
153 EOS
154         for my $k (qw(address listid infourl)) {
155                 defined(my $v = $ibx->{$k}) or next;
156                 $$txt .= "\t$k = $_\n" for @$v;
157         }
158         if (my $altid = $ibx->{altid}) {
159                 my $base_url = $ibx->base_url($ctx->{env});
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 -XPOST $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 watchheader)) {
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
185                 my $pi_config = $ctx->{www}->{pi_config};
186                 for my $cr_name (@$cr) {
187                         my $urls = $pi_config->{"coderepo.$cr_name.cgiturl"};
188                         my $path = "/path/to/$cr_name";
189                         $cr_name = dq_escape($cr_name);
190
191                         $$txt .= qq([coderepo "$cr_name"]\n);
192                         if ($urls && scalar(@$urls)) {
193                                 $$txt .= "\t; ";
194                                 $$txt .= join(" ||\n\t;\t", map {;
195                                         my $cpath = $path;
196                                         if ($path !~ m![a-z0-9_/\.\-]!i) {
197                                                 $cpath = dq_escape($cpath);
198                                         }
199                                         qq(git clone $_ "$cpath");
200                                 } @$urls);
201                                 $$txt .= "\n";
202                         }
203                         $$txt .= "\tdir = $path\n";
204                         $$txt .= "\tcgiturl = https://example.com/";
205                         $$txt .= uri_escape_utf8($cr_name, URI_PATH)."\n";
206                 }
207         }
208         1;
209 }
210
211 sub _default_text ($$$$) {
212         my ($ctx, $key, $hdr, $txt) = @_;
213         return _colors_help($ctx, $txt) if $key eq 'color';
214         return inbox_config($ctx, $hdr, $txt) if $key eq 'config';
215         return if $key ne 'help'; # TODO more keys?
216
217         my $ibx = $ctx->{-inbox};
218         my $base_url = $ibx->base_url($ctx->{env});
219         $$txt .= "public-inbox help for $base_url\n";
220         $$txt .= <<EOF;
221
222 overview
223 --------
224
225     public-inbox uses Message-ID identifiers in URLs.
226     One may look up messages by substituting Message-IDs
227     (without the leading '<' or trailing '>') into the URL.
228     Forward slash ('/') characters in the Message-IDs
229     need to be escaped as "%2F" (without quotes).
230
231     Thus, it is possible to retrieve any message by its
232     Message-ID by going to:
233
234         $base_url<Message-ID>/
235
236         (without the '<' or '>')
237
238     Message-IDs are described at:
239
240         $WIKI_URL/Message-ID
241
242 EOF
243
244         # n.b. we use the Xapian DB for any regeneratable,
245         # order-of-arrival-independent data.
246         my $srch = $ibx->search;
247         if ($srch) {
248                 $$txt .= <<EOF;
249 search
250 ------
251
252     This public-inbox has search functionality provided by Xapian.
253
254     It supports typical AND, OR, NOT, '+', '-' queries present
255     in other search engines.
256
257     We also support search prefixes to limit the scope of the
258     search to certain fields.
259
260     Prefixes supported in this installation include:
261
262 EOF
263                 _srch_prefix($srch, $txt);
264
265                 $$txt .= <<EOF;
266
267     Most prefixes are probabilistic, meaning they support stemming
268     and wildcards ('*').  Ranges (such as 'd:') and boolean prefixes
269     do not support stemming or wildcards.
270     The upstream Xapian query parser documentation fully explains
271     the query syntax:
272
273         $QP_URL
274
275 EOF
276         } # $srch
277         my $over = $ibx->over;
278         if ($over) {
279                 $$txt .= <<EOF;
280 message threading
281 -----------------
282
283     Message threading is enabled for this public-inbox,
284     additional endpoints for message threads are available:
285
286     * $base_url<Message-ID>/T/#u
287
288       Loads the thread belonging to the given <Message-ID>
289       in flat chronological order.  The "#u" anchor
290       focuses the browser on the given <Message-ID>.
291
292     * $base_url<Message-ID>/t/#u
293
294       Loads the thread belonging to the given <Message-ID>
295       in threaded order with nesting.  For deep threads,
296       this requires a wide display or horizontal scrolling.
297
298     Both of these HTML endpoints are suitable for offline reading
299     using the thread overview at the bottom of each page.
300
301     Users of feed readers may follow a particular thread using:
302
303     * $base_url<Message-ID>/t.atom
304
305       Which loads the thread in Atom Syndication Standard
306       described at Wikipedia and RFC4287:
307
308         $WIKI_URL/Atom_(standard)
309         https://tools.ietf.org/html/rfc4287
310
311       Atom Threading Extensions (RFC4685) is supported:
312
313         https://tools.ietf.org/html/rfc4685
314
315     Finally, the gzipped mbox for a thread is available for
316     downloading and importing into your favorite mail client:
317
318     * $base_url<Message-ID>/t.mbox.gz
319
320     We use the mboxrd variant of the mbox format described
321     at:
322
323         $WIKI_URL/Mbox
324
325 EOF
326         } # $over
327
328         $$txt .= <<EOF;
329 contact
330 -------
331
332     This help text is maintained by public-inbox developers
333     reachable via plain-text email at: meta\@public-inbox.org
334
335 EOF
336         # TODO: support admin contact info in ~/.public-inbox/config
337         1;
338 }
339
340 1;