PackageFile.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <?php
  2. /**
  3. * PEAR_PackageFile, package.xml parsing utility class
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * @category pear
  8. * @package PEAR
  9. * @author Greg Beaver <cellog@php.net>
  10. * @copyright 1997-2009 The Authors
  11. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  12. * @link http://pear.php.net/package/PEAR
  13. * @since File available since Release 1.4.0a1
  14. */
  15. /**
  16. * needed for PEAR_VALIDATE_* constants
  17. */
  18. require_once 'PEAR/Validate.php';
  19. /**
  20. * Error code if the package.xml <package> tag does not contain a valid version
  21. */
  22. define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1);
  23. /**
  24. * Error code if the package.xml <package> tag version is not supported (version 1.0 and 1.1 are the only supported versions,
  25. * currently
  26. */
  27. define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2);
  28. /**
  29. * Abstraction for the package.xml package description file
  30. *
  31. * @category pear
  32. * @package PEAR
  33. * @author Greg Beaver <cellog@php.net>
  34. * @copyright 1997-2009 The Authors
  35. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  36. * @version Release: 1.10.10
  37. * @link http://pear.php.net/package/PEAR
  38. * @since Class available since Release 1.4.0a1
  39. */
  40. class PEAR_PackageFile
  41. {
  42. /**
  43. * @var PEAR_Config
  44. */
  45. var $_config;
  46. var $_debug;
  47. var $_logger = false;
  48. /**
  49. * @var boolean
  50. */
  51. var $_rawReturn = false;
  52. /**
  53. * helper for extracting Archive_Tar errors
  54. * @var array
  55. * @access private
  56. */
  57. var $_extractErrors = array();
  58. /**
  59. *
  60. * @param PEAR_Config $config
  61. * @param ? $debug
  62. * @param string @tmpdir Optional temporary directory for uncompressing
  63. * files
  64. */
  65. function __construct(&$config, $debug = false)
  66. {
  67. $this->_config = $config;
  68. $this->_debug = $debug;
  69. }
  70. /**
  71. * Turn off validation - return a parsed package.xml without checking it
  72. *
  73. * This is used by the package-validate command
  74. */
  75. function rawReturn()
  76. {
  77. $this->_rawReturn = true;
  78. }
  79. function setLogger(&$l)
  80. {
  81. $this->_logger = &$l;
  82. }
  83. /**
  84. * Create a PEAR_PackageFile_Parser_v* of a given version.
  85. * @param int $version
  86. * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1
  87. */
  88. function &parserFactory($version)
  89. {
  90. if (!in_array($version[0], array('1', '2'))) {
  91. $a = false;
  92. return $a;
  93. }
  94. include_once 'PEAR/PackageFile/Parser/v' . $version[0] . '.php';
  95. $version = $version[0];
  96. $class = "PEAR_PackageFile_Parser_v$version";
  97. $a = new $class;
  98. return $a;
  99. }
  100. /**
  101. * For simpler unit-testing
  102. * @return string
  103. */
  104. function getClassPrefix()
  105. {
  106. return 'PEAR_PackageFile_v';
  107. }
  108. /**
  109. * Create a PEAR_PackageFile_v* of a given version.
  110. * @param int $version
  111. * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1
  112. */
  113. function &factory($version)
  114. {
  115. if (!in_array($version[0], array('1', '2'))) {
  116. $a = false;
  117. return $a;
  118. }
  119. include_once 'PEAR/PackageFile/v' . $version[0] . '.php';
  120. $version = $version[0];
  121. $class = $this->getClassPrefix() . $version;
  122. $a = new $class;
  123. return $a;
  124. }
  125. /**
  126. * Create a PEAR_PackageFile_v* from its toArray() method
  127. *
  128. * WARNING: no validation is performed, the array is assumed to be valid,
  129. * always parse from xml if you want validation.
  130. * @param array $arr
  131. * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2
  132. * @uses factory() to construct the returned object.
  133. */
  134. function &fromArray($arr)
  135. {
  136. if (isset($arr['xsdversion'])) {
  137. $obj = &$this->factory($arr['xsdversion']);
  138. if ($this->_logger) {
  139. $obj->setLogger($this->_logger);
  140. }
  141. $obj->setConfig($this->_config);
  142. $obj->fromArray($arr);
  143. return $obj;
  144. }
  145. if (isset($arr['package']['attribs']['version'])) {
  146. $obj = &$this->factory($arr['package']['attribs']['version']);
  147. } else {
  148. $obj = &$this->factory('1.0');
  149. }
  150. if ($this->_logger) {
  151. $obj->setLogger($this->_logger);
  152. }
  153. $obj->setConfig($this->_config);
  154. $obj->fromArray($arr);
  155. return $obj;
  156. }
  157. /**
  158. * Create a PEAR_PackageFile_v* from an XML string.
  159. * @access public
  160. * @param string $data contents of package.xml file
  161. * @param int $state package state (one of PEAR_VALIDATE_* constants)
  162. * @param string $file full path to the package.xml file (and the files
  163. * it references)
  164. * @param string $archive optional name of the archive that the XML was
  165. * extracted from, if any
  166. * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
  167. * @uses parserFactory() to construct a parser to load the package.
  168. */
  169. function &fromXmlString($data, $state, $file, $archive = false)
  170. {
  171. if (preg_match('/<package[^>]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) {
  172. if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) {
  173. return PEAR::raiseError('package.xml version "' . $packageversion[1] .
  174. '" is not supported, only 1.0, 2.0, and 2.1 are supported.');
  175. }
  176. $object = &$this->parserFactory($packageversion[1]);
  177. if ($this->_logger) {
  178. $object->setLogger($this->_logger);
  179. }
  180. $object->setConfig($this->_config);
  181. $pf = $object->parse($data, $file, $archive);
  182. if (PEAR::isError($pf)) {
  183. return $pf;
  184. }
  185. if ($this->_rawReturn) {
  186. return $pf;
  187. }
  188. if (!$pf->validate($state)) {;
  189. if ($this->_config->get('verbose') > 0
  190. && $this->_logger && $pf->getValidationWarnings(false)
  191. ) {
  192. foreach ($pf->getValidationWarnings(false) as $warning) {
  193. $this->_logger->log(0, 'ERROR: ' . $warning['message']);
  194. }
  195. }
  196. $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
  197. 2, null, null, $pf->getValidationWarnings());
  198. return $a;
  199. }
  200. if ($this->_logger && $pf->getValidationWarnings(false)) {
  201. foreach ($pf->getValidationWarnings() as $warning) {
  202. $this->_logger->log(0, 'WARNING: ' . $warning['message']);
  203. }
  204. }
  205. if (method_exists($pf, 'flattenFilelist')) {
  206. $pf->flattenFilelist(); // for v2
  207. }
  208. return $pf;
  209. } elseif (preg_match('/<package[^>]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) {
  210. $a = PEAR::raiseError('package.xml file "' . $file .
  211. '" has unsupported package.xml <package> version "' . $packageversion[1] . '"');
  212. return $a;
  213. } else {
  214. if (!class_exists('PEAR_ErrorStack')) {
  215. require_once 'PEAR/ErrorStack.php';
  216. }
  217. PEAR_ErrorStack::staticPush('PEAR_PackageFile',
  218. PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION,
  219. 'warning', array('xml' => $data), 'package.xml "' . $file .
  220. '" has no package.xml <package> version');
  221. $object = &$this->parserFactory('1.0');
  222. $object->setConfig($this->_config);
  223. $pf = $object->parse($data, $file, $archive);
  224. if (PEAR::isError($pf)) {
  225. return $pf;
  226. }
  227. if ($this->_rawReturn) {
  228. return $pf;
  229. }
  230. if (!$pf->validate($state)) {
  231. $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
  232. 2, null, null, $pf->getValidationWarnings());
  233. return $a;
  234. }
  235. if ($this->_logger && $pf->getValidationWarnings(false)) {
  236. foreach ($pf->getValidationWarnings() as $warning) {
  237. $this->_logger->log(0, 'WARNING: ' . $warning['message']);
  238. }
  239. }
  240. if (method_exists($pf, 'flattenFilelist')) {
  241. $pf->flattenFilelist(); // for v2
  242. }
  243. return $pf;
  244. }
  245. }
  246. /**
  247. * Register a temporary file or directory. When the destructor is
  248. * executed, all registered temporary files and directories are
  249. * removed.
  250. *
  251. * @param string $file name of file or directory
  252. * @return void
  253. */
  254. static function addTempFile($file)
  255. {
  256. $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
  257. }
  258. /**
  259. * Create a PEAR_PackageFile_v* from a compressed Tar or Tgz file.
  260. * @access public
  261. * @param string contents of package.xml file
  262. * @param int package state (one of PEAR_VALIDATE_* constants)
  263. * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
  264. * @using Archive_Tar to extract the files
  265. * @using fromPackageFile() to load the package after the package.xml
  266. * file is extracted.
  267. */
  268. function &fromTgzFile($file, $state)
  269. {
  270. if (!class_exists('Archive_Tar')) {
  271. require_once 'Archive/Tar.php';
  272. }
  273. $tar = new Archive_Tar($file);
  274. if ($this->_debug <= 1) {
  275. $tar->pushErrorHandling(PEAR_ERROR_RETURN);
  276. }
  277. $content = $tar->listContent();
  278. if ($this->_debug <= 1) {
  279. $tar->popErrorHandling();
  280. }
  281. if (!is_array($content)) {
  282. if (is_string($file) && strlen($file) < 255 &&
  283. (!file_exists($file) || !@is_file($file))) {
  284. $ret = PEAR::raiseError("could not open file \"$file\"");
  285. return $ret;
  286. }
  287. $file = realpath($file);
  288. $ret = PEAR::raiseError("Could not get contents of package \"$file\"".
  289. '. Invalid tgz file.');
  290. return $ret;
  291. }
  292. if (!count($content) && !@is_file($file)) {
  293. $ret = PEAR::raiseError("could not open file \"$file\"");
  294. return $ret;
  295. }
  296. $xml = null;
  297. $origfile = $file;
  298. foreach ($content as $file) {
  299. $name = $file['filename'];
  300. if ($name == 'package2.xml') { // allow a .tgz to distribute both versions
  301. $xml = $name;
  302. break;
  303. }
  304. if ($name == 'package.xml') {
  305. $xml = $name;
  306. break;
  307. } elseif (preg_match('/package.xml$/', $name, $match)) {
  308. $xml = $name;
  309. break;
  310. }
  311. }
  312. $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear');
  313. if ($tmpdir === false) {
  314. $ret = PEAR::raiseError("there was a problem with getting the configured temp directory");
  315. return $ret;
  316. }
  317. PEAR_PackageFile::addTempFile($tmpdir);
  318. $this->_extractErrors();
  319. PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors'));
  320. if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
  321. $extra = implode("\n", $this->_extractErrors());
  322. if ($extra) {
  323. $extra = ' ' . $extra;
  324. }
  325. PEAR::staticPopErrorHandling();
  326. $ret = PEAR::raiseError('could not extract the package.xml file from "' .
  327. $origfile . '"' . $extra);
  328. return $ret;
  329. }
  330. PEAR::staticPopErrorHandling();
  331. $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile);
  332. return $ret;
  333. }
  334. /**
  335. * helper callback for extracting Archive_Tar errors
  336. *
  337. * @param PEAR_Error|null $err
  338. * @return array
  339. * @access private
  340. */
  341. function _extractErrors($err = null)
  342. {
  343. static $errors = array();
  344. if ($err === null) {
  345. $e = $errors;
  346. $errors = array();
  347. return $e;
  348. }
  349. $errors[] = $err->getMessage();
  350. }
  351. /**
  352. * Create a PEAR_PackageFile_v* from a package.xml file.
  353. *
  354. * @access public
  355. * @param string $descfile name of package xml file
  356. * @param int $state package state (one of PEAR_VALIDATE_* constants)
  357. * @param string|false $archive name of the archive this package.xml came
  358. * from, if any
  359. * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
  360. * @uses PEAR_PackageFile::fromXmlString to create the oject after the
  361. * XML is loaded from the package.xml file.
  362. */
  363. function &fromPackageFile($descfile, $state, $archive = false)
  364. {
  365. $fp = false;
  366. if (is_string($descfile) && strlen($descfile) < 255 &&
  367. (
  368. !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile)
  369. || (!$fp = @fopen($descfile, 'r'))
  370. )
  371. ) {
  372. $a = PEAR::raiseError("Unable to open $descfile");
  373. return $a;
  374. }
  375. // read the whole thing so we only get one cdata callback
  376. // for each block of cdata
  377. fclose($fp);
  378. $data = file_get_contents($descfile);
  379. $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive);
  380. return $ret;
  381. }
  382. /**
  383. * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file.
  384. *
  385. * This method is able to extract information about a package from a .tgz
  386. * archive or from a XML package definition file.
  387. *
  388. * @access public
  389. * @param string $info file name
  390. * @param int $state package state (one of PEAR_VALIDATE_* constants)
  391. * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
  392. * @uses fromPackageFile() if the file appears to be XML
  393. * @uses fromTgzFile() to load all non-XML files
  394. */
  395. function &fromAnyFile($info, $state)
  396. {
  397. if (is_dir($info)) {
  398. $dir_name = realpath($info);
  399. if (file_exists($dir_name . '/package.xml')) {
  400. $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state);
  401. } elseif (file_exists($dir_name . '/package2.xml')) {
  402. $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state);
  403. } else {
  404. $info = PEAR::raiseError("No package definition found in '$info' directory");
  405. }
  406. return $info;
  407. }
  408. $fp = false;
  409. if (is_string($info) && strlen($info) < 255 &&
  410. (file_exists($info) || ($fp = @fopen($info, 'r')))
  411. ) {
  412. if ($fp) {
  413. fclose($fp);
  414. }
  415. $tmp = substr($info, -4);
  416. if ($tmp == '.xml') {
  417. $info = &PEAR_PackageFile::fromPackageFile($info, $state);
  418. } elseif ($tmp == '.tar' || $tmp == '.tgz') {
  419. $info = &PEAR_PackageFile::fromTgzFile($info, $state);
  420. } else {
  421. $fp = fopen($info, 'r');
  422. $test = fread($fp, 5);
  423. fclose($fp);
  424. if ($test == '<?xml') {
  425. $info = &PEAR_PackageFile::fromPackageFile($info, $state);
  426. } else {
  427. $info = &PEAR_PackageFile::fromTgzFile($info, $state);
  428. }
  429. }
  430. return $info;
  431. }
  432. $info = PEAR::raiseError("Cannot open '$info' for parsing");
  433. return $info;
  434. }
  435. }