File Coverage

blib/lib/Mojolicious/Plugin/ClientIP.pm
Criterion Covered Total %
statement 39 39 100.0
branch 12 14 85.7
condition 6 7 85.7
subroutine 6 6 100.0
pod 1 1 100.0
total 64 67 95.5


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