File Coverage

blib/lib/Net/Whois/RegistryFusion.pm
Criterion Covered Total %
statement 42 113 37.1
branch 0 30 0.0
condition 0 6 0.0
subroutine 14 31 45.1
pod 7 8 87.5
total 63 188 33.5


line stmt bran cond sub pod time code
1             package Net::Whois::RegistryFusion;
2              
3             =head1 NAME
4              
5             Net::Whois::RegistryFusion - perform cacheable whois lookups using RegistryFusion XML API
6              
7             =head1 SYNOPSIS
8              
9             $rf = Net::Whois::RegistryFusion->new();
10             # OR:
11             $rf = Net::Whois::RegistryFusion->new({ refreshCache=>1,
12             AUTH=>'http://hexillion.com/rf/xml/1.0/auth/',
13             WHOIS=>'http://hexillion.com/rf/xml/1.0/whois/'
14             });
15             $rf->isCached('domain.com')
16             &&
17             $xml = $rf->whois('domain.com');
18              
19             =head1 DESCRIPTION
20              
21             This class does not do any XML parsing. You must create a subclass inheriting and extending the whois method
22             where you can code the XML parsing using eg. XML::Simple.
23             You must also implement the _getUsername, _getPassword, _getXmlpath methods.
24              
25             The class does some basic on-disk caching of the raw xml retrieved from RegistryFusion.
26             The path to the cache is specified using _getXmlpath method.
27              
28             =head1 REQUIRED MODULES
29              
30             Date::Format (any)
31             Error (any)
32             File::Slurp (any)
33             File::stat (any)
34             IO::LockedFile (any)
35             LWP::Simple (any)
36             Module::Signature (any)
37             Set::Array (any)
38             Test::More (any)
39             Test::Signature (any)
40              
41             =head1 ABSTRACT METHODS
42              
43             =head2 _getUsername
44              
45             Must be implemented to return a RegistryFusion username
46              
47             =head2 _getPassword
48              
49             Must be implemented to return a RegistryFusion password
50              
51             =head2 _getXmlpath
52              
53             Must be implemented to return a path where XML cache files will be stored
54              
55             =head1 PUBLIC METHODS
56              
57             =head2 new
58              
59             This is the constructor. It takes as argument a hashref of options.
60              
61             One option is 'refreshCache'. Setting 'refreshCache' to a value of 1 will alter the behaviour of the whois method for the lifetime of the object; foregoing and deleting the cached domain (if any) and retrieving directly from RegistryFusion.
62              
63             You can also pass the auth url in the 'AUTH' key and the whois url in 'WHOIS' key, overriding the default values of:
64             AUTH => 'http://whois.RegistryFusion.com/rf/xml/1.0/auth/';
65             WHOIS => 'http://whois.RegistryFusion.com/rf/xml/1.0/whois/';
66              
67             =head2 whois ($domain)
68              
69             Returns (scalar) whois info in xml format for given $domain. Checks the cache first. If not found in the cache, retrieves from RegistryFusion. The whois xml info is cached in a file under the path as returned by _getXmlpath method. So, if the XMLPATH is '/registryfusion' and the $domain is 'example.com', the file will be stored as '/registryfusion/e/example.com.xml'
70              
71             =head2 getFetchedDomains
72              
73             Fetched domains are those domains that had the whois info fetched from RegistryFusion and not the cache.
74             Accessor returns array in list context or a Set::Array object in scalar context.
75              
76             =head2 isCached ($domain)
77              
78             Returns TRUE if given domain is cached. FALSE otherwise.
79              
80             =head2 deleteFromCache ($domain)
81              
82             Deletes the given $domain from the cache.
83              
84             =head2 logout
85              
86             Logs out of RegistryFusion, expiring the session. This method is called by the destructor, so you don't need to explicitly call it.
87              
88             =head2 getSessionKey
89              
90             returns session key.
91              
92             =head1 PRIVATE METHODS
93              
94             =head2 _login
95              
96             Login to RegistryFusion and return a session key. this method is called by the constructor.
97              
98             =head1 TODO
99              
100             Need generic test suite. eg. use Class::Generate to generate a subclass. Get the username, password, xmlpath values from user during make test.
101              
102             I've started doing this but encountered a problem with the fact that the username, password and xmlpath variables are static class variables and Class::Generate doesn't take care of this. Maybe we can improve Class::Generate.
103              
104             My current progress can be seen embedded in the pod (Test::Inline style).
105              
106              
107             =begin testing
108              
109             use Class::Generate;
110              
111             subclass Net::Whois::RegistryFusion::Test => [
112             '&_getUsername' => q{ return $username },
113             '&_getPassword' => q{ return $password },
114             '&_getXmlpath' => q{ return $xmlpath },
115              
116             ], -parent => 'Net::Whois::RegistryFusion';
117              
118             my $rf = new Net::Whois::RegistryFusion::Test;
119             can_ok('Net::Whois::RegistryFusion::Test', ('isCached', 'whois'));
120             ok(defined $rf->isCached('lobsanov.com'), "verify isCached");
121             ok(my @info = $rf->whois('lobsanov.com'), "get whois");
122             is($info[0], 'LOBSANOV.COM', 'verify whois content');
123              
124              
125             =end testing
126              
127             =cut
128              
129 1     1   56517 use strict;
  1         3  
  1         41  
