File Coverage

blib/lib/Net/xAP.pm
Criterion Covered Total %
statement 30 246 12.2
branch 0 112 0.0
condition 0 21 0.0
subroutine 10 36 27.7
pod 14 16 87.5
total 54 431 12.5


line stmt bran cond sub pod time code
1             #!/usr/local/bin/perl
2             #
3             # Copyright (c) 1997-1999 Kevin Johnson .
4             #
5             # All rights reserved. This program is free software; you can
6             # redistribute it and/or modify it under the same terms as Perl
7             # itself.
8             #
9             # $Id: xAP.pm,v 1.2 1999/10/03 15:00:19 kjj Exp $
10              
11             require 5.005;
12              
13             package Net::xAP;
14              
15 1     1   5 use strict;
  1         1  
  1         42  
16              
17             =head1 NAME
18              
19             Net::xAP - A base class for protocols such as IMAP, ACAP, IMSP, and ICAP.
20              
21             =head1 SYNOPSIS
22              
23             C
24              
25             B
26             change from release to release.>
27              
28             =head1 DESCRIPTION
29              
30             This base class implements the substrate common across the IMAP, ACAP,
31             IMSP, and ICAP protocols. It provides the interface to the network
32             calls and implements a small amount of glue to assist in implementing
33             interfaces to this protocol family.
34              
35             =cut
36              
37 1     1   1055 use IO::Socket;
  1         35820  
  1         5  
38 1     1   715 use Carp;
  1         2  
  1         48  
39              
40 1     1   5 use vars qw($VERSION @ISA);
  1         1  
  1         61  
41              
42             $VERSION = '0.02';
43              
44 1     1   4 use constant ATOM => 0;
  1         2  
  1         68  
45 1     1   4 use constant ASTRING => 1;
  1         1  
  1         30  
46 1     1   4 use constant PARENS => 2;
  1         1  
  1         43  
47 1     1   5 use constant STRING => 3;
  1         2  
  1         41  
48 1     1   3 use constant SASLRESP => 4;
  1         1  
  1         51  
49 1     1   4 use constant QSTRING => 5;
  1         1  
  1         7371  
