[inseparable changes from patch from perl5.003_12 to perl5.003_13]
[p5sagit/p5-mst-13.2.git] / lib / Net / Cmd.pm
CommitLineData
7e1af8bc 1# Net::Cmd.pm
2#
3# Copyright (c) 1995 Graham Barr <Graham.Barr@tiuk.ti.com>. All rights
4# reserved. 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
9=head1 NAME
10
11Net::Cmd - Network Command class (as used by FTP, SMTP etc)
12
13=head1 SYNOPSIS
14
15 use Net::Cmd;
16
17 @ISA = qw(Net::Cmd);
18
19=head1 DESCRIPTION
20
21C<Net::Cmd> is a collection of methods that can be inherited by a sub class
22of C<IO::Handle>. These methods implement the functionality required for a
23command based protocol, for example FTP and SMTP.
24
25=head1 USER METHODS
26
27These methods provide a user interface to the C<Net::Cmd> object.
28
29=over 4
30
31=item debug ( VALUE )
32
33Set the level of debug information for this object. If C<VALUE> is not given
34then the current state is returned. Otherwise the state is changed to
35C<VALUE> and the previous state returned. If C<VALUE> is C<undef> then
36the debug level will be set to the default debug level for the class.
37
38This method can also be called as a I<static> method to set/get the default
39debug level for a given class.
40
41=item message ()
42
43Returns the text message returned from the last command
44
45=item code ()
46
47Returns the 3-digit code from the last command. If a command is pending
48then the value 0 is returned
49
50=item ok ()
51
52Returns non-zero if the last code value was greater than zero and
53less than 400. This holds true for most command servers. Servers
54where this does not hold may override this method.
55
56=item status ()
57
58Returns the most significant digit of the current status code. If a command
59is pending then C<CMD_PENDING> is returned.
60
61=item datasend ( DATA )
62
63Send data to the remote server, delimiting lines with CRLF. Any lin starting
64with a '.' will be prefixed with another '.'.
65
66=item dataend ()
67
68End the sending of data to the remote server. This is done by ensureing that
69the data already sent ends with CRLF then sending '.CRLF' to end the
70transmission. Once this data has been sent C<dataend> calls C<response> and
71returns true if C<response> returns CMD_OK.
72
73=back
74
75=head1 CLASS METHODS
76
77These methods are not intended to be called by the user, but used or
78over-ridden by a sub-class of C<Net::Cmd>
79
80=over 4
81
82=item debug_print ( DIR, TEXT )
83
84Print debugging information. C<DIR> denotes the direction I<true> being
85data being sent to the server. Calls C<debug_text> before printing to
86STDERR.
87
88=item debug_text ( TEXT )
89
90This method is called to print debugging information. TEXT is
91the text being sent. The method should return the text to be printed
92
93This is primarily meant for the use of modules such as FTP where passwords
94are sent, but we do not want to display them in the debugging information.
95
96=item command ( CMD [, ARGS, ... ])
97
98Send a command to the command server. All arguments a first joined with
99a space character and CRLF is appended, this string is then sent to the
100command server.
101
102Returns undef upon failure
103
104=item unsupported ()
105
106Sets the status code to 580 and the response text to 'Unsupported command'.
107Returns zero.
108
109=item responce ()
110
111Obtain a responce from the server. Upon success the most significant digit
112of the status code is returned. Upon failure, timeout etc., I<undef> is
113returned.
114
115=item parse_response ( TEXT )
116
117This method is called by C<response> as a method with one argument. It should
118return an array of 2 values, the 3-digit status code and a flag which is true
119when this is part of a multi-line response and this line is not the list.
120
121=item getline ()
122
123Retreive one line, delimited by CRLF, from the remote server. Returns I<undef>
124upon failure.
125
126B<NOTE>: If you do use this method for any reason, please remember to add
127some C<debug_print> calls into your method.
128
129=item ungetline ( TEXT )
130
131Unget a line of text from the server.
132
133=item read_until_dot ()
134
135Read data from the remote server until a line consisting of a single '.'.
136Any lines starting with '..' will have one of the '.'s removed.
137
138Returns a reference to a list containing the lines, or I<undef> upon failure.
139
140=back
141
142=head1 EXPORTS
143
144C<Net::Cmd> exports six subroutines, five of these, C<CMD_INFO>, C<CMD_OK>,
145C<CMD_MORE>, C<CMD_REJECT> and C<CMD_ERROR> ,correspond to possible results
146of C<response> and C<status>. The sixth is C<CMD_PENDING>.
147
148=head1 AUTHOR
149
150Graham Barr <Graham.Barr@tiuk.ti.com>
151
152=head1 REVISION
153
154$Revision: 2.2 $
155
156=head1 COPYRIGHT
157
158Copyright (c) 1995 Graham Barr. All rights reserved. This program is free
159software; you can redistribute it and/or modify it under the same terms
160as Perl itself.
161
162=cut
163
164require 5.001;
165require Exporter;
166
167use strict;
168use vars qw(@ISA @EXPORT $VERSION);
169use Carp;
170
171$VERSION = sprintf("%d.%02d", q$Revision: 2.2 $ =~ /(\d+)\.(\d+)/);
172@ISA = qw(Exporter);
173@EXPORT = qw(CMD_INFO CMD_OK CMD_MORE CMD_REJECT CMD_ERROR CMD_PENDING);
174
175sub CMD_INFO { 1 }
176sub CMD_OK { 2 }
177sub CMD_MORE { 3 }
178sub CMD_REJECT { 4 }
179sub CMD_ERROR { 5 }
180sub CMD_PENDING { 0 }
181
182my %debug = ();
183
184sub _print_isa
185{
186 no strict qw(refs);
187
188 my $pkg = shift;
189 my $cmd = $pkg;
190
191 $debug{$pkg} ||= 0;
192
193 my %done = ();
194 my @do = ($pkg);
195 my %spc = ( $pkg , "");
196
197 print STDERR "\n";
198 while ($pkg = shift @do)
199 {
200 next if defined $done{$pkg};
201
202 $done{$pkg} = 1;
203
204 my $v = defined ${"${pkg}::VERSION"}
205 ? "(" . ${"${pkg}::VERSION"} . ")"
206 : "";
207
208 my $spc = $spc{$pkg};
209 print STDERR "$cmd: ${spc}${pkg}${v}\n";
210
211 if(defined @{"${pkg}::ISA"})
212 {
213 @spc{@{"${pkg}::ISA"}} = (" " . $spc{$pkg}) x @{"${pkg}::ISA"};
214 unshift(@do, @{"${pkg}::ISA"});
215 }
216 }
217
218 print STDERR "\n";
219}
220
221sub debug
222{
223 @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([LEVEL])';
224
225 my($cmd,$level) = @_;
226 my $pkg = ref($cmd) || $cmd;
227 my $oldval = 0;
228
229 if(ref($cmd))
230 {
231 $oldval = ${*$cmd}{'net_cmd_debug'} || 0;
232 }
233 else
234 {
235 $oldval = $debug{$pkg} || 0;
236 }
237
238 return $oldval
239 unless @_ == 2;
240
241 $level = $debug{$pkg} || 0
242 unless defined $level;
243
244 _print_isa($pkg)
245 if($level && !exists $debug{$pkg});
246
247 if(ref($cmd))
248 {
249 ${*$cmd}{'net_cmd_debug'} = $level;
250 }
251 else
252 {
253 $debug{$pkg} = $level;
254 }
255
256 $oldval;
257}
258
259sub message
260{
261 @_ == 1 or croak 'usage: $obj->message()';
262
263 my $cmd = shift;
264
265 wantarray ? @{${*$cmd}{'net_cmd_resp'}}
266 : join("", @{${*$cmd}{'net_cmd_resp'}});
267}
268
269sub debug_text { $_[2] }
270
271sub debug_print
272{
273 my($cmd,$out,$text) = @_;
274 print STDERR $cmd,($out ? '>>> ' : '<<< '), $cmd->debug_text($out,$text);
275}
276
277sub code
278{
279 @_ == 1 or croak 'usage: $obj->code()';
280
281 my $cmd = shift;
282
283 ${*$cmd}{'net_cmd_code'};
284}
285
286sub status
287{
288 @_ == 1 or croak 'usage: $obj->code()';
289
290 my $cmd = shift;
291
292 substr(${*$cmd}{'net_cmd_code'},0,1);
293}
294
295sub set_status
296{
297 @_ == 3 or croak 'usage: $obj->set_status( CODE, MESSAGE)';
298
299 my $cmd = shift;
300
301 (${*$cmd}{'net_cmd_code'},${*$cmd}{'net_cmd_resp'}) = @_;
302
303 1;
304}
305
306sub command
307{
308 my $cmd = shift;
309
310 $cmd->dataend()
311 if(exists ${*$cmd}{'net_cmd_lastch'});
312
313 if (scalar(@_))
314 {
315 my $str = join(" ", @_) . "\015\012";
316
317 syswrite($cmd,$str,length $str);
318
319 $cmd->debug_print(1,$str)
320 if($cmd->debug);
321
322 ${*$cmd}{'net_cmd_resp'} = []; # the responce
323 ${*$cmd}{'net_cmd_code'} = "000"; # Made this one up :-)
324 }
325
326 $cmd;
327}
328
329sub ok
330{
331 @_ == 1 or croak 'usage: $obj->ok()';
332
333 my $code = $_[0]->code;
334 0 < $code && $code < 400;
335}
336
337sub unsupported
338{
339 my $cmd = shift;
340
341 ${*$cmd}{'net_cmd_resp'} = [ 'Unsupported command' ];
342 ${*$cmd}{'net_cmd_code'} = 580;
343 0;
344}
345
346sub getline
347{
348 my $cmd = shift;
349
350 ${*$cmd}{'net_cmd_lines'} ||= [];
351
352 return shift @{${*$cmd}{'net_cmd_lines'}}
353 if scalar(@{${*$cmd}{'net_cmd_lines'}});
354
355 my $partial = ${*$cmd}{'net_cmd_partial'} || "";
356
357 my $rin = "";
358 vec($rin,fileno($cmd),1) = 1;
359
360 my $buf;
361
362 until(scalar(@{${*$cmd}{'net_cmd_lines'}}))
363 {
364 my $timeout = $cmd->timeout || undef;
365 my $rout;
366 if (select($rout=$rin, undef, undef, $timeout))
367 {
368 unless (sysread($cmd, $buf="", 1024))
369 {
370 carp ref($cmd) . ": Unexpected EOF on command channel";
371 return undef;
372 }
373
374 substr($buf,0,0) = $partial; ## prepend from last sysread
375
376 my @buf = split(/\015?\012/, $buf); ## break into lines
377
378 $partial = length($buf) == 0 || substr($buf, -1, 1) eq "\012"
379 ? ''
380 : pop(@buf);
381
382 map { $_ .= "\n" } @buf;
383
384 push(@{${*$cmd}{'net_cmd_lines'}},@buf);
385
386 }
387 else
388 {
389 carp "$cmd: Timeout" if($cmd->debug);
390 return undef;
391 }
392 }
393
394 ${*$cmd}{'net_cmd_partial'} = $partial;
395
396 shift @{${*$cmd}{'net_cmd_lines'}};
397}
398
399sub ungetline
400{
401 my($cmd,$str) = @_;
402
403 ${*$cmd}{'net_cmd_lines'} ||= [];
404 unshift(@{${*$cmd}{'net_cmd_lines'}}, $str);
405}
406
407sub parse_response
408{
409 return ()
410 unless $_[1] =~ s/^(\d\d\d)(.)//o;
411 ($1, $2 eq "-");
412}
413
414sub response
415{
416 my $cmd = shift;
417 my($code,$more) = (undef) x 2;
418
419 ${*$cmd}{'net_cmd_resp'} ||= [];
420
421 while(1)
422 {
423 my $str = $cmd->getline();
424
425 $cmd->debug_print(0,$str)
426 if ($cmd->debug);
427
428 if($str =~ s/^(\d\d\d)(.?)//o)
429 {
430 ($code,$more) = ($1,$2 && $2 eq "-");
431 }
432 elsif(!$more)
433 {
434 $cmd->ungetline($str);
435 last;
436 }
437
438 push(@{${*$cmd}{'net_cmd_resp'}},$str);
439
440 last unless($more);
441 }
442
443 ${*$cmd}{'net_cmd_code'} = $code;
444
445 substr($code,0,1);
446}
447
448sub read_until_dot
449{
450 my $cmd = shift;
451 my $arr = [];
452
453 while(1)
454 {
455 my $str = $cmd->getline();
456
457 $cmd->debug_print(0,$str)
458 if ($cmd->debug & 4);
459
460 last if($str =~ /^\.\n/o);
461
462 $str =~ s/^\.\././o;
463
464 push(@$arr,$str);
465 }
466
467 $arr;
468}
469
470sub datasend
471{
472 my $cmd = shift;
473 my $lch = exists ${*$cmd}{'net_cmd_lastch'} ? ${*$cmd}{'net_cmd_lastch'}
474 : " ";
475 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
476 my $line = $lch . join("" ,@$arr);
477
478 ${*$cmd}{'net_cmd_lastch'} = substr($line,-1,1);
479
480 return 1
481 unless length($line) > 1;
482
483 if($cmd->debug)
484 {
485 my $ln = substr($line,1);
486 my $b = "$cmd>>> ";
487 print STDERR $b,join("\n$b",split(/\n/,$ln)),"\n";
488 }
489
490 $line =~ s/\n/\015\012/sgo;
491 $line =~ s/(?=\012\.)/./sgo;
492
493 my $len = length($line) - 1;
494
495 return $len < 1 ||
496 syswrite($cmd, $line, $len, 1) == $len;
497}
498
499sub dataend
500{
501 my $cmd = shift;
502
503 return 1
504 unless(exists ${*$cmd}{'net_cmd_lastch'});
505
506 if(${*$cmd}{'net_cmd_lastch'} eq "\015")
507 {
508 syswrite($cmd,"\012",1);
509 print STDERR "\n"
510 if($cmd->debug);
511 }
512 elsif(${*$cmd}{'net_cmd_lastch'} ne "\012")
513 {
514 syswrite($cmd,"\015\012",2);
515 print STDERR "\n"
516 if($cmd->debug);
517 }
518
519 print STDERR "$cmd>>> .\n"
520 if($cmd->debug);
521
522 syswrite($cmd,".\015\012",3);
523
524 delete ${*$cmd}{'net_cmd_lastch'};
525
526 $cmd->response() == CMD_OK;
527}
528
5291;