130              
131 1     1   11865 use LWP::Simple;
  1         248550  
  1         152  
132 1     1   11391 use File::Slurp (); # don't import File::Slurp symbols to avoid collisions and reduce overhead
  1         22755  
  1         24  
133 1     1   3077 use File::stat;
  1         17003  
  1         11  
134 1     1   4474 use IO::LockedFile;
  1         19476  
  1         9  
135 1     1   14494 use Date::Format;
  1         9360  
  1         111  
136 1     1   3531 use Set::Array;
  1         33580  
  1         37  
137 1     1   6455 use Error;
  1         5960  
  1         8  
138              
139 1     1   427 use constant AUTH => 'http://whois.RegistryFusion.com/rf/xml/1.0/auth/';
  1         2  
  1         89  
140 1     1   6 use constant WHOIS => 'http://whois.RegistryFusion.com/rf/xml/1.0/whois/';
  1         2  
  1         52  
141              
142 1     1   7 use constant TRUE => 1;
  1         2  
  1         56  
143 1     1   6 use constant FALSE => 0;
  1         3  
  1         535  
144              
145 1     1   7 use vars qw($VERSION);
  1         3  
  1         59  
146             $VERSION = '0.05';
147              
148              
149 1     1   8145 use fields qw(sessionKey fetchedDomains refreshCache AUTH WHOIS);
  1         2480  
  1         7  
