File Coverage

blib/lib/HTTP/CSPHeader.pm
Criterion Covered Total %
statement 63 65 96.9
branch 12 20 60.0
condition n/a
subroutine 14 14 100.0
pod 2 2 100.0
total 91 101 90.1


line stmt bran cond sub pod time code
1             package HTTP::CSPHeader;
2              
3             # ABSTRACT: manage dynamic content security policy headers
4              
5 1     1   128262 use v5.14;
  1         13  
6              
7 1     1   613 use Moo;
  1         12016  
  1         7  
8              
9 1     1   1590 use Fcntl qw/ O_NONBLOCK O_RDONLY /;
  1         2  
  1         63  
10 1     1   9 use List::Util 1.29 qw/ pairmap pairs /;
  1         29  
  1         114  
11 1     1   590 use Math::Random::ISAAC;
  1         1285  
  1         39  
12 1     1   716 use Types::Standard qw/ ArrayRef is_ArrayRef Bool HashRef Str /;
  1         78049  
  1         12  
13              
14             # RECOMMEND PREREQ: Math::Random::ISAAC::XS
15             # RECOMMEND PREREQ: Type::Tiny::XS
16              
17 1     1   1854 use namespace::autoclean;
  1         14210  
  1         4  
18              
19             our $VERSION = 'v0.2.1';
20              
21              
22             has _base_policy => (
23             is => 'ro',
24             isa => HashRef,
25             required => 1,
26             init_arg => 'policy',
27             );
28              
29             has policy => (
30             is => 'lazy',
31             isa => HashRef,
32             clearer => '_clear_policy',
33             init_arg => undef,
34             );
35              
36             sub _build_policy {
37 4     4   36 my ($self) = @_;
38 4         7 my %policy = %{ $self->_base_policy };
  4         61  
39 4 100       12 if ( my @dirs = @{ $self->nonces_for } ) {
  4         92  
40 2         62 my $nonce = "'nonce-" . $self->nonce . "'";
41 2         20 for my $dir (@dirs) {
42 4 50       11 if ( defined $policy{$dir} ) {
43 4         14 $policy{$dir} .= " " . $nonce;
44             }
45             else {
46 0         0 $policy{$dir} = $nonce;
47             }
48             }
49 2         33 $self->_changed(1);
50             }
51 4         162 return \%policy;
52             }
53              
54             has _changed => (
55             is => 'rw',
56             isa => Bool,
57             lazy => 1,
58             default => 0,
59             init_arg => undef,
60             );
61              
62              
63             has nonces_for => (
64             is => 'lazy',
65             isa => ArrayRef [Str],
66 1     1   25 builder => sub { return [] },
67             coerce => sub { my $val = is_ArrayRef( $_[0] ) ? $_[0] : [ $_[0] ] },
68             );
69              
70              
71             has nonce => (
72             is => 'lazy',
73             isa => Str,
74             clearer => '_clear_nonce',
75             unit_arg => undef,
76             );
77              
78             sub _build_nonce {
79 2     2   531 my ($self) = @_;
80              
81 2         9 state $rng = do {
82 1 50       48 sysopen( my $fh, '/dev/urandom', O_NONBLOCK | O_RDONLY ) or die $!;
83 1 50       16 sysread( $fh, my $data, 16 ) or die $!;
84 1         18 close $fh;
85              
86 1         17 Math::Random::ISAAC->new( unpack( "C*", $data ) );
87             };
88              
89 2         58 return sprintf( '%x', $rng->irand ^ $$ );
90             }
91              
92              
93             has header => (
94             is => 'lazy',
95             isa => Str,
96             clearer => '_clear_header',
97             init_arg => undef,
98             );
99              
100             sub _build_header {
101 8     8   6702 my ($self) = @_;
102 8         125 my $policy = $self->policy;
103 8     27   163 return join( "; ", pairmap { $a . " " . $b } %$policy );
  27         198  
104             }
105              
106              
107             sub reset {
108 2     2 1 2447 my ($self) = @_;
109 2 50       48 return unless $self->_changed;
110 2         49 $self->_clear_nonce;
111 2         41 $self->_clear_policy;
112 2         41 $self->_clear_header;
113 2         38 $self->_changed(0);
114             }
115              
116              
117             sub amend {
118 4     4 1 5360 my ($self, @args) = @_;
119 4         100 my $policy = $self->policy;
120              
121 4 50       36 if (@args) {
122              
123 4         29 for my $pol ( pairs @args ) {
124              
125 4         16 my ( $dir, $val ) = @$pol;
126              
127 4 100       21 if ( $dir =~ s/^\+// ) { # append to directive
128 1 50       4 if ( exists $policy->{$dir} ) {
    0          
129 1         6 $policy->{$dir} .= " " . $val;
130             }
131             elsif ( defined $val ) {
132 0         0 $policy->{$dir} = $val;
133             }
134              
135             }
136             else {
137 3 100       8 if ( defined $val ) {
138 2         16 $policy->{$dir} = $val;
139             }
140             else {
141 1         3 delete $policy->{$dir};
142             }
143             }
144             }
145              
146 4         78 $self->_clear_header;
147 4         80 $self->_changed(1);
148             }
149              
150 4         110 return $policy;
151             }
152              
153             1;
154              
155             __END__