50              
51             =head1 END-PROGRAMMER METHODS
52              
53             The following methods are potentially useful for end-programmers.
54              
55             =head2 last_command_time
56              
57             Return what time the most recent command was sent to the server. The
58             return value is a C
59              
60             =cut
61              
62 0     0 1   sub last_command_time { return $_[0]->{LastCmdTime} }
63              
64             =head2 connection
65              
66             Returns the connection object being used by the object.
67              
68             =cut
69              
70 0     0 1   sub connection { return $_[0]->{Connection} }
71              
72             =head1 PROTOCOL-DEVELOPER METHODS
73              
74             The following methods are probably only useful to protocol developers.
75              
76             =head2 new $host, $peerport [, %options]
77              
78             Create a new instance of Net::xAP, connects to C<$host>, and returns a
79             reference to the object.
80              
81             The C<$host> parameter is the name of the host to contact. If
82             C<$host> starts with a C character, the parameter is assumed to
83             contain the name of a program and the given program is spawned as a
84             child process. This is useful for driving programs that can be
85             operated interactively from the command-line, such as UW-imapd.
86              
87             The C<$peerport> parameter specifies the TCP port used for the network
88             connection. The parameter should be in the syntax understood by
89             Cnew>. This parameter is ignored if a child
90             process is spawned.
91              
92             The C<%options> parameter specifies any options to use. The following
93             list enumerates the options, and their default values, currently
94             understood by C:
95              
96             =over 4
97              
98             =item C 1>
99              
100             Setting this option causes C to issue a C method
101             immediately after sending the command to the server. Currently, this
102             option should always be left on. Non-synchronous command/response
103             processing has not been tested.
104              
105             One down-side to Synchronous mode is that commands cannot be sent to
106             the server from within a callback. Instead, the results should be
107             saved, and the commands should be sent after the current command has
108             completed.
109              
110             =item C 0>
111              
112             Setting this option causes C to use non-synchronizing
113             literals. This should only be enabled if the protocol and server this
114             feature.
115              
116             =item C 0>
117              
118             Setting this option causes debug output to be written to C.
119             See the C method for a description of the output format.
120              
121             =item C 0>
122              
123             Setting this option adds support for various extensions that are still
124             in Internet Draft. This option is only intended to be used by
125             protocol developers. Most bug reports related to this feature will be
126             ignored.
127              
128             =back
129              
130             All options are also passed to the internal call to
131             Cnew>, unless a child IMAP process is spawned.
132              
133             =cut
134              
135             sub new {
136 0     0 1   my $class = shift;
137 0   0       my $type = ref($class) || $class;
138 0           my $host = shift;
139 0           my $peerport = shift;
140 0           my %options = @_;
141              
142 0           my $self = bless {}, $class;
143              
144 0           $self->{Options} = {%options};
145              
146             # some default option settings
147 0   0       $self->{Options}{Synchronous} ||= 1;
148 0   0       $self->{Options}{Debug} ||= 0;
149 0   0       $self->{Options}{NonSyncLits} ||= 0;
150              
151 0 0         if (substr($host, 0, 1) eq '/') {
152 0 0         my ($child, $parent) = IO::Socket->socketpair(AF_UNIX,
153             SOCK_STREAM, PF_UNSPEC)
154             or croak "socketpair: $!";
155 0           $child->autoflush(1);
156 0           $parent->autoflush(1);
157 0           my $pid;
158 0 0         if ($pid = fork) {
159 0           $self->{Connection} = $child;
160 0           $parent->close;
161             } else {
162 0 0         croak "can't fork: $!\n" unless defined($pid);
163 0           $child->close;
164 0 0         open(STDIN, "<&" . $parent->fileno)
165             or croak "can't dup parent to stdin: $!\n";
166 0 0         open(STDOUT, ">&" . $parent->fileno)
167             or croak "can't dup parent to stdout: $!\n";
168 0           $^W = 0; # squelch warning emitted by exec()
169 0 0         exec($host) or croak "can't exec $host: $!\n";
170             }
171             } else {
172 0 0         $self->{Connection} = IO::Socket::INET->new(PeerAddr => $host,
173             PeerPort => $peerport,
174             Proto => 'tcp',
175             %options) or return undef;
176 0           $self->{Connection}->autoflush(1);
177             }
178              
179 0           $self->{Pending} = ();
180 0           $self->{Sequence} = 0;
181              
182 0           return $self;
183             }
184              
185             =head2 command $callback, $command [, @args]
186              
187             The C is used to send commands to the server.
188              
189             The C<$callback> parameter should be a reference to a subroutine. It
190             will be called when a response is received from the server.
191              
192             C<@args> is a list of C<$type>-C<$value> pairs. The C<$type>
193             indicates what type of data type to use for C<$value>. This is used
194             to control the encoding necessary to pass the command arguments to the
195             server.
196              
197             The following C<$type>s are understood:
198              
199             =over 4
200              
201             =item C
202              
203             The data will sent raw to the server.
204              
205             =item C
206              
207             The data will be sent to the server as an atom, a quoted string, or a
208             literal depending on the content of C<$value>.
209              
210             =item C
211              
212             The data in C<$value> will be interpreted as an array reference and be
213             sent inside a pair of parentheses.
214              
215             =item C
216              
217             The data will be sent to the server as either a quoted string or
218             literal depending on the content of C<$value>.
219              
220             =item C
221              
222             The data will be sent to the server as a quoted string.
223              
224             =back
225              
226             If the C option is set this method will return a response
227             object, otherwise it will return the sequence number associated with
228             the command just sent to the server.
229              
230             =cut
231              
232             sub command {
233 0     0 1   my $self = shift;
234 0           my $cmd_callback = shift;
235 0           my $cmd = shift;
236              
237 0 0         unless ($#_ % 2) {
238 0           carp("odd number of args given to Net::xAP command method");
239 0           return undef;
240             }
241 0 0         unless (defined($self->{Connection})) {
242 0           carp("no connection open in $self");
243 0           return undef;
244             }
245              
246 0           my $resp;
247              
248 0           $self->{Sequence}++;
249              
250 0           $self->{Pending}{$self->{Sequence}} = $cmd_callback;
251              
252 0           my @list = ($self->{Sequence}, $cmd);
253 0           while (my ($type, $value) = splice @_, 0, 2) {
254 0 0 0       if ($type == ATOM) { # maybe we should check for non-ATOM chars
    0          
    0          
    0          
    0          
255 0           push @list, $value;
256             } elsif ($type == PARENS) {
257 0           push @list, '(' . join(' ', @{$value}) . ')';
  0            
258             } elsif ($type == QSTRING) {
259 0           $value =~ s/([\\\"])/\\$1/g;
260 0           push @list, "\"$value\"";
261             } elsif (($type == ASTRING) || ($type == STRING)) {
262 0 0         my $astring
263             = ($type == ASTRING)
264             ? $self->_as_astring($value)
265             : $self->_as_string($value);
266 0 0         if (ref($astring) eq 'ARRAY') {
267 0 0         if ($self->{Options}{NonSyncLits}) {
268 0           push @list, "{$astring->[0]+}\r\n$astring->[1]";
269             } else {
270 0           push @list, "{$astring->[0]}";
271 0           $self->_send_string(join(' ', @list))->_send_eol;
272 0           my $list;
273             my $tag;
274             # loop until we get a continuation request or a
275             # command-completion response
276 0           while (1) {
277 0           my $str = $self->getline;
278 0           $tag = substr($str, 0, index($str, ' '));
279 0 0         last if ($tag eq '+');
280 0 0         last if (defined($self->_process_response($str)));
281             }
282 0 0         @list = ($astring->[1]) if $tag eq '+'
283             }
284             } else {
285 0           push @list, $astring;
286             }
287             } elsif ($type == SASLRESP) {
288 0           $self->_send_string(join(' ', @list))->_send_eol;
289 0           my $list;
290             my $tag;
291 0           my $func = $value;
292 0           my $i = 0;
293 0           SASL: while (1) {
294 0           my $str;
295 0           while (1) {
296 0           $str = $self->getline;
297 0           ($tag) = split(/\s/, $str);
298             # $tag = substr($str, 0, index($str, ' '));
299 0 0         last if ($tag eq '+');
300 0 0         last SASL if (defined($resp = $self->_process_response($str)));
301             }
302 0 0         if ($tag eq '+') {
303 0           $str = substr($str, 2);
304 0           my $saslresp = &$func($i++, $str);
305 0 0         last unless defined($saslresp);
306 0           $self->_send_string($saslresp)->_send_eol;
307 0           next;
308             }
309             }
310 0           @list = ();
311             } else {
312 0           croak "unknown argument type: $type";
313             }
314             }
315 0 0         $self->_send_string(join(' ', @list))->_send_eol if (scalar @list);
316 0           $self->{LastCmdTime} = time;
317 0 0         if ($self->{Options}{Synchronous}) {
318 0 0         return $resp if defined($resp);
319 0           return $self->response;
320             }
321 0           return $self->{Sequence};
322             }
323              
324             =head2 parse_fields $str
325              
326             Splits the specified C<$str> into fields. A list reference is
327             returned contain the individual fields. Parenthetical clauses are
328             represented as nested list references of arbitrary depth. Quoted
329             strings are stripped of their surrounding quotes and escaped C<\\> and
330             C<\"> characters are unescaped.
331              
332             =cut
333              
334             sub parse_fields {
335 0     0 1   my $self = shift;
336 0           my $str = shift;
337 0 0         return undef unless defined($str);
338 0           my @list;
339 0           my @stack = ([]);
340              
341 0           my $pos = 0;
342 0           my $len = length($str);
343              
344 0           while ($pos < $len) {
345 0           my $c = substr($str, $pos, 1);
346 0 0         if ($c eq ' ') {
    0          
    0          
    0          
    0          
    0          
347 0           $pos++;
348             } elsif ($c eq '(') {
349 0           push @{$stack[-1]}, [];
  0            
350 0           push @stack, $stack[-1]->[-1];
351 0           $pos++;
352             } elsif ($c eq ')') {
353 0           pop(@stack);
354 0           $pos++;
355             } elsif (substr($str, $pos) =~ /^(\"(?:[^\\\"]|\\\")*\")/) { # qstring
356 0           my $str = substr($1, 1, -1);
357 0           $pos += length $1;
358 0           $str =~ s/\\([\\\"])/$1/g;
359 0           push @{$stack[-1]}, $str;
  0            
360             } elsif (substr($str, $pos) =~ /^\{(\d+)\}/) { # literal
361 0           $pos += length($1) + 2;
362 0           push @{$stack[-1]}, substr($str, $pos, $1);
  0            
363 0           $pos += $1;
364             } elsif (substr($str, $pos)
365             =~ /^([^\x00-\x1f\x7f\(\)\{\s\"]+)/) {
366 0           push @{$stack[-1]}, $1;
  0            
367 0           $pos += length $1;
368             } else {
369 0           croak "parse_fields: eeek! bad parse at position $pos [$str]\n";
370             }
371             }
372 0           return $stack[0];
373             }
374              
375             sub _as_astring {
376 0     0     my $self = shift;
377 0           my $str = shift;
378 0           my $type = 0;
379              
380 0           my $len = length $str;
381              
382 0 0 0       if (($len > 1024) || ($str =~ /[\x00\x0a\x0d\x80-\xff]/)) { # literal
    0          
    0          
383 0           return [($len, $str)];
384             } elsif ($str =~ /[\"\\\x01-\x20\x22\x25\x28-\x2a\{]/) { # qstring
385 0           $str =~ s/([\\\"])/\\$1/g;
386 0           return "\"$str\"";
387             } elsif ($str eq '') {
388 0           return '""';
389             } else {
390 0           return $str;
391             }
392             }
393              
394             sub _as_string {
395 0     0     my $self = shift;
396 0           my $str = shift;
397 0           my $type = 0;
398              
399 0           my $len = length $str;
400              
401 0 0 0       if (($len > 1024) || ($str =~ /[\x00\x0a\x0d\"\\\x80-\xff]/)) { # literal
    0          
402 0           return [($len, $str)];
403             } elsif ($str eq '') {
404 0           return '""';
405             } else {
406 0           $str =~ s/([\\\"])/\\$1/g;
407 0           return "\"$str\"";
408             }
409             }
410              
411             sub _send_string {
412 0     0     my $self = shift;
413 0           my $str = shift;
414 0           my $len = length $str;
415              
416 0 0         ($self->{Connection}->syswrite($str, $len) == $len) or return undef;
417 0 0         $self->debug_print(1, $str) if $self->debug;
418 0           return $self;
419             }
420              
421             sub _send_eol {
422 0     0     my $self = shift;
423 0 0         ($self->{Connection}->syswrite("\r\n", 2) == 2) or return undef;
424 0 0         $self->debug_print(1, "eol") if $self->debug;
425 0           return $self;
426             }
427              
428             =head2 response
429              
430             Reads response lines from the server until one of the lines is a
431             completion response. For each response, the appropriate callbacks are
432             triggered. This is automatically called if the C option
433             is on.
434              
435             =cut
436              
437             sub response {
438 0     0 1   my $self = shift;
439              
440 0           my $response;
441 0           do {
442 0           $response = $self->_process_response($self->getline);
443             } until defined($response);
444              
445 0           return $response;
446             }
447              
448             sub _process_response {
449 0     0     my $self = shift;
450 0           my $str = shift;
451              
452             # trigger response callback
453 0           my $response = &{$self->{ResponseCallback}}($str);
  0            
454 0 0         return undef unless defined($response);
455 0 0         $self->debug_print(0, "callback returned $response") if $self->debug;
456              
457             # if we get this far it's a completion response, so trigger
458             # completion callback
459              
460 0           my $tag = $response->tag;
461 0 0         if (defined($self->{Pending}{$tag})) {
462 0           &{$self->{Pending}{$tag}}($response);
  0            
463 0           delete $self->{Pending}{$tag}; # forget the pending command
464             }
465 0           return $response;
466             }
467              
468             =head2 getline
469              
470             Get one 'line' of data from the server, including any literal payloads.
471              
472             =cut
473              
474             sub getline {
475 0     0 1   my $self = shift;
476 0           my $pstr;
477              
478 0           while (1) {
479 0 0         my $str = $self->{Connection}->getline or return undef;
480 0           $str =~ s/\r?\n$//; # strip trailing EOL
481 0           $pstr .= $str;
482 0 0         last if ($str !~ /\{(\d+)\}$/); # done if no literal at end of string
483 0           my $amt = $1;
484 0           my $literal;
485 0 0         $self->{Connection}->read($literal, $amt) == $amt or return undef;
486 0           $pstr .= $literal;
487             }
488 0 0         $self->debug_print(0, $pstr) if $self->debug;
489 0           return $pstr;
490             }
491              
492             =head2 close_connection
493              
494             Closes the connection to the server, returning the results of the
495             operation.
496              
497             =cut
498              
499             sub close_connection {
500 0     0 1   my $ret = $_[0]->connection->close;
501 0           $_[0]->{Connection} = undef;
502 0           return $ret;
503             }
504              
505             =head2 sequence
506              
507             Returns the sequence number of the last command issued to the server.
508              
509             =cut
510              
511 0     0 1   sub sequence { $_[0]->{Sequence} }
512              
513             =head2 next_sequence
514              
515             Returns the sequence number that will be assigned to the next command issued.
516              
517             =cut
518              
519 0     0 1   sub next_sequence { $_[0]->{Sequence} + 1 }
520              
521             =head2 pending
522              
523             Returns a list of sequence numbers for the commands that are still
524             awaiting a complete response from the server.
525              
526             The list is sorted numerically.
527              
528             =cut
529              
530 0     0 1   sub pending { sort { $a <=> $b } keys %{$_[0]->{Pending}} }
  0            
  0            
531              
532             ###############################################################################
533              
534             sub quote {
535 0     0 0   my $self = shift;
536 0           my $str = shift;
537 0 0         return 'nil' unless defined($str);
538 0           $str =~ s/([\\\"])/\\$1/g;
539 0           return "\"$str\"";
540             }
541              
542             sub dequote {
543 0     0 0   my $self = shift;
544 0           my $str = shift;
545 0 0         return undef if (lc($str) eq 'nil');
546 0 0         return $str unless ($str =~ /^\"(.*)\"$/);
547 0           $str = $1;
548 0           $str =~ s/\\(.)/$1/g;
549 0           return $str;
550             }
551              
552             ###############################################################################
553              
554             =head2 debug [$boolean]
555              
556             Returns the value of the debug option for the object.
557              
558             If C<$boolean> is specified, the debug state is set to the given value.
559              
560             =cut
561              
562             sub debug {
563 0 0   0 1   $_[0]->{Options}{Debug} = $_[1] if (defined($_[1]));
564 0           return $_[0]->{Options}{Debug};
565             }
566              
567             =head2 debug_print $direction, $text
568              
569             Prints C<$text> to C, preceded by an indication of traffic
570             direction, the object reference, and a timestamp. The parameter
571             C<$direction> is used to indicate the direction of the traffic related
572             to the debug call. Use C<0> for data being sent to the server, or
573             C<1> for data coming from the server.
574              
575             =cut
576              
577             sub debug_print {
578 0     0 1   my @time = localtime;
579 0 0         print(STDERR
580             $_[1]?'->':'<-',
581             " $_[0] ",
582             sprintf("%02d:%02d:%02d", @time[2..4]),
583             " [", $_[0]->debug_text($_[1], $_[2]), "]\n");
584             }
585              
586             =head2 debug_text $text
587              
588             A stub method intended to be overridden by subclasses. It provides
589             subclasses with the ability to make alterations to C<$text> before
590             being output by C method. The base class version does no
591             alteration of C<$text>.
592              
593             =cut
594              
595 0     0 1   sub debug_text { $_[2] }
596              
597             ###############################################################################
598             package Net::xAP::Response;
599              
600             =head1 RESPONSE OBJECTS
601              
602             A response object is the data type returned by the C method.
603             A few convenience routines are provided at the Net::xAP level that are
604             likely to be common across several protocols.
605              
606             =head2 new
607              
608             Creates a new response object.
609              
610             =cut
611              
612             sub new {
613 0     0     my $class = shift;
614 0   0       my $type = ref($class) || $class;
615              
616 0           my $self = bless {}, $class;
617              
618 0           $self->{Sequence} = 0;
619 0           $self->{Status} = '';
620 0           $self->{Text} = '';
621              
622 0           return $self;
623             }
624              
625             =head2 tag
626              
627             Returns the tag associated with the response object.
628              
629             =cut
630              
631 0     0     sub tag { $_[0]->{Sequence} }
632              
633             =head2 status
634              
635             Returns the command status associated with the response object. This
636             will be C, C, or C.
637              
638             =cut
639              
640 0     0     sub status { $_[0]->{Status} }
641              
642             =head2 text
643              
644             Returns the human readable text assocated with the status of the
645             response object.
646              
647             This will typically be overridden by a subclass of the C class to
648             handle things like status codes.
649              
650             =cut
651              
652 0     0     sub text { $_[0]->{Text} }
653              
654             =head2 status_code
655              
656             Returns a list reference containing the response code portion of the
657             server response.
658              
659             =cut
660              
661 0     0     sub status_code { $_[0]->{StatusCode} }
662              
663             ###############################################################################
664              
665             =head1 CAVEATS
666              
667             With only a few exceptions, the methods provided in this class are
668             intended for use by developers adding support for additional
669             protocols. Don't muck with this level, unless you know what you're
670             doing.
671              
672             =head1 AUTHOR
673              
674             Kevin Johnson EFE
675              
676             =head1 COPYRIGHT
677              
678             Copyright (c) 1997-1999 Kevin Johnson .
679              
680             All rights reserved. This program is free software; you can
681             redistribute it and/or modify it under the same terms as Perl itself.
682              
683             =cut
684              
685             1;