File Coverage

blib/lib/Ph.pm
Criterion Covered Total %
statement 117 188 62.2
branch 20 56 35.7
condition n/a
subroutine 19 26 73.0
pod 15 16 93.7
total 171 286 59.7


line stmt bran cond sub pod time code
1              
2             #
3             # Ph.pm - a module for talking to CCSO Ph servers
4             #
5             # Copyright 1995-1998, Garrett D'Amore.
6             # See the Artistic License for licensing details and use
7             # agreements.
8             #
9             # Important: READ THE Artistic FILE -- it limits my liability
10             # and provids the specific agreements to which you must agree
11             # if you use this package.
12             #
13             package Ph;
14              
15 1     1   721 use strict;
  1         2  
  1         40  
16 1     1   6 use vars qw($VERSION $DefaultPhPort $DefaultPhServer @ISA);
  1         1  
  1         102  
17              
18             $VERSION = '2.01';
19             @ISA = qw();
20              
21             require 5.003;
22              
23 1     1   1015 use IO::Socket;
  1         400997  
  1         4  
24 1     1   531 use Carp;
  1         1  
  1         2005  
25              
26             $DefaultPhPort = "ns(105)";
27             $DefaultPhServer = "ns";
28              
29             sub new
30             {
31 2     2 1 74 my ($class, %arg) = @_;
32 2         7 return bless \%arg, $class;
33             }
34              
35             sub Add
36             {
37 0     0 1 0 my $self = shift;
38 0         0 my $entry = shift;
39 0         0 my $request = "add";
40              
41 0         0 $request .= $self->_MakeFieldsLine($entry);
42 0         0 $self->_SendRequest($request);
43              
44 0         0 return $self->_IsSuccessful($self->_GetCode());
45             }
46              
47             sub Change
48             {
49 0     0 1 0 my $self = shift;
50 0         0 my ($query,$changes) = @_;
51 0         0 my $request = "change ";
52              
53 0         0 $request .= $self->_MakeFieldsLine($query);
54 0         0 $request .= " make";
55 0         0 $request .= $self->_MakeFieldsLine($changes);
56              
57 0         0 $self->_SendRequest($request);
58              
59 0         0 return $self->_IsSuccessful($self->_GetCode());
60             }
61              
62             sub Connect
63             {
64 4     4 1 238 my $self = shift;
65 4         8 my $code;
66 4         12 my ($PhServer, $PhPort) = @_;
67 4 50       113 if (!defined $PhServer)
68             {
69 0         0 $PhServer = $DefaultPhServer;
70             }
71 4 50       12 if (!defined $PhPort)
72             {
73 0         0 $PhPort = $DefaultPhPort;
74             }
75              
76 4         244 my $sock = IO::Socket::INET->new(PeerAddr => $PhServer,
77             PeerPort => $PhPort,
78             Proto => 'tcp');
79              
80 4 50       127675915 if (!defined $sock)
81             {
82 4         21 $self->{last_message} = $@;
83 4         76 $self->{last_message} =~ s/^IO::Socket::INET/connect/;
84 4         9 $self->{last_code} = 998;
85 4         23 return 0;
86             }
87              
88             # make socket unbuffered, this is the default under 5.004_04
89 0         0 $sock->autoflush;
90 0         0 $self->{sock} = $sock;
91              
92             # send id -- we send our UNIX account name -- this is not
93             # strictly required but it is polite
94 0         0 $self->_SendRequest("id $<");
95 0         0 $code = $self->_GetCode();
96              
97 0 0       0 if (!$self->_IsSuccessful($code))
98             {
99 0         0 return 0;
100             }
101              
102             # get the database status
103 0         0 $self->_SendRequest("status");
104 0         0 return $self->_IsSuccessful($self->_GetCode());
105             }
106              
107             sub Delete
108             {
109 0     0 1 0 my $self = shift;
110 0         0 my $entry = shift;
111 0         0 my $request = "delete";
112              
113 0         0 $request .= $self->_MakeFieldsLine($entry);
114 0         0 $self->_SendRequest($request);
115              
116 0         0 return $self->_IsSuccessful($self->_GetCode());
117             }
118              
119             sub Disconnect
120             {
121 1     1 1 34 my $self = shift;
122 1         1 my $code;
123 1 50       5 if (defined $self->{sock})
124             {
125 0         0 $self->_SendRequest("quit");
126             # we don't care about the value, we just throw it away
127 0         0 $self->_GetCode();
128 0         0 close($self->{sock});
129 0         0 delete $self->{sock};
130             }
131             }
132              
133             sub ErrorMessage
134             {
135 0     0 1 0 my $self = shift;
136 0         0 my $code = shift;
137              
138 0         0 my %messages = (
139             100 => 'In progress (general).',
140             101 => 'Echo of current command.',
141             102 => 'Count of number of matches to query.',
142             200 => 'Success (general).',
143             201 => 'Database ready, but read only.',
144             300 => 'More information (general).',
145             301 => 'Encrypt this string.',
146             400 => 'Temporary error (general).',
147             401 => 'Internal database error.',
148             402 => 'Lock not obtained within timeout period.',
149             475 => 'Database unavailable; try later.',
150             500 => 'Permanent error (general).',
151             501 => 'No matches to query.',
152             502 => 'Too many matches to query.',
153             503 => 'Not authorized for requested information.',
154             504 => 'Not authorized for requested search criteria.',
155             505 => 'Not authorized to change requested field.',
156             506 => 'Request refused; must be logged in to execute.',
157             507 => 'Field does not exist.',
158             508 => 'Field is not present in requested entry.',
159             509 => 'Alias already in use.',
160             510 => 'Not authorized to change this entry.',
161             511 => 'Not authorized to add entries.',
162             512 => 'Illegal value.',
163             513 => 'Unknown option.',
164             514 => 'Unknown command.',
165             515 => 'No indexed field in query.',
166             516 => 'No authorization for request.',
167             517 => 'Operation failed because database is read only.',
168             518 => 'Too many entries selected by change command.',
169             520 => 'CPU usage limit exceeded.',
170             521 => 'Change command would have overridden existing field, and the "addonly" option is on.',
171             522 => 'Attempt to view "Encrypted" field.',
172             523 => 'Expecting "answer" or "clear"',
173             524 => 'Names of help topics may not contain "/".',
174             598 => 'Command unknown.',
175             599 => 'Syntax error.',
176             995 => '[socket failure]',
177             996 => '[bind failure]',
178             997 => '[unknown host]',
179             998 => '[connection failure]',
180             999 => '[connection closed]'
181             );
182 0         0 return $messages{$code};
183             }
184              
185             sub Fields
186             {
187 2     2 1 28 my $self = shift;
188 2         3 my %result = ();
189 2         3 my $name;
190             my $code;
191              
192             # get the field descriptions
193 2         15 $self->_SendRequest("fields");
194             do
195 2         2 {
196 2         29 my @response = $self->_ParseResponse($self->_GetResponse());
197 2         4 $code = $response[0];
198 2         2 $name = $response[2];
199             # second line is just the description
200 2 50       7 if (defined $result{$name})
201             {
202 0         0 $result{$name}->{desc} = $response[3];
203             }
204             # first line contains the good stuff
205             else
206             {
207 2         3 my $flag;
208 2         5 my @flags = split(/\s+/, $response[3]);
209              
210 2         5 $result{$name} = {};
211 2         5 $result{$name}->{number} = $response[1];
212 2         11 $result{$name}->{name} = $name;
213              
214 2         12 foreach $flag (@flags)
215             {
216 0 0       0 if ($flag =~ /^\d+$/)
    0          
217             {
218 0         0 $result{$name}->{max} = $flag;
219             }
220             elsif ($flag eq 'max')
221             {
222             # nop
223             }
224             else
225             {
226 0         0 $result{$name}->{$flag} = $flag;
227             }
228             } # foreach $flag
229             } # defined $result{$name}, i.e. end of field description
230             } while ($code < 200);
231 2 50       5 return ($self->_IsSuccessful($code)) ? %result : undef;
232             }
233              
234             sub _IsSuccessful
235             {
236 6     6   9 my $self = shift;
237 6         9 my $code = shift;
238 6         40 return (int($code / 100) == 2);
239             }
240              
241             sub _GetCode
242             {
243 1     1   2 my $self = shift;
244 1         1 my $code;
245             do
246 1         2 {
247 1         5 my @result = $self->_ParseResponse($self->_GetResponse());
248 1         7 $code = $result[0];
249             } while ($code < 200);
250 1         3 return $code;
251             }
252              
253             sub _GetResponse
254             {
255 7     7   11 my $self = shift;
256 7         10 my $sock = $self->{sock};
257 7         7 my $response;
258              
259 7 50       36 if (!defined $sock)
260             {
261 7         9 $response="999: [no active connection]\n";
262             }
263             else
264             {
265 0         0 $response = $sock->getline();
266             }
267              
268 7 50       16 if (!defined $response)
269             {
270 0         0 $response="999: [connection closed]\n";
271 0         0 delete $self->{sock};
272             }
273 7         11 chomp $response;
274 7 50       16 print STDERR "server> $response\n" if ($self->{Debug});
275 7         23 return $response;
276             }
277              
278             sub GetLastCode
279             {
280 1     1 1 5 my $self = shift;
281 1         5 return $self->{last_code};
282             }
283              
284             sub GetLastMessage
285             {
286 0     0 1 0 my $self = shift;
287 0         0 return $self->{last_message};
288             }
289              
290             sub IsConnected
291             {
292 1     1 1 27 my $self = shift;
293 1         12 return defined($self->{sock});
294             }
295              
296             sub Login
297             {
298 1     1 1 44 my $self = shift;
299 1         3 my ($alias, $password) = @_;
300 1         1 my ($code, $challenge, $answer);
301              
302 1         5 $self->_SendRequest("login $alias");
303              
304 1         3 $code = $self->_GetCode();
305              
306             # abort if we were not challenged!
307 1 50       5 return 0 if $code != 301;
308              
309             #
310             # unfortunately, I don't know how to answer the challenge
311             # Dorner's code is *very* hard to understand! But if we
312             # ever figure it out, here's where we'll put the challenge
313             # response.
314             #
315              
316             # so for now we send the password in the clear (blech!)
317 0         0 $self->_SendRequest("clear $password");
318              
319             # overwrite password to minimize exposure; not sure if
320             # this really helps with perl
321 0         0 $password = "xxxxxxxxxxxxxx";
322              
323 0         0 return $self->_IsSuccessful($self->_GetCode());
324             }
325              
326             sub Logout
327             {
328 0     0 1 0 my $self = shift;
329            
330 0         0 $self->_SendRequest("logout");
331 0         0 return $self->_IsSuccessful($self->_GetCode());
332             }
333              
334             sub _MakeFieldsLine
335             {
336 2     2   3 my $self = shift;
337 2         2 my $fields = shift;
338 2         2 my ($output, $field);
339              
340 2         4 $output = "";
341              
342 2 100       17 if (ref($fields) eq 'HASH')
    50          
343             {
344 1         5 foreach $field (keys %$fields)
345             {
346 1         3 $fields->{$field} =~ s/"/\\"/;
347 1         5 $output .= " $field=\"$fields->{$field}\"";
348             }
349             }
350             elsif (ref($fields) eq 'ARRAY')
351             {
352 1         2 foreach $field (@$fields)
353             {
354 1         4 $output .= " $field";
355             }
356             }
357             else
358             {
359 0         0 $output = " $fields";
360             }
361 2         6 return $output;
362             }
363              
364             sub _ParseResponse
365             {
366 7     7   10 my $self = shift;
367 7         8 my $unparsed = shift;
368 7         37 my @parsed = split(/: */, $unparsed, 4);
369 7         14 $self->{last_code} = $parsed[0];
370 7         10 $self->{last_message} = $parsed[1];
371 7         22 return @parsed;
372             }
373              
374             sub Query
375             {
376 2     2 0 137 my $self = shift;
377 2         4 my ($query, $fieldlist) = @_;
378 2         4 my ($key, $request, $index, @response, $lastfield, $code, $field, $value);
379 2         3 my @matches = ();
380              
381 2         3 $index = 0;
382              
383 2         4 $request = "query";
384 2         8 $request .= $self->_MakeFieldsLine($query);
385 2 50       6 if (defined ($fieldlist))
386             {
387 0         0 $request .= " return";
388 0         0 $request .= $self->_MakeFieldsLine($fieldlist);
389             }
390 2         41 $self->_SendRequest($request);
391              
392             do
393 2         3 {
394 2         3 @response = ();
395 2         12 @response = $self->_ParseResponse($self->_GetResponse());
396 2         4 $code = $response[0];
397 2 50       9 if (defined $response[3])
398             {
399 0         0 $index = $response[1] - 1;
400 0 0       0 $field = length($response[2]) ? $response[2] : $lastfield;
401 0         0 $lastfield = $field;
402 0         0 $value = $response[3];
403              
404 0 0       0 if (!defined $matches[$index])
405             {
406 0         0 $matches[$index] = { };
407             }
408              
409 0 0       0 if (!defined $matches[$index]->{$field} )
410             {
411 0         0 $matches[$index]->{$field} = '';
412             }
413             else
414             {
415 0         0 $matches[$index]->{$field} .= "\n";
416             }
417              
418 0         0 $matches[$index]->{$field} .= $value;
419             }
420             } while ($code < 200);
421 2 50       6 return $self->_IsSuccessful($code) ? @matches : undef;
422             }
423              
424             sub _SendRequest
425             {
426 7     7   10 my $self = shift;
427 7         9 my $request = shift;
428 7         10 my $sock = $self->{sock};
429 7 50       18 if (!defined $sock)
    0          
430             {
431 7 50       48 print STDERR "client> [connection not open]\n" if $self->{Debug};
432             }
433             elsif (print $sock "$request\r\n")
434             {
435 0 0       0 print STDERR "client> $request\n" if $self->{Debug};
436             }
437             else
438             {
439 0 0       0 print STDERR "client> [connection closed]\n" if $self->{Debug};
440 0         0 delete $self->{sock};
441             }
442             }
443              
444             sub SiteInfo
445             {
446 2     2 1 136 my $self = shift;
447 2         4 my %results;
448             my $code;
449 2         5 $self->_SendRequest("siteinfo");
450             do
451 2         4 {
452 2         6 my @response = $self->_ParseResponse($self->_GetResponse());
453 2 50       7 if ($response[2])
454             {
455 0         0 $results{$response[2]} = $response[3];
456             }
457 2         13 $code = $response[0];
458             } while ($code < 200);
459              
460 2 50       7 return $self->_IsSuccessful($code) ? %results : undef;
461             }
462              
463             sub Version
464             {
465 0     0 1   return $VERSION;
466             }
467             1;
468              
469             __END__