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