Reader.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. * Mime-type in order to use zlib, bzip2 or no compression.
  20. * In case ext-fileinfo is not present only the relevant types
  21. * 'application/x-gzip' and 'application/x-bzip2' are assigned
  22. * to this class property.
  23. *
  24. * @var string
  25. */
  26. private $fileType;
  27. /**
  28. * @param string $fileName
  29. */
  30. public function __construct($fileName)
  31. {
  32. if (strpos($fileName, '://') !== false) {
  33. throw new ReaderException(
  34. 'File name must not contain stream prefix',
  35. 1539623708
  36. );
  37. }
  38. $this->fileName = $fileName;
  39. $this->fileType = $this->determineFileType();
  40. }
  41. /**
  42. * @return Container
  43. */
  44. public function resolveContainer()
  45. {
  46. $data = $this->extractData($this->resolveStream() . $this->fileName);
  47. if ($data['stubContent'] === null) {
  48. throw new ReaderException(
  49. 'Cannot resolve stub',
  50. 1547807881
  51. );
  52. }
  53. if ($data['manifestContent'] === null || $data['manifestLength'] === null) {
  54. throw new ReaderException(
  55. 'Cannot resolve manifest',
  56. 1547807882
  57. );
  58. }
  59. if (strlen($data['manifestContent']) < $data['manifestLength']) {
  60. throw new ReaderException(
  61. sprintf(
  62. 'Exected manifest length %d, got %d',
  63. strlen($data['manifestContent']),
  64. $data['manifestLength']
  65. ),
  66. 1547807883
  67. );
  68. }
  69. return new Container(
  70. Stub::fromContent($data['stubContent']),
  71. Manifest::fromContent($data['manifestContent'])
  72. );
  73. }
  74. /**
  75. * @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar'
  76. * @return array
  77. */
  78. private function extractData($fileName)
  79. {
  80. $stubContent = null;
  81. $manifestContent = null;
  82. $manifestLength = null;
  83. $resource = fopen($fileName, 'r');
  84. if (!is_resource($resource)) {
  85. throw new ReaderException(
  86. sprintf('Resource %s could not be opened', $fileName),
  87. 1547902055
  88. );
  89. }
  90. while (!feof($resource)) {
  91. $line = fgets($resource);
  92. // stop reading file when manifest can be extracted
  93. if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) {
  94. break;
  95. }
  96. $manifestPosition = strpos($line, '__HALT_COMPILER();');
  97. // first line contains start of manifest
  98. if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) {
  99. $stubContent = substr($line, 0, $manifestPosition - 1);
  100. $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
  101. $manifestLength = $this->resolveManifestLength($manifestContent);
  102. // line contains start of stub
  103. } elseif ($stubContent === null) {
  104. $stubContent = $line;
  105. // line contains start of manifest
  106. } elseif ($manifestContent === null && $manifestPosition !== false) {
  107. $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
  108. $manifestLength = $this->resolveManifestLength($manifestContent);
  109. // manifest has been started (thus is cannot be stub anymore), add content
  110. } elseif ($manifestContent !== null) {
  111. $manifestContent .= $line;
  112. $manifestLength = $this->resolveManifestLength($manifestContent);
  113. // stub has been started (thus cannot be manifest here, yet), add content
  114. } elseif ($stubContent !== null) {
  115. $stubContent .= $line;
  116. }
  117. }
  118. fclose($resource);
  119. return array(
  120. 'stubContent' => $stubContent,
  121. 'manifestContent' => $manifestContent,
  122. 'manifestLength' => $manifestLength,
  123. );
  124. }
  125. /**
  126. * Resolves stream in order to handle compressed Phar archives.
  127. *
  128. * @return string
  129. */
  130. private function resolveStream()
  131. {
  132. if ($this->fileType === 'application/x-gzip' || $this->fileType === 'application/gzip') {
  133. return 'compress.zlib://';
  134. } elseif ($this->fileType === 'application/x-bzip2') {
  135. return 'compress.bzip2://';
  136. }
  137. return '';
  138. }
  139. /**
  140. * @return string
  141. */
  142. private function determineFileType()
  143. {
  144. if (class_exists('\\finfo')) {
  145. $fileInfo = new \finfo();
  146. return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE);
  147. }
  148. return $this->determineFileTypeByHeader();
  149. }
  150. /**
  151. * In case ext-fileinfo is not present only the relevant types
  152. * 'application/x-gzip' and 'application/x-bzip2' are resolved.
  153. *
  154. * @return string
  155. */
  156. private function determineFileTypeByHeader()
  157. {
  158. $resource = fopen($this->fileName, 'r');
  159. if (!is_resource($resource)) {
  160. throw new ReaderException(
  161. sprintf('Resource %s could not be opened', $this->fileName),
  162. 1557753055
  163. );
  164. }
  165. $header = fgets($resource, 4);
  166. fclose($resource);
  167. $mimeType = '';
  168. if (strpos($header, "\x42\x5a\x68") === 0) {
  169. $mimeType = 'application/x-bzip2';
  170. } elseif (strpos($header, "\x1f\x8b") === 0) {
  171. $mimeType = 'application/x-gzip';
  172. }
  173. return $mimeType;
  174. }
  175. /**
  176. * @param string $content
  177. * @return int|null
  178. */
  179. private function resolveManifestLength($content)
  180. {
  181. if (strlen($content) < 4) {
  182. return null;
  183. }
  184. return static::resolveFourByteLittleEndian($content, 0);
  185. }
  186. /**
  187. * @param string $content
  188. * @param int $start
  189. * @return int
  190. */
  191. public static function resolveFourByteLittleEndian($content, $start)
  192. {
  193. $payload = substr($content, $start, 4);
  194. if (!is_string($payload)) {
  195. throw new ReaderException(
  196. sprintf('Cannot resolve value at offset %d', $start),
  197. 1539614260
  198. );
  199. }
  200. $value = unpack('V', $payload);
  201. if (!isset($value[1])) {
  202. throw new ReaderException(
  203. sprintf('Cannot resolve value at offset %d', $start),
  204. 1539614261
  205. );
  206. }
  207. return $value[1];
  208. }
  209. /**
  210. * @param string $content
  211. * @param int $start
  212. * @return int
  213. */
  214. public static function resolveTwoByteBigEndian($content, $start)
  215. {
  216. $payload = substr($content, $start, 2);
  217. if (!is_string($payload)) {
  218. throw new ReaderException(
  219. sprintf('Cannot resolve value at offset %d', $start),
  220. 1539614263
  221. );
  222. }
  223. $value = unpack('n', $payload);
  224. if (!isset($value[1])) {
  225. throw new ReaderException(
  226. sprintf('Cannot resolve value at offset %d', $start),
  227. 1539614264
  228. );
  229. }
  230. return $value[1];
  231. }
  232. }