1<?php 2/** 3 * PEAR_Downloader, the PEAR Installer's download utility class 4 * 5 * PHP versions 4 and 5 6 * 7 * LICENSE: This source file is subject to version 3.0 of the PHP license 8 * that is available through the world-wide-web at the following URI: 9 * http://www.php.net/license/3_0.txt. If you did not receive a copy of 10 * the PHP License and are unable to obtain it through the web, please 11 * send a note to license@php.net so we can mail you a copy immediately. 12 * 13 * @category pear 14 * @package PEAR 15 * @author Greg Beaver <cellog@php.net> 16 * @author Stig Bakken <ssb@php.net> 17 * @author Tomas V. V. Cox <cox@idecnet.com> 18 * @author Martin Jansen <mj@php.net> 19 * @copyright 1997-2006 The PHP Group 20 * @license http://www.php.net/license/3_0.txt PHP License 3.0 21 * @version CVS: $Id: Downloader.php,v 1.99.2.2 2006/06/16 12:35:12 pajoye Exp $ 22 * @link http://pear.php.net/package/PEAR 23 * @since File available since Release 1.3.0 24 */ 25 26/** 27 * Needed for constants, extending 28 */ 29require_once 'PEAR/Common.php'; 30 31define('PEAR_INSTALLER_OK', 1); 32define('PEAR_INSTALLER_FAILED', 0); 33define('PEAR_INSTALLER_SKIPPED', -1); 34define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); 35 36/** 37 * Administration class used to download anything from the internet (PEAR Packages, 38 * static URLs, xml files) 39 * 40 * @category pear 41 * @package PEAR 42 * @author Greg Beaver <cellog@php.net> 43 * @author Stig Bakken <ssb@php.net> 44 * @author Tomas V. V. Cox <cox@idecnet.com> 45 * @author Martin Jansen <mj@php.net> 46 * @copyright 1997-2006 The PHP Group 47 * @license http://www.php.net/license/3_0.txt PHP License 3.0 48 * @version Release: 1.4.11 49 * @link http://pear.php.net/package/PEAR 50 * @since Class available since Release 1.3.0 51 */ 52class PEAR_Downloader extends PEAR_Common 53{ 54 /** 55 * @var PEAR_Registry 56 * @access private 57 */ 58 var $_registry; 59 60 /** 61 * @var PEAR_Remote 62 * @access private 63 */ 64 var $_remote; 65 66 /** 67 * Preferred Installation State (snapshot, devel, alpha, beta, stable) 68 * @var string|null 69 * @access private 70 */ 71 var $_preferredState; 72 73 /** 74 * Options from command-line passed to Install. 75 * 76 * Recognized options:<br /> 77 * - onlyreqdeps : install all required dependencies as well 78 * - alldeps : install all dependencies, including optional 79 * - installroot : base relative path to install files in 80 * - force : force a download even if warnings would prevent it 81 * - nocompress : download uncompressed tarballs 82 * @see PEAR_Command_Install 83 * @access private 84 * @var array 85 */ 86 var $_options; 87 88 /** 89 * Downloaded Packages after a call to download(). 90 * 91 * Format of each entry: 92 * 93 * <code> 94 * array('pkg' => 'package_name', 'file' => '/path/to/local/file', 95 * 'info' => array() // parsed package.xml 96 * ); 97 * </code> 98 * @access private 99 * @var array 100 */ 101 var $_downloadedPackages = array(); 102 103 /** 104 * Packages slated for download. 105 * 106 * This is used to prevent downloading a package more than once should it be a dependency 107 * for two packages to be installed. 108 * Format of each entry: 109 * 110 * <pre> 111 * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml, 112 * ); 113 * </pre> 114 * @access private 115 * @var array 116 */ 117 var $_toDownload = array(); 118 119 /** 120 * Array of every package installed, with names lower-cased. 121 * 122 * Format: 123 * <code> 124 * array('package1' => 0, 'package2' => 1, ); 125 * </code> 126 * @var array 127 */ 128 var $_installed = array(); 129 130 /** 131 * @var array 132 * @access private 133 */ 134 var $_errorStack = array(); 135 136 /** 137 * @var boolean 138 * @access private 139 */ 140 var $_internalDownload = false; 141 142 /** 143 * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()} 144 * @var array 145 * @access private 146 */ 147 var $_packageSortTree; 148 149 /** 150 * Temporary directory, or configuration value where downloads will occur 151 * @var string 152 */ 153 var $_downloadDir; 154 // {{{ PEAR_Downloader() 155 156 /** 157 * @param PEAR_Frontend_* 158 * @param array 159 * @param PEAR_Config 160 */ 161 function PEAR_Downloader(&$ui, $options, &$config) 162 { 163 parent::PEAR_Common(); 164 $this->_options = $options; 165 $this->config = &$config; 166 $this->_preferredState = $this->config->get('preferred_state'); 167 $this->ui = &$ui; 168 if (!$this->_preferredState) { 169 // don't inadvertantly use a non-set preferred_state 170 $this->_preferredState = null; 171 } 172 173 if (isset($this->_options['installroot'])) { 174 $this->config->setInstallRoot($this->_options['installroot']); 175 } 176 $this->_registry = &$config->getRegistry(); 177 $this->_remote = &$config->getRemote(); 178 179 if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { 180 $this->_installed = $this->_registry->listAllPackages(); 181 foreach ($this->_installed as $key => $unused) { 182 if (!count($unused)) { 183 continue; 184 } 185 @array_walk($this->_installed[$key], 'strtolower'); 186 } 187 } 188 } 189 190 /** 191 * Attempt to discover a channel's remote capabilities from 192 * its server name 193 * @param string 194 * @return boolean 195 */ 196 function discover($channel) 197 { 198 $this->log(1, 'Attempting to discover channel "' . $channel . '"...'); 199 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 200 $callback = $this->ui ? array(&$this, '_downloadCallback') : null; 201 if (!class_exists('System')) { 202 require_once 'System.php'; 203 } 204 $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, 205 System::mktemp(array('-d')), $callback, false); 206 PEAR::popErrorHandling(); 207 if (PEAR::isError($a)) { 208 return false; 209 } 210 list($a, $lastmodified) = $a; 211 if (!class_exists('PEAR/ChannelFile.php')) { 212 require_once 'PEAR/ChannelFile.php'; 213 } 214 $b = new PEAR_ChannelFile; 215 if ($b->fromXmlFile($a)) { 216 @unlink($a); 217 if ($this->config->get('auto_discover')) { 218 $this->_registry->addChannel($b, $lastmodified); 219 $alias = $b->getName(); 220 if ($b->getName() == $this->_registry->channelName($b->getAlias())) { 221 $alias = $b->getAlias(); 222 } 223 $this->log(1, 'Auto-discovered channel "' . $channel . 224 '", alias "' . $alias . '", adding to registry'); 225 } 226 return true; 227 } 228 @unlink($a); 229 return false; 230 } 231 232 /** 233 * For simpler unit-testing 234 * @param PEAR_Downloader 235 * @return PEAR_Downloader_Package 236 */ 237 function &newDownloaderPackage(&$t) 238 { 239 if (!class_exists('PEAR_Downloader_Package')) { 240 require_once 'PEAR/Downloader/Package.php'; 241 } 242 $a = &new PEAR_Downloader_Package($t); 243 return $a; 244 } 245 246 /** 247 * For simpler unit-testing 248 * @param PEAR_Config 249 * @param array 250 * @param array 251 * @param int 252 */ 253 function &getDependency2Object(&$c, $i, $p, $s) 254 { 255 if (!class_exists('PEAR/Dependency2.php')) { 256 require_once 'PEAR/Dependency2.php'; 257 } 258 $z = &new PEAR_Dependency2($c, $i, $p, $s); 259 return $z; 260 } 261 262 function &download($params) 263 { 264 if (!count($params)) { 265 $a = array(); 266 return $a; 267 } 268 if (!isset($this->_registry)) { 269 $this->_registry = &$this->config->getRegistry(); 270 } 271 if (!isset($this->_remote)) { 272 $this->_remote = &$this->config->getRemote(); 273 } 274 $channelschecked = array(); 275 // convert all parameters into PEAR_Downloader_Package objects 276 foreach ($params as $i => $param) { 277 $params[$i] = &$this->newDownloaderPackage($this); 278 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 279 $err = $params[$i]->initialize($param); 280 PEAR::staticPopErrorHandling(); 281 if (!$err) { 282 // skip parameters that were missed by preferred_state 283 continue; 284 } 285 if (PEAR::isError($err)) { 286 if (!isset($this->_options['soft'])) { 287 $this->log(0, $err->getMessage()); 288 } 289 $params[$i] = false; 290 if (is_object($param)) { 291 $param = $param->getChannel() . '/' . $param->getPackage(); 292 } 293 $this->pushError('Package "' . $param . '" is not valid', 294 PEAR_INSTALLER_SKIPPED); 295 } else { 296 do { 297 if ($params[$i] && $params[$i]->getType() == 'local') { 298 // bug #7090 299 // skip channel.xml check for local packages 300 break; 301 } 302 if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) && 303 !isset($this->_options['offline'])) { 304 $channelschecked[$params[$i]->getChannel()] = true; 305 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 306 if (!class_exists('System')) { 307 require_once 'System.php'; 308 } 309 $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); 310 if (PEAR::isError($curchannel)) { 311 PEAR::staticPopErrorHandling(); 312 return $this->raiseError($curchannel); 313 } 314 $a = $this->downloadHttp('http://' . $params[$i]->getChannel() . 315 '/channel.xml', $this->ui, 316 System::mktemp(array('-t' . $this->getDownloadDir())), null, $curchannel->lastModified()); 317 318 PEAR::staticPopErrorHandling(); 319 if (PEAR::isError($a) || !$a) { 320 break; 321 } 322 $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' . 323 'updated its protocols, use "channel-update ' . $params[$i]->getChannel() . 324 '" to update'); 325 } 326 } while (false); 327 if ($params[$i] && !isset($this->_options['downloadonly'])) { 328 if (isset($this->_options['packagingroot'])) { 329 $checkdir = $this->_prependPath( 330 $this->config->get('php_dir', null, $params[$i]->getChannel()), 331 $this->_options['packagingroot']); 332 } else { 333 $checkdir = $this->config->get('php_dir', 334 null, $params[$i]->getChannel()); 335 } 336 while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) { 337 $checkdir = dirname($checkdir); 338 } 339 if ($checkdir == '.') { 340 $checkdir = '/'; 341 } 342 if (!@is_writeable($checkdir)) { 343 return PEAR::raiseError('Cannot install, php_dir for channel "' . 344 $params[$i]->getChannel() . '" is not writeable by the current user'); 345 } 346 } 347 } 348 } 349 unset($channelschecked); 350 PEAR_Downloader_Package::removeDuplicates($params); 351 if (!count($params)) { 352 $a = array(); 353 return $a; 354 } 355 if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) { 356 $reverify = true; 357 while ($reverify) { 358 $reverify = false; 359 foreach ($params as $i => $param) { 360 $ret = $params[$i]->detectDependencies($params); 361 if (PEAR::isError($ret)) { 362 $reverify = true; 363 $params[$i] = false; 364 PEAR_Downloader_Package::removeDuplicates($params); 365 if (!isset($this->_options['soft'])) { 366 $this->log(0, $ret->getMessage()); 367 } 368 continue 2; 369 } 370 } 371 } 372 } 373 if (isset($this->_options['offline'])) { 374 $this->log(3, 'Skipping dependency download check, --offline specified'); 375 } 376 if (!count($params)) { 377 $a = array(); 378 return $a; 379 } 380 while (PEAR_Downloader_Package::mergeDependencies($params)); 381 PEAR_Downloader_Package::removeInstalled($params); 382 if (!count($params)) { 383 $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); 384 $a = array(); 385 return $a; 386 } 387 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 388 $err = $this->analyzeDependencies($params); 389 PEAR::popErrorHandling(); 390 if (!count($params)) { 391 $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); 392 $a = array(); 393 return $a; 394 } 395 $ret = array(); 396 $newparams = array(); 397 if (isset($this->_options['pretend'])) { 398 return $params; 399 } 400 foreach ($params as $i => $package) { 401 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 402 $pf = &$params[$i]->download(); 403 PEAR::staticPopErrorHandling(); 404 if (PEAR::isError($pf)) { 405 if (!isset($this->_options['soft'])) { 406 $this->log(1, $pf->getMessage()); 407 $this->log(0, 'Error: cannot download "' . 408 $this->_registry->parsedPackageNameToString($package->getParsedPackage(), 409 true) . 410 '"'); 411 } 412 continue; 413 } 414 $newparams[] = &$params[$i]; 415 $ret[] = array('file' => $pf->getArchiveFile(), 416 'info' => &$pf, 417 'pkg' => $pf->getPackage()); 418 } 419 $this->_downloadedPackages = $ret; 420 return $newparams; 421 } 422 423 /** 424 * @param array all packages to be installed 425 */ 426 function analyzeDependencies(&$params) 427 { 428 $hasfailed = $failed = false; 429 if (isset($this->_options['downloadonly'])) { 430 return; 431 } 432 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 433 $redo = true; 434 $reset = false; 435 while ($redo) { 436 $redo = false; 437 foreach ($params as $i => $param) { 438 $deps = $param->getDeps(); 439 if (!$deps) { 440 $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), 441 $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); 442 if ($param->getType() == 'xmlrpc') { 443 $send = $param->getDownloadURL(); 444 } else { 445 $send = $param->getPackageFile(); 446 } 447 $installcheck = $depchecker->validatePackage($send, $this, $params); 448 if (PEAR::isError($installcheck)) { 449 if (!isset($this->_options['soft'])) { 450 $this->log(0, $installcheck->getMessage()); 451 } 452 $hasfailed = true; 453 $params[$i] = false; 454 $reset = true; 455 $redo = true; 456 $failed = false; 457 PEAR_Downloader_Package::removeDuplicates($params); 458 continue 2; 459 } 460 continue; 461 } 462 if (!$reset && $param->alreadyValidated()) { 463 continue; 464 } 465 if (count($deps)) { 466 $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), 467 $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); 468 if ($param->getType() == 'xmlrpc') { 469 $send = $param->getDownloadURL(); 470 } else { 471 $send = $param->getPackageFile(); 472 } 473 $installcheck = $depchecker->validatePackage($send, $this, $params); 474 if (PEAR::isError($installcheck)) { 475 if (!isset($this->_options['soft'])) { 476 $this->log(0, $installcheck->getMessage()); 477 } 478 $hasfailed = true; 479 $params[$i] = false; 480 $reset = true; 481 $redo = true; 482 $failed = false; 483 PEAR_Downloader_Package::removeDuplicates($params); 484 continue 2; 485 } 486 $failed = false; 487 if (isset($deps['required'])) { 488 foreach ($deps['required'] as $type => $dep) { 489 // note: Dependency2 will never return a PEAR_Error if ignore-errors 490 // is specified, so soft is needed to turn off logging 491 if (!isset($dep[0])) { 492 if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep, 493 true, $params))) { 494 $failed = true; 495 if (!isset($this->_options['soft'])) { 496 $this->log(0, $e->getMessage()); 497 } 498 } elseif (is_array($e) && !$param->alreadyValidated()) { 499 if (!isset($this->_options['soft'])) { 500 $this->log(0, $e[0]); 501 } 502 } 503 } else { 504 foreach ($dep as $d) { 505 if (PEAR::isError($e = 506 $depchecker->{"validate{$type}Dependency"}($d, 507 true, $params))) { 508 $failed = true; 509 if (!isset($this->_options['soft'])) { 510 $this->log(0, $e->getMessage()); 511 } 512 } elseif (is_array($e) && !$param->alreadyValidated()) { 513 if (!isset($this->_options['soft'])) { 514 $this->log(0, $e[0]); 515 } 516 } 517 } 518 } 519 } 520 if (isset($deps['optional'])) { 521 foreach ($deps['optional'] as $type => $dep) { 522 if (!isset($dep[0])) { 523 if (PEAR::isError($e = 524 $depchecker->{"validate{$type}Dependency"}($dep, 525 false, $params))) { 526 $failed = true; 527 if (!isset($this->_options['soft'])) { 528 $this->log(0, $e->getMessage()); 529 } 530 } elseif (is_array($e) && !$param->alreadyValidated()) { 531 if (!isset($this->_options['soft'])) { 532 $this->log(0, $e[0]); 533 } 534 } 535 } else { 536 foreach ($dep as $d) { 537 if (PEAR::isError($e = 538 $depchecker->{"validate{$type}Dependency"}($d, 539 false, $params))) { 540 $failed = true; 541 if (!isset($this->_options['soft'])) { 542 $this->log(0, $e->getMessage()); 543 } 544 } elseif (is_array($e) && !$param->alreadyValidated()) { 545 if (!isset($this->_options['soft'])) { 546 $this->log(0, $e[0]); 547 } 548 } 549 } 550 } 551 } 552 } 553 $groupname = $param->getGroup(); 554 if (isset($deps['group']) && $groupname) { 555 if (!isset($deps['group'][0])) { 556 $deps['group'] = array($deps['group']); 557 } 558 $found = false; 559 foreach ($deps['group'] as $group) { 560 if ($group['attribs']['name'] == $groupname) { 561 $found = true; 562 break; 563 } 564 } 565 if ($found) { 566 unset($group['attribs']); 567 foreach ($group as $type => $dep) { 568 if (!isset($dep[0])) { 569 if (PEAR::isError($e = 570 $depchecker->{"validate{$type}Dependency"}($dep, 571 false, $params))) { 572 $failed = true; 573 if (!isset($this->_options['soft'])) { 574 $this->log(0, $e->getMessage()); 575 } 576 } elseif (is_array($e) && !$param->alreadyValidated()) { 577 if (!isset($this->_options['soft'])) { 578 $this->log(0, $e[0]); 579 } 580 } 581 } else { 582 foreach ($dep as $d) { 583 if (PEAR::isError($e = 584 $depchecker->{"validate{$type}Dependency"}($d, 585 false, $params))) { 586 $failed = true; 587 if (!isset($this->_options['soft'])) { 588 $this->log(0, $e->getMessage()); 589 } 590 } elseif (is_array($e) && !$param->alreadyValidated()) { 591 if (!isset($this->_options['soft'])) { 592 $this->log(0, $e[0]); 593 } 594 } 595 } 596 } 597 } 598 } 599 } 600 } else { 601 foreach ($deps as $dep) { 602 if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) { 603 $failed = true; 604 if (!isset($this->_options['soft'])) { 605 $this->log(0, $e->getMessage()); 606 } 607 } elseif (is_array($e) && !$param->alreadyValidated()) { 608 if (!isset($this->_options['soft'])) { 609 $this->log(0, $e[0]); 610 } 611 } 612 } 613 } 614 $params[$i]->setValidated(); 615 } 616 if ($failed) { 617 $hasfailed = true; 618 $params[$i] = false; 619 $reset = true; 620 $redo = true; 621 $failed = false; 622 PEAR_Downloader_Package::removeDuplicates($params); 623 continue 2; 624 } 625 } 626 } 627 PEAR::staticPopErrorHandling(); 628 if ($hasfailed && (isset($this->_options['ignore-errors']) || 629 isset($this->_options['nodeps']))) { 630 // this is probably not needed, but just in case 631 if (!isset($this->_options['soft'])) { 632 $this->log(0, 'WARNING: dependencies failed'); 633 } 634 } 635 } 636 637 /** 638 * Retrieve the directory that downloads will happen in 639 * @access private 640 * @return string 641 */ 642 function getDownloadDir() 643 { 644 if (isset($this->_downloadDir)) { 645 return $this->_downloadDir; 646 } 647 $downloaddir = $this->config->get('download_dir'); 648 if (empty($downloaddir)) { 649 if (!class_exists('System')) { 650 require_once 'System.php'; 651 } 652 if (PEAR::isError($downloaddir = System::mktemp('-d'))) { 653 return $downloaddir; 654 } 655 $this->log(3, '+ tmp dir created at ' . $downloaddir); 656 } 657 return $this->_downloadDir = $downloaddir; 658 } 659 660 function setDownloadDir($dir) 661 { 662 $this->_downloadDir = $dir; 663 } 664 665 // }}} 666 // {{{ configSet() 667 function configSet($key, $value, $layer = 'user', $channel = false) 668 { 669 $this->config->set($key, $value, $layer, $channel); 670 $this->_preferredState = $this->config->get('preferred_state', null, $channel); 671 if (!$this->_preferredState) { 672 // don't inadvertantly use a non-set preferred_state 673 $this->_preferredState = null; 674 } 675 } 676 677 // }}} 678 // {{{ setOptions() 679 function setOptions($options) 680 { 681 $this->_options = $options; 682 } 683 684 // }}} 685 // {{{ setOptions() 686 function getOptions() 687 { 688 return $this->_options; 689 } 690 691 // }}} 692 693 /** 694 * For simpler unit-testing 695 * @param PEAR_Config 696 * @param int 697 * @param string 698 */ 699 function &getPackagefileObject(&$c, $d, $t = false) 700 { 701 if (!class_exists('PEAR_PackageFile')) { 702 require_once 'PEAR/PackageFile.php'; 703 } 704 $a = &new PEAR_PackageFile($c, $d, $t); 705 return $a; 706 } 707 708 // {{{ _getPackageDownloadUrl() 709 710 /** 711 * @param array output of {@link parsePackageName()} 712 * @access private 713 */ 714 function _getPackageDownloadUrl($parr) 715 { 716 $curchannel = $this->config->get('default_channel'); 717 $this->configSet('default_channel', $parr['channel']); 718 // getDownloadURL returns an array. On error, it only contains information 719 // on the latest release as array(version, info). On success it contains 720 // array(version, info, download url string) 721 $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); 722 if (!$this->_registry->channelExists($parr['channel'])) { 723 do { 724 if ($this->config->get('auto_discover')) { 725 if ($this->discover($parr['channel'])) { 726 break; 727 } 728 } 729 $this->configSet('default_channel', $curchannel); 730 return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); 731 } while (false); 732 } 733 $chan = &$this->_registry->getChannel($parr['channel']); 734 if (PEAR::isError($chan)) { 735 return $chan; 736 } 737 $version = $this->_registry->packageInfo($parr['package'], 'version', 738 $parr['channel']); 739 if ($chan->supportsREST($this->config->get('preferred_mirror')) && 740 $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { 741 $rest = &$this->config->getREST('1.0', $this->_options); 742 if (!isset($parr['version']) && !isset($parr['state']) && $version 743 && !isset($this->_options['downloadonly'])) { 744 $url = $rest->getDownloadURL($base, $parr, $state, $version); 745 } else { 746 $url = $rest->getDownloadURL($base, $parr, $state, false); 747 } 748 if (PEAR::isError($url)) { 749 $this->configSet('default_channel', $curchannel); 750 return $url; 751 } 752 if ($parr['channel'] != $curchannel) { 753 $this->configSet('default_channel', $curchannel); 754 } 755 if (!is_array($url)) { 756 return $url; 757 } 758 $url['raw'] = false; // no checking is necessary for REST 759 if (!is_array($url['info'])) { 760 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . 761 'this should never happen'); 762 } 763 if (isset($url['info']['required']) || $url['compatible']) { 764 require_once 'PEAR/PackageFile/v2.php'; 765 $pf = new PEAR_PackageFile_v2; 766 $pf->setRawChannel($parr['channel']); 767 if ($url['compatible']) { 768 $pf->setRawCompatible($url['compatible']); 769 } 770 } else { 771 require_once 'PEAR/PackageFile/v1.php'; 772 $pf = new PEAR_PackageFile_v1; 773 } 774 $pf->setRawPackage($url['package']); 775 $pf->setDeps($url['info']); 776 $pf->setRawState($url['stability']); 777 $url['info'] = &$pf; 778 if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { 779 $ext = '.tar'; 780 } else { 781 $ext = '.tgz'; 782 } 783 if (is_array($url)) { 784 if (isset($url['url'])) { 785 $url['url'] .= $ext; 786 } 787 } 788 return $url; 789 } elseif ($chan->supports('xmlrpc', 'package.getDownloadURL', false, '1.1')) { 790 // don't install with the old version information unless we're doing a plain 791 // vanilla simple installation. If the user says to install a particular 792 // version or state, ignore the current installed version 793 if (!isset($parr['version']) && !isset($parr['state']) && $version 794 && !isset($this->_options['downloadonly'])) { 795 $url = $this->_remote->call('package.getDownloadURL', $parr, $state, $version); 796 } else { 797 $url = $this->_remote->call('package.getDownloadURL', $parr, $state); 798 } 799 } else { 800 $url = $this->_remote->call('package.getDownloadURL', $parr, $state); 801 } 802 if (PEAR::isError($url)) { 803 return $url; 804 } 805 if ($parr['channel'] != $curchannel) { 806 $this->configSet('default_channel', $curchannel); 807 } 808 if (isset($url['__PEAR_ERROR_CLASS__'])) { 809 return PEAR::raiseError($url['message']); 810 } 811 if (!is_array($url)) { 812 return $url; 813 } 814 $url['raw'] = $url['info']; 815 if (isset($this->_options['downloadonly'])) { 816 $pkg = &$this->getPackagefileObject($this->config, $this->debug); 817 } else { 818 $pkg = &$this->getPackagefileObject($this->config, $this->debug, 819 $this->getDownloadDir()); 820 } 821 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 822 $pinfo = &$pkg->fromXmlString($url['info'], PEAR_VALIDATE_DOWNLOADING, 'remote'); 823 PEAR::staticPopErrorHandling(); 824 if (PEAR::isError($pinfo)) { 825 if (!isset($this->_options['soft'])) { 826 $this->log(0, $pinfo->getMessage()); 827 } 828 return PEAR::raiseError('Remote package.xml is not valid - this should never happen'); 829 } 830 $url['info'] = &$pinfo; 831 if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { 832 $ext = '.tar'; 833 } else { 834 $ext = '.tgz'; 835 } 836 if (is_array($url)) { 837 if (isset($url['url'])) { 838 $url['url'] .= $ext; 839 } 840 } 841 return $url; 842 } 843 // }}} 844 // {{{ getDepPackageDownloadUrl() 845 846 /** 847 * @param array dependency array 848 * @access private 849 */ 850 function _getDepPackageDownloadUrl($dep, $parr) 851 { 852 $xsdversion = isset($dep['rel']) ? '1.0' : '2.0'; 853 $curchannel = $this->config->get('default_channel'); 854 if (isset($dep['uri'])) { 855 $xsdversion = '2.0'; 856 $chan = &$this->_registry->getChannel('__uri'); 857 if (PEAR::isError($chan)) { 858 return $chan; 859 } 860 $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri'); 861 $this->configSet('default_channel', '__uri'); 862 } else { 863 if (isset($dep['channel'])) { 864 $remotechannel = $dep['channel']; 865 } else { 866 $remotechannel = 'pear.php.net'; 867 } 868 if (!$this->_registry->channelExists($remotechannel)) { 869 do { 870 if ($this->config->get('auto_discover')) { 871 if ($this->discover($remotechannel)) { 872 break; 873 } 874 } 875 return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); 876 } while (false); 877 } 878 $chan = &$this->_registry->getChannel($remotechannel); 879 if (PEAR::isError($chan)) { 880 return $chan; 881 } 882 $version = $this->_registry->packageInfo($dep['name'], 'version', 883 $remotechannel); 884 $this->configSet('default_channel', $remotechannel); 885 } 886 $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); 887 if (isset($parr['state']) && isset($parr['version'])) { 888 unset($parr['state']); 889 } 890 if (isset($dep['uri'])) { 891 $info = &$this->newDownloaderPackage($this); 892 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 893 $err = $info->initialize($dep); 894 PEAR::staticPopErrorHandling(); 895 if (!$err) { 896 // skip parameters that were missed by preferred_state 897 return PEAR::raiseError('Cannot initialize dependency'); 898 } 899 if (PEAR::isError($err)) { 900 if (!isset($this->_options['soft'])) { 901 $this->log(0, $err->getMessage()); 902 } 903 if (is_object($info)) { 904 $param = $info->getChannel() . '/' . $info->getPackage(); 905 } 906 return PEAR::raiseError('Package "' . $param . '" is not valid'); 907 } 908 return $info; 909 } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && 910 $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { 911 $rest = &$this->config->getREST('1.0', $this->_options); 912 $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr, 913 $state, $version); 914 if (PEAR::isError($url)) { 915 return $url; 916 } 917 if ($parr['channel'] != $curchannel) { 918 $this->configSet('default_channel', $curchannel); 919 } 920 if (!is_array($url)) { 921 return $url; 922 } 923 $url['raw'] = false; // no checking is necessary for REST 924 if (!is_array($url['info'])) { 925 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . 926 'this should never happen'); 927 } 928 if (isset($url['info']['required'])) { 929 if (!class_exists('PEAR_PackageFile_v2')) { 930 require_once 'PEAR/PackageFile/v2.php'; 931 } 932 $pf = new PEAR_PackageFile_v2; 933 $pf->setRawChannel($remotechannel); 934 } else { 935 if (!class_exists('PEAR_PackageFile_v1')) { 936 require_once 'PEAR/PackageFile/v1.php'; 937 } 938 $pf = new PEAR_PackageFile_v1; 939 } 940 $pf->setRawPackage($url['package']); 941 $pf->setDeps($url['info']); 942 $pf->setRawState($url['stability']); 943 $url['info'] = &$pf; 944 if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { 945 $ext = '.tar'; 946 } else { 947 $ext = '.tgz'; 948 } 949 if (is_array($url)) { 950 if (isset($url['url'])) { 951 $url['url'] .= $ext; 952 } 953 } 954 return $url; 955 } elseif ($chan->supports('xmlrpc', 'package.getDepDownloadURL', false, '1.1')) { 956 if ($version) { 957 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr, 958 $state, $version); 959 } else { 960 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr, 961 $state); 962 } 963 } else { 964 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr, $state); 965 } 966 if ($this->config->get('default_channel') != $curchannel) { 967 $this->configSet('default_channel', $curchannel); 968 } 969 if (!is_array($url)) { 970 return $url; 971 } 972 if (isset($url['__PEAR_ERROR_CLASS__'])) { 973 return PEAR::raiseError($url['message']); 974 } 975 $url['raw'] = $url['info']; 976 $pkg = &$this->getPackagefileObject($this->config, $this->debug); 977 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 978 $pinfo = &$pkg->fromXmlString($url['info'], PEAR_VALIDATE_DOWNLOADING, 'remote'); 979 PEAR::staticPopErrorHandling(); 980 if (PEAR::isError($pinfo)) { 981 if (!isset($this->_options['soft'])) { 982 $this->log(0, $pinfo->getMessage()); 983 } 984 return PEAR::raiseError('Remote package.xml is not valid - this should never happen'); 985 } 986 $url['info'] = &$pinfo; 987 if (is_array($url)) { 988 if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { 989 $ext = '.tar'; 990 } else { 991 $ext = '.tgz'; 992 } 993 if (isset($url['url'])) { 994 $url['url'] .= $ext; 995 } 996 } 997 return $url; 998 } 999 // }}} 1000 // {{{ getPackageDownloadUrl() 1001 1002 /** 1003 * @deprecated in favor of _getPackageDownloadUrl 1004 */ 1005 function getPackageDownloadUrl($package, $version = null, $channel = false) 1006 { 1007 if ($version) { 1008 $package .= "-$version"; 1009 } 1010 if ($this === null || $this->_registry === null) { 1011 $package = "http://pear.php.net/get/$package"; 1012 } else { 1013 $chan = $this->_registry->getChannel($channel); 1014 if (PEAR::isError($chan)) { 1015 return ''; 1016 } 1017 $package = "http://" . $chan->getServer() . "/get/$package"; 1018 } 1019 if (!extension_loaded("zlib")) { 1020 $package .= '?uncompress=yes'; 1021 } 1022 return $package; 1023 } 1024 1025 // }}} 1026 // {{{ getDownloadedPackages() 1027 1028 /** 1029 * Retrieve a list of downloaded packages after a call to {@link download()}. 1030 * 1031 * Also resets the list of downloaded packages. 1032 * @return array 1033 */ 1034 function getDownloadedPackages() 1035 { 1036 $ret = $this->_downloadedPackages; 1037 $this->_downloadedPackages = array(); 1038 $this->_toDownload = array(); 1039 return $ret; 1040 } 1041 1042 // }}} 1043 // {{{ _downloadCallback() 1044 1045 function _downloadCallback($msg, $params = null) 1046 { 1047 switch ($msg) { 1048 case 'saveas': 1049 $this->log(1, "downloading $params ..."); 1050 break; 1051 case 'done': 1052 $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes'); 1053 break; 1054 case 'bytesread': 1055 static $bytes; 1056 if (empty($bytes)) { 1057 $bytes = 0; 1058 } 1059 if (!($bytes % 10240)) { 1060 $this->log(1, '.', false); 1061 } 1062 $bytes += $params; 1063 break; 1064 case 'start': 1065 $this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)"); 1066 break; 1067 } 1068 if (method_exists($this->ui, '_downloadCallback')) 1069 $this->ui->_downloadCallback($msg, $params); 1070 } 1071 1072 // }}} 1073 // {{{ _prependPath($path, $prepend) 1074 1075 function _prependPath($path, $prepend) 1076 { 1077 if (strlen($prepend) > 0) { 1078 if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { 1079 if (preg_match('/^[a-z]:/i', $prepend)) { 1080 $prepend = substr($prepend, 2); 1081 } elseif ($prepend{0} != '\\') { 1082 $prepend = "\\$prepend"; 1083 } 1084 $path = substr($path, 0, 2) . $prepend . substr($path, 2); 1085 } else { 1086 $path = $prepend . $path; 1087 } 1088 } 1089 return $path; 1090 } 1091 // }}} 1092 // {{{ pushError($errmsg, $code) 1093 1094 /** 1095 * @param string 1096 * @param integer 1097 */ 1098 function pushError($errmsg, $code = -1) 1099 { 1100 array_push($this->_errorStack, array($errmsg, $code)); 1101 } 1102 1103 // }}} 1104 // {{{ getErrorMsgs() 1105 1106 function getErrorMsgs() 1107 { 1108 $msgs = array(); 1109 $errs = $this->_errorStack; 1110 foreach ($errs as $err) { 1111 $msgs[] = $err[0]; 1112 } 1113 $this->_errorStack = array(); 1114 return $msgs; 1115 } 1116 1117 // }}} 1118 1119 /** 1120 * for BC 1121 */ 1122 function sortPkgDeps(&$packages, $uninstall = false) 1123 { 1124 $uninstall ? 1125 $this->sortPackagesForUninstall($packages) : 1126 $this->sortPackagesForInstall($packages); 1127 } 1128 1129 function _getDepTreeDP($package, $packages, &$deps, &$checked) 1130 { 1131 $pf = $package->getPackageFile(); 1132 $checked[strtolower($package->getChannel())][strtolower($package->getPackage())] 1133 = true; 1134 $pdeps = $pf->getDeps(true); 1135 if (!$pdeps) { 1136 return; 1137 } 1138 if ($pf->getPackagexmlVersion() == '1.0') { 1139 foreach ($pdeps as $dep) { 1140 if ($dep['type'] != 'pkg') { 1141 continue; 1142 } 1143 $deps['pear.php.net'][strtolower($dep['name'])] = true; 1144 foreach ($packages as $p) { 1145 $dep['channel'] = 'pear.php.net'; 1146 $dep['package'] = $dep['name']; 1147 if ($p->isEqual($dep)) { 1148 if (!isset($checked[strtolower($p->getChannel())] 1149 [strtolower($p->getPackage())])) { 1150 // add the dependency's dependencies to the tree 1151 $this->_getDepTreeDP($p, $packages, $deps, $checked); 1152 } 1153 } 1154 } 1155 } 1156 } else { 1157 $tdeps = array(); 1158 if (isset($pdeps['required']['package'])) { 1159 $t = $pdeps['required']['package']; 1160 if (!isset($t[0])) { 1161 $t = array($t); 1162 } 1163 $tdeps = array_merge($tdeps, $t); 1164 } 1165 if (isset($pdeps['required']['subpackage'])) { 1166 $t = $pdeps['required']['subpackage']; 1167 if (!isset($t[0])) { 1168 $t = array($t); 1169 } 1170 $tdeps = array_merge($tdeps, $t); 1171 } 1172 if (isset($pdeps['optional']['package'])) { 1173 $t = $pdeps['optional']['package']; 1174 if (!isset($t[0])) { 1175 $t = array($t); 1176 } 1177 $tdeps = array_merge($tdeps, $t); 1178 } 1179 if (isset($pdeps['optional']['subpackage'])) { 1180 $t = $pdeps['optional']['subpackage']; 1181 if (!isset($t[0])) { 1182 $t = array($t); 1183 } 1184 $tdeps = array_merge($tdeps, $t); 1185 } 1186 if (isset($pdeps['group'])) { 1187 if (!isset($pdeps['group'][0])) { 1188 $pdeps['group'] = array($pdeps['group']); 1189 } 1190 foreach ($pdeps['group'] as $group) { 1191 if (isset($group['package'])) { 1192 $t = $group['package']; 1193 if (!isset($t[0])) { 1194 $t = array($t); 1195 } 1196 $tdeps = array_merge($tdeps, $t); 1197 } 1198 if (isset($group['subpackage'])) { 1199 $t = $group['subpackage']; 1200 if (!isset($t[0])) { 1201 $t = array($t); 1202 } 1203 $tdeps = array_merge($tdeps, $t); 1204 } 1205 } 1206 } 1207 foreach ($tdeps as $dep) { 1208 if (!isset($dep['channel'])) { 1209 $depchannel = '__uri'; 1210 } else { 1211 $depchannel = $dep['channel']; 1212 } 1213 $deps[$depchannel][strtolower($dep['name'])] = true; 1214 foreach ($packages as $p) { 1215 $dep['channel'] = $depchannel; 1216 $dep['package'] = $dep['name']; 1217 if ($p->isEqual($dep)) { 1218 if (!isset($checked[strtolower($p->getChannel())] 1219 [strtolower($p->getPackage())])) { 1220 // add the dependency's dependencies to the tree 1221 $this->_getDepTreeDP($p, $packages, $deps, $checked); 1222 } 1223 } 1224 } 1225 } 1226 } 1227 } 1228 1229 /** 1230 * Sort a list of arrays of array(downloaded packagefilename) by dependency. 1231 * 1232 * It also removes duplicate dependencies 1233 * @param array an array of downloaded PEAR_Downloader_Packages 1234 * @return array array of array(packagefilename, package.xml contents) 1235 */ 1236 function sortPackagesForInstall(&$packages) 1237 { 1238 foreach ($packages as $i => $package) { 1239 $checked = $deps = array(); 1240 $this->_getDepTreeDP($packages[$i], $packages, $deps, $checked); 1241 $this->_depTree[$package->getChannel()][$package->getPackage()] = $deps; 1242 } 1243 usort($packages, array(&$this, '_sortInstall')); 1244 } 1245 1246 function _dependsOn($a, $b) 1247 { 1248 return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), 1249 $b); 1250 } 1251 1252 function _checkDepTree($channel, $package, $b, $checked = array()) 1253 { 1254 $checked[$channel][$package] = true; 1255 if (!isset($this->_depTree[$channel][$package])) { 1256 return false; 1257 } 1258 if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())] 1259 [strtolower($b->getPackage())])) { 1260 return true; 1261 } 1262 foreach ($this->_depTree[$channel][$package] as $ch => $packages) { 1263 foreach ($packages as $pa => $true) { 1264 if ($this->_checkDepTree($ch, $pa, $b, $checked)) { 1265 return true; 1266 } 1267 } 1268 } 1269 return false; 1270 } 1271 1272 function _sortInstall($a, $b) 1273 { 1274 if (!$a->getDeps() && !$b->getDeps()) { 1275 return 0; // neither package has dependencies, order is insignificant 1276 } 1277 if ($a->getDeps() && !$b->getDeps()) { 1278 return 1; // $a must be installed after $b because $a has dependencies 1279 } 1280 if (!$a->getDeps() && $b->getDeps()) { 1281 return -1; // $b must be installed after $a because $b has dependencies 1282 } 1283 // both packages have dependencies 1284 if ($this->_dependsOn($a, $b)) { 1285 return 1; 1286 } 1287 if ($this->_dependsOn($b, $a)) { 1288 return -1; 1289 } 1290 return 0; 1291 } 1292 1293 /** 1294 * Download a file through HTTP. Considers suggested file name in 1295 * Content-disposition: header and can run a callback function for 1296 * different events. The callback will be called with two 1297 * parameters: the callback type, and parameters. The implemented 1298 * callback types are: 1299 * 1300 * 'setup' called at the very beginning, parameter is a UI object 1301 * that should be used for all output 1302 * 'message' the parameter is a string with an informational message 1303 * 'saveas' may be used to save with a different file name, the 1304 * parameter is the filename that is about to be used. 1305 * If a 'saveas' callback returns a non-empty string, 1306 * that file name will be used as the filename instead. 1307 * Note that $save_dir will not be affected by this, only 1308 * the basename of the file. 1309 * 'start' download is starting, parameter is number of bytes 1310 * that are expected, or -1 if unknown 1311 * 'bytesread' parameter is the number of bytes read so far 1312 * 'done' download is complete, parameter is the total number 1313 * of bytes read 1314 * 'connfailed' if the TCP/SSL connection fails, this callback is called 1315 * with array(host,port,errno,errmsg) 1316 * 'writefailed' if writing to disk fails, this callback is called 1317 * with array(destfile,errmsg) 1318 * 1319 * If an HTTP proxy has been configured (http_proxy PEAR_Config 1320 * setting), the proxy will be used. 1321 * 1322 * @param string $url the URL to download 1323 * @param object $ui PEAR_Frontend_* instance 1324 * @param object $config PEAR_Config instance 1325 * @param string $save_dir directory to save file in 1326 * @param mixed $callback function/method to call for status 1327 * updates 1328 * @param false|string|array $lastmodified header values to check against for caching 1329 * use false to return the header values from this download 1330 * @param false|array $accept Accept headers to send 1331 * @return string|array Returns the full path of the downloaded file or a PEAR 1332 * error on failure. If the error is caused by 1333 * socket-related errors, the error object will 1334 * have the fsockopen error code available through 1335 * getCode(). If caching is requested, then return the header 1336 * values. 1337 * 1338 * @access public 1339 */ 1340 function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, 1341 $accept = false) 1342 { 1343 static $redirect = 0; 1344 // allways reset , so we are clean case of error 1345 $wasredirect = $redirect; 1346 $redirect = 0; 1347 if ($callback) { 1348 call_user_func($callback, 'setup', array(&$ui)); 1349 } 1350 $info = parse_url($url); 1351 if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { 1352 return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); 1353 } 1354 if (!isset($info['host'])) { 1355 return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); 1356 } else { 1357 $host = @$info['host']; 1358 $port = @$info['port']; 1359 $path = @$info['path']; 1360 } 1361 if (isset($this)) { 1362 $config = &$this->config; 1363 } else { 1364 $config = &PEAR_Config::singleton(); 1365 } 1366 $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; 1367 if ($config->get('http_proxy')&& 1368 $proxy = parse_url($config->get('http_proxy'))) { 1369 $proxy_host = @$proxy['host']; 1370 if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') { 1371 $proxy_host = 'ssl://' . $proxy_host; 1372 } 1373 $proxy_port = @$proxy['port']; 1374 $proxy_user = @$proxy['user']; 1375 $proxy_pass = @$proxy['pass']; 1376 1377 if ($proxy_port == '') { 1378 $proxy_port = 8080; 1379 } 1380 if ($callback) { 1381 call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); 1382 } 1383 } 1384 if (empty($port)) { 1385 if (isset($info['scheme']) && $info['scheme'] == 'https') { 1386 $port = 443; 1387 } else { 1388 $port = 80; 1389 } 1390 } 1391 if ($proxy_host != '') { 1392 $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); 1393 if (!$fp) { 1394 if ($callback) { 1395 call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, 1396 $errno, $errstr)); 1397 } 1398 return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); 1399 } 1400 if ($lastmodified === false || $lastmodified) { 1401 $request = "GET $url HTTP/1.1\r\n"; 1402 } else { 1403 $request = "GET $url HTTP/1.0\r\n"; 1404 } 1405 } else { 1406 if (isset($info['scheme']) && $info['scheme'] == 'https') { 1407 $host = 'ssl://' . $host; 1408 } 1409 $fp = @fsockopen($host, $port, $errno, $errstr); 1410 if (!$fp) { 1411 if ($callback) { 1412 call_user_func($callback, 'connfailed', array($host, $port, 1413 $errno, $errstr)); 1414 } 1415 return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); 1416 } 1417 if ($lastmodified === false || $lastmodified) { 1418 $request = "GET $path HTTP/1.1\r\n"; 1419 } else { 1420 $request = "GET $path HTTP/1.0\r\n"; 1421 } 1422 } 1423 $ifmodifiedsince = ''; 1424 if (is_array($lastmodified)) { 1425 if (isset($lastmodified['Last-Modified'])) { 1426 $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; 1427 } 1428 if (isset($lastmodified['ETag'])) { 1429 $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; 1430 } 1431 } else { 1432 $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); 1433 } 1434 $request .= "Host: $host:$port\r\n" . $ifmodifiedsince . 1435 "User-Agent: PEAR/1.4.11/PHP/" . PHP_VERSION . "\r\n"; 1436 if (isset($this)) { // only pass in authentication for non-static calls 1437 $username = $config->get('username'); 1438 $password = $config->get('password'); 1439 if ($username && $password) { 1440 $tmp = base64_encode("$username:$password"); 1441 $request .= "Authorization: Basic $tmp\r\n"; 1442 } 1443 } 1444 if ($proxy_host != '' && $proxy_user != '') { 1445 $request .= 'Proxy-Authorization: Basic ' . 1446 base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; 1447 } 1448 if ($accept) { 1449 $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; 1450 } 1451 $request .= "Connection: close\r\n"; 1452 $request .= "\r\n"; 1453 fwrite($fp, $request); 1454 $headers = array(); 1455 $reply = 0; 1456 while (trim($line = fgets($fp, 1024))) { 1457 if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) { 1458 $headers[strtolower($matches[1])] = trim($matches[2]); 1459 } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { 1460 $reply = (int) $matches[1]; 1461 if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { 1462 return false; 1463 } 1464 if (! in_array($reply, array(200, 301, 302, 303, 305, 307))) { 1465 return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)"); 1466 } 1467 } 1468 } 1469 if ($reply != 200) { 1470 if (isset($headers['location'])) { 1471 if ($wasredirect < 5) { 1472 $redirect = $wasredirect + 1; 1473 return $this->downloadHttp($headers['location'], 1474 $ui, $save_dir, $callback, $lastmodified, $accept); 1475 } else { 1476 return PEAR::raiseError("File http://$host:$port$path not valid (redirection looped more than 5 times)"); 1477 } 1478 } else { 1479 return PEAR::raiseError("File http://$host:$port$path not valid (redirected but no location)"); 1480 } 1481 } 1482 if (isset($headers['content-disposition']) && 1483 preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/', $headers['content-disposition'], $matches)) { 1484 $save_as = basename($matches[1]); 1485 } else { 1486 $save_as = basename($url); 1487 } 1488 if ($callback) { 1489 $tmp = call_user_func($callback, 'saveas', $save_as); 1490 if ($tmp) { 1491 $save_as = $tmp; 1492 } 1493 } 1494 $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; 1495 if (!$wp = @fopen($dest_file, 'wb')) { 1496 fclose($fp); 1497 if ($callback) { 1498 call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); 1499 } 1500 return PEAR::raiseError("could not open $dest_file for writing"); 1501 } 1502 if (isset($headers['content-length'])) { 1503 $length = $headers['content-length']; 1504 } else { 1505 $length = -1; 1506 } 1507 $bytes = 0; 1508 if ($callback) { 1509 call_user_func($callback, 'start', array(basename($dest_file), $length)); 1510 } 1511 while ($data = @fread($fp, 1024)) { 1512 $bytes += strlen($data); 1513 if ($callback) { 1514 call_user_func($callback, 'bytesread', $bytes); 1515 } 1516 if (!@fwrite($wp, $data)) { 1517 fclose($fp); 1518 if ($callback) { 1519 call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); 1520 } 1521 return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); 1522 } 1523 } 1524 fclose($fp); 1525 fclose($wp); 1526 if ($callback) { 1527 call_user_func($callback, 'done', $bytes); 1528 } 1529 if ($lastmodified === false || $lastmodified) { 1530 if (isset($headers['etag'])) { 1531 $lastmodified = array('ETag' => $headers['etag']); 1532 } 1533 if (isset($headers['last-modified'])) { 1534 if (is_array($lastmodified)) { 1535 $lastmodified['Last-Modified'] = $headers['last-modified']; 1536 } else { 1537 $lastmodified = $headers['last-modified']; 1538 } 1539 } 1540 return array($dest_file, $lastmodified, $headers); 1541 } 1542 return $dest_file; 1543 } 1544} 1545// }}} 1546 1547?> 1548