File Coverage

blib/lib/Trickster/Middleware/CORS.pm
Criterion Covered Total %
statement 49 55 89.0
branch 17 34 50.0
condition 3 8 37.5
subroutine 11 11 100.0
pod 2 2 100.0
total 82 110 74.5


line stmt bran cond sub pod time code
1             # lib/Trickster/Middleware/CORS.pm
2             package Trickster::Middleware::CORS;
3              
4 1     1   1709 use strict;
  1         2  
  1         29  
5 1     1   3 use warnings;
  1         1  
  1         33  
6 1     1   8 use v5.14;
  1         2  
7              
8 1     1   3 use parent 'Plack::Middleware';
  1         1  
  1         5  
9 1         3 use Plack::Util::Accessor qw(
10             origins methods headers credentials max_age expose_headers
11 1     1   4117 );
  1         2  
12              
13             sub prepare_app {
14 1     1 1 53 my $self = shift;
15              
16 1 50       3 $self->origins(['*']) unless $self->origins;
17 1 50       6 $self->methods(['GET','POST','PUT','PATCH','DELETE','OPTIONS'])
18             unless $self->methods;
19 1 50       5 $self->headers(['Content-Type','Authorization','X-Requested-With'])
20             unless $self->headers;
21 1 50       8 $self->max_age(86400) unless defined $self->max_age;
22 1 50       7 $self->credentials(0) unless defined $self->credentials;
23             }
24              
25             sub call {
26 2     2 1 1243 my ($self, $env) = @_;
27              
28             # Always handle preflight
29 2 100       6 if ($env->{REQUEST_METHOD} eq 'OPTIONS') {
30 1         2 return $self->_preflight_response($env);
31             }
32              
33 1         4 my $res = $self->app->($env);
34              
35             # Add CORS headers to normal responses
36             return $self->response_cb($res, sub {
37 1     1   13 my $r = shift;
38 1 50 33     4 return unless ref($r) eq 'ARRAY' && ref($r->[1]) eq 'ARRAY';
39 1         2 $self->_add_cors_headers($r->[1], $env);
40 1         90 });
41             }
42              
43             sub _preflight_response {
44 1     1   2 my ($self, $env) = @_;
45 1         2 my @headers = ('Content-Length' => 0);
46 1         3 $self->_add_cors_headers(\@headers, $env, 1); # 1 = preflight
47 1         7 return [200, \@headers, []];
48             }
49              
50             sub _add_cors_headers {
51 2     2   4 my ($self, $headers, $env, $is_preflight) = @_;
52 2   50     11 my $origin = $env->{HTTP_ORIGIN} || '';
53 2 50       3 my $allowed = $self->_origin_allowed($origin) or return;
54              
55 2         3 push @$headers,
56             'Access-Control-Allow-Origin' => $allowed;
57              
58 2 50       4 push @$headers, 'Access-Control-Allow-Credentials' => 'true'
59             if $self->credentials;
60              
61 2 100       7 if ($is_preflight) {
62             push @$headers,
63 1         2 'Access-Control-Allow-Methods' => join(', ', @{$self->methods}),
64 1         2 'Access-Control-Allow-Headers' => join(', ', @{$self->headers}),
  1         5  
65             'Access-Control-Max-Age' => $self->max_age;
66             }
67              
68 2 50 33     8 if ($self->expose_headers && @{$self->expose_headers}) {
  0         0  
69             push @$headers,
70 0         0 'Access-Control-Expose-Headers' => join(', ', @{$self->expose_headers});
  0         0  
71             }
72             }
73              
74             sub _origin_allowed {
75 2     2   4 my ($self, $origin) = @_;
76 2 50       3 return '' unless $origin;
77              
78 2         2 for my $allowed (@{$self->origins}) {
  2         5  
79 2 50       13 if ($allowed eq '*') {
80 0 0       0 return $origin eq 'null' ? '*' : $origin;
81             }
82 2 50       4 if (ref($allowed) eq 'Regexp') {
83 0 0       0 return $origin if $origin =~ $allowed;
84             }
85 2 50       11 return $origin if $origin eq $allowed;
86             }
87 0           return '';
88             }
89              
90             1;