Reader.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. namespace TYPO3\PharStreamWrapper\Phar;
  3. /*
  4. * This file is part of the TYPO3 project.
  5. *
  6. * It is free software; you can redistribute it and/or modify it under the terms
  7. * of the MIT License (MIT). For the full copyright and license information,
  8. * please read the LICENSE file that was distributed with this source code.
  9. *
  10. * The TYPO3 project - inspiring people to share!
  11. */
  12. class Reader
  13. {
  14. /**
  15. * @var string
  16. */
  17. private $fileName;
  18. /**
  19. * @var string
  20. */
  21. private $fileType;
  22. /**
  23. * @param string $fileName
  24. */
  25. public function __construct($fileName)
  26. {
  27. if (strpos($fileName, '://') !== false) {
  28. throw new ReaderException(
  29. 'File name must not contain stream prefix',
  30. 1539623708
  31. );
  32. }
  33. $this->fileName = $fileName;
  34. $this->fileType = $this->determineFileType();
  35. }
  36. /**
  37. * @return Container
  38. */
  39. public function resolveContainer()
  40. {
  41. $data = $this->extractData($this->resolveStream() . $this->fileName);
  42. if ($data['stubContent'] === null) {
  43. throw new ReaderException(
  44. 'Cannot resolve stub',
  45. 1547807881
  46. );
  47. }
  48. if ($data['manifestContent'] === null || $data['manifestLength'] === null) {
  49. throw new ReaderException(
  50. 'Cannot resolve manifest',
  51. 1547807882
  52. );
  53. }
  54. if (strlen($data['manifestContent']) < $data['manifestLength']) {
  55. throw new ReaderException(
  56. sprintf(
  57. 'Exected manifest length %d, got %d',
  58. strlen($data['manifestContent']),
  59. $data['manifestLength']
  60. ),
  61. 1547807883
  62. );
  63. }
  64. return new Container(
  65. Stub::fromContent($data['stubContent']),
  66. Manifest::fromContent($data['manifestContent'])
  67. );
  68. }
  69. /**
  70. * @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar'
  71. * @return array
  72. */
  73. private function extractData($fileName)
  74. {
  75. $stubContent = null;
  76. $manifestContent = null;
  77. $manifestLength = null;
  78. $resource = fopen($fileName, 'r');
  79. if (!is_resource($resource)) {
  80. throw new ReaderException(
  81. sprintf('Resource %s could not be opened', $fileName),
  82. 1547902055
  83. );
  84. }
  85. while (!feof($resource)) {
  86. $line = fgets($resource);
  87. // stop reading file when manifest can be extracted
  88. if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) {
  89. break;
  90. }
  91. $manifestPosition = strpos($line, '__HALT_COMPILER();');
  92. // first line contains start of manifest
  93. if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) {
  94. $stubContent = substr($line, 0, $manifestPosition - 1);
  95. $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
  96. $manifestLength = $this->resolveManifestLength($manifestContent);
  97. // line contains start of stub
  98. } elseif ($stubContent === null) {
  99. $stubContent = $line;
  100. // line contains start of manifest
  101. } elseif ($manifestContent === null && $manifestPosition !== false) {
  102. $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
  103. $manifestLength = $this->resolveManifestLength($manifestContent);
  104. // manifest has been started (thus is cannot be stub anymore), add content
  105. } elseif ($manifestContent !== null) {
  106. $manifestContent .= $line;
  107. $manifestLength = $this->resolveManifestLength($manifestContent);
  108. // stub has been started (thus cannot be manifest here, yet), add content
  109. } elseif ($stubContent !== null) {
  110. $stubContent .= $line;
  111. }
  112. }
  113. fclose($resource);
  114. return array(
  115. 'stubContent' => $stubContent,
  116. 'manifestContent' => $manifestContent,
  117. 'manifestLength' => $manifestLength,
  118. );
  119. }
  120. /**
  121. * Resolves stream in order to handle compressed Phar archives.
  122. *
  123. * @return string
  124. */
  125. private function resolveStream()
  126. {
  127. if ($this->fileType === 'application/x-gzip') {
  128. return 'compress.zlib://';
  129. } elseif ($this->fileType === 'application/x-bzip2') {
  130. return 'compress.bzip2://';
  131. }
  132. return '';
  133. }
  134. /**
  135. * @return string
  136. */
  137. private function determineFileType()
  138. {
  139. $fileInfo = new \finfo();
  140. return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE);
  141. }
  142. /**
  143. * @param string $content
  144. * @return int|null
  145. */
  146. private function resolveManifestLength($content)
  147. {
  148. if (strlen($content) < 4) {
  149. return null;
  150. }
  151. return static::resolveFourByteLittleEndian($content, 0);
  152. }
  153. /**
  154. * @param string $content
  155. * @param int $start
  156. * @return int
  157. */
  158. public static function resolveFourByteLittleEndian($content, $start)
  159. {
  160. $payload = substr($content, $start, 4);
  161. if (!is_string($payload)) {
  162. throw new ReaderException(
  163. sprintf('Cannot resolve value at offset %d', $start),
  164. 1539614260
  165. );
  166. }
  167. $value = unpack('V', $payload);
  168. if (!isset($value[1])) {
  169. throw new ReaderException(
  170. sprintf('Cannot resolve value at offset %d', $start),
  171. 1539614261
  172. );
  173. }
  174. return $value[1];
  175. }
  176. /**
  177. * @param string $content
  178. * @param int $start
  179. * @return int
  180. */
  181. public static function resolveTwoByteBigEndian($content, $start)
  182. {
  183. $payload = substr($content, $start, 2);
  184. if (!is_string($payload)) {
  185. throw new ReaderException(
  186. sprintf('Cannot resolve value at offset %d', $start),
  187. 1539614263
  188. );
  189. }
  190. $value = unpack('n', $payload);
  191. if (!isset($value[1])) {
  192. throw new ReaderException(
  193. sprintf('Cannot resolve value at offset %d', $start),
  194. 1539614264
  195. );
  196. }
  197. return $value[1];
  198. }
  199. }