]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/WwwText.pm
wwwtext: show thread endpoint w/ indexlevel=basic
[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 EOF
260         } # $srch
261         my $over = $ibx->over;
262         if ($over) {
263                 $$txt .= <<EOF;
264 message threading
265 -----------------
266
267     Message threading is enabled for this public-inbox,
268     additional endpoints for message threads are available:
269
270     * $base_url<Message-ID>/T/#u
271
272       Loads the thread belonging to the given <Message-ID>
273       in flat chronological order.  The "#u" anchor
274       focuses the browser on the given <Message-ID>.
275
276     * $base_url<Message-ID>/t/#u
277
278       Loads the thread belonging to the given <Message-ID>
279       in threaded order with nesting.  For deep threads,
280       this requires a wide display or horizontal scrolling.
281
282     Both of these HTML endpoints are suitable for offline reading
283     using the thread overview at the bottom of each page.
284
285     Users of feed readers may follow a particular thread using:
286
287     * $base_url<Message-ID>/t.atom
288
289       Which loads the thread in Atom Syndication Standard
290       described at Wikipedia and RFC4287:
291
292         $WIKI_URL/Atom_(standard)
293         https://tools.ietf.org/html/rfc4287
294
295       Atom Threading Extensions (RFC4685) is supported:
296
297         https://tools.ietf.org/html/rfc4685
298
299     Finally, the gzipped mbox for a thread is available for
300     downloading and importing into your favorite mail client:
301
302     * $base_url<Message-ID>/t.mbox.gz
303
304     We use the mboxrd variant of the mbox format described
305     at:
306
307         $WIKI_URL/Mbox
308
309 EOF
310         } # $over
311
312         $$txt .= <<EOF;
313 contact
314 -------
315
316     This help text is maintained by public-inbox developers
317     reachable via plain-text email at: meta\@public-inbox.org
318
319 EOF
320         # TODO: support admin contact info in ~/.public-inbox/config
321         1;
322 }
323
324 1;