File Coverage

blib/lib/Net/SPOCP/Protocol.pm
Criterion Covered Total %
statement 117 177 66.1
branch 21 60 35.0
condition 6 23 26.0
subroutine 34 55 61.8
pod 0 12 0.0
total 178 327 54.4


line stmt bran cond sub pod time code
1             package Net::SPOCP::Protocol;
2              
3 1     1   19 use 5.006;
  1         3  
  1         36  
4 1     1   4 use strict;
  1         2  
  1         22  
5 1     1   5 use warnings;
  1         2  
  1         33  
6              
7             @Net::SPOCP::Protocol::ISA = qw(Net::SPOCP);
8              
9 1     1   4 use Carp;
  1         1  
  1         47  
10 1     1   755 use IO::Socket::INET;
  1         23831  
  1         8  
11 1     1   1749 use IO::Socket::SSL;
  1         59809  
  1         9  
12 1     1   878 use Authen::SASL;
  1         1304  
  1         6  
13 1     1   730 use MIME::Base64;
  1         751  
  1         1663  
14              
15             sub init
16             {
17 1     1 0 10 $_[0]->connect();
18             }
19              
20             sub connect
21             {
22 1     1 0 2 my $self = shift;
23              
24 1 50       10 $self->disconnect() if ref $self->{_sock};
25 1   50     29 $self->{_sock} = IO::Socket::INET->new(PeerAddr=>$self->{server},
26             Proto=>'tcp',
27             Timeout=>$self->{timeout} || 300);
28              
29 1 50 33     1072545 croak "Net::SPOCP::connect failed: $!\n"
30             unless $self->{_sock} && $self->{_sock}->connected;
31             }
32              
33             sub DESTROY
34             {
35 1     1   91 my $self = shift;
36 1 50 33     17 $self->disconnect() if $self->{_sock} && $self->{_sock}->connected;
37             }
38              
39             sub disconnect
40             {
41 1     1 0 32 my $self = shift;
42             eval
43 1         3 {
44 1         10 $self->logout();
45 1 50       6 $self->{_sock}->close(SSL_no_shutdown=>1) if $self->{_tls};
46 1         88 $self->{_sock}->shutdown(2);
47             };
48 1 50       3712 if ($@) { carp "Net::SPOCP::disconnect: $@\n"; }
  0         0  
49 1         310 $self->{_sock} = undef;
50             }
51              
52             sub starttls
53             {
54 0     0 0 0 my $self = shift;
55 0         0 my $res = $self->send(Net::SPOCP::Request::Starttls->new())->recv;
56 0 0       0 if($res->code() == 205)
57             {
58 0         0 $self->{_sock} = IO::Socket::SSL->start_SSL($self->{_sock},
59             SSL_verify_mode => 0x01,
60             SSL_ca_file => $self->{ssl_ca_file});
61             }
62 0 0       0 if($res->code() != 205)
63             {
64 0         0 croak("Net::SPOCP: Failed starting tls, probably forbidden by server.")
65             }
66 0         0 $res;
67             }
68              
69             sub query
70             {
71 1     1 0 143 my $self = shift;
72              
73 1         3 my $rule = $_[0];
74 1 50       12 unless (UNIVERSAL::isa('Net::SPOCP::SExpr',$_[0]))
75             {
76 1         11 $rule = Net::SPOCP::SExpr->new($_[0]);
77             }
78              
79 1         21 $self->send(Net::SPOCP::Request::Query->new(rule=>$rule,path=>'/'))->recv();
80             }
81              
82             sub capa
83             {
84 0     0 0 0 my $self = shift;
85 0         0 $self->send(Net::SPOCP::Request::Capa->new())->recv();
86             }
87              
88             sub auth
89             {
90 0     0 0 0 my $self = shift;
91 0         0 my $mech = shift;
92 0         0 my $callbacks = shift;
93 0         0 my $res;
94              
95 0         0 $mech =~ m/(\w+):(\w+)/;
96              
97 0 0       0 $callbacks = "" unless $callbacks;
98              
99 0         0 my $sasl = Authen::SASL->new(
100             mechanism => "$2",
101             callback => "$callbacks",
102             );
103              
104 0         0 $self->{server} =~ m/([\w\d\.-]+):(\d+)/;
105 0         0 my $server = $1;
106              
107 0         0 my $conn = $sasl->client_new("spocp", "$server");
108 0 0       0 die($conn->code()) if $conn->code() < 0;
109              
110             {
111 0         0 my $data = encode_base64($conn->client_start(), '');
  0         0  
112              
113 0         0 $res = $self->send(Net::SPOCP::Request::Auth->new(
114             mech => $mech,
115             data => $data))->recv();
116             }
117              
118 0         0 while($res->code == 301)
119             {
120 0         0 my $dec_data = decode_base64($res->[0]->data);
121 0         0 my $raw_data = $conn->client_step($dec_data);
122 0 0       0 my $data = encode_base64($raw_data, '') if $raw_data;
123 0 0       0 $data = "" unless $data;
124 0         0 $res = $self->send(Net::SPOCP::Request::Auth->new(
125             data => $data))->recv();
126             }
127 0 0       0 if($res->code == 200)
128             {
129 0         0 $self->{sasl} = $conn;
130             }
131             else
132             {
133 0         0 croak("Net::SPOCP: Sasl auth failed.")
134             }
135 0         0 $res;
136             }
137              
138             sub logout
139             {
140 1     1 0 3 my $self = shift;
141 1         14 my $res = $self->send(Net::SPOCP::Request::Logout->new())->recv();
142 1         27 $self->{sasl} = undef;
143 1         4 $self->{rest_buf} = undef;
144 1         5 $res;
145             }
146              
147             sub noop
148             {
149 0     0 0 0 my $self = shift;
150 0         0 $self->send(Net::SPOCP::Request::Noop->new())->recv();
151             }
152              
153             sub send
154             {
155 2     2 0 5 my $self = shift;
156 2         5 my $msg = shift;
157 2         5 my $tosend;
158              
159 2 50 33     21 carp "Net::SPOCP::send disconnected\n" unless
160             $self->{_sock} && $self->{_sock}->connected;
161              
162 2 50       31 if($self->{sasl})
163             {
164 0         0 $tosend = $self->{sasl}->encode($msg->toString());
165             }
166             else
167             {
168 2         15 $tosend = $msg->toString();
169             }
170 2         28 $self->{_sock}->print($tosend);
171 2         270 $self;
172             }
173              
174              
175             sub read
176             {
177 2     2 0 5 my $self = shift;
178              
179 2 50 33     16 carp "Net::SPOCP::send disconnected\n" unless
180             $self->{_sock} && $self->{_sock}->connected;
181              
182 2         25 my $buf = '';
183              
184 2 50       8 if(!$self->{rest_buf})
185             {
186 2         3 my $nread = 0;
187 2         6 my $tbuf = '';
188 2         4 my $maxread = 1024;
189 2         256970 while($nread = sysread($self->{_sock}, $tbuf, $maxread))
190             {
191 2 50       15 last if $nread == 0; # EOF
192 2         15 $buf .= $tbuf;
193 2 50       15 last if ($maxread - $nread) != 0;
194             }
195 2 50       19 croak "Net::SPOCP::recv read error: $!\n" unless defined $nread;
196              
197 2 50       21 if($self->{sasl})
198             {
199 0         0 $buf = $self->{sasl}->decode($buf);
200             }
201             }
202             else
203             {
204 0         0 $buf = $self->{rest_buf};
205             }
206              
207 2         40 $buf =~ m/^(\d+):/;
208 2 50       19 my $len = $1 if $1;
209 2 50       9 carp("couldn't get len in buf at Net::SPOCP::recv read") unless $len;
210 2         134 $buf =~ m/^(\d+):(.{$len})(.*)$/;
211 2 50       16 $buf = $2 if $2;
212 2 50       6 carp("couldn't get buf in of $len at Net::SPOCP::recv read") unless $buf;
213             # there is a second message after the first one. we store this in
214             # $self->{rest_buf} and take it out on the next read.
215 2         9 $self->{rest_buf} = $3;
216 2         26 $buf;
217             }
218              
219             sub recv
220             {
221 2     2 0 6 my $self = shift;
222              
223 2         60 my $res = Net::SPOCP::Response->new();
224 2         4 my $r;
225             do
226 2   33     3 {
227 2         11 $r = Net::SPOCP::Reply->parse($self->read());
228 2         12 $res->add_reply($r);
229             } while ($r->code == 201 || $r->code == 301);
230              
231 2         11 $res;
232             }
233              
234             package Net::SPOCP::Client;
235             @Net::SPOCP::Client::ISA = qw(Net::SPOCP::Protocol);
236              
237             package Net::SPOCP::Request;
238             @Net::SPOCP::Request::ISA = qw(Net::SPOCP);
239              
240             sub toString
241             {
242 2     2   10 $_[0]->l_encode($_[0]->l_encode($_[0]->type).$_[0]->encode());
243             }
244              
245 2     2   6 sub init { }
246              
247             sub type {
248 0     0   0 die "Implementation error calling type: ".join(',',caller())."\n";
249             }
250              
251             sub encode
252             {
253 0     0   0 die $_[0]->type . " not implemented yet"
254             }
255              
256             package Net::SPOCP::Request::Query;
257             @Net::SPOCP::Request::Query::ISA = qw(Net::SPOCP::Request);
258              
259 1     1   9 sub type { 'QUERY' }
260              
261             sub encode
262             {
263 1     1   22 $_[0]->l_encode($_[0]->{path}).$_[0]->l_encode($_[0]->{rule}->toString()).$_[0]->l_encode($_[0]->{data});
264             }
265              
266             package Net::SPOCP::Request::List;
267             @Net::SPOCP::Request::List::ISA = qw(Net::SPOCP::Request);
268              
269 0     0   0 sub type { 'LIST' }
270              
271             package Net::SPOCP::Request::BSearch;
272             @Net::SPOCP::Request::BSearch::ISA = qw(Net::SPOCP::Request);
273              
274 0     0   0 sub type { 'BSEARCH' }
275              
276             package Net::SPOCP::Request::Add;
277             @Net::SPOCP::Request::Add::ISA = qw(Net::SPOCP::Request);
278              
279 0     0   0 sub type { 'ADD' }
280              
281             package Net::SPOCP::Request::Capa;
282             @Net::SPOCP::Request::Capa::ISA = qw(Net::SPOCP::Request);
283              
284 0     0   0 sub type { 'CAPA' }
285              
286             sub encode
287             {
288 0     0   0 return("")
289             }
290              
291             package Net::SPOCP::Request::Auth;
292             @Net::SPOCP::Request::Auth::ISA = qw(Net::SPOCP::Request);
293              
294 0     0   0 sub type { 'AUTH' }
295              
296             sub encode
297             {
298 0     0   0 my $mech = "";
299 0 0       0 $mech = $_[0]->l_encode($_[0]->{mech}) if $_[0]->{mech};
300 0         0 $mech.$_[0]->l_encode($_[0]->{data});
301             }
302              
303             package Net::SPOCP::Request::Logout;
304             @Net::SPOCP::Request::Logout::ISA = qw(Net::SPOCP::Request);
305              
306 1     1   11 sub type { 'LOGOUT' }
307              
308             sub encode
309             {
310 1     1   15 return("");
311             }
312              
313             package Net::SPOCP::Request::Noop;
314             @Net::SPOCP::Request::Noop::ISA = qw(Net::SPOCP::Request);
315              
316 0     0   0 sub type { 'NOOP' }
317              
318             sub encode
319             {
320 0     0   0 return("");
321             }
322              
323             package Net::SPOCP::Request::Starttls;
324             @Net::SPOCP::Request::Starttls::ISA = qw(Net::SPOCP::Request);
325              
326 0     0   0 sub type { 'STARTTLS' }
327              
328             sub encode
329             {
330 0     0   0 return("");
331             }
332              
333             package Net::SPOCP::Response;
334             @Net::SPOCP::Response::ISA = qw(Net::SPOCP);
335              
336 1     1   8 use Carp;
  1         1  
  1         216  
