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