PharStreamWrapper.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. <?php
  2. namespace TYPO3\PharStreamWrapper;
  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 PharStreamWrapper
  13. {
  14. /**
  15. * Internal stream constants that are not exposed to PHP, but used...
  16. * @see https://github.com/php/php-src/blob/e17fc0d73c611ad0207cac8a4a01ded38251a7dc/main/php_streams.h
  17. */
  18. const STREAM_OPEN_FOR_INCLUDE = 128;
  19. /**
  20. * @var resource
  21. */
  22. public $context;
  23. /**
  24. * @var resource
  25. */
  26. protected $internalResource;
  27. /**
  28. * @return bool
  29. */
  30. public function dir_closedir()
  31. {
  32. if (!is_resource($this->internalResource)) {
  33. return false;
  34. }
  35. $this->invokeInternalStreamWrapper(
  36. 'closedir',
  37. $this->internalResource
  38. );
  39. return !is_resource($this->internalResource);
  40. }
  41. /**
  42. * @param string $path
  43. * @param int $options
  44. * @return bool
  45. */
  46. public function dir_opendir($path, $options)
  47. {
  48. $this->assert($path, Behavior::COMMAND_DIR_OPENDIR);
  49. $this->internalResource = $this->invokeInternalStreamWrapper(
  50. 'opendir',
  51. $path,
  52. $this->context
  53. );
  54. return is_resource($this->internalResource);
  55. }
  56. /**
  57. * @return string|false
  58. */
  59. public function dir_readdir()
  60. {
  61. return $this->invokeInternalStreamWrapper(
  62. 'readdir',
  63. $this->internalResource
  64. );
  65. }
  66. /**
  67. * @return bool
  68. */
  69. public function dir_rewinddir()
  70. {
  71. if (!is_resource($this->internalResource)) {
  72. return false;
  73. }
  74. $this->invokeInternalStreamWrapper(
  75. 'rewinddir',
  76. $this->internalResource
  77. );
  78. return is_resource($this->internalResource);
  79. }
  80. /**
  81. * @param string $path
  82. * @param int $mode
  83. * @param int $options
  84. * @return bool
  85. */
  86. public function mkdir($path, $mode, $options)
  87. {
  88. $this->assert($path, Behavior::COMMAND_MKDIR);
  89. return $this->invokeInternalStreamWrapper(
  90. 'mkdir',
  91. $path,
  92. $mode,
  93. (bool) ($options & STREAM_MKDIR_RECURSIVE),
  94. $this->context
  95. );
  96. }
  97. /**
  98. * @param string $path_from
  99. * @param string $path_to
  100. * @return bool
  101. */
  102. public function rename($path_from, $path_to)
  103. {
  104. $this->assert($path_from, Behavior::COMMAND_RENAME);
  105. $this->assert($path_to, Behavior::COMMAND_RENAME);
  106. return $this->invokeInternalStreamWrapper(
  107. 'rename',
  108. $path_from,
  109. $path_to,
  110. $this->context
  111. );
  112. }
  113. /**
  114. * @param string $path
  115. * @param int $options
  116. * @return bool
  117. */
  118. public function rmdir($path, $options)
  119. {
  120. $this->assert($path, Behavior::COMMAND_RMDIR);
  121. return $this->invokeInternalStreamWrapper(
  122. 'rmdir',
  123. $path,
  124. $this->context
  125. );
  126. }
  127. /**
  128. * @param int $cast_as
  129. */
  130. public function stream_cast($cast_as)
  131. {
  132. throw new Exception(
  133. 'Method stream_select() cannot be used',
  134. 1530103999
  135. );
  136. }
  137. public function stream_close()
  138. {
  139. $this->invokeInternalStreamWrapper(
  140. 'fclose',
  141. $this->internalResource
  142. );
  143. }
  144. /**
  145. * @return bool
  146. */
  147. public function stream_eof()
  148. {
  149. return $this->invokeInternalStreamWrapper(
  150. 'feof',
  151. $this->internalResource
  152. );
  153. }
  154. /**
  155. * @return bool
  156. */
  157. public function stream_flush()
  158. {
  159. return $this->invokeInternalStreamWrapper(
  160. 'fflush',
  161. $this->internalResource
  162. );
  163. }
  164. /**
  165. * @param int $operation
  166. * @return bool
  167. */
  168. public function stream_lock($operation)
  169. {
  170. return $this->invokeInternalStreamWrapper(
  171. 'flock',
  172. $this->internalResource,
  173. $operation
  174. );
  175. }
  176. /**
  177. * @param string $path
  178. * @param int $option
  179. * @param string|int $value
  180. * @return bool
  181. */
  182. public function stream_metadata($path, $option, $value)
  183. {
  184. $this->assert($path, Behavior::COMMAND_STEAM_METADATA);
  185. if ($option === STREAM_META_TOUCH) {
  186. return call_user_func_array(
  187. array($this, 'invokeInternalStreamWrapper'),
  188. array_merge(array('touch', $path), (array) $value)
  189. );
  190. }
  191. if ($option === STREAM_META_OWNER_NAME || $option === STREAM_META_OWNER) {
  192. return $this->invokeInternalStreamWrapper(
  193. 'chown',
  194. $path,
  195. $value
  196. );
  197. }
  198. if ($option === STREAM_META_GROUP_NAME || $option === STREAM_META_GROUP) {
  199. return $this->invokeInternalStreamWrapper(
  200. 'chgrp',
  201. $path,
  202. $value
  203. );
  204. }
  205. if ($option === STREAM_META_ACCESS) {
  206. return $this->invokeInternalStreamWrapper(
  207. 'chmod',
  208. $path,
  209. $value
  210. );
  211. }
  212. return false;
  213. }
  214. /**
  215. * @param string $path
  216. * @param string $mode
  217. * @param int $options
  218. * @param string|null $opened_path
  219. * @return bool
  220. */
  221. public function stream_open(
  222. $path,
  223. $mode,
  224. $options,
  225. &$opened_path = null
  226. ) {
  227. $this->assert($path, Behavior::COMMAND_STREAM_OPEN);
  228. $arguments = array($path, $mode, (bool) ($options & STREAM_USE_PATH));
  229. // only add stream context for non include/require calls
  230. if (!($options & static::STREAM_OPEN_FOR_INCLUDE)) {
  231. $arguments[] = $this->context;
  232. // work around https://bugs.php.net/bug.php?id=66569
  233. // for including files from Phar stream with OPcache enabled
  234. } else {
  235. Helper::resetOpCache();
  236. }
  237. $this->internalResource = call_user_func_array(
  238. array($this, 'invokeInternalStreamWrapper'),
  239. array_merge(array('fopen'), $arguments)
  240. );
  241. if (!is_resource($this->internalResource)) {
  242. return false;
  243. }
  244. if ($opened_path !== null) {
  245. $metaData = stream_get_meta_data($this->internalResource);
  246. $opened_path = $metaData['uri'];
  247. }
  248. return true;
  249. }
  250. /**
  251. * @param int $count
  252. * @return string
  253. */
  254. public function stream_read($count)
  255. {
  256. return $this->invokeInternalStreamWrapper(
  257. 'fread',
  258. $this->internalResource,
  259. $count
  260. );
  261. }
  262. /**
  263. * @param int $offset
  264. * @param int $whence
  265. * @return bool
  266. */
  267. public function stream_seek($offset, $whence = SEEK_SET)
  268. {
  269. return $this->invokeInternalStreamWrapper(
  270. 'fseek',
  271. $this->internalResource,
  272. $offset,
  273. $whence
  274. ) !== -1;
  275. }
  276. /**
  277. * @param int $option
  278. * @param int $arg1
  279. * @param int $arg2
  280. * @return bool
  281. */
  282. public function stream_set_option($option, $arg1, $arg2)
  283. {
  284. if ($option === STREAM_OPTION_BLOCKING) {
  285. return $this->invokeInternalStreamWrapper(
  286. 'stream_set_blocking',
  287. $this->internalResource,
  288. $arg1
  289. );
  290. }
  291. if ($option === STREAM_OPTION_READ_TIMEOUT) {
  292. return $this->invokeInternalStreamWrapper(
  293. 'stream_set_timeout',
  294. $this->internalResource,
  295. $arg1,
  296. $arg2
  297. );
  298. }
  299. if ($option === STREAM_OPTION_WRITE_BUFFER) {
  300. return $this->invokeInternalStreamWrapper(
  301. 'stream_set_write_buffer',
  302. $this->internalResource,
  303. $arg2
  304. ) === 0;
  305. }
  306. return false;
  307. }
  308. /**
  309. * @return array
  310. */
  311. public function stream_stat()
  312. {
  313. return $this->invokeInternalStreamWrapper(
  314. 'fstat',
  315. $this->internalResource
  316. );
  317. }
  318. /**
  319. * @return int
  320. */
  321. public function stream_tell()
  322. {
  323. return $this->invokeInternalStreamWrapper(
  324. 'ftell',
  325. $this->internalResource
  326. );
  327. }
  328. /**
  329. * @param int $new_size
  330. * @return bool
  331. */
  332. public function stream_truncate($new_size)
  333. {
  334. return $this->invokeInternalStreamWrapper(
  335. 'ftruncate',
  336. $this->internalResource,
  337. $new_size
  338. );
  339. }
  340. /**
  341. * @param string $data
  342. * @return int
  343. */
  344. public function stream_write($data)
  345. {
  346. return $this->invokeInternalStreamWrapper(
  347. 'fwrite',
  348. $this->internalResource,
  349. $data
  350. );
  351. }
  352. /**
  353. * @param string $path
  354. * @return bool
  355. */
  356. public function unlink($path)
  357. {
  358. $this->assert($path, Behavior::COMMAND_UNLINK);
  359. return $this->invokeInternalStreamWrapper(
  360. 'unlink',
  361. $path,
  362. $this->context
  363. );
  364. }
  365. /**
  366. * @param string $path
  367. * @param int $flags
  368. * @return array|false
  369. */
  370. public function url_stat($path, $flags)
  371. {
  372. $this->assert($path, Behavior::COMMAND_URL_STAT);
  373. $functionName = $flags & STREAM_URL_STAT_QUIET ? '@stat' : 'stat';
  374. return $this->invokeInternalStreamWrapper($functionName, $path);
  375. }
  376. /**
  377. * @param string $path
  378. * @param string $command
  379. */
  380. protected function assert($path, $command)
  381. {
  382. if ($this->resolveAssertable()->assert($path, $command) === true) {
  383. return;
  384. }
  385. throw new Exception(
  386. sprintf(
  387. 'Denied invocation of "%s" for command "%s"',
  388. $path,
  389. $command
  390. ),
  391. 1535189880
  392. );
  393. }
  394. /**
  395. * @return Assertable
  396. */
  397. protected function resolveAssertable()
  398. {
  399. return Manager::instance();
  400. }
  401. /**
  402. * Invokes commands on the native PHP Phar stream wrapper.
  403. *
  404. * @param string $functionName
  405. * @param mixed ...$arguments
  406. * @return mixed
  407. */
  408. private function invokeInternalStreamWrapper($functionName)
  409. {
  410. $arguments = func_get_args();
  411. array_shift($arguments);
  412. $silentExecution = $functionName{0} === '@';
  413. $functionName = ltrim($functionName, '@');
  414. $this->restoreInternalSteamWrapper();
  415. try {
  416. if ($silentExecution) {
  417. $result = @call_user_func_array($functionName, $arguments);
  418. } else {
  419. $result = call_user_func_array($functionName, $arguments);
  420. }
  421. } catch (\Exception $exception) {
  422. $this->registerStreamWrapper();
  423. throw $exception;
  424. } catch (\Throwable $throwable) {
  425. $this->registerStreamWrapper();
  426. throw $throwable;
  427. }
  428. $this->registerStreamWrapper();
  429. return $result;
  430. }
  431. private function restoreInternalSteamWrapper()
  432. {
  433. stream_wrapper_restore('phar');
  434. }
  435. private function registerStreamWrapper()
  436. {
  437. stream_wrapper_unregister('phar');
  438. stream_wrapper_register('phar', get_class($this));
  439. }
  440. }