Wrapper.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2017, Hoa community. All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * * Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. * * Neither the name of the Hoa nor the names of its contributors may be
  20. * used to endorse or promote products derived from this software without
  21. * specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. namespace Hoa\Protocol
  36. {
  37. /**
  38. * Class \Hoa\Protocol\Wrapper.
  39. *
  40. * Stream wrapper for the `hoa://` protocol.
  41. *
  42. * @copyright Copyright © 2007-2017 Hoa community
  43. * @license New BSD License
  44. */
  45. class Wrapper
  46. {
  47. /**
  48. * Opened stream.
  49. *
  50. * @var resource
  51. */
  52. private $_stream = null;
  53. /**
  54. * Stream name (filename).
  55. *
  56. * @var string
  57. */
  58. private $_streamName = null;
  59. /**
  60. * Stream context (given by the streamWrapper class).
  61. *
  62. * @var resource
  63. */
  64. public $context = null;
  65. /**
  66. * Get the real path of the given URL.
  67. * Could return false if the path cannot be reached.
  68. *
  69. * @param string $path Path (or URL).
  70. * @param bool $exists If true, try to find the first that exists,
  71. * @return mixed
  72. */
  73. public static function realPath($path, $exists = true)
  74. {
  75. return Node::getRoot()->resolve($path, $exists);
  76. }
  77. /**
  78. * Retrieve the underlying resource.
  79. *
  80. * @param int $castAs Can be STREAM_CAST_FOR_SELECT when
  81. * stream_select() is calling stream_cast() or
  82. * STREAM_CAST_AS_STREAM when stream_cast() is
  83. * called for other uses.
  84. * @return resource
  85. */
  86. public function stream_cast($castAs)
  87. {
  88. return false;
  89. }
  90. /**
  91. * Close a resource.
  92. * This method is called in response to fclose().
  93. * All resources that were locked, or allocated, by the wrapper should be
  94. * released.
  95. *
  96. * @return void
  97. */
  98. public function stream_close()
  99. {
  100. if (true === @fclose($this->getStream())) {
  101. $this->_stream = null;
  102. $this->_streamName = null;
  103. }
  104. return;
  105. }
  106. /**
  107. * Tests for end-of-file on a file pointer.
  108. * This method is called in response to feof().
  109. *
  110. * access public
  111. * @return bool
  112. */
  113. public function stream_eof()
  114. {
  115. return feof($this->getStream());
  116. }
  117. /**
  118. * Flush the output.
  119. * This method is called in respond to fflush().
  120. * If we have cached data in our stream but not yet stored it into the
  121. * underlying storage, we should do so now.
  122. *
  123. * @return bool
  124. */
  125. public function stream_flush()
  126. {
  127. return fflush($this->getStream());
  128. }
  129. /**
  130. * Advisory file locking.
  131. * This method is called in response to flock(), when file_put_contents()
  132. * (when flags contains LOCK_EX), stream_set_blocking() and when closing the
  133. * stream (LOCK_UN).
  134. *
  135. * @param int $operation Operation is one the following:
  136. * * LOCK_SH to acquire a shared lock (reader) ;
  137. * * LOCK_EX to acquire an exclusive lock (writer) ;
  138. * * LOCK_UN to release a lock (shared or exclusive) ;
  139. * * LOCK_NB if we don't want flock() to
  140. * block while locking (not supported on
  141. * Windows).
  142. * @return bool
  143. */
  144. public function stream_lock($operation)
  145. {
  146. return flock($this->getStream(), $operation);
  147. }
  148. /**
  149. * Change stream options.
  150. * This method is called to set metadata on the stream. It is called when
  151. * one of the following functions is called on a stream URL: touch, chmod,
  152. * chown or chgrp.
  153. *
  154. * @param string $path The file path or URL to set metadata.
  155. * @param int $option One of the following constant:
  156. * * STREAM_META_TOUCH,
  157. * * STREAM_META_OWNER_NAME,
  158. * * STREAM_META_OWNER,
  159. * * STREAM_META_GROUP_NAME,
  160. * * STREAM_META_GROUP,
  161. * * STREAM_META_ACCESS.
  162. * @param mixed $values Arguments of touch, chmod, chown and chgrp.
  163. * @return bool
  164. */
  165. public function stream_metadata($path, $option, $values)
  166. {
  167. $path = static::realPath($path, false);
  168. switch ($option) {
  169. case STREAM_META_TOUCH:
  170. $arity = count($values);
  171. if (0 === $arity) {
  172. $out = touch($path);
  173. } elseif (1 === $arity) {
  174. $out = touch($path, $values[0]);
  175. } else {
  176. $out = touch($path, $values[0], $values[1]);
  177. }
  178. break;
  179. case STREAM_META_OWNER_NAME:
  180. case STREAM_META_OWNER:
  181. $out = chown($path, $values);
  182. break;
  183. case STREAM_META_GROUP_NAME:
  184. case STREAM_META_GROUP:
  185. $out = chgrp($path, $values);
  186. break;
  187. case STREAM_META_ACCESS:
  188. $out = chmod($path, $values);
  189. break;
  190. default:
  191. $out = false;
  192. }
  193. return $out;
  194. }
  195. /**
  196. * Open file or URL.
  197. * This method is called immediately after the wrapper is initialized (f.e.
  198. * by fopen() and file_get_contents()).
  199. *
  200. * @param string $path Specifies the URL that was passed to the
  201. * original function.
  202. * @param string $mode The mode used to open the file, as
  203. * detailed for fopen().
  204. * @param int $options Holds additional flags set by the
  205. * streams API. It can hold one or more of
  206. * the following values OR'd together:
  207. * * STREAM_USE_PATH, if path is relative,
  208. * search for the resource using the
  209. * include_path;
  210. * * STREAM_REPORT_ERRORS, if this is
  211. * set, you are responsible for raising
  212. * errors using trigger_error during
  213. * opening the stream. If this is not
  214. * set, you should not raise any errors.
  215. * @param string &$openedPath If the $path is opened successfully, and
  216. * STREAM_USE_PATH is set in $options,
  217. * $openedPath should be set to the full
  218. * path of the file/resource that was
  219. * actually opened.
  220. * @return bool
  221. */
  222. public function stream_open($path, $mode, $options, &$openedPath)
  223. {
  224. $path = static::realPath($path, 'r' === $mode[0]);
  225. if (Protocol::NO_RESOLUTION === $path) {
  226. return false;
  227. }
  228. if (null === $this->context) {
  229. $openedPath = fopen($path, $mode, $options & STREAM_USE_PATH);
  230. } else {
  231. $openedPath = fopen(
  232. $path,
  233. $mode,
  234. $options & STREAM_USE_PATH,
  235. $this->context
  236. );
  237. }
  238. if (false === is_resource($openedPath)) {
  239. return false;
  240. }
  241. $this->_stream = $openedPath;
  242. $this->_streamName = $path;
  243. return true;
  244. }
  245. /**
  246. * Read from stream.
  247. * This method is called in response to fread() and fgets().
  248. *
  249. * @param int $count How many bytes of data from the current
  250. * position should be returned.
  251. * @return string
  252. */
  253. public function stream_read($count)
  254. {
  255. return fread($this->getStream(), $count);
  256. }
  257. /**
  258. * Seek to specific location in a stream.
  259. * This method is called in response to fseek().
  260. * The read/write position of the stream should be updated according to the
  261. * $offset and $whence.
  262. *
  263. * @param int $offset The stream offset to seek to.
  264. * @param int $whence Possible values:
  265. * * SEEK_SET to set position equal to $offset
  266. * bytes ;
  267. * * SEEK_CUR to set position to current
  268. * location plus $offsete ;
  269. * * SEEK_END to set position to end-of-file
  270. * plus $offset.
  271. * @return bool
  272. */
  273. public function stream_seek($offset, $whence = SEEK_SET)
  274. {
  275. return 0 === fseek($this->getStream(), $offset, $whence);
  276. }
  277. /**
  278. * Retrieve information about a file resource.
  279. * This method is called in response to fstat().
  280. *
  281. * @return array
  282. */
  283. public function stream_stat()
  284. {
  285. return fstat($this->getStream());
  286. }
  287. /**
  288. * Retrieve the current position of a stream.
  289. * This method is called in response to ftell().
  290. *
  291. * @return int
  292. */
  293. public function stream_tell()
  294. {
  295. return ftell($this->getStream());
  296. }
  297. /**
  298. * Truncate a stream to a given length.
  299. *
  300. * @param int $size Size.
  301. * @return bool
  302. */
  303. public function stream_truncate($size)
  304. {
  305. return ftruncate($this->getStream(), $size);
  306. }
  307. /**
  308. * Write to stream.
  309. * This method is called in response to fwrite().
  310. *
  311. * @param string $data Should be stored into the underlying stream.
  312. * @return int
  313. */
  314. public function stream_write($data)
  315. {
  316. return fwrite($this->getStream(), $data);
  317. }
  318. /**
  319. * Close directory handle.
  320. * This method is called in to closedir().
  321. * Any resources which were locked, or allocated, during opening and use of
  322. * the directory stream should be released.
  323. *
  324. * @return void
  325. */
  326. public function dir_closedir()
  327. {
  328. closedir($this->getStream());
  329. $this->_stream = null;
  330. $this->_streamName = null;
  331. return;
  332. }
  333. /**
  334. * Open directory handle.
  335. * This method is called in response to opendir().
  336. *
  337. * @param string $path Specifies the URL that was passed to opendir().
  338. * @param int $options Whether or not to enforce safe_mode (0x04).
  339. * It is not used here.
  340. * @return bool
  341. */
  342. public function dir_opendir($path, $options)
  343. {
  344. $path = static::realPath($path);
  345. $handle = null;
  346. if (null === $this->context) {
  347. $handle = @opendir($path);
  348. } else {
  349. $handle = @opendir($path, $this->context);
  350. }
  351. if (false === $handle) {
  352. return false;
  353. }
  354. $this->_stream = $handle;
  355. $this->_streamName = $path;
  356. return true;
  357. }
  358. /**
  359. * Read entry from directory handle.
  360. * This method is called in response to readdir().
  361. *
  362. * @return mixed
  363. */
  364. public function dir_readdir()
  365. {
  366. return readdir($this->getStream());
  367. }
  368. /**
  369. * Rewind directory handle.
  370. * This method is called in response to rewinddir().
  371. * Should reset the output generated by self::dir_readdir, i.e. the next
  372. * call to self::dir_readdir should return the first entry in the location
  373. * returned by self::dir_opendir.
  374. *
  375. * @return void
  376. */
  377. public function dir_rewinddir()
  378. {
  379. return rewinddir($this->getStream());
  380. }
  381. /**
  382. * Create a directory.
  383. * This method is called in response to mkdir().
  384. *
  385. * @param string $path Directory which should be created.
  386. * @param int $mode The value passed to mkdir().
  387. * @param int $options A bitwise mask of values.
  388. * @return bool
  389. */
  390. public function mkdir($path, $mode, $options)
  391. {
  392. if (null === $this->context) {
  393. return mkdir(
  394. static::realPath($path, false),
  395. $mode,
  396. $options | STREAM_MKDIR_RECURSIVE
  397. );
  398. }
  399. return mkdir(
  400. static::realPath($path, false),
  401. $mode,
  402. $options | STREAM_MKDIR_RECURSIVE,
  403. $this->context
  404. );
  405. }
  406. /**
  407. * Rename a file or directory.
  408. * This method is called in response to rename().
  409. * Should attempt to rename $from to $to.
  410. *
  411. * @param string $from The URL to current file.
  412. * @param string $to The URL which $from should be renamed to.
  413. * @return bool
  414. */
  415. public function rename($from, $to)
  416. {
  417. if (null === $this->context) {
  418. return rename(static::realPath($from), static::realPath($to, false));
  419. }
  420. return rename(
  421. static::realPath($from),
  422. static::realPath($to, false),
  423. $this->context
  424. );
  425. }
  426. /**
  427. * Remove a directory.
  428. * This method is called in response to rmdir().
  429. *
  430. * @param string $path The directory URL which should be removed.
  431. * @param int $options A bitwise mask of values. It is not used
  432. * here.
  433. * @return bool
  434. */
  435. public function rmdir($path, $options)
  436. {
  437. if (null === $this->context) {
  438. return rmdir(static::realPath($path));
  439. }
  440. return rmdir(static::realPath($path), $this->context);
  441. }
  442. /**
  443. * Delete a file.
  444. * This method is called in response to unlink().
  445. *
  446. * @param string $path The file URL which should be deleted.
  447. * @return bool
  448. */
  449. public function unlink($path)
  450. {
  451. if (null === $this->context) {
  452. return unlink(static::realPath($path));
  453. }
  454. return unlink(static::realPath($path), $this->context);
  455. }
  456. /**
  457. * Retrieve information about a file.
  458. * This method is called in response to all stat() related functions.
  459. *
  460. * @param string $path The file URL which should be retrieve
  461. * information about.
  462. * @param int $flags Holds additional flags set by the streams API.
  463. * It can hold one or more of the following
  464. * values OR'd together.
  465. * STREAM_URL_STAT_LINK: for resource with the
  466. * ability to link to other resource (such as an
  467. * HTTP location: forward, or a filesystem
  468. * symlink). This flag specified that only
  469. * information about the link itself should be
  470. * returned, not the resource pointed to by the
  471. * link. This flag is set in response to calls to
  472. * lstat(), is_link(), or filetype().
  473. * STREAM_URL_STAT_QUIET: if this flag is set,
  474. * our wrapper should not raise any errors. If
  475. * this flag is not set, we are responsible for
  476. * reporting errors using the trigger_error()
  477. * function during stating of the path.
  478. * @return array
  479. */
  480. public function url_stat($path, $flags)
  481. {
  482. $path = static::realPath($path);
  483. if (Protocol::NO_RESOLUTION === $path) {
  484. if ($flags & STREAM_URL_STAT_QUIET) {
  485. return 0;
  486. } else {
  487. return trigger_error(
  488. 'Path ' . $path . ' cannot be resolved.',
  489. E_WARNING
  490. );
  491. }
  492. }
  493. if ($flags & STREAM_URL_STAT_LINK) {
  494. return @lstat($path);
  495. }
  496. return @stat($path);
  497. }
  498. /**
  499. * Get stream resource.
  500. *
  501. * @return resource
  502. */
  503. public function getStream()
  504. {
  505. return $this->_stream;
  506. }
  507. /**
  508. * Get stream name.
  509. *
  510. * @return resource
  511. */
  512. public function getStreamName()
  513. {
  514. return $this->_streamName;
  515. }
  516. }
  517. /**
  518. * Register the `hoa://` protocol.
  519. */
  520. stream_wrapper_register('hoa', Wrapper::class);
  521. }
  522. namespace
  523. {
  524. /**
  525. * Alias of `Hoa\Protocol::resolve` method.
  526. *
  527. * @param string $path Path to resolve.
  528. * @param bool $exists If `true`, try to find the first that exists,
  529. * else return the first solution.
  530. * @param bool $unfold Return all solutions instead of one.
  531. * @return mixed
  532. */
  533. if (!function_exists('resolve')) {
  534. function resolve($path, $exists = true, $unfold = false)
  535. {
  536. return Hoa\Protocol::getInstance()->resolve($path, $exists, $unfold);
  537. }
  538. }
  539. }