]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/WwwText.pm
treewide: run update-copyrights from gnulib for 2019
[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         $$txt .= <<EOS;
142 ; example public-inbox config snippet for "$name"
143 ; see public-inbox-config(5) manpage for more details:
144 ; https://public-inbox.org/public-inbox-config.html
145 [publicinbox "$name"]
146         inboxdir = /path/to/top-level-inbox
147         ; note: public-inbox before v1.2.0 used "mainrepo"
148         ; instead of "inboxdir", both remain supported after 1.2
149         mainrepo = /path/to/top-level-inbox
150         url = https://example.com/$name/
151         url = http://example.onion/$name/
152 EOS
153         for my $k (qw(address listid infourl)) {
154                 defined(my $v = $ibx->{$k}) or next;
155                 $$txt .= "\t$k = $_\n" for @$v;
156         }
157
158         for my $k (qw(filter newsgroup obfuscate replyto watchheader)) {
159                 defined(my $v = $ibx->{$k}) or next;
160                 $$txt .= "\t$k = $v\n";
161         }
162         $$txt .= "\tnntpmirror = $_\n" for (@{$ibx->nntp_url});
163
164         # note: this doesn't preserve cgitrc layout, since we parse cgitrc
165         # and drop the original structure
166         if (defined(my $cr = $ibx->{coderepo})) {
167                 $$txt .= "\tcoderepo = $_\n" for @$cr;
168
169                 my $pi_config = $ctx->{www}->{pi_config};
170                 for my $cr_name (@$cr) {
171                         my $urls = $pi_config->{"coderepo.$cr_name.cgiturl"};
172                         my $path = "/path/to/$cr_name";
173                         $cr_name = dq_escape($cr_name);
174
175                         $$txt .= qq([coderepo "$cr_name"]\n);
176                         if ($urls && scalar(@$urls)) {
177                                 $$txt .= "\t; ";
178                                 $$txt .= join(" ||\n\t;\t", map {;
179                                         my $cpath = $path;
180                                         if ($path !~ m![a-z0-9_/\.\-]!i) {
181                                                 $cpath = dq_escape($cpath);
182                                         }
183                                         qq(git clone $_ "$cpath");
184                                 } @$urls);
185                                 $$txt .= "\n";
186                         }
187                         $$txt .= "\tdir = $path\n";
188                         $$txt .= "\tcgiturl = https://example.com/";
189                         $$txt .= uri_escape_utf8($cr_name, URI_PATH)."\n";
190                 }
191         }
192         1;
193 }
194
195 sub _default_text ($$$$) {
196         my ($ctx, $key, $hdr, $txt) = @_;
197         return _colors_help($ctx, $txt) if $key eq 'color';
198         return inbox_config($ctx, $hdr, $txt) if $key eq 'config';
199         return if $key ne 'help'; # TODO more keys?
200
201         my $ibx = $ctx->{-inbox};
202         my $base_url = $ibx->base_url($ctx->{env});
203         $$txt .= "public-inbox help for $base_url\n";
204         $$txt .= <<EOF;
205
206 overview
207 --------
208
209     public-inbox uses Message-ID identifiers in URLs.
210     One may look up messages by substituting Message-IDs
211     (without the leading '<' or trailing '>') into the URL.
212     Forward slash ('/') characters in the Message-IDs
213     need to be escaped as "%2F" (without quotes).
214
215     Thus, it is possible to retrieve any message by its
216     Message-ID by going to:
217
218         $base_url<Message-ID>/
219
220         (without the '<' or '>')
221
222     Message-IDs are described at:
223
224         $WIKI_URL/Message-ID
225
226 EOF
227
228         # n.b. we use the Xapian DB for any regeneratable,
229         # order-of-arrival-independent data.
230         my $srch = $ibx->search;
231         if ($srch) {
232                 $$txt .= <<EOF;
233 search
234 ------
235
236     This public-inbox has search functionality provided by Xapian.
237
238     It supports typical AND, OR, NOT, '+', '-' queries present
239     in other search engines.
240
241     We also support search prefixes to limit the scope of the
242     search to certain fields.
243
244     Prefixes supported in this installation include:
245
246 EOF
247                 _srch_prefix($srch, $txt);
248
249                 $$txt .= <<EOF;
250
251     Most prefixes are probabilistic, meaning they support stemming
252     and wildcards ('*').  Ranges (such as 'd:') and boolean prefixes
253     do not support stemming or wildcards.
254     The upstream Xapian query parser documentation fully explains
255     the query syntax:
256
257         $QP_URL
258
259 message threading
260 -----------------
261
262     Message threading is enabled for this public-inbox,
263     additional endpoints for message threads are available:
264
265     * $base_url<Message-ID>/T/#u
266
267       Loads the thread belonging to the given <Message-ID>
268       in flat chronological order.  The "#u" anchor
269       focuses the browser on the given <Message-ID>.
270
271     * $base_url<Message-ID>/t/#u
272
273       Loads the thread belonging to the given <Message-ID>
274       in threaded order with nesting.  For deep threads,
275       this requires a wide display or horizontal scrolling.
276
277     Both of these HTML endpoints are suitable for offline reading
278     using the thread overview at the bottom of each page.
279
280     Users of feed readers may follow a particular thread using:
281
282     * $base_url<Message-ID>/t.atom
283
284       Which loads the thread in Atom Syndication Standard
285       described at Wikipedia and RFC4287:
286
287         $WIKI_URL/Atom_(standard)
288         https://tools.ietf.org/html/rfc4287
289
290       Atom Threading Extensions (RFC4685) is supported:
291
292         https://tools.ietf.org/html/rfc4685
293
294     Finally, the gzipped mbox for a thread is available for
295     downloading and importing into your favorite mail client:
296
297     * $base_url<Message-ID>/t.mbox.gz
298
299     We use the mboxrd variant of the mbox format described
300     at:
301
302         $WIKI_URL/Mbox
303
304 contact
305 -------
306
307     This help text is maintained by public-inbox developers
308     reachable via plain-text email at: meta\@public-inbox.org
309
310 EOF
311         # TODO: support admin contact info in ~/.public-inbox/config
312         }
313         1;
314 }
315
316 1;