File Coverage

blib/lib/Mojolicious/Plugin/XRD.pm
Criterion Covered Total %
statement 37 91 40.6
branch 10 66 15.1
condition 9 30 30.0
subroutine 6 8 75.0
pod 1 1 100.0
total 63 196 32.1


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::XRD;
2 2     2   1988 use Mojo::Base 'Mojolicious::Plugin';
  2         4  
  2         17  
3 2     2   453 use Mojo::Util qw/quote deprecated/;
  2         12  
  2         3319  
4            
5             our $VERSION = '0.22';
6            
7             # Todo: Support
8             # $self->reply->xrd( $xrd => {
9             # resource => 'acct:akron@sojolicious.example',
10             # expires => (30 * 24 * 60 * 60),
11             # cache => ...,
12             # chi => ...
13             # });
14             #
15             # - Add Acceptance for XRD and JRD and JSON as a header
16            
17             # UserAgent name
18             my $UA_NAME = __PACKAGE__ . ' v' . $VERSION;
19            
20             # UserAgent maximum redirects
21             my $UA_MAX_REDIRECTS = 10;
22            
23             # UserAgent connect timeout
24             my $UA_CONNECT_TIMEOUT = 7;
25            
26            
27             # Register Plugin
28             sub register {
29 2     2 1 80 my ($plugin, $mojo) = @_;
30            
31             # Add types
32 2         18 for ($mojo->types) {
33 2         59 $_->type(jrd => 'application/jrd+json');
34 2         123 $_->type(xrd => 'application/xrd+xml');
35             };
36            
37             my $reply_xrd = sub {
38 14     14   78536 my ($c, $xrd, $res) = @_;
39            
40             # Define xrd or jrd
41 14 50       57 unless ($c->stash('format')) {
42 14         213 $c->stash('format' => scalar $c->param('format'));
43             };
44            
45             # Add CORS header
46 14         3109 $c->res->headers->header(
47             'Access-Control-Allow-Origin' => '*'
48             );
49            
50 14         634 my $status = 200;
51            
52             # Not found
53 14 100 66     105 if (!defined $xrd || !ref($xrd)) {
    100          
54 6         15 $status = 404;
55 6         22 $xrd = $c->helpers->new_xrd;
56 6 50       1616 $xrd->subject("$res") if $res;
57             }
58            
59             # rel parameter
60             elsif ($c->param('rel')) {
61            
62             # Clone and filter relations
63 6         384 $xrd = $xrd->filter_rel( $c->every_param('rel') );
64             };
65            
66 14 100       56112 my $head_data = $c->req->method eq 'HEAD' ? '' : undef;
67            
68             # content negotiation
69             return $c->respond_to(
70            
71             # JSON request
72 2   66     1225 json => sub { $c->render(
73             status => $status,
74             data => $head_data // $xrd->to_json,
75             format => 'json'
76             )},
77            
78             # JRD request
79 2   66     1145 jrd => sub { $c->render(
80             status => $status,
81             data => $head_data // $xrd->to_json,
82             format => 'jrd'
83             )},
84            
85             # XML default
86 10   66     5590 any => sub { $c->render(
87             status => $status,
88             data => $head_data // $xrd->to_pretty_xml,
89             format => 'xrd'
90             )}
91 14         437 );
92 2         36 };
93            
94             # Add DEPRECATED 'render_xrd' helper
95             $mojo->helper(
96             render_xrd => sub {
97 8     8   294631 deprecated 'render_xrd is deprecated in favor of reply->xrd';
98 8         2320 $reply_xrd->(@_)
99             }
100 2         17 );
101            
102             # Add 'reply->xrd' helper
103 2         309 $mojo->helper( 'reply.xrd' => $reply_xrd);
104            
105             # Add 'get_xrd' helper
106 2         1060 $mojo->helper( get_xrd => \&_get_xrd );
107            
108             # Add 'new_xrd' helper
109 2 50       186 unless (exists $mojo->renderer->helpers->{'new_xrd'}) {
110 2         30 $mojo->plugin('XML::Loy' => {
111             new_xrd => [-XRD]
112             });
113             };
114             };
115            
116             # Get XRD document
117             sub _get_xrd {
118 1     1   16633 my $c = shift;
119 1         13 my $resource = Mojo::URL->new( shift );
120            
121             # Trim tail
122 1   33     350 pop while @_ && !defined $_[-1];
123            
124             # No valid resource
125 1 50       6 return unless $resource->host;
126            
127 0           my $header = {};
128 0 0 0       if ($_[0] && ref $_[0] && ref $_[0] eq 'HASH') {
      0        
129 0           $header = shift;
130             };
131            
132             # Check if security is forced
133 0           my $prot = $resource->protocol;
134 0           my $secure;
135 0 0 0       $secure = 1 if $prot && $prot eq 'https';
136            
137             # Get callback
138 0 0 0       my $cb = pop if ref($_[-1]) && ref($_[-1]) eq 'CODE';
139            
140             # Build relations parameter
141 0           my $rel;
142 0 0 0       $rel = shift if $_[0] && ref $_[0] eq 'ARRAY';
143            
144             # Get secure user agent
145 0 0         my $ua = Mojo::UserAgent->new(
146             name => $UA_NAME,
147             max_redirects => ($secure ? 0 : $UA_MAX_REDIRECTS),
148             connect_timeout => $UA_CONNECT_TIMEOUT
149             );
150            
151 0           my $xrd;
152            
153             # Set to secure, if not defined
154 0 0         $resource->scheme('https') unless $resource->scheme;
155            
156             # Get helpers proxy object
157 0           my $h = $c->helpers;
158            
159             # Is blocking
160 0 0         unless ($cb) {
161            
162             # Fetch Host-Meta XRD - first try ssl
163 0           my $tx = $ua->get($resource => $header);
164 0           my $xrd_res;
165            
166             # Transaction was not successful
167 0 0         return unless $xrd_res = $tx->success;
168            
169 0 0         unless ($xrd_res->is_success) {
170            
171             # Only support secure retrieval
172 0 0         return if $secure;
173            
174             # Was already insecure
175 0 0         return if $resource->protocol eq 'http';
176            
177             # Make request insecure
178 0           $resource->scheme('http');
179            
180             # Update insecure max_redirects;
181 0           $ua->max_redirects($UA_MAX_REDIRECTS);
182            
183             # Then try insecure
184 0           $tx = $ua->get($resource => $header);
185            
186             # Transaction was not successful
187 0 0         return unless $xrd_res = $tx->success;
188            
189             # Retrieval was successful
190 0 0         return unless $xrd_res->is_success;
191             };
192            
193             # Parse xrd document
194 0 0         $xrd = $h->new_xrd($xrd_res->body) or return;
195            
196             # Filter relations
197 0 0         $xrd = $xrd->filter_rel($rel) if $rel;
198            
199             # Return xrd
200 0 0         return ($xrd, $xrd_res->headers->clone) if wantarray;
201 0           return $xrd;
202             };
203            
204             # Non-blocking
205             # Create delay for https with or without redirection
206             my $delay = Mojo::IOLoop->delay(
207             sub {
208 0     0     my $delay = shift;
209            
210             # Get with https - possibly without redirects
211 0           $ua->get($resource => $header => $delay->begin);
212             },
213             sub {
214 0     0     my ($delay, $tx) = @_;
215            
216             # Get response
217 0 0         if (my $xrd_res = $tx->success) {
218            
219             # Fine
220 0 0         if ($xrd_res->is_success) {
221            
222             # Parse xrd document
223 0 0         $xrd = $h->new_xrd($xrd_res->body) or return $cb->(undef);
224            
225             # Filter relations
226 0 0         $xrd = $xrd->filter_rel($rel) if $rel;
227            
228             # Send to callback
229 0           return $cb->($xrd, $xrd_res->headers->clone);
230             };
231            
232             # Only support secure retrieval
233 0 0         return $cb->(undef) if $secure;
234             }
235            
236             # Fail
237             else {
238 0           return $cb->(undef);
239             };
240            
241             # Was already insecure
242 0 0         return if $resource->protocol eq 'http';
243            
244             # Try http with redirects
245             $delay->steps(
246             sub {
247 0           my $delay = shift;
248            
249 0           $resource->scheme('http');
250            
251             # Get with http and redirects
252 0           $ua->max_redirects($UA_MAX_REDIRECTS);
253 0           $ua->get($resource => $header => $delay->begin );
254             },
255             sub {
256 0           my $delay = shift;
257            
258             # Transaction was successful
259 0 0         if (my $xrd_res = pop->success) {
260            
261             # Parse xrd document
262 0 0         $xrd = $h->new_xrd($xrd_res->body) or return $cb->(undef);
263            
264             # Filter relations
265 0 0         $xrd = $xrd->filter_rel($rel) if $rel;
266            
267             # Send to callback
268 0           return $cb->($xrd, $xrd_res->headers->clone);
269             };
270            
271             # Fail
272 0           return $cb->(undef);
273 0           });
274             }
275 0           );
276            
277             # Wait if IOLoop is not running
278 0 0         $delay->wait unless Mojo::IOLoop->is_running;
279 0           return;
280             };
281            
282            
283             1;
284            
285            
286             __END__