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