File Coverage

blib/lib/IP/QQWry.pm
Criterion Covered Total %
statement 27 130 20.7
branch 4 42 9.5
condition 1 6 16.6
subroutine 8 19 42.1
pod 6 6 100.0
total 46 203 22.6


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