File Coverage

blib/lib/Mojolicious/Plugin/HostMeta.pm
Criterion Covered Total %
statement 84 106 79.2
branch 38 70 54.2
condition 30 47 63.8
subroutine 9 10 90.0
pod 1 1 100.0
total 162 234 69.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::HostMeta;
2 1     1   1577 use Mojo::Base 'Mojolicious::Plugin';
  1         2  
  1         9  
3 1     1   214 use Mojo::Headers;
  1         11  
  1         8  
4 1     1   32 use Mojo::Util qw/quote/;
  1         2  
  1         1812  
5            
6             our $VERSION = '0.26';
7            
8             our $WK_PATH = '/.well-known/host-meta';
9            
10            
11             # Register plugin
12             sub register {
13 1     1 1 42 my ($plugin, $app, $param) = @_;
14            
15 1   50     5 $param ||= {};
16            
17             # Load parameter from Config file
18 1 50       9 if (my $config_param = $app->config('HostMeta')) {
19 0         0 $param = { %$param, %$config_param };
20             };
21            
22             # Get helpers object
23 1         23 my $helpers = $app->renderer->helpers;
24            
25             # Load Util-Endpoint/Callback if not already loaded
26 1         16 foreach (qw/Endpoint Callback/) {
27 2 50       3393 $app->plugin("Util::$_") unless exists $helpers->{ lc $_ };
28             };
29            
30             # Load XML if not already loaded
31 1 50       1549 unless (exists $helpers->{new_xrd}) {
32 1         6 $app->plugin('XRD');
33             };
34            
35             # Set callbacks on registration
36 1         19138 $app->callback(fetch_hostmeta => $param);
37            
38             # Get seconds to expiration
39 1         183 my $seconds = (60 * 60 * 24 * 10);
40 1 50 33     20 if ($param->{expires} && $param->{expires} =~ /^\d+$/) {
41 0         0 $seconds = delete $param->{expires};
42             };
43            
44             # Create new hostmeta document
45 1         11 my $hostmeta = $app->new_xrd;
46 1         395 $hostmeta->extension( -HostMeta );
47            
48             # Get host information on first request
49             $app->hook(
50             prepare_hostmeta =>
51             sub {
52 1     1   13 my ($c, $hostmeta) = @_;
53 1         4 my $host = $c->req->url->to_abs->host;
54            
55             # Add host-information to host-meta
56 1 50       256 $hostmeta->host( $host ) if $host;
57             }
58 1         1118 );
59            
60             # Establish 'hostmeta' helper
61             $app->helper(
62             hostmeta => sub {
63 11     11   80790 my $c = shift;
64            
65             # Undefined host name
66 11 100       37 shift if !defined $_[0];
67            
68             # Host name is provided
69 11 100 100     66 if (!$_[0] || ref $_[0]) {
70            
71             # Return local hostmeta
72 9         28 return _serve_hostmeta( $c, $hostmeta, @_ );
73             };
74            
75             # Return discovered hostmeta
76 2         9 return _fetch_hostmeta( $c, @_ );
77 1         26 });
78            
79             # Establish /.well-known/host-meta route
80 1         108 my $route = $app->routes->any( $WK_PATH => [format => [qw!json xml jrd xrd!]] );
81            
82             # Define endpoint
83 1         424 $route->endpoint('host-meta');
84            
85             # Set route callback
86             $route->to(
87             format => 'xrd',
88             cb => sub {
89 4     4   90531 my $c = shift;
90            
91             # Seconds given
92 4 50       16 if ($seconds) {
93            
94             # Set cache control
95 4         29 my $headers = $c->res->headers;
96 4         91 $headers->cache_control(
97             "public, max-age=$seconds"
98             );
99            
100             # Set expires element
101 4         44 $hostmeta->expires( time + $seconds );
102            
103             # Set expires header
104 4         21551 $headers->expires( $hostmeta->expires );
105             };
106            
107             # Serve host-meta document
108 4         3487 return $c->helpers->reply->xrd(
109             _serve_hostmeta( $c, $hostmeta )
110             );
111 1         46 });
112             };
113            
114            
115             # Get HostMeta document
116             sub _fetch_hostmeta {
117 2     2   4 my $c = shift;
118 2         8 my $host = lc shift;
119            
120             # Trim tail
121 2   66     22 pop while @_ && !defined $_[-1];
122            
123             # Get headers
124 2         5 my $header = {};
125 2 50 66     15 if ($_[0] && ref $_[0] && ref($_[0]) eq 'HASH') {
      66        
126 0         0 $header = shift;
127             };
128            
129             # Check if security is forced
130 2 50 66     10 my $secure = defined $_[-1] && $_[-1] eq '-secure' ? pop : 0;
131            
132             # Get callback
133 2 100 66     9 my $cb = pop if ref($_[-1]) && ref($_[-1]) eq 'CODE';
134            
135             # Get host information
136 2 50       24 unless ($host =~ s!^\s*(?:http(s?)://)?([^/]+)/*\s*$!$2!) {
137 0         0 return;
138             };
139 2 50       8 $secure = 1 if $1;
140            
141             # Build relations parameter
142 2         17 my $rel;
143 2 50 33     10 $rel = shift if $_[0] && ref($_[0]) eq 'ARRAY';
144            
145             # Helpers proxy
146 2         14 my $h = $c->helpers;
147            
148             # Callback for caching
149 2         66 my ($xrd, $headers) = $h->callback(
150             fetch_hostmeta => $host
151             );
152            
153             # HostMeta document was cached
154 2 50       5058 if ($xrd) {
155            
156             # Filter relations
157 2 50       17 $xrd = $xrd->filter_rel( $rel ) if $rel;
158            
159             # Set headers to default
160 2 100 33     20 $headers ||= Mojo::Headers->new if $cb || wantarray;
      66        
161            
162             # Return cached hostmeta document
163 2 100       15 return $cb->( $xrd, $headers ) if $cb;
164 1 50       4 return ( $xrd, $headers ) if wantarray;
165 1         6 return $xrd;
166             };
167            
168             # Create host-meta path
169 0         0 my $path = '//' . $host . $WK_PATH;
170 0 0       0 $path = 'https:' . $path if $secure;
171            
172            
173             # Non-blocking
174 0 0       0 if ($cb) {
175            
176             return $h->get_xrd(
177             $path => $header => sub {
178 0     0   0 my ($xrd, $headers) = @_;
179 0 0       0 if ($xrd) {
180            
181             # Add hostmeta extension
182 0         0 $xrd->extension(-HostMeta);
183            
184             # Hook for caching
185 0         0 $c->app->plugins->emit_hook(
186             after_fetching_hostmeta => (
187             $c, $host, $xrd, $headers
188             )
189             );
190            
191             # Filter based on relations
192 0 0       0 $xrd = $xrd->filter_rel( $rel ) if $rel;
193            
194             # Send to callback
195 0         0 return $cb->( $xrd, $headers );
196             };
197            
198             # Fail
199 0         0 return $cb->();
200 0         0 });
201             };
202            
203             # Blocking
204 0         0 ($xrd, $headers) = $h->get_xrd( $path => $header );
205            
206             # No host-meta found
207 0 0       0 return unless $xrd;
208            
209             # Add hostmeta extension
210 0         0 $xrd->extension( -HostMeta );
211            
212             # Hook for caching
213 0         0 $c->app->plugins->emit_hook(
214             after_fetching_hostmeta => (
215             $c, $host, $xrd, $headers
216             )
217             );
218            
219             # Filter based on relations
220 0 0       0 $xrd = $xrd->filter_rel( $rel ) if $rel;
221            
222             # Return
223 0 0       0 return ($xrd, $headers) if wantarray;
224 0         0 return $xrd;
225             };
226            
227            
228             # Run hooks for preparation and serving of hostmeta
229             sub _serve_hostmeta {
230 13     13   131 my $c = shift;
231 13         21 my $xrd = shift;
232            
233             # Delete tail
234 13   66     50 pop while @_ && !defined $_[-1];
235            
236             # Ignore security flag
237 13 50 66     51 pop if defined $_[-1] && $_[-1] eq '-secure';
238            
239             # Ignore header information
240 13 50 66     87 shift if $_[0] && ref($_[0]) && ref($_[0]) eq 'HASH';
      66        
241            
242             # Get callback
243 13 100 100     38 my $cb = pop if ref($_[-1]) && ref($_[-1]) eq 'CODE';
244            
245 13         20 my $rel = shift;
246            
247 13         42 my $plugins = $c->app->plugins;
248 13         99 my $phm = 'prepare_hostmeta';
249            
250            
251             # prepare_hostmeta has subscribers
252 13 100       48 if ($plugins->has_subscribers( $phm )) {
253            
254             # Emit hook for subscribers
255 2         17 $plugins->emit_hook( $phm => ( $c, $xrd ));
256            
257             # Unsubscribe all subscribers
258 2         6189 foreach (@{ $plugins->subscribers( $phm ) }) {
  2         10  
259 2         18 $plugins->unsubscribe( $phm => $_ );
260             };
261             };
262            
263             # No further modifications wanted
264 13 100       115 unless ($plugins->has_subscribers('before_serving_hostmeta')) {
265            
266             # Filter relations
267 1 50       8 $xrd = $xrd->filter_rel( $rel ) if $rel;
268            
269             # Return document
270 1 50       3 return $cb->( $xrd ) if $cb;
271 1         15 return $xrd;
272             };
273            
274             # Clone hostmeta reference
275 12         80 $xrd = $c->helpers->new_xrd( $xrd->to_string );
276            
277             # Emit 'before_serving_hostmeta' hook
278 12         11390 $plugins->emit_hook(
279             before_serving_hostmeta => (
280             $c, $xrd
281             ));
282            
283             # Filter relations
284 12 100       158258 $xrd = $xrd->filter_rel( $rel ) if $rel;
285            
286             # Return hostmeta clone
287 12 100       9767 return $cb->( $xrd ) if $cb;
288 9         94 return $xrd;
289             };
290            
291            
292             1;
293            
294            
295             __END__