]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/ExtMsg.pm
remove direct CGI.pm support
[public-inbox.git] / lib / PublicInbox / ExtMsg.pm
1 # Copyright (C) 2015 all contributors <meta@public-inbox.org>
2 # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
3 #
4 # Used by the web interface to link to messages outside of the our
5 # public-inboxes.  Mail threads may cross projects/threads; so
6 # we should ensure users can find more easily find them on other
7 # sites.
8 package PublicInbox::ExtMsg;
9 use strict;
10 use warnings;
11 use URI::Escape qw(uri_escape_utf8);
12 use PublicInbox::Hval;
13 use PublicInbox::MID qw/mid2path/;
14
15 # TODO: user-configurable
16 our @EXT_URL = (
17         'http://mid.gmane.org/%s',
18         'https://lists.debian.org/msgid-search/%s',
19         # leading "//" denotes protocol-relative (http:// or https://)
20         '//mid.mail-archive.com/%s',
21         '//marc.info/?i=%s',
22 );
23
24 sub ext_msg {
25         my ($ctx) = @_;
26         my $pi_config = $ctx->{pi_config};
27         my $listname = $ctx->{listname};
28         my $mid = $ctx->{mid};
29
30         eval { require PublicInbox::Search };
31         my $have_xap = $@ ? 0 : 1;
32         my (@nox, @pfx);
33
34         foreach my $k (keys %$pi_config) {
35                 $k =~ /\Apublicinbox\.([A-Z0-9a-z-]+)\.url\z/ or next;
36                 my $list = $1;
37                 next if $list eq $listname;
38
39                 my $git_dir = $pi_config->{"publicinbox.$list.mainrepo"};
40                 defined $git_dir or next;
41
42                 my $url = $pi_config->{"publicinbox.$list.url"};
43                 defined $url or next;
44
45                 $url =~ s!/+\z!!;
46
47                 # try to find the URL with Xapian to avoid forking
48                 if ($have_xap) {
49                         my $s;
50                         my $doc_id = eval {
51                                 $s = PublicInbox::Search->new($git_dir);
52                                 $s->find_unique_doc_id('mid', $mid);
53                         };
54                         if ($@) {
55                                 # xapian not configured for this repo
56                         } else {
57                                 # maybe we found it!
58                                 return r302($url, $mid) if (defined $doc_id);
59
60                                 # no point in trying the fork fallback if we
61                                 # know Xapian is up-to-date but missing the
62                                 # message in the current repo
63                                 push @pfx, { git_dir => $git_dir, url => $url };
64                                 next;
65                         }
66                 }
67
68                 # queue up for forking after we've tried Xapian on all of them
69                 push @nox, { git_dir => $git_dir, url => $url };
70         }
71
72         # Xapian not installed or configured for some repos
73         my $path = "HEAD:" . mid2path($mid);
74
75         foreach my $n (@nox) {
76                 # TODO: reuse existing PublicInbox::Git objects to save forks
77                 my $git = PublicInbox::Git->new($n->{git_dir});
78                 my (undef, $type, undef) = $git->check($path);
79                 return r302($n->{url}, $mid) if ($type && $type eq 'blob');
80         }
81
82         # fall back to partial MID matching
83         my $n_partial = 0;
84         my @partial;
85
86         eval { require PublicInbox::Msgmap };
87         my $have_mm = $@ ? 0 : 1;
88         my $cgi = $ctx->{cgi};
89         my $base_url = $cgi->base->as_string;
90         if ($have_mm) {
91                 my $tmp_mid = $mid;
92                 my $url;
93 again:
94                 $url = $base_url . $listname;
95                 unshift @pfx, { git_dir => $ctx->{git_dir}, url => $url };
96                 foreach my $pfx (@pfx) {
97                         my $git_dir = delete $pfx->{git_dir} or next;
98                         my $mm = eval { PublicInbox::Msgmap->new($git_dir) };
99
100                         $mm or next;
101                         if (my $res = $mm->mid_prefixes($tmp_mid)) {
102                                 $n_partial += scalar(@$res);
103                                 $pfx->{res} = $res;
104                                 push @partial, $pfx;
105                         }
106                 }
107                 # fixup common errors:
108                 if (!$n_partial && $tmp_mid =~ s,/[tTf],,) {
109                         goto again;
110                 }
111         }
112
113         my $code = 404;
114         my $h = PublicInbox::Hval->new_msgid($mid, 1);
115         my $href = $h->as_href;
116         my $html = $h->as_html;
117         my $title = "Message-ID &lt;$html&gt; not found";
118         my $s = "<html><head><title>$title</title>" .
119                 "</head><body><pre><b>$title</b>\n";
120
121         if ($n_partial) {
122                 $code = 300;
123                 my $es = $n_partial == 1 ? '' : 'es';
124                 $s.= "\n$n_partial partial match$es found:\n\n";
125                 foreach my $pfx (@partial) {
126                         my $u = $pfx->{url};
127                         foreach my $m (@{$pfx->{res}}) {
128                                 my $p = PublicInbox::Hval->new_msgid($m);
129                                 my $r = $p->as_href;
130                                 my $t = $p->as_html;
131                                 $s .= qq{<a\nhref="$u/$r/">$u/$t/</a>\n};
132                         }
133                 }
134         }
135
136         # Fall back to external repos if configured
137         if (@EXT_URL && index($mid, '@') >= 0) {
138                 $code = 300;
139                 $s .= "\nPerhaps try an external site:\n\n";
140                 my $scheme = $cgi->scheme;
141                 foreach my $u (@EXT_URL) {
142                         $u = "$scheme:$u" if $u =~ m!\A//!;
143                         my $r = sprintf($u, $href);
144                         my $t = sprintf($u, $html);
145                         $s .= qq{<a\nhref="$r">$t</a>\n};
146                 }
147         }
148         $s .= '</pre></body></html>';
149
150         [300, ['Content-Type'=>'text/html; charset=UTF-8'], [$s]];
151 }
152
153 # Redirect to another public-inbox which is mapped by $pi_config
154 sub r302 {
155         my ($url, $mid) = @_;
156         $url .= '/' . uri_escape_utf8($mid) . '/';
157         [ 302,
158           [ 'Location' => $url, 'Content-Type' => 'text/plain' ],
159           [ "Redirecting to\n$url\n" ] ]
160 }
161
162 1;