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