]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/WwwText.pm
wwwtext: don't blindly quote "git clone" destination
[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 our $QP_URL = 'https://xapian.org/docs/queryparser.html';
15 our $WIKI_URL = 'https://en.wikipedia.org/wiki';
16 my $hl = eval {
17         require PublicInbox::HlMod;
18         PublicInbox::HlMod->new
19 };
20
21 # /$INBOX/_/text/$KEY/ # KEY may contain slashes
22 # For now, "help" is the only supported $KEY
23 sub get_text {
24         my ($ctx, $key) = @_;
25         my $code = 200;
26
27         $key = 'help' if !defined $key; # this 302s to _/text/help/
28
29         # get the raw text the same way we get mboxrds
30         my $raw = ($key =~ s!/raw\z!!);
31         my $have_tslash = ($key =~ s!/\z!!) if !$raw;
32
33         my $txt = '';
34         my $hdr = [ 'Content-Type', 'text/plain', 'Content-Length', undef ];
35         if (!_default_text($ctx, $key, $hdr, \$txt)) {
36                 $code = 404;
37                 $txt = "404 Not Found ($key)\n";
38         }
39         my $env = $ctx->{env};
40         if ($raw) {
41                 if ($code == 200) {
42                         my $gzf = gzf_maybe($hdr, $env);
43                         $txt = $gzf->translate($txt);
44                         $txt .= $gzf->zflush;
45                 }
46                 $hdr->[3] = bytes::length($txt);
47                 return [ $code, $hdr, [ $txt ] ]
48         }
49
50         # enforce trailing slash for "wget -r" compatibility
51         if (!$have_tslash && $code == 200) {
52                 my $url = $ctx->{-inbox}->base_url($env);
53                 $url .= "_/text/$key/";
54
55                 return [ 302, [ 'Content-Type', 'text/plain',
56                                 'Location', $url ],
57                         [ "Redirecting to $url\n" ] ];
58         }
59
60         # Follow git commit message conventions,
61         # first line is the Subject/title
62         my ($title) = ($txt =~ /\A([^\n]*)/s);
63         $ctx->{-title_html} = ascii_html($title);
64         my $nslash = ($key =~ tr!/!/!);
65         $ctx->{-upfx} = '../../../' . ('../' x $nslash);
66         my $l = PublicInbox::Linkify->new;
67         $l->linkify_1($txt);
68         if ($hl) {
69                 $hl->do_hl_text(\$txt);
70         } else {
71                 $txt = ascii_html($txt);
72         }
73         $txt = '<pre>' . $l->linkify_2($txt) . '</pre>';
74         PublicInbox::WwwStream::html_oneshot($ctx, $code, \$txt);
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 watchheader)) {
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)) {
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_config = $ctx->{www}->{pi_config};
192                 for my $cr_name (@$cr) {
193                         my $urls = $pi_config->{"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 sub _default_text ($$$$) {
218         my ($ctx, $key, $hdr, $txt) = @_;
219         return _colors_help($ctx, $txt) if $key eq 'color';
220         return inbox_config($ctx, $hdr, $txt) if $key eq 'config';
221         return if $key ne 'help'; # TODO more keys?
222
223         my $ibx = $ctx->{-inbox};
224         my $base_url = $ibx->base_url($ctx->{env});
225         $$txt .= "public-inbox help for $base_url\n";
226         $$txt .= <<EOF;
227
228 overview
229 --------
230
231     public-inbox uses Message-ID identifiers in URLs.
232     One may look up messages by substituting Message-IDs
233     (without the leading '<' or trailing '>') into the URL.
234     Forward slash ('/') characters in the Message-IDs
235     need to be escaped as "%2F" (without quotes).
236
237     Thus, it is possible to retrieve any message by its
238     Message-ID by going to:
239
240         $base_url<Message-ID>/
241
242         (without the '<' or '>')
243
244     Message-IDs are described at:
245
246         $WIKI_URL/Message-ID
247
248 EOF
249
250         # n.b. we use the Xapian DB for any regeneratable,
251         # order-of-arrival-independent data.
252         my $srch = $ibx->search;
253         if ($srch) {
254                 $$txt .= <<EOF;
255 search
256 ------
257
258     This public-inbox has search functionality provided by Xapian.
259
260     It supports typical AND, OR, NOT, '+', '-' queries present
261     in other search engines.
262
263     We also support search prefixes to limit the scope of the
264     search to certain fields.
265
266     Prefixes supported in this installation include:
267
268 EOF
269                 _srch_prefix($srch, $txt);
270
271                 $$txt .= <<EOF;
272
273     Most prefixes are probabilistic, meaning they support stemming
274     and wildcards ('*').  Ranges (such as 'd:') and boolean prefixes
275     do not support stemming or wildcards.
276     The upstream Xapian query parser documentation fully explains
277     the query syntax:
278
279         $QP_URL
280
281 EOF
282         } # $srch
283         my $over = $ibx->over;
284         if ($over) {
285                 $$txt .= <<EOF;
286 message threading
287 -----------------
288
289     Message threading is enabled for this public-inbox,
290     additional endpoints for message threads are available:
291
292     * $base_url<Message-ID>/T/#u
293
294       Loads the thread belonging to the given <Message-ID>
295       in flat chronological order.  The "#u" anchor
296       focuses the browser on the given <Message-ID>.
297
298     * $base_url<Message-ID>/t/#u
299
300       Loads the thread belonging to the given <Message-ID>
301       in threaded order with nesting.  For deep threads,
302       this requires a wide display or horizontal scrolling.
303
304     Both of these HTML endpoints are suitable for offline reading
305     using the thread overview at the bottom of each page.
306
307     Users of feed readers may follow a particular thread using:
308
309     * $base_url<Message-ID>/t.atom
310
311       Which loads the thread in Atom Syndication Standard
312       described at Wikipedia and RFC4287:
313
314         $WIKI_URL/Atom_(standard)
315         https://tools.ietf.org/html/rfc4287
316
317       Atom Threading Extensions (RFC4685) is supported:
318
319         https://tools.ietf.org/html/rfc4685
320
321     Finally, the gzipped mbox for a thread is available for
322     downloading and importing into your favorite mail client:
323
324     * $base_url<Message-ID>/t.mbox.gz
325
326     We use the mboxrd variant of the mbox format described
327     at:
328
329         $WIKI_URL/Mbox
330
331 EOF
332         } # $over
333
334         $$txt .= <<EOF;
335 contact
336 -------
337
338     This help text is maintained by public-inbox developers
339     reachable via plain-text email at: meta\@public-inbox.org
340
341 EOF
342         # TODO: support admin contact info in ~/.public-inbox/config
343         1;
344 }
345
346 1;