150              
151             sub new {
152 0     0 1   my ($self, $opts) = @_;
153              
154 0 0         $self = fields::new($self) unless ref $self;
155              
156 0           $self->{'fetchedDomains'} = new Set::Array;
157 0   0       $self->{'refreshCache'} = $opts->{'refreshCache'} || FALSE;
158 0   0       $self->{'AUTH'} = $opts->{'AUTH'} || AUTH;
159 0   0       $self->{'WHOIS'} = $opts->{'WHOIS'} || WHOIS;
160 0           $self->{'sessionKey'} = $self->_login();
161 0           return $self;
162             }
163              
164             sub _login {
165 0     0     my ($self) = @_;
166              
167 0 0         if ( defined $self->{'sessionKey'} ) {
168             # already logged in
169 0           return;
170             }
171 0           my $url = $self->{AUTH} . "?username=" . $self->_getUsername() . "&password=" . $self->_getPassword();
172 0 0         my $xml = get($url)
173             or throw Error::Simple("failed to get $url");
174 0 0         $xml =~ m#(.*)#
175             or throw Error::Simple("Couldn't open session");
176 0           return $1;
177             }
178              
179             sub logout {
180 0     0 1   my ($self) = @_;
181              
182 0           my $url = $self->{AUTH} . "?sessionkey=" . $self->getSessionKey();
183 0           get($url);
184             }
185              
186             sub _getUsername {
187 0     0     throw Error::Simple("this is an abstract method and must be implemented by subclass");
188             }
189              
190             sub _getPassword {
191 0     0     throw Error::Simple("this is an abstract method and must be implemented by subclass");
192             }
193              
194             sub _getXmlpath {
195 0     0     throw Error::Simple("this is an abstract method and must be implemented by subclass");
196             }
197              
198             sub getSessionKey {
199 0     0 1   my ($self) = @_;
200 0           return $self->{'sessionKey'};
201             }
202              
203             sub whois {
204 0     0 1   my ($self, $domain) = @_;
205              
206 0           my $xml;
207 0 0         if ( $self->isCached($domain) ) {
208 0 0         if ( $self->{refreshCache} ) {
209 0           $self->deleteFromCache($domain);
210 0           $xml = $self->_whois($domain);
211             }
212             else {
213 0           $xml = $self->_getCached($domain);
214             }
215             }
216             else {
217 0           $xml = $self->_whois($domain);
218             }
219 0           return $xml;
220             }
221              
222             sub _whois {
223             # here we do the actual whois lookup
224 0     0     my ($self, $domain) = @_;
225              
226 0           my $url = $self->{WHOIS} . "?sessionkey=". $self->getSessionKey() . "&query=$domain";
227 0 0         my $xml = get($url)
228             or throw Error::Simple("get $url failed");
229              
230             # record the domain as fetched for reporting purposes
231 0           $self->{fetchedDomains}->push($domain);
232             # cache the xml
233 0           $self->_cache($xml, $domain);
234              
235 0           return $xml;
236             }
237              
238             sub getFetchedDomains {
239 0     0 1   my ($self) = @_;
240              
241 0 0         return wantarray ? @{ $self->{fetchedDomains} } : $self->{fetchedDomains};
  0            
242             }
243              
244             sub _getFilename {
245 0     0     my ($self, $domain) = @_;
246              
247 0 0         unless ( $domain ) {
248 0           throw Error::Simple("_getFilename called without domain argument");
249             }
250              
251 0           my $subdir = lc(substr($domain, 0, 1));
252 0           my $filename = $self->_getXmlpath() . "/$subdir/$domain.xml";
253              
254 0 0         return wantarray ? ($filename, $subdir) : $filename;
255             }
256              
257             sub getCacheDate {
258 0     0 0   my ($self, $domain) = @_;
259              
260 0           my $stat = stat($self->_getFilename($domain));
261 0           return time2str("%x", $stat->mtime());
262             }
263              
264             sub isCached {
265 0     0 1   my ($self, $domain) = @_;
266            
267 0 0         if ( -e $self->_getFilename($domain) ) {
268 0           return TRUE;
269             }
270             else {
271 0           return FALSE;
272             }
273             }
274              
275             sub _getCached {
276 0     0     my ($self, $domain) = @_;
277              
278 0           my $filename = $self->_getFilename($domain);
279 0           my $file = new IO::LockedFile $filename;
280 0 0         if ( my $xml = File::Slurp::read_file($file) ) { #YYY: Perl6::Slurp is nicer but requires perl5.8+
281 0           return $xml;
282             }
283             }
284              
285             sub _cache {
286 0     0     my ($self, $xml, $domain) = @_;
287            
288 0           my ($filename, $subdir) = $self->_getFilename($domain);
289 0           $subdir = $self->_getXmlpath() . "/$subdir";
290 0 0         mkdir $subdir if ! -d $subdir;
291 0           my $file = new IO::LockedFile ">$filename";
292 0           File::Slurp::write_file($file, $xml);
293             }
294              
295             sub deleteFromCache {
296 0     0 1   my ($self, $domain) = @_;
297              
298 0           my $filename = $self->_getFilename($domain);
299 0 0         if ( -e $filename ) {
300 0 0         unlink $filename
301             or throw Error::Simple("Failed to unlink $filename. $!");
302             }
303             }
304              
305             sub DESTROY {
306 0     0     my ($self) = @_;
307 0           $self->logout();
308             }
309              
310             1;
311              
312             __END__