File Coverage

blib/lib/Net/Whois/RegistryFusion.pm
Criterion Covered Total %
statement 36 103 34.9
branch 0 26 0.0
condition 0 2 0.0
subroutine 12 29 41.3
pod 7 8 87.5
total 55 168 32.7


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             my $rf = new Net::Whois::RegistryFusion;
10             $rf->isCached('domain.com')
11             &&
12             my $xml = $rf->whois('domain.com');
13              
14             =head1 DESCRIPTION
15              
16             This class does not do any XML parsing. You must create a subclass inheriting and extending the whois method
17             where you can code the XML parsing using eg. XML::Simple.
18             You must also implement the _getUsername, _getPassword, _getXmlpath methods.
19              
20             The class does some basic on-disk caching of the raw xml retrieved from RegistryFusion.
21             The path to the cache is specified using _getXmlpath method.
22              
23             =head1 ABSTRACT METHODS
24              
25             =head2 _getUsername
26              
27             Must be implemented to return a RegistryFusion username
28              
29             =head2 _getPassword
30              
31             Must be implemented to return a RegistryFusion password
32              
33             =head2 _getXmlpath
34              
35             Must be implemented to return a path where XML cache files will be stored
36              
37             =head1 PUBLIC METHODS
38              
39             =head2 new
40              
41             This is the constructor. It takes as argument an optional hash with a 'resetCache' key. Setting 'resetCache' 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.
42              
43             =head2 whois ($domain)
44              
45             Returns 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'
46              
47             =head2 getFetchedDomains
48              
49             Accessor returns arrayref of domains that had the whois info fetched from RegistryFusion (and not the cache)
50              
51             =head2 isCached ($domain)
52              
53             Returns TRUE if given domain is cached. FALSE otherwise.
54              
55             =head2 deleteFromCache ($domain)
56              
57             Deletes the given $domain from the cache.
58              
59             =head2 logout
60              
61             Logs out of RegistryFusion, expiring the session. This method is called by the destructor, so you don't need to explicitly call it.
62              
63             =head2 getSessionKey
64              
65             returns session key.
66              
67             =head1 PRIVATE METHODS
68              
69             =head2 _login
70              
71             Login to RegistryFusion and return a session key. this method is called by the constructor.
72              
73             =head1 TODO
74              
75             Need generic test suite. eg. use Class::Generate to generate a subclass. Get the username, password, xmlpath values from user during make test.
76              
77             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.
78              
79             My current progress can be seen embedded in the pod (Test::Inline style).
80              
81              
82             =begin testing
83              
84             use Class::Generate;
85              
86             subclass Net::Whois::RegistryFusion::Test => [
87             '&_getUsername' => q{ return $username },
88             '&_getPassword' => q{ return $password },
89             '&_getXmlpath' => q{ return $xmlpath },
90              
91             ], -parent => 'Net::Whois::RegistryFusion';
92              
93             my $rf = new Net::Whois::RegistryFusion::Test;
94             can_ok('Net::Whois::RegistryFusion::Test', ('isCached', 'whois'));
95             ok(defined $rf->isCached('lobsanov.com'), "verify isCached");
96             ok(my @info = $rf->whois('lobsanov.com'), "get whois");
97             is($info[0], 'LOBSANOV.COM', 'verify whois content');
98              
99              
100             =end testing
101              
102             =cut
103              
104 1     1   15744 use strict;
  1         3  
  1         39  
105              
106 1     1   380 use LWP::Simple;
  1         55017  
  1         9  
107 1     1   728 use File::Slurp (); # don't import File::Slurp symbols to avoid collisions and reduce overhead
  1         12110  
  1         27  
108 1     1   499 use File::stat;
  1         6266  
  1         8  
109 1     1   540 use IO::LockedFile;
  1         8311  
  1         6  
110 1     1   1243 use Date::Format;
  1         3833  
  1         57  
111 1     1   496 use Error;
  1         2668  
  1         5  
112              
113 1     1   54 use constant AUTH => 'http://whois.RegistryFusion.com/rf/xml/1.0/auth/';
  1         2  
  1         64  
114 1     1   6 use constant WHOIS => 'http://whois.RegistryFusion.com/rf/xml/1.0/whois/';
  1         2  
  1         35  
115              
116 1     1   5 use constant TRUE => 1;
  1         2  
  1         34  
117 1     1   5 use constant FALSE => 0;
  1         2  
  1         43  
118              
119 1     1   5 use vars qw($VERSION);
  1         2  
  1         743  
