File Coverage

lib/CGI/Application/Plugin/RemoteIP.pm
Criterion Covered Total %
statement 18 41 43.9
branch 0 10 0.0
condition 0 4 0.0
subroutine 4 7 57.1
pod 3 3 100.0
total 25 65 38.4


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3            
4             CGI::Application::Plugin::RemoteIP - Unified Remote IP handling
5            
6             =head1 SYNOPSIS
7            
8             use CGI::Application::Plugin::RemoteIP;
9            
10            
11             # Your application
12             sub run_mode {
13             my ($self) = ( @_);
14            
15             my $ip = $self->remote_ip();
16             }
17            
18             =cut
19              
20              
21             =head1 DESCRIPTION
22            
23             This module simplifies the detection of the remote IP address of your visitors.
24            
25             =cut
26              
27             =head1 MOTIVATION
28            
29             This module allows you to remove scattered references in your code, such as:
30            
31             =for example begin
32            
33             # Get IP
34             my $ip = $ENV{'REMOTE_ADDR'};
35            
36             # Remove faux IPv6-prefix.
37             $ip =~ s/^::ffff://g;
38             ..
39            
40             =for example end
41            
42             Instead your code and use the simpler expression:
43            
44             =for example begin
45            
46             my $ip = $self->remote_ip();
47            
48             =for example end
49            
50             =cut
51              
52             =head1 SECURITY
53            
54             The code in this module will successfully understand the C<X-Forwarded-For>
55             header and B<trust> it.
56            
57             Unless you have setup any proxy, or webserver, to scrub this header this means
58             the value that is used is at risk of being spoofed, bogus, or otherwise
59             malicious.
60            
61             =cut
62              
63 1     1   16633 use strict;
  1         2  
  1         35  
64 1     1   3 use warnings;
  1         1  
  1         99  
65              
66             package CGI::Application::Plugin::RemoteIP;
67              
68              
69             our $VERSION = '0.1';
70              
71              
72             =head1 METHODS
73            
74            
75             =head2 import
76            
77             Add our three public-methods into the caller's namespace:
78            
79             =over 8
80            
81             =item remote_ip
82            
83             The remote IP of the client.
84            
85             =item is_ipv4
86            
87             A method to return 1 if the visitor is using IPv4 and 0 otherwise.
88            
89             =item is_ipv6
90            
91             A method to return 1 if the visitor is using IPv6 and 0 otherwise.
92            
93             =back
94            
95             =cut
96              
97             sub import
98             {
99 1     1   8     my $pkg = shift;
100 1         2     my $callpkg = caller;
101              
102                 {
103             ## no critic
104 1     1   6         no strict qw(refs);
  1         5  
  1         389  
  1         1  
105             ## use critic
106 1         1         *{ $callpkg . '::remote_ip' } = \&remote_ip;
  1         4  
107 1         2         *{ $callpkg . '::is_ipv6' } = \&is_ipv6;
  1         3  
108 1         1         *{ $callpkg . '::is_ipv4' } = \&is_ipv4;
  1         10  
109                 }
110             }
111              
112              
113             =head2 remote_ip
114            
115             Return the remote IP of the visitor, whether via the C<X-Forwarded-For> header
116             or via the standard CGI environmental variable C<REMOTE_ADDR>.
117            
118             =cut
119              
120             sub remote_ip
121             {
122 0     0 1       my $cgi_app = shift;
123              
124             # X-Forwarded-For header is the first thing we look for.
125 0   0           my $forwarded = $ENV{ 'HTTP_X_FORWARDED_FOR' } || "";
126 0 0             if ( length $forwarded )
127                 {
128              
129             # Split in case there are multiple values
130 0                   my @vals = split( /[ ,]/, $forwarded );
131              
132 0 0                 if (@vals)
133                     {
134              
135             # Get the first/trusted value.
136 0                       my $ip = $vals[0];
137              
138             # drop IPv6 prefix
139 0                       $ip =~ s/^::ffff://gi;
140              
141             # Drop any optional port
142 0                       $ip =~ s/:([0-9]+)$//g;
143              
144 0                       return $ip;
145                     }
146                 }
147              
148             # This should always work.
149 0   0           my $ip = $ENV{ 'REMOTE_ADDR' } || "";
150              
151             # drop IPv6 prefix
152 0               $ip =~ s/^::ffff://gi;
153              
154             # Drop any optional port
155 0               $ip =~ s/:([0-9]+)$//g;
156              
157 0               return ($ip);
158              
159             }
160              
161              
162             =head2 is_ipv4
163            
164             Determine whether the remote IP address is IPv4.
165            
166             =cut
167              
168             sub is_ipv4
169             {
170              
171             # Get the IP
172 0     0 1       my $self = shift;
173 0               my $ip = $self->remote_ip();
174              
175             # Dotted quad?
176 0 0             if ( $ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ )
177                 {
178 0                   return 1;
179                 }
180                 else
181                 {
182 0                   return 0;
183                 }
184             }
185              
186              
187             =head2 is_ipv6
188            
189             Determine whether the remote IP address is IPv6.
190            
191             =cut
192              
193             sub is_ipv6
194             {
195              
196             # Get the IP
197 0     0 1       my $self = shift;
198 0               my $ip = $self->remote_ip();
199              
200             # not IPv6 if IPv4
201 0 0             return 0 if ( $ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ );
202              
203             # Not IPv6 unless it has a colon
204 0 0             return 0 unless ( $ip =~ /:/ );
205              
206             # Probably OK
207 0               return 1;
208             }
209              
210              
211              
212             =head1 AUTHOR
213            
214             Steve Kemp <steve@steve.org.uk>
215            
216             =cut
217              
218             =head1 COPYRIGHT AND LICENSE
219            
220             Copyright (C) 2015 Steve Kemp <steve@steve.org.uk>.
221            
222             This library is free software. You can modify and or distribute it under
223             the same terms as Perl itself.
224            
225             =cut
226              
227              
228              
229             1;
230