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