File Coverage

blib/lib/IP/QQWry.pm
Criterion Covered Total %
statement 27 129 20.9
branch 4 42 9.5
condition 1 6 16.6
subroutine 8 19 42.1
pod 6 6 100.0
total 46 202 22.7


line stmt bran cond sub pod time code
1             package IP::QQWry;
2              
3 2     2   49719 use 5.008;
  2         7  
  2         76  
4 2     2   9 use warnings;
  2         3  
  2         48  
5 2     2   9 use strict;
  2         7  
  2         65  
6 2     2   9 use Carp;
  2         3  
  2         208  
7 2     2   1645 use version; our $VERSION = qv('0.0.16');
  2         4504  
  2         13  
8              
9             my %cache;
10             my $tmp; # used for hold temporary data
11              
12             sub new {
13 2     2 1 590 my ( $class, $db ) = @_;
14 2         6 my $self = {};
15 2         5 bless $self, $class;
16 2 100       8 if ($db) {
17 1         7 $self->set_db($db);
18             }
19 2         7 return $self;
20             }
21              
22             # set db file of which the name is `QQWry.Dat' most of the time.
23             sub set_db {
24 1     1 1 3 my ( $self, $db ) = @_;
25 1 50 33     38 if ( $db && -r $db ) {
26 0 0       0 open $self->{fh}, '<', $db or croak "how can this happen? $!";
27 0         0 $self->_init_db;
28 0         0 return 1;
29             }
30 1         350 carp 'set_db failed';
31 1         3 return;
32             }
33              
34             sub _init_db {
35 0     0   0 my $self = shift;
36 0         0 read $self->{fh}, $tmp, 4;
37 0         0 $self->{first_index} = unpack 'V', $tmp;
38 0         0 read $self->{fh}, $tmp, 4;
39 0         0 $self->{last_index} = unpack 'V', $tmp;
40             }
41              
42             # sub query is the the interface for user.
43             # the parameter is a IPv4 address
44              
45             sub query {
46 0     0 1 0 my ( $self, $input ) = @_;
47 0 0       0 unless ( $self->{fh} ) {
48 0         0 carp 'database is not provided';
49 0         0 return;
50             }
51              
52 0         0 my $ip = $self->_convert_input($input);
53              
54 0 0       0 if ($ip) {
55 0 0       0 $cache{$ip} = [ $self->_result($ip) ] unless $self->cached($ip);
56 0 0       0 return wantarray ? @{ $cache{$ip} } : join '', @{ $cache{$ip} };
  0         0  
  0         0  
57             }
58             }
59              
60             sub _convert_input {
61 0     0   0 my ( $self, $input ) = @_;
62 0 0       0 if ( $input =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/ ) {
    0          
63 0         0 return $1 * 256**3 + $2 * 256**2 + $3 * 256 + $4;
64             }
65             elsif ( $input =~ /(\d+)/ ) {
66 0         0 return $1;
67             }
68             else {
69 0         0 return;
70             }
71             }
72              
73             sub cached {
74 0     0 1 0 my ( $self, $input ) = @_;
75 0         0 my $ip = $self->_convert_input($input);
76 0 0       0 return $cache{$ip} ? 1 : 0;
77             }
78              
79             sub clear {
80 0     0 1 0 my ( $self, $ip ) = @_;
81 0 0       0 if ($ip) {
82 0         0 undef $cache{$ip};
83             }
84             else {
85 0         0 undef %cache;
86             }
87             }
88              
89             sub db_version {
90 0     0 1 0 return shift->query('255.255.255.0'); # db version info is held there
91             }
92              
93             # get the useful infomation which will be returned to user
94              
95             sub _result {
96 0     0   0 my ( $self, $ip ) = @_;
97 0         0 my $index = $self->_index($ip);
98 0 0       0 return unless $index; # can't find index
99              
100 0         0 my ( $base, $ext ) = (q{}) x 2;
101              
102 0         0 seek $self->{fh}, $index + 4, 0;
103 0         0 read $self->{fh}, $tmp, 3;
104              
105 0         0 my $offset = unpack 'V', $tmp . chr 0;
106 0         0 seek $self->{fh}, $offset + 4, 0;
107 0         0 read $self->{fh}, $tmp, 1;
108              
109 0         0 my $mode = ord $tmp;
110              
111 0 0       0 if ( $mode == 1 ) {
    0          
112 0         0 $self->_seek;
113 0         0 $offset = tell $self->{fh};
114 0         0 read $self->{fh}, $tmp, 1;
115 0         0 $mode = ord $tmp;
116 0 0       0 if ( $mode == 2 ) {
117 0         0 $self->_seek;
118 0         0 $base = $self->_str;
119 0         0 seek $self->{fh}, $offset + 4, 0;
120 0         0 $ext = $self->_ext;
121             }
122             else {
123 0         0 seek $self->{fh}, -1, 1;
124 0         0 $base = $self->_str;
125 0         0 $ext = $self->_ext;
126             }
127             }
128             elsif ( $mode == 2 ) {
129 0         0 $self->_seek;
130 0         0 $base = $self->_str;
131 0         0 seek $self->{fh}, $offset + 8, 0;
132 0         0 $ext = $self->_ext;
133             }
134             else {
135 0         0 seek $self->{fh}, -1, 1;
136 0         0 $base = $self->_str;
137 0         0 $ext = $self->_ext;
138             }
139              
140             # 'CZ88.NET' means we don't have useful information
141 0 0       0 $base = '' if $base =~ /CZ88\.NET/;
142 0 0       0 $ext = '' if $ext =~ /CZ88\.NET/;
143 0         0 return ( $base, $ext );
144             }
145              
146             sub _index {
147 0     0   0 my ( $self, $ip ) = @_;
148 0         0 my $low = 0;
149 0         0 my $up = ( $self->{last_index} - $self->{first_index} ) / 7;
150 0         0 my ( $mid, $ip_start, $ip_end );
151              
152             # find the index using binary search
153 0         0 while ( $low <= $up ) {
154 0         0 $mid = int( ( $low + $up ) / 2 );
155 0         0 seek $self->{fh}, $self->{first_index} + $mid * 7, 0;
156 0         0 read $self->{fh}, $tmp, 4;
157 0         0 $ip_start = unpack 'V', $tmp;
158              
159 0 0       0 if ( $ip < $ip_start ) {
160 0         0 $up = $mid - 1;
161             }
162             else {
163 0         0 read $self->{fh}, $tmp, 3;
164 0         0 $tmp = unpack 'V', $tmp . chr 0;
165 0         0 seek $self->{fh}, $tmp, 0;
166 0         0 read $self->{fh}, $tmp, 4;
167 0         0 $ip_end = unpack 'V', $tmp;
168              
169 0 0       0 if ( $ip > $ip_end ) {
170 0         0 $low = $mid + 1;
171             }
172             else {
173 0         0 return $self->{first_index} + $mid * 7;
174             }
175             }
176             }
177              
178 0         0 return;
179             }
180              
181             sub _seek {
182 0     0   0 my $self = shift;
183 0         0 read $self->{fh}, $tmp, 3;
184 0         0 my $offset = unpack 'V', $tmp . chr 0;
185 0         0 seek $self->{fh}, $offset, 0;
186             }
187              
188             # get string ended by \0
189              
190             sub _str {
191 0     0   0 my $self = shift;
192 0         0 my $str;
193              
194 0         0 read $self->{fh}, $tmp, 1;
195 0         0 while ( ord $tmp > 0 ) {
196 0         0 $str .= $tmp;
197 0         0 read $self->{fh}, $tmp, 1;
198             }
199 0         0 return $str;
200             }
201              
202             sub _ext {
203 0     0   0 my $self = shift;
204 0         0 read $self->{fh}, $tmp, 1;
205 0         0 my $mode = ord $tmp;
206              
207 0 0 0     0 if ( $mode == 1 || $mode == 2 ) {
208 0         0 $self->_seek;
209 0         0 return $self->_str;
210             }
211             else {
212 0         0 return chr($mode) . $self->_str;
213             }
214             }
215              
216             sub DESTROY {
217 2     2   150 my $self = shift;
218 2 50       144 close $self->{fh} if $self->{fh};
219             }
220              
221             1;
222              
223             __END__