File Coverage

blib/lib/Mojolicious/Plugin/ClientIP.pm
Criterion Covered Total %
statement 38 38 100.0
branch 14 16 87.5
condition 4 5 80.0
subroutine 6 6 100.0
pod 1 1 100.0
total 63 66 95.4


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::ClientIP;
2              
3 4     4   3263 use Mojo::Base 'Mojolicious::Plugin';
  4         185104  
  4         32  
4              
5             our $VERSION = '0.02';
6              
7             has 'ignore';
8              
9             sub register {
10 3     3 1 121 my $self = shift;
11 3         9 my ($app, $conf) = @_;
12              
13             $self->ignore([
14 3 100       21 @{$conf->{private} || [qw(127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16) ]},
15 3 100       6 @{$conf->{ignore} || []}
  3         22  
16             ]);
17              
18             $app->helper(client_ip => sub {
19 20     20   190609 my ($c) = @_;
20              
21 20         45 state $key = '__plugin_clientip_ip';
22              
23 20 50       59 return $c->stash($key) if $c->stash($key);
24              
25 20   100     253 my $xff = $c->req->headers->header('X-Forwarded-For') // '';
26 20         653 my @candidates = reverse grep { $_ } split /,\s*/, $xff;
  32         103  
27 20   66     76 my $ip = $self->_find(\@candidates) // $c->tx->remote_address;
28 20         277 $c->stash($key => $ip);
29              
30 20         428 return $ip;
31 3         61 });
32             }
33              
34             sub _find {
35 20     20   37 my $self = shift;
36 20         54 my ($candidates) = @_;
37              
38 20         36 state $octet = '(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})';
39 20         279 state $ip4 = qr/\A$octet\.$octet\.$octet\.$octet\z/;
40              
41 20         60 for (@$candidates) {
42 26 50       237 next unless /$ip4/;
43 26 100       83 next if _match($_, $self->ignore);
44 10         35 return $_;
45             }
46              
47 10         93 return;
48             }
49              
50             sub _match {
51 26     26   156 my ($ip, $ips) = @_;
52              
53 26         52 my $ip_bit = _to_bit($ip);
54              
55 26         68 for (@$ips) {
56 87 100       208 return 1 if $ip eq $_;
57              
58 82 100       406 if (my ($net, $prefix) = m{^([\d\.]+)/(\d+)$}) {
59 76         186 my $match_ip_bit = _to_bit($1);
60 76 100       336 return 1 if substr($ip_bit, 0, $2) eq substr($match_ip_bit, 0, $2);
61             }
62             }
63              
64 10         29 return;
65             }
66              
67             sub _to_bit {
68 102     102   230 my ($ip) = @_;
69              
70 102         261 join '', map { unpack('B8', pack('C', $_)) } split /\./, $ip;
  408         1136  
71             }
72              
73             1;
74             __END__