Zip.php 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Util;
  12. /**
  13. * @author Andreas Schempp <andreas.schempp@terminal42.ch>
  14. */
  15. class Zip
  16. {
  17. /**
  18. * Gets content of the root composer.json inside a ZIP archive.
  19. *
  20. * @param string $pathToZip
  21. *
  22. * @return string|null
  23. */
  24. public static function getComposerJson($pathToZip)
  25. {
  26. if (!extension_loaded('zip')) {
  27. throw new \RuntimeException('The Zip Util requires PHP\'s zip extension');
  28. }
  29. $zip = new \ZipArchive();
  30. if ($zip->open($pathToZip) !== true) {
  31. return null;
  32. }
  33. if (0 == $zip->numFiles) {
  34. $zip->close();
  35. return null;
  36. }
  37. $foundFileIndex = self::locateFile($zip, 'composer.json');
  38. if (false === $foundFileIndex) {
  39. $zip->close();
  40. return null;
  41. }
  42. $content = null;
  43. $configurationFileName = $zip->getNameIndex($foundFileIndex);
  44. $stream = $zip->getStream($configurationFileName);
  45. if (false !== $stream) {
  46. $content = stream_get_contents($stream);
  47. }
  48. $zip->close();
  49. return $content;
  50. }
  51. /**
  52. * Find a file by name, returning the one that has the shortest path.
  53. *
  54. * @param \ZipArchive $zip
  55. * @param string $filename
  56. *
  57. * @return bool|int
  58. */
  59. private static function locateFile(\ZipArchive $zip, $filename)
  60. {
  61. $indexOfShortestMatch = false;
  62. $lengthOfShortestMatch = -1;
  63. for ($i = 0; $i < $zip->numFiles; $i++) {
  64. $stat = $zip->statIndex($i);
  65. if (strcmp(basename($stat['name']), $filename) === 0) {
  66. $directoryName = dirname($stat['name']);
  67. if ($directoryName === '.') {
  68. //if composer.json is in root directory
  69. //it has to be the one to use.
  70. return $i;
  71. }
  72. if (strpos($directoryName, '\\') !== false ||
  73. strpos($directoryName, '/') !== false) {
  74. //composer.json files below first directory are rejected
  75. continue;
  76. }
  77. $length = strlen($stat['name']);
  78. if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
  79. //Check it's not a directory.
  80. $contents = $zip->getFromIndex($i);
  81. if ($contents !== false) {
  82. $indexOfShortestMatch = $i;
  83. $lengthOfShortestMatch = $length;
  84. }
  85. }
  86. }
  87. }
  88. return $indexOfShortestMatch;
  89. }
  90. }