more consting
[p5sagit/p5-mst-13.2.git] / lib / Net / Cmd.pm
CommitLineData
f92f3fcb 1# Net::Cmd.pm $Id: //depot/libnet/Net/Cmd.pm#34 $
406c51ee 2#
7cf5cf7c 3# Copyright (c) 1995-2006 Graham Barr <gbarr@pobox.com>. All rights reserved.
406c51ee 4# This program is free software; you can redistribute it and/or
5# modify it under the same terms as Perl itself.
6
7package Net::Cmd;
8
9require 5.001;
10require Exporter;
11
12use strict;
13use vars qw(@ISA @EXPORT $VERSION);
14use Carp;
12df23ee 15use Symbol 'gensym';
406c51ee 16
686337f3 17BEGIN {
18 if ($^O eq 'os390') {
19 require Convert::EBCDIC;
20# Convert::EBCDIC->import;
21 }
22}
23
a95ba31d 24$VERSION = "2.27_01";
406c51ee 25@ISA = qw(Exporter);
26@EXPORT = qw(CMD_INFO CMD_OK CMD_MORE CMD_REJECT CMD_ERROR CMD_PENDING);
27
28sub CMD_INFO { 1 }
29sub CMD_OK { 2 }
30sub CMD_MORE { 3 }
31sub CMD_REJECT { 4 }
32sub CMD_ERROR { 5 }
33sub CMD_PENDING { 0 }
34
35my %debug = ();
36
686337f3 37my $tr = $^O eq 'os390' ? Convert::EBCDIC->new() : undef;
38
39sub toebcdic
40{
41 my $cmd = shift;
42
43 unless (exists ${*$cmd}{'net_cmd_asciipeer'})
44 {
45 my $string = $_[0];
46 my $ebcdicstr = $tr->toebcdic($string);
47 ${*$cmd}{'net_cmd_asciipeer'} = $string !~ /^\d+/ && $ebcdicstr =~ /^\d+/;
48 }
49
50 ${*$cmd}{'net_cmd_asciipeer'}
51 ? $tr->toebcdic($_[0])
52 : $_[0];
53}
54
55sub toascii
56{
57 my $cmd = shift;
58 ${*$cmd}{'net_cmd_asciipeer'}
59 ? $tr->toascii($_[0])
60 : $_[0];
61}
62
406c51ee 63sub _print_isa
64{
65 no strict qw(refs);
66
67 my $pkg = shift;
68 my $cmd = $pkg;
69
70 $debug{$pkg} ||= 0;
71
72 my %done = ();
73 my @do = ($pkg);
74 my %spc = ( $pkg , "");
75
406c51ee 76 while ($pkg = shift @do)
77 {
78 next if defined $done{$pkg};
79
80 $done{$pkg} = 1;
81
82 my $v = defined ${"${pkg}::VERSION"}
83 ? "(" . ${"${pkg}::VERSION"} . ")"
84 : "";
85
86 my $spc = $spc{$pkg};
edd55068 87 $cmd->debug_print(1,"${spc}${pkg}${v}\n");
406c51ee 88
89 if(@{"${pkg}::ISA"})
90 {
91 @spc{@{"${pkg}::ISA"}} = (" " . $spc{$pkg}) x @{"${pkg}::ISA"};
92 unshift(@do, @{"${pkg}::ISA"});
93 }
94 }
406c51ee 95}
96
97sub debug
98{
99 @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([LEVEL])';
100
101 my($cmd,$level) = @_;
102 my $pkg = ref($cmd) || $cmd;
103 my $oldval = 0;
104
105 if(ref($cmd))
106 {
107 $oldval = ${*$cmd}{'net_cmd_debug'} || 0;
108 }
109 else
110 {
111 $oldval = $debug{$pkg} || 0;
112 }
113
114 return $oldval
115 unless @_ == 2;
116
117 $level = $debug{$pkg} || 0
118 unless defined $level;
119
120 _print_isa($pkg)
121 if($level && !exists $debug{$pkg});
122
123 if(ref($cmd))
124 {
125 ${*$cmd}{'net_cmd_debug'} = $level;
126 }
127 else
128 {
129 $debug{$pkg} = $level;
130 }
131
132 $oldval;
133}
134
135sub message
136{
137 @_ == 1 or croak 'usage: $obj->message()';
138
139 my $cmd = shift;
140
141 wantarray ? @{${*$cmd}{'net_cmd_resp'}}
142 : join("", @{${*$cmd}{'net_cmd_resp'}});
143}
144
145sub debug_text { $_[2] }
146
147sub debug_print
148{
149 my($cmd,$out,$text) = @_;
150 print STDERR $cmd,($out ? '>>> ' : '<<< '), $cmd->debug_text($out,$text);
151}
152
153sub code
154{
155 @_ == 1 or croak 'usage: $obj->code()';
156
157 my $cmd = shift;
158
159 ${*$cmd}{'net_cmd_code'} = "000"
160 unless exists ${*$cmd}{'net_cmd_code'};
161
162 ${*$cmd}{'net_cmd_code'};
163}
164
165sub status
166{
167 @_ == 1 or croak 'usage: $obj->status()';
168
169 my $cmd = shift;
170
171 substr(${*$cmd}{'net_cmd_code'},0,1);
172}
173
174sub set_status
175{
176 @_ == 3 or croak 'usage: $obj->set_status(CODE, MESSAGE)';
177
178 my $cmd = shift;
179 my($code,$resp) = @_;
180
181 $resp = [ $resp ]
182 unless ref($resp);
183
184 (${*$cmd}{'net_cmd_code'},${*$cmd}{'net_cmd_resp'}) = ($code, $resp);
185
186 1;
187}
188
189sub command
190{
191 my $cmd = shift;
192
686337f3 193 unless (defined fileno($cmd))
194 {
195 $cmd->set_status("599", "Connection closed");
196 return $cmd;
197 }
198
199
406c51ee 200 $cmd->dataend()
f92f3fcb 201 if(exists ${*$cmd}{'net_cmd_last_ch'});
406c51ee 202
203 if (scalar(@_))
204 {
686337f3 205 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
206
207 my $str = join(" ", map { /\n/ ? do { my $n = $_; $n =~ tr/\n/ /; $n } : $_; } @_);
208 $str = $cmd->toascii($str) if $tr;
209 $str .= "\015\012";
406c51ee 210
406c51ee 211 my $len = length $str;
212 my $swlen;
686337f3 213
406c51ee 214 $cmd->close
215 unless (defined($swlen = syswrite($cmd,$str,$len)) && $swlen == $len);
216
217 $cmd->debug_print(1,$str)
218 if($cmd->debug);
219
220 ${*$cmd}{'net_cmd_resp'} = []; # the response
221 ${*$cmd}{'net_cmd_code'} = "000"; # Made this one up :-)
222 }
223
224 $cmd;
225}
226
227sub ok
228{
229 @_ == 1 or croak 'usage: $obj->ok()';
230
231 my $code = $_[0]->code;
232 0 < $code && $code < 400;
233}
234
235sub unsupported
236{
237 my $cmd = shift;
238
239 ${*$cmd}{'net_cmd_resp'} = [ 'Unsupported command' ];
240 ${*$cmd}{'net_cmd_code'} = 580;
241 0;
242}
243
244sub getline
245{
246 my $cmd = shift;
247
248 ${*$cmd}{'net_cmd_lines'} ||= [];
249
250 return shift @{${*$cmd}{'net_cmd_lines'}}
251 if scalar(@{${*$cmd}{'net_cmd_lines'}});
252
253 my $partial = defined(${*$cmd}{'net_cmd_partial'})
254 ? ${*$cmd}{'net_cmd_partial'} : "";
255 my $fd = fileno($cmd);
686337f3 256
406c51ee 257 return undef
258 unless defined $fd;
259
260 my $rin = "";
261 vec($rin,$fd,1) = 1;
262
263 my $buf;
264
265 until(scalar(@{${*$cmd}{'net_cmd_lines'}}))
266 {
267 my $timeout = $cmd->timeout || undef;
268 my $rout;
7cf5cf7c 269
270 my $select_ret = select($rout=$rin, undef, undef, $timeout);
271 if ($select_ret > 0)
406c51ee 272 {
273 unless (sysread($cmd, $buf="", 1024))
274 {
275 carp(ref($cmd) . ": Unexpected EOF on command channel")
276 if $cmd->debug;
277 $cmd->close;
278 return undef;
279 }
280
281 substr($buf,0,0) = $partial; ## prepend from last sysread
282
283 my @buf = split(/\015?\012/, $buf, -1); ## break into lines
284
285 $partial = pop @buf;
286
287 push(@{${*$cmd}{'net_cmd_lines'}}, map { "$_\n" } @buf);
288
289 }
290 else
291 {
7cf5cf7c 292 my $msg = $select_ret ? "Error or Interrupted: $!" : "Timeout";
293 carp("$cmd: $msg") if($cmd->debug);
406c51ee 294 return undef;
295 }
296 }
297
298 ${*$cmd}{'net_cmd_partial'} = $partial;
299
686337f3 300 if ($tr)
301 {
302 foreach my $ln (@{${*$cmd}{'net_cmd_lines'}})
303 {
304 $ln = $cmd->toebcdic($ln);
305 }
306 }
307
406c51ee 308 shift @{${*$cmd}{'net_cmd_lines'}};
309}
310
311sub ungetline
312{
313 my($cmd,$str) = @_;
314
315 ${*$cmd}{'net_cmd_lines'} ||= [];
316 unshift(@{${*$cmd}{'net_cmd_lines'}}, $str);
317}
318
319sub parse_response
320{
321 return ()
322 unless $_[1] =~ s/^(\d\d\d)(.?)//o;
323 ($1, $2 eq "-");
324}
325
326sub response
327{
328 my $cmd = shift;
329 my($code,$more) = (undef) x 2;
330
331 ${*$cmd}{'net_cmd_resp'} ||= [];
332
333 while(1)
334 {
335 my $str = $cmd->getline();
336
337 return CMD_ERROR
338 unless defined($str);
339
340 $cmd->debug_print(0,$str)
341 if ($cmd->debug);
342
343 ($code,$more) = $cmd->parse_response($str);
344 unless(defined $code)
345 {
346 $cmd->ungetline($str);
347 last;
348 }
349
350 ${*$cmd}{'net_cmd_code'} = $code;
351
352 push(@{${*$cmd}{'net_cmd_resp'}},$str);
353
354 last unless($more);
355 }
356
357 substr($code,0,1);
358}
359
360sub read_until_dot
361{
362 my $cmd = shift;
363 my $fh = shift;
364 my $arr = [];
365
366 while(1)
367 {
368 my $str = $cmd->getline() or return undef;
369
370 $cmd->debug_print(0,$str)
371 if ($cmd->debug & 4);
372
373 last if($str =~ /^\.\r?\n/o);
374
375 $str =~ s/^\.\././o;
376
377 if (defined $fh)
378 {
379 print $fh $str;
380 }
381 else
382 {
383 push(@$arr,$str);
384 }
385 }
386
387 $arr;
388}
389
390sub datasend
391{
392 my $cmd = shift;
393 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
394 my $line = join("" ,@$arr);
395
396 return 0 unless defined(fileno($cmd));
397
f92f3fcb 398 my $last_ch = ${*$cmd}{'net_cmd_last_ch'};
399 $last_ch = ${*$cmd}{'net_cmd_last_ch'} = "\012" unless defined $last_ch;
400
401 return 1 unless length $line;
406c51ee 402
edd55068 403 if($cmd->debug) {
404 foreach my $b (split(/\n/,$line)) {
405 $cmd->debug_print(1, "$b\n");
406 }
406c51ee 407 }
408
dea4d7df 409 $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015";
406c51ee 410
f92f3fcb 411 my $first_ch = '';
412
413 if ($last_ch eq "\015") {
414 $first_ch = "\012" if $line =~ s/^\012//;
415 }
416 elsif ($last_ch eq "\012") {
417 $first_ch = "." if $line =~ /^\./;
418 }
419
420 $line =~ s/\015?\012(\.?)/\015\012$1$1/sg;
406c51ee 421
f92f3fcb 422 substr($line,0,0) = $first_ch;
423
424 ${*$cmd}{'net_cmd_last_ch'} = substr($line,-1,1);
406c51ee 425
dea4d7df 426 my $len = length($line);
427 my $offset = 0;
406c51ee 428 my $win = "";
429 vec($win,fileno($cmd),1) = 1;
430 my $timeout = $cmd->timeout || undef;
431
dea4d7df 432 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
433
406c51ee 434 while($len)
435 {
436 my $wout;
df76848e 437 my $s = select(undef,$wout=$win, undef, $timeout);
438 if ((defined $s and $s > 0) or -f $cmd) # -f for testing on win32
406c51ee 439 {
440 my $w = syswrite($cmd, $line, $len, $offset);
441 unless (defined($w))
442 {
443 carp("$cmd: $!") if $cmd->debug;
444 return undef;
445 }
446 $len -= $w;
447 $offset += $w;
448 }
449 else
450 {
451 carp("$cmd: Timeout") if($cmd->debug);
452 return undef;
453 }
454 }
455
456 1;
457}
458
dea4d7df 459sub rawdatasend
406c51ee 460{
461 my $cmd = shift;
dea4d7df 462 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
463 my $line = join("" ,@$arr);
406c51ee 464
465 return 0 unless defined(fileno($cmd));
466
467 return 1
dea4d7df 468 unless length($line);
406c51ee 469
dea4d7df 470 if($cmd->debug)
406c51ee 471 {
dea4d7df 472 my $b = "$cmd>>> ";
473 print STDERR $b,join("\n$b",split(/\n/,$line)),"\n";
406c51ee 474 }
dea4d7df 475
476 my $len = length($line);
477 my $offset = 0;
478 my $win = "";
479 vec($win,fileno($cmd),1) = 1;
480 my $timeout = $cmd->timeout || undef;
481
482 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
483 while($len)
406c51ee 484 {
dea4d7df 485 my $wout;
486 if (select(undef,$wout=$win, undef, $timeout) > 0)
487 {
488 my $w = syswrite($cmd, $line, $len, $offset);
489 unless (defined($w))
490 {
491 carp("$cmd: $!") if $cmd->debug;
492 return undef;
493 }
494 $len -= $w;
495 $offset += $w;
496 }
497 else
498 {
499 carp("$cmd: Timeout") if($cmd->debug);
500 return undef;
501 }
406c51ee 502 }
503
dea4d7df 504 1;
505}
506
507sub dataend
508{
509 my $cmd = shift;
510
511 return 0 unless defined(fileno($cmd));
512
f92f3fcb 513 my $ch = ${*$cmd}{'net_cmd_last_ch'};
514 my $tosend;
515
516 if (!defined $ch) {
517 return 1;
518 }
519 elsif ($ch ne "\012") {
520 $tosend = "\015\012";
521 }
522
523 $tosend .= ".\015\012";
dea4d7df 524
525 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
dea4d7df 526
edd55068 527 $cmd->debug_print(1, ".\n")
406c51ee 528 if($cmd->debug);
529
f92f3fcb 530 syswrite($cmd,$tosend, length $tosend);
406c51ee 531
f92f3fcb 532 delete ${*$cmd}{'net_cmd_last_ch'};
406c51ee 533
534 $cmd->response() == CMD_OK;
535}
536
12df23ee 537# read and write to tied filehandle
538sub tied_fh {
539 my $cmd = shift;
540 ${*$cmd}{'net_cmd_readbuf'} = '';
541 my $fh = gensym();
542 tie *$fh,ref($cmd),$cmd;
543 return $fh;
544}
545
546# tie to myself
547sub TIEHANDLE {
548 my $class = shift;
549 my $cmd = shift;
550 return $cmd;
551}
552
553# Tied filehandle read. Reads requested data length, returning
554# end-of-file when the dot is encountered.
555sub READ {
556 my $cmd = shift;
edd55068 557 my ($len,$offset) = @_[1,2];
12df23ee 558 return unless exists ${*$cmd}{'net_cmd_readbuf'};
559 my $done = 0;
560 while (!$done and length(${*$cmd}{'net_cmd_readbuf'}) < $len) {
561 ${*$cmd}{'net_cmd_readbuf'} .= $cmd->getline() or return;
562 $done++ if ${*$cmd}{'net_cmd_readbuf'} =~ s/^\.\r?\n\Z//m;
563 }
564
565 $_[0] = '';
566 substr($_[0],$offset+0) = substr(${*$cmd}{'net_cmd_readbuf'},0,$len);
567 substr(${*$cmd}{'net_cmd_readbuf'},0,$len) = '';
568 delete ${*$cmd}{'net_cmd_readbuf'} if $done;
569
570 return length $_[0];
571}
572
573sub READLINE {
574 my $cmd = shift;
575 # in this context, we use the presence of readbuf to
576 # indicate that we have not yet reached the eof
577 return unless exists ${*$cmd}{'net_cmd_readbuf'};
578 my $line = $cmd->getline;
579 return if $line =~ /^\.\r?\n/;
580 $line;
581}
582
583sub PRINT {
584 my $cmd = shift;
585 my ($buf,$len,$offset) = @_;
586 $len ||= length ($buf);
587 $offset += 0;
588 return unless $cmd->datasend(substr($buf,$offset,$len));
589 ${*$cmd}{'net_cmd_sending'}++; # flag that we should call dataend()
590 return $len;
591}
592
593sub CLOSE {
594 my $cmd = shift;
595 my $r = exists(${*$cmd}{'net_cmd_sending'}) ? $cmd->dataend : 1;
596 delete ${*$cmd}{'net_cmd_readbuf'};
597 delete ${*$cmd}{'net_cmd_sending'};
598 $r;
599}
600
406c51ee 6011;
602
603__END__
604
605
606=head1 NAME
607
608Net::Cmd - Network Command class (as used by FTP, SMTP etc)
609
610=head1 SYNOPSIS
611
612 use Net::Cmd;
686337f3 613
406c51ee 614 @ISA = qw(Net::Cmd);
615
616=head1 DESCRIPTION
617
618C<Net::Cmd> is a collection of methods that can be inherited by a sub class
619of C<IO::Handle>. These methods implement the functionality required for a
620command based protocol, for example FTP and SMTP.
621
622=head1 USER METHODS
623
624These methods provide a user interface to the C<Net::Cmd> object.
625
626=over 4
627
628=item debug ( VALUE )
629
630Set the level of debug information for this object. If C<VALUE> is not given
631then the current state is returned. Otherwise the state is changed to
632C<VALUE> and the previous state returned.
633
510179aa 634Different packages
635may implement different levels of debug but a non-zero value results in
406c51ee 636copies of all commands and responses also being sent to STDERR.
637
638If C<VALUE> is C<undef> then the debug level will be set to the default
639debug level for the class.
640
641This method can also be called as a I<static> method to set/get the default
642debug level for a given class.
643
644=item message ()
645
646Returns the text message returned from the last command
647
648=item code ()
649
650Returns the 3-digit code from the last command. If a command is pending
651then the value 0 is returned
652
653=item ok ()
654
655Returns non-zero if the last code value was greater than zero and
656less than 400. This holds true for most command servers. Servers
657where this does not hold may override this method.
658
659=item status ()
660
661Returns the most significant digit of the current status code. If a command
662is pending then C<CMD_PENDING> is returned.
663
664=item datasend ( DATA )
665
666Send data to the remote server, converting LF to CRLF. Any line starting
667with a '.' will be prefixed with another '.'.
668C<DATA> may be an array or a reference to an array.
669
670=item dataend ()
671
672End the sending of data to the remote server. This is done by ensuring that
673the data already sent ends with CRLF then sending '.CRLF' to end the
674transmission. Once this data has been sent C<dataend> calls C<response> and
675returns true if C<response> returns CMD_OK.
676
677=back
678
679=head1 CLASS METHODS
680
681These methods are not intended to be called by the user, but used or
682over-ridden by a sub-class of C<Net::Cmd>
683
684=over 4
685
686=item debug_print ( DIR, TEXT )
687
688Print debugging information. C<DIR> denotes the direction I<true> being
689data being sent to the server. Calls C<debug_text> before printing to
690STDERR.
691
692=item debug_text ( TEXT )
693
694This method is called to print debugging information. TEXT is
695the text being sent. The method should return the text to be printed
696
697This is primarily meant for the use of modules such as FTP where passwords
698are sent, but we do not want to display them in the debugging information.
699
700=item command ( CMD [, ARGS, ... ])
701
702Send a command to the command server. All arguments a first joined with
703a space character and CRLF is appended, this string is then sent to the
704command server.
705
706Returns undef upon failure
707
708=item unsupported ()
709
710Sets the status code to 580 and the response text to 'Unsupported command'.
711Returns zero.
712
713=item response ()
714
715Obtain a response from the server. Upon success the most significant digit
716of the status code is returned. Upon failure, timeout etc., I<undef> is
717returned.
718
719=item parse_response ( TEXT )
720
721This method is called by C<response> as a method with one argument. It should
722return an array of 2 values, the 3-digit status code and a flag which is true
723when this is part of a multi-line response and this line is not the list.
724
725=item getline ()
726
727Retrieve one line, delimited by CRLF, from the remote server. Returns I<undef>
728upon failure.
729
730B<NOTE>: If you do use this method for any reason, please remember to add
731some C<debug_print> calls into your method.
732
733=item ungetline ( TEXT )
734
735Unget a line of text from the server.
736
dea4d7df 737=item rawdatasend ( DATA )
738
739Send data to the remote server without performing any conversions. C<DATA>
740is a scalar.
741
406c51ee 742=item read_until_dot ()
743
744Read data from the remote server until a line consisting of a single '.'.
745Any lines starting with '..' will have one of the '.'s removed.
746
747Returns a reference to a list containing the lines, or I<undef> upon failure.
748
12df23ee 749=item tied_fh ()
750
751Returns a filehandle tied to the Net::Cmd object. After issuing a
752command, you may read from this filehandle using read() or <>. The
753filehandle will return EOF when the final dot is encountered.
754Similarly, you may write to the filehandle in order to send data to
3c4b39be 755the server after issuing a command that expects data to be written.
12df23ee 756
757See the Net::POP3 and Net::SMTP modules for examples of this.
758
406c51ee 759=back
760
761=head1 EXPORTS
762
763C<Net::Cmd> exports six subroutines, five of these, C<CMD_INFO>, C<CMD_OK>,
510179aa 764C<CMD_MORE>, C<CMD_REJECT> and C<CMD_ERROR>, correspond to possible results
406c51ee 765of C<response> and C<status>. The sixth is C<CMD_PENDING>.
766
767=head1 AUTHOR
768
769Graham Barr <gbarr@pobox.com>
770
771=head1 COPYRIGHT
772
7cf5cf7c 773Copyright (c) 1995-2006 Graham Barr. All rights reserved.
406c51ee 774This program is free software; you can redistribute it and/or modify
775it under the same terms as Perl itself.
776
777=cut