337              
338             sub new
339             {
340 2     2   5 my $class = shift;
341              
342 2         10 bless \@_,$class;
343             }
344              
345             sub add_reply
346             {
347 2     2   3 push(@{$_[0]},$_[1]);
  2         27  
348             }
349              
350             sub replies
351             {
352 0     0   0 @{$_[0]};
  0         0  
353             }
354              
355             sub reply
356             {
357 2     2   11 $_[0]->[$_[1]];
358             }
359              
360             sub is_error
361             {
362 0     0   0 my $code = $_[0]->reply(0)->code;
363             # multi-part, ok, authdata, auth ok
364 0 0 0     0 $code != 201 && $code != 200 && $code != 301 && $code != 300
      0        
365             }
366              
367             sub error
368             {
369 1     1   180 $_[0]->reply(0)->error;
370             }
371              
372             sub code
373             {
374 1     1   9 $_[0]->reply(0)->code;
375             }
376              
377             package Net::SPOCP::Reply;
378             @Net::SPOCP::Reply::ISA = qw(Net::SPOCP);
379              
380 2     2   5 sub init {}
381              
382 1     1   4 use Carp;
  1         2  
  1         300  
383              
384             my %CODE = (
385             200 => 'Ok',
386             201 => 'Multiline',
387             202 => 'Denied',
388             203 => 'Bye',
389             204 => 'Transaction complete',
390             205 => 'Ready to start TLS',
391             300 => 'Authentication in progress',
392             301 => 'Authentication Data',
393             401 => 'Service not available',
394             402 => 'Information unavailable',
395             500 => 'Syntax error',
396             501 => 'Operations error',
397             502 => 'Not supported',
398             503 => 'Already in operation',
399             504 => 'Line too long',
400             505 => 'Unknown ID',
401             506 => 'Already exists',
402             507 => 'Line too long',
403             508 => 'Unknown command',
404             509 => 'Access denied',
405             510 => 'Argument error',
406             511 => 'Already active',
407             512 => 'Internal error',
408             513 => 'Input error',
409             514 => 'Timelimit exceeded',
410             515 => 'Sizelimit exceeded',
411             516 => 'Other'
412             );
413              
414             sub parse
415             {
416 2     2   6 my $self = shift;
417 2         4 my $str = shift;
418              
419 2         27 my $me = Net::SPOCP::Reply->new();
420              
421 2 50       35 carp "Net::SPOCP::Reply::parse format error: missing error code\n" unless
422             $str =~ s/^3:([0-9]{3})//o;
423              
424 2         22 $me->{code} = $1;
425              
426 2 50       17 carp "Net::SPOCP::Reply::parse format error: format error\n" unless
427             $str =~ s/^([0-9]+):(.*)//o;
428              
429 2         7 $me->{length} = $1;
430 2         8 $me->{data} = $2;
431              
432 2         5 $me;
433             }
434              
435             sub code
436             {
437 5     5   96 $_[0]->{code};
438             }
439              
440             sub length
441             {
442 0     0   0 $_[0]->{length};
443             }
444              
445             sub data
446             {
447 0     0   0 $_[0]->{data};
448             }
449              
450             sub error
451             {
452 1     1   4 my $code = $_[0]->{code};
453              
454 1 50       7 return "Unknown error" unless exists $CODE{$code};
455 1         124 $CODE{$code};
456             }
457              
458             package Net::SPOCP;
459              
460             1;