120             $VERSION = '0.00_01';
121              
122              
123             sub new {
124 0     0 1   my ($class, %opts) = @_;
125              
126             my $self = {
127             'sessionKey' => undef,
128             'fetchedDomains' => [],
129 0   0       'refreshCache' => $opts{refreshCache} || FALSE,
130             };
131              
132 0           bless $self, $class;
133 0           $self->{'sessionKey'} = $self->_login();
134 0           return $self;
135             }
136              
137             sub _login {
138 0     0     my ($self) = @_;
139              
140 0 0         if ( defined $self->{'sessionKey'} ) {
141             # already logged in
142 0           return;
143             }
144 0           my $url = AUTH . "?username=" . $self->_getUsername() . "&password=" . $self->_getPassword();
145 0 0         my $xml = get($url)
146             or throw Error::Simple("failed to get $url");
147 0 0         $xml =~ m#(.*)#
148             or throw Error::Simple("Couldn't open session");
149 0           return $1;
150             }
151              
152             sub logout {
153 0     0 1   my ($self) = @_;
154              
155 0           my $url = AUTH . "?sessionkey=" . $self->getSessionKey();
156 0           get($url);
157             }
158              
159             sub _getUsername {
160 0     0     throw Error::Simple("this is an abstract method and must be implemented by subclass");
161             }
162              
163             sub _getPassword {
164 0     0     throw Error::Simple("this is an abstract method and must be implemented by subclass");
165             }
166              
167             sub _getXmlpath {
168 0     0     throw Error::Simple("this is an abstract method and must be implemented by subclass");
169             }
170              
171             sub getSessionKey {
172 0     0 1   my ($self) = @_;
173 0           return $self->{'sessionKey'};
174             }
175              
176             sub whois {
177 0     0 1   my ($self, $domain) = @_;
178              
179 0           my $xml;
180 0 0         if ( $self->isCached($domain) ) {
181 0 0         if ( $self->{refreshCache} ) {
182 0           $self->deleteFromCache($domain);
183 0           $xml = $self->_whois($domain);
184             }
185             else {
186 0           $xml = $self->_getCached($domain);
187             }
188             }
189             else {
190 0           $xml = $self->_whois($domain);
191             }
192 0           return $xml;
193             }
194              
195             sub _whois {
196             # here we do the actual whois lookup
197 0     0     my ($self, $domain) = @_;
198              
199 0           my $url = WHOIS . "?sessionkey=". $self->getSessionKey() . "&query=$domain";
200 0 0         my $xml = get($url)
201             or throw Error::Simple("get $url failed");
202              
203             # record the domain as fetched for reporting purposes
204 0           push @{ $self->{fetchedDomains} }, $domain;
  0            
205             # cache the xml
206 0           $self->_cache($xml, $domain);
207              
208 0           return $xml;
209             }
210              
211             sub getFetchedDomains {
212 0     0 1   my ($self) = @_;
213              
214 0           return $self->{fetchedDomains};
215             }
216              
217             sub _getFilename {
218 0     0     my ($self, $domain) = @_;
219              
220 0 0         unless ( $domain ) {
221             }
222 0           my $subdir = lc(substr($domain, 0, 1));
223 0           my $filename = $self->_getXmlpath() . "/$subdir/$domain.xml";
224              
225             # YYY: not nice to return different variable types based on context
226 0 0         return wantarray ? ($filename, $subdir) : $filename;
227             }
228              
229             sub getCacheDate {
230 0     0 0   my ($self, $domain) = @_;
231              
232 0           my $stat = stat($self->_getFilename($domain));
233 0           return time2str("%x", $stat->mtime());
234             }
235              
236             sub isCached {
237 0     0 1   my ($self, $domain) = @_;
238            
239 0 0         if ( -e $self->_getFilename($domain) ) {
240 0           return TRUE;
241             }
242             else {
243 0           return FALSE;
244             }
245             }
246              
247             sub _getCached {
248 0     0     my ($self, $domain) = @_;
249              
250 0           my $filename = $self->_getFilename($domain);
251 0           my $file = new IO::LockedFile $filename;
252 0 0         if ( my $xml = File::Slurp::read_file($file) ) { #YYY: Perl6::Slurp is nicer but requires perl5.8
253 0           return $xml;
254             }
255             }
256              
257             sub _cache {
258 0     0     my ($self, $xml, $domain) = @_;
259            
260 0           my ($filename, $subdir) = $self->_getFilename($domain);
261 0           $subdir = $self->_getXmlpath() . "/$subdir";
262 0 0         mkdir $subdir if ! -d $subdir;
263 0           my $file = new IO::LockedFile ">$filename";
264 0           File::Slurp::write_file($file, $xml);
265             }
266              
267             sub deleteFromCache {
268 0     0 1   my ($self, $domain) = @_;
269              
270 0           my $filename = $self->_getFilename($domain);
271 0 0         if ( -e $filename ) {
272 0 0         unlink $filename
273             or throw Error::Simple("Failed to unlink $filename. $!");
274             }
275             }
276              
277             sub DESTROY {
278 0     0     my ($self) = @_;
279 0           $self->logout();
280             }
281              
282             1;
283              
284             __END__