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>
4 # used for displaying help texts and other non-mail content
5 package PublicInbox::WwwText;
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';
18 require PublicInbox::HlMod;
19 PublicInbox::HlMod->new
22 # /$INBOX/_/text/$KEY/ # KEY may contain slashes
23 # For now, "help" is the only supported $KEY
28 $key = 'help' if !defined $key; # this 302s to _/text/help/
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;
35 my $hdr = [ 'Content-Type', 'text/plain', 'Content-Length', undef ];
36 if (!_default_text($ctx, $key, $hdr, \$txt)) {
38 $txt = "404 Not Found ($key)\n";
40 my $env = $ctx->{env};
43 if (my $gzf = $code == 200 ? gzf_maybe($hdr, $env) : undef) {
44 my $zbuf = $gzf->translate($txt);
46 $body = [ $zbuf .= $gzf->translate(undef) ];
50 $hdr->[3] = bytes::length($body->[0]);
51 return [ $code, $hdr, $body ]
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/";
59 return [ 302, [ 'Content-Type', 'text/plain',
61 [ "Redirecting to $url\n" ] ];
64 # Follow git commit message conventions,
65 # first line is the Subject/title
66 my ($title) = ($txt =~ /\A([^\n]*)/s);
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);
76 return unless $nr == 1;
77 my $l = PublicInbox::Linkify->new;
78 my $txt = delete $ctx->{txt};
81 $hl->do_hl_text($txt);
83 $$txt = ascii_html($$txt);
85 '<pre>' . $l->linkify_2($$txt) . '</pre>';
88 sub _srch_prefix ($$) {
89 my ($srch, $txt) = @_;
92 my $help = $srch->help;
94 for ($i = 0; $i < @$help; $i += 2) {
95 my $pfx = $help->[$i];
97 $pad = $n if $n > $pad;
99 $htxt .= $help->[$i + 1];
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;
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";
120 public-inbox provides a stable set of CSS classes for users to
121 customize colors for highlighting diffs and code.
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:
127 ${base_url}userContent.css
133 $$txt .= PublicInbox::UserContent::sample($ibx, $env) . "```\n";
136 # git-config section names are quoted in the config file, so escape them
139 $name =~ s/\\/\\\\/g;
144 sub URI_PATH () { '^A-Za-z0-9\-\._~/' }
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';
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"]
159 ; note: public-inbox before v1.2.0 used "mainrepo"
160 ; instead of "inboxdir", both remain supported after 1.2
162 url = https://example.com/$name/
163 url = http://example.onion/$name/
165 for my $k (qw(address listid infourl watchheader)) {
166 defined(my $v = $ibx->{$k}) or next;
167 $$txt .= "\t$k = $_\n" for @$v;
169 if (my $altid = $ibx->{altid}) {
170 my $base_url = $ibx->base_url($ctx->{env});
171 my $altid_map = $ibx->altid_map;
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:
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";
185 for my $k (qw(filter newsgroup obfuscate replyto)) {
186 defined(my $v = $ibx->{$k}) or next;
187 $$txt .= "\t$k = $v\n";
189 $$txt .= "\tnntpmirror = $_\n" for (@{$ibx->nntp_url});
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;
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);
202 $$txt .= qq([coderepo "$cr_name"]\n);
203 if ($urls && scalar(@$urls)) {
205 $$txt .= join(" ||\n\t;\t", map {;
207 if ($path !~ m![a-z0-9_/\.\-]!i) {
208 $cpath = dq_escape($cpath);
210 qq(git clone $_ "$cpath");
214 $$txt .= "\tdir = $path\n";
215 $$txt .= "\tcgiturl = https://example.com/";
216 $$txt .= uri_escape_utf8($cr_name, URI_PATH)."\n";
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?
228 my $ibx = $ctx->{-inbox};
229 my $base_url = $ibx->base_url($ctx->{env});
230 $$txt .= "public-inbox help for $base_url\n";
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).
242 Thus, it is possible to retrieve any message by its
243 Message-ID by going to:
245 $base_url<Message-ID>/
247 (without the '<' or '>')
249 Message-IDs are described at:
255 # n.b. we use the Xapian DB for any regeneratable,
256 # order-of-arrival-independent data.
257 my $srch = $ibx->search;
263 This public-inbox has search functionality provided by Xapian.
265 It supports typical AND, OR, NOT, '+', '-' queries present
266 in other search engines.
268 We also support search prefixes to limit the scope of the
269 search to certain fields.
271 Prefixes supported in this installation include:
274 _srch_prefix($srch, $txt);
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
288 my $over = $ibx->over;
294 Message threading is enabled for this public-inbox,
295 additional endpoints for message threads are available:
297 * $base_url<Message-ID>/T/#u
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>.
303 * $base_url<Message-ID>/t/#u
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.
309 Both of these HTML endpoints are suitable for offline reading
310 using the thread overview at the bottom of each page.
312 Users of feed readers may follow a particular thread using:
314 * $base_url<Message-ID>/t.atom
316 Which loads the thread in Atom Syndication Standard
317 described at Wikipedia and RFC4287:
319 $WIKI_URL/Atom_(standard)
320 https://tools.ietf.org/html/rfc4287
322 Atom Threading Extensions (RFC4685) is supported:
324 https://tools.ietf.org/html/rfc4685
326 Finally, the gzipped mbox for a thread is available for
327 downloading and importing into your favorite mail client:
329 * $base_url<Message-ID>/t.mbox.gz
331 We use the mboxrd variant of the mbox format described
343 This help text is maintained by public-inbox developers
344 reachable via plain-text email at: meta\@public-inbox.org
347 # TODO: support admin contact info in ~/.public-inbox/config