PackageFile.php 16 KB

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