Deprecate assignment to $[
[p5sagit/p5-mst-13.2.git] / lib / CPAN / FTP.pm
CommitLineData
f9916dde 1# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*-
2# vim: ts=4 sts=4 sw=4:
3package CPAN::FTP;
4use strict;
5
6use Fcntl qw(:flock);
7use CPAN::FTP::netrc;
8use vars qw($connect_to_internet_ok $Ua $Thesite $ThesiteURL $Themethod);
9@CPAN::FTP::ISA = qw(CPAN::Debug);
10
11use vars qw(
12 $VERSION
13);
14$VERSION = "5.5";
15
16#-> sub CPAN::FTP::ftp_statistics
17# if they want to rewrite, they need to pass in a filehandle
18sub _ftp_statistics {
19 my($self,$fh) = @_;
20 my $locktype = $fh ? LOCK_EX : LOCK_SH;
21 $fh ||= FileHandle->new;
22 my $file = File::Spec->catfile($CPAN::Config->{cpan_home},"FTPstats.yml");
23 open $fh, "+>>$file" or $CPAN::Frontend->mydie("Could not open '$file': $!");
24 my $sleep = 1;
25 my $waitstart;
26 while (!CPAN::_flock($fh, $locktype|LOCK_NB)) {
27 $waitstart ||= localtime();
28 if ($sleep>3) {
29 $CPAN::Frontend->mywarn("Waiting for a read lock on '$file' (since $waitstart)\n");
30 }
31 $CPAN::Frontend->mysleep($sleep);
32 if ($sleep <= 3) {
33 $sleep+=0.33;
34 } elsif ($sleep <=6) {
35 $sleep+=0.11;
36 }
37 }
38 my $stats = eval { CPAN->_yaml_loadfile($file); };
39 if ($@) {
40 if (ref $@) {
41 if (ref $@ eq "CPAN::Exception::yaml_not_installed") {
42 $CPAN::Frontend->myprint("Warning (usually harmless): $@");
43 return;
44 } elsif (ref $@ eq "CPAN::Exception::yaml_process_error") {
45 $CPAN::Frontend->mydie($@);
46 }
47 } else {
48 $CPAN::Frontend->mydie($@);
49 }
50 }
51 return $stats->[0];
52}
53
54#-> sub CPAN::FTP::_mytime
55sub _mytime () {
56 if (CPAN->has_inst("Time::HiRes")) {
57 return Time::HiRes::time();
58 } else {
59 return time;
60 }
61}
62
63#-> sub CPAN::FTP::_new_stats
64sub _new_stats {
65 my($self,$file) = @_;
66 my $ret = {
67 file => $file,
68 attempts => [],
69 start => _mytime,
70 };
71 $ret;
72}
73
74#-> sub CPAN::FTP::_add_to_statistics
75sub _add_to_statistics {
76 my($self,$stats) = @_;
77 my $yaml_module = CPAN::_yaml_module();
78 $self->debug("yaml_module[$yaml_module]") if $CPAN::DEBUG;
79 if ($CPAN::META->has_inst($yaml_module)) {
80 $stats->{thesiteurl} = $ThesiteURL;
81 $stats->{end} = CPAN::FTP::_mytime();
82 my $fh = FileHandle->new;
83 my $time = time;
84 my $sdebug = 0;
85 my @debug;
86 @debug = $time if $sdebug;
87 my $fullstats = $self->_ftp_statistics($fh);
88 close $fh;
89 $fullstats->{history} ||= [];
90 push @debug, scalar @{$fullstats->{history}} if $sdebug;
91 push @debug, time if $sdebug;
92 push @{$fullstats->{history}}, $stats;
93 # YAML.pm 0.62 is unacceptably slow with 999;
94 # YAML::Syck 0.82 has no noticable performance problem with 999;
95 my $ftpstats_size = $CPAN::Config->{ftpstats_size} || 99;
96 my $ftpstats_period = $CPAN::Config->{ftpstats_period} || 14;
97 while (
98 @{$fullstats->{history}} > $ftpstats_size
99 || $time - $fullstats->{history}[0]{start} > 86400*$ftpstats_period
100 ) {
101 shift @{$fullstats->{history}}
102 }
103 push @debug, scalar @{$fullstats->{history}} if $sdebug;
104 push @debug, time if $sdebug;
105 push @debug, scalar localtime($fullstats->{history}[0]{start}) if $sdebug;
106 # need no eval because if this fails, it is serious
107 my $sfile = File::Spec->catfile($CPAN::Config->{cpan_home},"FTPstats.yml");
108 CPAN->_yaml_dumpfile("$sfile.$$",$fullstats);
109 if ( $sdebug ) {
110 local $CPAN::DEBUG = 512; # FTP
111 push @debug, time;
112 CPAN->debug(sprintf("DEBUG history: before_read[%d]before[%d]at[%d]".
113 "after[%d]at[%d]oldest[%s]dumped backat[%d]",
114 @debug,
115 ));
116 }
117 # Win32 cannot rename a file to an existing filename
118 unlink($sfile) if ($^O eq 'MSWin32');
119 _copy_stat($sfile, "$sfile.$$") if -e $sfile;
120 rename "$sfile.$$", $sfile
121 or $CPAN::Frontend->mydie("Could not rename '$sfile.$$' to '$sfile': $!\n");
122 }
123}
124
125# Copy some stat information (owner, group, mode and) from one file to
126# another.
127# This is a utility function which might be moved to a utility repository.
128#-> sub CPAN::FTP::_copy_stat
129sub _copy_stat {
130 my($src, $dest) = @_;
131 my @stat = stat($src);
132 if (!@stat) {
133 $CPAN::Frontend->mywarn("Can't stat '$src': $!\n");
134 return;
135 }
136
137 eval {
138 chmod $stat[2], $dest
139 or $CPAN::Frontend->mywarn("Can't chmod '$dest' to " . sprintf("0%o", $stat[2]) . ": $!\n");
140 };
141 warn $@ if $@;
142 eval {
143 chown $stat[4], $stat[5], $dest
144 or do {
145 my $save_err = $!; # otherwise it's lost in the get... calls
146 $CPAN::Frontend->mywarn("Can't chown '$dest' to " .
147 (getpwuid($stat[4]))[0] . "/" .
148 (getgrgid($stat[5]))[0] . ": $save_err\n"
149 );
150 };
151 };
152 warn $@ if $@;
153}
154
155# if file is CHECKSUMS, suggest the place where we got the file to be
156# checked from, maybe only for young files?
157#-> sub CPAN::FTP::_recommend_url_for
158sub _recommend_url_for {
159 my($self, $file) = @_;
160 my $urllist = $self->_get_urllist;
161 if ($file =~ s|/CHECKSUMS(.gz)?$||) {
162 my $fullstats = $self->_ftp_statistics();
163 my $history = $fullstats->{history} || [];
164 while (my $last = pop @$history) {
165 last if $last->{end} - time > 3600; # only young results are interesting
166 next unless $last->{file}; # dirname of nothing dies!
167 next unless $file eq File::Basename::dirname($last->{file});
168 return $last->{thesiteurl};
169 }
170 }
171 if ($CPAN::Config->{randomize_urllist}
172 &&
173 rand(1) < $CPAN::Config->{randomize_urllist}
174 ) {
175 $urllist->[int rand scalar @$urllist];
176 } else {
177 return ();
178 }
179}
180
181#-> sub CPAN::FTP::_get_urllist
182sub _get_urllist {
183 my($self) = @_;
184 $CPAN::Config->{urllist} ||= [];
185 unless (ref $CPAN::Config->{urllist} eq 'ARRAY') {
186 $CPAN::Frontend->mywarn("Malformed urllist; ignoring. Configuration file corrupt?\n");
187 $CPAN::Config->{urllist} = [];
188 }
189 my @urllist = grep { defined $_ and length $_ } @{$CPAN::Config->{urllist}};
190 for my $u (@urllist) {
191 CPAN->debug("u[$u]") if $CPAN::DEBUG;
192 if (UNIVERSAL::can($u,"text")) {
193 $u->{TEXT} .= "/" unless substr($u->{TEXT},-1) eq "/";
194 } else {
195 $u .= "/" unless substr($u,-1) eq "/";
196 $u = CPAN::URL->new(TEXT => $u, FROM => "USER");
197 }
198 }
199 \@urllist;
200}
201
202#-> sub CPAN::FTP::ftp_get ;
203sub ftp_get {
204 my($class,$host,$dir,$file,$target) = @_;
205 $class->debug(
206 qq[Going to fetch file [$file] from dir [$dir]
207 on host [$host] as local [$target]\n]
208 ) if $CPAN::DEBUG;
209 my $ftp = Net::FTP->new($host);
210 unless ($ftp) {
211 $CPAN::Frontend->mywarn(" Could not connect to host '$host' with Net::FTP\n");
212 return;
213 }
214 return 0 unless defined $ftp;
215 $ftp->debug(1) if $CPAN::DEBUG{'FTP'} & $CPAN::DEBUG;
216 $class->debug(qq[Going to login("anonymous","$Config::Config{cf_email}")]);
217 unless ( $ftp->login("anonymous",$Config::Config{'cf_email'}) ) {
218 my $msg = $ftp->message;
219 $CPAN::Frontend->mywarn(" Couldn't login on $host: $msg");
220 return;
221 }
222 unless ( $ftp->cwd($dir) ) {
223 my $msg = $ftp->message;
224 $CPAN::Frontend->mywarn(" Couldn't cwd $dir: $msg");
225 return;
226 }
227 $ftp->binary;
228 $class->debug(qq[Going to ->get("$file","$target")\n]) if $CPAN::DEBUG;
229 unless ( $ftp->get($file,$target) ) {
230 my $msg = $ftp->message;
231 $CPAN::Frontend->mywarn(" Couldn't fetch $file from $host: $msg");
232 return;
233 }
234 $ftp->quit; # it's ok if this fails
235 return 1;
236}
237
238# If more accuracy is wanted/needed, Chris Leach sent me this patch...
239
240 # > *** /install/perl/live/lib/CPAN.pm- Wed Sep 24 13:08:48 1997
241 # > --- /tmp/cp Wed Sep 24 13:26:40 1997
242 # > ***************
243 # > *** 1562,1567 ****
244 # > --- 1562,1580 ----
245 # > return 1 if substr($url,0,4) eq "file";
246 # > return 1 unless $url =~ m|://([^/]+)|;
247 # > my $host = $1;
248 # > + my $proxy = $CPAN::Config->{'http_proxy'} || $ENV{'http_proxy'};
249 # > + if ($proxy) {
250 # > + $proxy =~ m|://([^/:]+)|;
251 # > + $proxy = $1;
252 # > + my $noproxy = $CPAN::Config->{'no_proxy'} || $ENV{'no_proxy'};
253 # > + if ($noproxy) {
254 # > + if ($host !~ /$noproxy$/) {
255 # > + $host = $proxy;
256 # > + }
257 # > + } else {
258 # > + $host = $proxy;
259 # > + }
260 # > + }
261 # > require Net::Ping;
262 # > return 1 unless $Net::Ping::VERSION >= 2;
263 # > my $p;
264
265
266#-> sub CPAN::FTP::localize ;
267sub localize {
268 my($self,$file,$aslocal,$force) = @_;
269 $force ||= 0;
270 Carp::croak( "Usage: ->localize(cpan_file,as_local_file[,$force])" )
271 unless defined $aslocal;
272 $self->debug("file[$file] aslocal[$aslocal] force[$force]")
273 if $CPAN::DEBUG;
274
275 if ($^O eq 'MacOS') {
276 # Comment by AK on 2000-09-03: Uniq short filenames would be
277 # available in CHECKSUMS file
278 my($name, $path) = File::Basename::fileparse($aslocal, '');
279 if (length($name) > 31) {
280 $name =~ s/(
281 \.(
282 readme(\.(gz|Z))? |
283 (tar\.)?(gz|Z) |
284 tgz |
285 zip |
286 pm\.(gz|Z)
287 )
288 )$//x;
289 my $suf = $1;
290 my $size = 31 - length($suf);
291 while (length($name) > $size) {
292 chop $name;
293 }
294 $name .= $suf;
295 $aslocal = File::Spec->catfile($path, $name);
296 }
297 }
298
299 if (-f $aslocal && -r _ && !($force & 1)) {
300 my $size;
301 if ($size = -s $aslocal) {
302 $self->debug("aslocal[$aslocal]size[$size]") if $CPAN::DEBUG;
303 return $aslocal;
304 } else {
305 # empty file from a previous unsuccessful attempt to download it
306 unlink $aslocal or
307 $CPAN::Frontend->mydie("Found a zero-length '$aslocal' that I ".
308 "could not remove.");
309 }
310 }
311 my($maybe_restore) = 0;
312 if (-f $aslocal) {
313 rename $aslocal, "$aslocal.bak$$";
314 $maybe_restore++;
315 }
316
317 my($aslocal_dir) = File::Basename::dirname($aslocal);
318 $self->mymkpath($aslocal_dir); # too early for file URLs / RT #28438
319 # Inheritance is not easier to manage than a few if/else branches
320 if ($CPAN::META->has_usable('LWP::UserAgent')) {
321 unless ($Ua) {
322 CPAN::LWP::UserAgent->config;
323 eval {$Ua = CPAN::LWP::UserAgent->new;}; # Why is has_usable still not fit enough?
324 if ($@) {
325 $CPAN::Frontend->mywarn("CPAN::LWP::UserAgent->new dies with $@\n")
326 if $CPAN::DEBUG;
327 } else {
328 my($var);
329 $Ua->proxy('ftp', $var)
330 if $var = $CPAN::Config->{ftp_proxy} || $ENV{ftp_proxy};
331 $Ua->proxy('http', $var)
332 if $var = $CPAN::Config->{http_proxy} || $ENV{http_proxy};
333 $Ua->no_proxy($var)
334 if $var = $CPAN::Config->{no_proxy} || $ENV{no_proxy};
335 }
336 }
337 }
338 for my $prx (qw(ftp_proxy http_proxy no_proxy)) {
339 $ENV{$prx} = $CPAN::Config->{$prx} if $CPAN::Config->{$prx};
340 }
341
342 # Try the list of urls for each single object. We keep a record
343 # where we did get a file from
344 my(@reordered,$last);
345 my $ccurllist = $self->_get_urllist;
346 $last = $#$ccurllist;
347 if ($force & 2) { # local cpans probably out of date, don't reorder
348 @reordered = (0..$last);
349 } else {
350 @reordered =
351 sort {
352 (substr($ccurllist->[$b],0,4) eq "file")
353 <=>
354 (substr($ccurllist->[$a],0,4) eq "file")
355 or
356 defined($ThesiteURL)
357 and
358 ($ccurllist->[$b] eq $ThesiteURL)
359 <=>
360 ($ccurllist->[$a] eq $ThesiteURL)
361 } 0..$last;
362 }
363 my(@levels);
364 $Themethod ||= "";
365 $self->debug("Themethod[$Themethod]reordered[@reordered]") if $CPAN::DEBUG;
366 my @all_levels = (
367 ["dleasy", "file"],
368 ["dleasy"],
369 ["dlhard"],
370 ["dlhardest"],
371 ["dleasy", "http","defaultsites"],
372 ["dlhard", "http","defaultsites"],
373 ["dleasy", "ftp", "defaultsites"],
374 ["dlhard", "ftp", "defaultsites"],
375 ["dlhardest","", "defaultsites"],
376 );
377 if ($Themethod) {
378 @levels = grep {$_->[0] eq $Themethod} @all_levels;
379 push @levels, grep {$_->[0] ne $Themethod} @all_levels;
380 } else {
381 @levels = @all_levels;
382 }
383 @levels = qw/dleasy/ if $^O eq 'MacOS';
384 my($levelno);
385 local $ENV{FTP_PASSIVE} =
386 exists $CPAN::Config->{ftp_passive} ?
387 $CPAN::Config->{ftp_passive} : 1;
388 my $ret;
389 my $stats = $self->_new_stats($file);
390 for ($CPAN::Config->{connect_to_internet_ok}) {
391 $connect_to_internet_ok = $_ if not defined $connect_to_internet_ok and defined $_;
392 }
393 LEVEL: for $levelno (0..$#levels) {
394 my $level_tuple = $levels[$levelno];
395 my($level,$scheme,$sitetag) = @$level_tuple;
396 my $defaultsites = $sitetag && $sitetag eq "defaultsites";
397 my @urllist;
398 if ($defaultsites) {
399 unless (defined $connect_to_internet_ok) {
400 $CPAN::Frontend->myprint(sprintf qq{
401I would like to connect to one of the following sites to get '%s':
402
403%s
404},
405 $file,
406 join("",map { " ".$_->text."\n" } @CPAN::Defaultsites),
407 );
408 my $answer = CPAN::Shell::colorable_makemaker_prompt("Is it OK to try to connect to the Internet?", "yes");
409 if ($answer =~ /^y/i) {
410 $connect_to_internet_ok = 1;
411 } else {
412 $connect_to_internet_ok = 0;
413 }
414 }
415 if ($connect_to_internet_ok) {
416 @urllist = @CPAN::Defaultsites;
417 } else {
418 my $sleep = 5;
419 $CPAN::Frontend->mywarn(sprintf qq{
420
421You have not configured a urllist and did not allow to connect to the
422internet. I will continue but it is very likely that we will face
423problems. If this happens, please consider to call either
424
425 o conf init connect_to_internet_ok
426or
427 o conf init urllist
428
429Sleeping $sleep seconds now.
430});
431 $CPAN::Frontend->mysleep($sleep);
432 @urllist = ();
433 }
434 } else {
435 my @host_seq = $level =~ /dleasy/ ?
436 @reordered : 0..$last; # reordered has file and $Thesiteurl first
437 @urllist = map { $ccurllist->[$_] } @host_seq;
438 }
439 $self->debug("synth. urllist[@urllist]") if $CPAN::DEBUG;
440 my $aslocal_tempfile = $aslocal . ".tmp" . $$;
441 if (my $recommend = $self->_recommend_url_for($file)) {
442 @urllist = grep { $_ ne $recommend } @urllist;
443 unshift @urllist, $recommend;
444 }
445 $self->debug("synth. urllist[@urllist]") if $CPAN::DEBUG;
446 $ret = $self->hostdlxxx($level,$scheme,\@urllist,$file,$aslocal_tempfile,$stats);
447 if ($ret) {
448 CPAN->debug("ret[$ret]aslocal[$aslocal]") if $CPAN::DEBUG;
449 if ($ret eq $aslocal_tempfile) {
450 # if we got it exactly as we asked for, only then we
451 # want to rename
452 rename $aslocal_tempfile, $aslocal
453 or $CPAN::Frontend->mydie("Error while trying to rename ".
454 "'$ret' to '$aslocal': $!");
455 $ret = $aslocal;
456 }
457 $Themethod = $level;
458 my $now = time;
459 # utime $now, $now, $aslocal; # too bad, if we do that, we
460 # might alter a local mirror
461 $self->debug("level[$level]") if $CPAN::DEBUG;
462 last LEVEL;
463 } else {
464 unlink $aslocal_tempfile;
465 last if $CPAN::Signal; # need to cleanup
466 }
467 }
468 if ($ret) {
469 $stats->{filesize} = -s $ret;
470 }
471 $self->debug("before _add_to_statistics") if $CPAN::DEBUG;
472 $self->_add_to_statistics($stats);
473 $self->debug("after _add_to_statistics") if $CPAN::DEBUG;
474 if ($ret) {
475 unlink "$aslocal.bak$$";
476 return $ret;
477 }
478 unless ($CPAN::Signal) {
479 my(@mess);
480 local $" = " ";
481 if (@{$CPAN::Config->{urllist}}) {
482 push @mess,
483 qq{Please check, if the URLs I found in your configuration file \(}.
484 join(", ", @{$CPAN::Config->{urllist}}).
485 qq{\) are valid.};
486 } else {
487 push @mess, qq{Your urllist is empty!};
488 }
489 push @mess, qq{The urllist can be edited.},
490 qq{E.g. with 'o conf urllist push ftp://myurl/'};
491 $CPAN::Frontend->mywarn(Text::Wrap::wrap("","","@mess"). "\n\n");
492 $CPAN::Frontend->mywarn("Could not fetch $file\n");
493 $CPAN::Frontend->mysleep(2);
494 }
495 if ($maybe_restore) {
496 rename "$aslocal.bak$$", $aslocal;
497 $CPAN::Frontend->myprint("Trying to get away with old file:\n" .
498 $self->ls($aslocal));
499 return $aslocal;
500 }
501 return;
502}
503
504sub mymkpath {
505 my($self, $aslocal_dir) = @_;
506 File::Path::mkpath($aslocal_dir);
507 $CPAN::Frontend->mywarn(qq{Warning: You are not allowed to write into }.
508 qq{directory "$aslocal_dir".
509 I\'ll continue, but if you encounter problems, they may be due
510 to insufficient permissions.\n}) unless -w $aslocal_dir;
511}
512
513sub hostdlxxx {
514 my $self = shift;
515 my $level = shift;
516 my $scheme = shift;
517 my $h = shift;
518 $h = [ grep /^\Q$scheme\E:/, @$h ] if $scheme;
519 my $method = "host$level";
520 $self->$method($h, @_);
521}
522
523sub _set_attempt {
524 my($self,$stats,$method,$url) = @_;
525 push @{$stats->{attempts}}, {
526 method => $method,
527 start => _mytime,
528 url => $url,
529 };
530}
531
532# package CPAN::FTP;
533sub hostdleasy {
534 my($self,$host_seq,$file,$aslocal,$stats) = @_;
535 my($ro_url);
536 HOSTEASY: for $ro_url (@$host_seq) {
537 $self->_set_attempt($stats,"dleasy",$ro_url);
538 my $url .= "$ro_url$file";
539 $self->debug("localizing perlish[$url]") if $CPAN::DEBUG;
540 if ($url =~ /^file:/) {
541 my $l;
542 if ($CPAN::META->has_inst('URI::URL')) {
543 my $u = URI::URL->new($url);
544 $l = $u->path;
545 } else { # works only on Unix, is poorly constructed, but
546 # hopefully better than nothing.
547 # RFC 1738 says fileurl BNF is
548 # fileurl = "file://" [ host | "localhost" ] "/" fpath
549 # Thanks to "Mark D. Baushke" <mdb@cisco.com> for
550 # the code
551 ($l = $url) =~ s|^file://[^/]*/|/|; # discard the host part
552 $l =~ s|^file:||; # assume they
553 # meant
554 # file://localhost
555 $l =~ s|^/||s
556 if ! -f $l && $l =~ m|^/\w:|; # e.g. /P:
557 }
558 $self->debug("local file[$l]") if $CPAN::DEBUG;
559 if ( -f $l && -r _) {
560 $ThesiteURL = $ro_url;
561 return $l;
562 }
563 if ($l =~ /(.+)\.gz$/) {
564 my $ungz = $1;
565 if ( -f $ungz && -r _) {
566 $ThesiteURL = $ro_url;
567 return $ungz;
568 }
569 }
570 # Maybe mirror has compressed it?
571 if (-f "$l.gz") {
572 $self->debug("found compressed $l.gz") if $CPAN::DEBUG;
573 eval { CPAN::Tarzip->new("$l.gz")->gunzip($aslocal) };
574 if ( -f $aslocal) {
575 $ThesiteURL = $ro_url;
576 return $aslocal;
577 }
578 }
579 $CPAN::Frontend->mywarn("Could not find '$l'\n");
580 }
581 $self->debug("it was not a file URL") if $CPAN::DEBUG;
582 if ($CPAN::META->has_usable('LWP')) {
583 $CPAN::Frontend->myprint("Fetching with LWP:
584 $url
585");
586 unless ($Ua) {
587 CPAN::LWP::UserAgent->config;
588 eval { $Ua = CPAN::LWP::UserAgent->new; };
589 if ($@) {
590 $CPAN::Frontend->mywarn("CPAN::LWP::UserAgent->new dies with $@\n");
591 }
592 }
593 my $res = $Ua->mirror($url, $aslocal);
594 if ($res->is_success) {
595 $ThesiteURL = $ro_url;
596 my $now = time;
597 utime $now, $now, $aslocal; # download time is more
598 # important than upload
599 # time
600 return $aslocal;
601 } elsif ($url !~ /\.gz(?!\n)\Z/) {
602 my $gzurl = "$url.gz";
603 $CPAN::Frontend->myprint("Fetching with LWP:
604 $gzurl
605");
606 $res = $Ua->mirror($gzurl, "$aslocal.gz");
607 if ($res->is_success) {
608 if (eval {CPAN::Tarzip->new("$aslocal.gz")->gunzip($aslocal)}) {
609 $ThesiteURL = $ro_url;
610 return $aslocal;
611 }
612 }
613 } else {
614 $CPAN::Frontend->myprint(sprintf(
615 "LWP failed with code[%s] message[%s]\n",
616 $res->code,
617 $res->message,
618 ));
619 # Alan Burlison informed me that in firewall environments
620 # Net::FTP can still succeed where LWP fails. So we do not
621 # skip Net::FTP anymore when LWP is available.
622 }
623 } else {
624 $CPAN::Frontend->mywarn(" LWP not available\n");
625 }
626 return if $CPAN::Signal;
627 if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) {
628 # that's the nice and easy way thanks to Graham
629 $self->debug("recognized ftp") if $CPAN::DEBUG;
630 my($host,$dir,$getfile) = ($1,$2,$3);
631 if ($CPAN::META->has_usable('Net::FTP')) {
632 $dir =~ s|/+|/|g;
633 $CPAN::Frontend->myprint("Fetching with Net::FTP:
634 $url
635");
636 $self->debug("getfile[$getfile]dir[$dir]host[$host]" .
637 "aslocal[$aslocal]") if $CPAN::DEBUG;
638 if (CPAN::FTP->ftp_get($host,$dir,$getfile,$aslocal)) {
639 $ThesiteURL = $ro_url;
640 return $aslocal;
641 }
642 if ($aslocal !~ /\.gz(?!\n)\Z/) {
643 my $gz = "$aslocal.gz";
644 $CPAN::Frontend->myprint("Fetching with Net::FTP
645 $url.gz
646");
647 if (CPAN::FTP->ftp_get($host,
648 $dir,
649 "$getfile.gz",
650 $gz) &&
651 eval{CPAN::Tarzip->new($gz)->gunzip($aslocal)}
652 ) {
653 $ThesiteURL = $ro_url;
654 return $aslocal;
655 }
656 }
657 # next HOSTEASY;
658 } else {
659 CPAN->debug("Net::FTP does not count as usable atm") if $CPAN::DEBUG;
660 }
661 }
662 if (
663 UNIVERSAL::can($ro_url,"text")
664 and
665 $ro_url->{FROM} eq "USER"
666 ) {
667 ##address #17973: default URLs should not try to override
668 ##user-defined URLs just because LWP is not available
669 my $ret = $self->hostdlhard([$ro_url],$file,$aslocal,$stats);
670 return $ret if $ret;
671 }
672 return if $CPAN::Signal;
673 }
674}
675
676# package CPAN::FTP;
677sub hostdlhard {
678 my($self,$host_seq,$file,$aslocal,$stats) = @_;
679
680 # Came back if Net::FTP couldn't establish connection (or
681 # failed otherwise) Maybe they are behind a firewall, but they
682 # gave us a socksified (or other) ftp program...
683
684 my($ro_url);
685 my($devnull) = $CPAN::Config->{devnull} || "";
686 # < /dev/null ";
687 my($aslocal_dir) = File::Basename::dirname($aslocal);
688 File::Path::mkpath($aslocal_dir);
689 HOSTHARD: for $ro_url (@$host_seq) {
690 $self->_set_attempt($stats,"dlhard",$ro_url);
691 my $url = "$ro_url$file";
692 my($proto,$host,$dir,$getfile);
693
694 # Courtesy Mark Conty mark_conty@cargill.com change from
695 # if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) {
696 # to
697 if ($url =~ m|^([^:]+)://(.*?)/(.*)/(.*)|) {
698 # proto not yet used
699 ($proto,$host,$dir,$getfile) = ($1,$2,$3,$4);
700 } else {
701 next HOSTHARD; # who said, we could ftp anything except ftp?
702 }
703 next HOSTHARD if $proto eq "file"; # file URLs would have had
704 # success above. Likely a bogus URL
705
706 $self->debug("localizing funkyftpwise[$url]") if $CPAN::DEBUG;
707
708 # Try the most capable first and leave ncftp* for last as it only
709 # does FTP.
710 my $proxy_vars = $self->_proxy_vars($ro_url);
711 DLPRG: for my $f (qw(curl wget lynx ncftpget ncftp)) {
712 my $funkyftp = CPAN::HandleConfig->safe_quote($CPAN::Config->{$f});
713 next unless defined $funkyftp;
714 next if $funkyftp =~ /^\s*$/;
715
716 my($asl_ungz, $asl_gz);
717 ($asl_ungz = $aslocal) =~ s/\.gz//;
718 $asl_gz = "$asl_ungz.gz";
719
720 my($src_switch) = "";
721 my($chdir) = "";
722 my($stdout_redir) = " > $asl_ungz";
723 if ($f eq "lynx") {
724 $src_switch = " -source";
725 } elsif ($f eq "ncftp") {
726 $src_switch = " -c";
727 } elsif ($f eq "wget") {
728 $src_switch = " -O $asl_ungz";
729 $stdout_redir = "";
730 } elsif ($f eq 'curl') {
731 $src_switch = ' -L -f -s -S --netrc-optional';
732 if ($proxy_vars->{http_proxy}) {
733 $src_switch .= qq{ -U "$proxy_vars->{proxy_user}:$proxy_vars->{proxy_pass}" -x "$proxy_vars->{http_proxy}"};
734 }
735 }
736
737 if ($f eq "ncftpget") {
738 $chdir = "cd $aslocal_dir && ";
739 $stdout_redir = "";
740 }
741 $CPAN::Frontend->myprint(
742 qq[
743Trying with "$funkyftp$src_switch" to get
744 "$url"
745]);
746 my($system) =
747 "$chdir$funkyftp$src_switch \"$url\" $devnull$stdout_redir";
748 $self->debug("system[$system]") if $CPAN::DEBUG;
749 my($wstatus) = system($system);
750 if ($f eq "lynx") {
751 # lynx returns 0 when it fails somewhere
752 if (-s $asl_ungz) {
753 my $content = do { local *FH;
754 open FH, $asl_ungz or die;
755 local $/;
756 <FH> };
757 if ($content =~ /^<.*(<title>[45]|Error [45])/si) {
758 $CPAN::Frontend->mywarn(qq{
759No success, the file that lynx has downloaded looks like an error message:
760$content
761});
762 $CPAN::Frontend->mysleep(1);
763 next DLPRG;
764 }
765 } else {
766 $CPAN::Frontend->myprint(qq{
767No success, the file that lynx has downloaded is an empty file.
768});
769 next DLPRG;
770 }
771 }
772 if ($wstatus == 0) {
773 if (-s $aslocal) {
774 # Looks good
775 } elsif ($asl_ungz ne $aslocal) {
776 # test gzip integrity
777 if (eval{CPAN::Tarzip->new($asl_ungz)->gtest}) {
778 # e.g. foo.tar is gzipped --> foo.tar.gz
779 rename $asl_ungz, $aslocal;
780 } else {
781 eval{CPAN::Tarzip->new($asl_gz)->gzip($asl_ungz)};
782 }
783 }
784 $ThesiteURL = $ro_url;
785 return $aslocal;
786 } elsif ($url !~ /\.gz(?!\n)\Z/) {
787 unlink $asl_ungz if
788 -f $asl_ungz && -s _ == 0;
789 my $gz = "$aslocal.gz";
790 my $gzurl = "$url.gz";
791 $CPAN::Frontend->myprint(
792 qq[
793 Trying with "$funkyftp$src_switch" to get
794 "$url.gz"
795 ]);
796 my($system) = "$funkyftp$src_switch \"$url.gz\" $devnull > $asl_gz";
797 $self->debug("system[$system]") if $CPAN::DEBUG;
798 my($wstatus);
799 if (($wstatus = system($system)) == 0
800 &&
801 -s $asl_gz
802 ) {
803 # test gzip integrity
804 my $ct = eval{CPAN::Tarzip->new($asl_gz)};
805 if ($ct && $ct->gtest) {
806 $ct->gunzip($aslocal);
807 } else {
808 # somebody uncompressed file for us?
809 rename $asl_ungz, $aslocal;
810 }
811 $ThesiteURL = $ro_url;
812 return $aslocal;
813 } else {
814 unlink $asl_gz if -f $asl_gz;
815 }
816 } else {
817 my $estatus = $wstatus >> 8;
818 my $size = -f $aslocal ?
819 ", left\n$aslocal with size ".-s _ :
820 "\nWarning: expected file [$aslocal] doesn't exist";
821 $CPAN::Frontend->myprint(qq{
822 Function system("$system")
823 returned status $estatus (wstat $wstatus)$size
824 });
825 }
826 return if $CPAN::Signal;
827 } # transfer programs
828 } # host
829}
830
831#-> CPAN::FTP::_proxy_vars
832sub _proxy_vars {
833 my($self,$url) = @_;
834 my $ret = +{};
835 my $http_proxy = $CPAN::Config->{'http_proxy'} || $ENV{'http_proxy'};
836 if ($http_proxy) {
837 my($host) = $url =~ m|://([^/:]+)|;
838 my $want_proxy = 1;
839 my $noproxy = $CPAN::Config->{'no_proxy'} || $ENV{'no_proxy'} || "";
840 my @noproxy = split /\s*,\s*/, $noproxy;
841 if ($host) {
842 DOMAIN: for my $domain (@noproxy) {
843 if ($host =~ /\Q$domain\E$/) { # cf. LWP::UserAgent
844 $want_proxy = 0;
845 last DOMAIN;
846 }
847 }
848 } else {
849 $CPAN::Frontend->mywarn(" Could not determine host from http_proxy '$http_proxy'\n");
850 }
851 if ($want_proxy) {
852 my($user, $pass) =
853 &CPAN::LWP::UserAgent::get_proxy_credentials();
854 $ret = {
855 proxy_user => $user,
856 proxy_pass => $pass,
857 http_proxy => $http_proxy
858 };
859 }
860 }
861 return $ret;
862}
863
864# package CPAN::FTP;
865sub hostdlhardest {
866 my($self,$host_seq,$file,$aslocal,$stats) = @_;
867
868 return unless @$host_seq;
869 my($ro_url);
870 my($aslocal_dir) = File::Basename::dirname($aslocal);
871 File::Path::mkpath($aslocal_dir);
872 my $ftpbin = $CPAN::Config->{ftp};
873 unless ($ftpbin && length $ftpbin && MM->maybe_command($ftpbin)) {
874 $CPAN::Frontend->myprint("No external ftp command available\n\n");
875 return;
876 }
877 $CPAN::Frontend->mywarn(qq{
878As a last ressort we now switch to the external ftp command '$ftpbin'
879to get '$aslocal'.
880
881Doing so often leads to problems that are hard to diagnose.
882
883If you're the victim of such problems, please consider unsetting the
884ftp config variable with
885
886 o conf ftp ""
887 o conf commit
888
889});
890 $CPAN::Frontend->mysleep(2);
891 HOSTHARDEST: for $ro_url (@$host_seq) {
892 $self->_set_attempt($stats,"dlhardest",$ro_url);
893 my $url = "$ro_url$file";
894 $self->debug("localizing ftpwise[$url]") if $CPAN::DEBUG;
895 unless ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) {
896 next;
897 }
898 my($host,$dir,$getfile) = ($1,$2,$3);
899 my $timestamp = 0;
900 my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
901 $ctime,$blksize,$blocks) = stat($aslocal);
902 $timestamp = $mtime ||= 0;
903 my($netrc) = CPAN::FTP::netrc->new;
904 my($netrcfile) = $netrc->netrc;
905 my($verbose) = $CPAN::DEBUG{'FTP'} & $CPAN::DEBUG ? " -v" : "";
906 my $targetfile = File::Basename::basename($aslocal);
907 my(@dialog);
908 push(
909 @dialog,
910 "lcd $aslocal_dir",
911 "cd /",
912 map("cd $_", split /\//, $dir), # RFC 1738
913 "bin",
914 "get $getfile $targetfile",
915 "quit"
916 );
917 if (! $netrcfile) {
918 CPAN->debug("No ~/.netrc file found") if $CPAN::DEBUG;
919 } elsif ($netrc->hasdefault || $netrc->contains($host)) {
920 CPAN->debug(sprintf("hasdef[%d]cont($host)[%d]",
921 $netrc->hasdefault,
922 $netrc->contains($host))) if $CPAN::DEBUG;
923 if ($netrc->protected) {
924 my $dialog = join "", map { " $_\n" } @dialog;
925 my $netrc_explain;
926 if ($netrc->contains($host)) {
927 $netrc_explain = "Relying that your .netrc entry for '$host' ".
928 "manages the login";
929 } else {
930 $netrc_explain = "Relying that your default .netrc entry ".
931 "manages the login";
932 }
933 $CPAN::Frontend->myprint(qq{
934 Trying with external ftp to get
935 '$url'
936 $netrc_explain
937 Going to send the dialog
938$dialog
939}
940 );
941 $self->talk_ftp("$ftpbin$verbose $host",
942 @dialog);
943 ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
944 $atime,$mtime,$ctime,$blksize,$blocks) = stat($aslocal);
945 $mtime ||= 0;
946 if ($mtime > $timestamp) {
947 $CPAN::Frontend->myprint("GOT $aslocal\n");
948 $ThesiteURL = $ro_url;
949 return $aslocal;
950 } else {
951 $CPAN::Frontend->myprint("Hmm... Still failed!\n");
952 }
953 return if $CPAN::Signal;
954 } else {
955 $CPAN::Frontend->mywarn(qq{Your $netrcfile is not }.
956 qq{correctly protected.\n});
957 }
958 } else {
959 $CPAN::Frontend->mywarn("Your ~/.netrc neither contains $host
960 nor does it have a default entry\n");
961 }
962
963 # OK, they don't have a valid ~/.netrc. Use 'ftp -n'
964 # then and login manually to host, using e-mail as
965 # password.
966 $CPAN::Frontend->myprint(qq{Issuing "$ftpbin$verbose -n"\n});
967 unshift(
968 @dialog,
969 "open $host",
970 "user anonymous $Config::Config{'cf_email'}"
971 );
972 my $dialog = join "", map { " $_\n" } @dialog;
973 $CPAN::Frontend->myprint(qq{
974 Trying with external ftp to get
975 $url
976 Going to send the dialog
977$dialog
978}
979 );
980 $self->talk_ftp("$ftpbin$verbose -n", @dialog);
981 ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
982 $atime,$mtime,$ctime,$blksize,$blocks) = stat($aslocal);
983 $mtime ||= 0;
984 if ($mtime > $timestamp) {
985 $CPAN::Frontend->myprint("GOT $aslocal\n");
986 $ThesiteURL = $ro_url;
987 return $aslocal;
988 } else {
989 $CPAN::Frontend->myprint("Bad luck... Still failed!\n");
990 }
991 return if $CPAN::Signal;
992 $CPAN::Frontend->mywarn("Can't access URL $url.\n\n");
993 $CPAN::Frontend->mysleep(2);
994 } # host
995}
996
997# package CPAN::FTP;
998sub talk_ftp {
999 my($self,$command,@dialog) = @_;
1000 my $fh = FileHandle->new;
1001 $fh->open("|$command") or die "Couldn't open ftp: $!";
1002 foreach (@dialog) { $fh->print("$_\n") }
1003 $fh->close; # Wait for process to complete
1004 my $wstatus = $?;
1005 my $estatus = $wstatus >> 8;
1006 $CPAN::Frontend->myprint(qq{
1007Subprocess "|$command"
1008 returned status $estatus (wstat $wstatus)
1009}) if $wstatus;
1010}
1011
1012# find2perl needs modularization, too, all the following is stolen
1013# from there
1014# CPAN::FTP::ls
1015sub ls {
1016 my($self,$name) = @_;
1017 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$sizemm,
1018 $atime,$mtime,$ctime,$blksize,$blocks) = lstat($name);
1019
1020 my($perms,%user,%group);
1021 my $pname = $name;
1022
1023 if ($blocks) {
1024 $blocks = int(($blocks + 1) / 2);
1025 }
1026 else {
1027 $blocks = int(($sizemm + 1023) / 1024);
1028 }
1029
1030 if (-f _) { $perms = '-'; }
1031 elsif (-d _) { $perms = 'd'; }
1032 elsif (-c _) { $perms = 'c'; $sizemm = &sizemm; }
1033 elsif (-b _) { $perms = 'b'; $sizemm = &sizemm; }
1034 elsif (-p _) { $perms = 'p'; }
1035 elsif (-S _) { $perms = 's'; }
1036 else { $perms = 'l'; $pname .= ' -> ' . readlink($_); }
1037
1038 my(@rwx) = ('---','--x','-w-','-wx','r--','r-x','rw-','rwx');
1039 my(@moname) = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
1040 my $tmpmode = $mode;
1041 my $tmp = $rwx[$tmpmode & 7];
1042 $tmpmode >>= 3;
1043 $tmp = $rwx[$tmpmode & 7] . $tmp;
1044 $tmpmode >>= 3;
1045 $tmp = $rwx[$tmpmode & 7] . $tmp;
1046 substr($tmp,2,1) =~ tr/-x/Ss/ if -u _;
1047 substr($tmp,5,1) =~ tr/-x/Ss/ if -g _;
1048 substr($tmp,8,1) =~ tr/-x/Tt/ if -k _;
1049 $perms .= $tmp;
1050
1051 my $user = $user{$uid} || $uid; # too lazy to implement lookup
1052 my $group = $group{$gid} || $gid;
1053
1054 my($sec,$min,$hour,$mday,$mon,$year) = localtime($mtime);
1055 my($timeyear);
1056 my($moname) = $moname[$mon];
1057 if (-M _ > 365.25 / 2) {
1058 $timeyear = $year + 1900;
1059 }
1060 else {
1061 $timeyear = sprintf("%02d:%02d", $hour, $min);
1062 }
1063
1064 sprintf "%5lu %4ld %-10s %2d %-8s %-8s %8s %s %2d %5s %s\n",
1065 $ino,
1066 $blocks,
1067 $perms,
1068 $nlink,
1069 $user,
1070 $group,
1071 $sizemm,
1072 $moname,
1073 $mday,
1074 $timeyear,
1075 $pname;
1076}
1077
10781;