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