file_example_session_streams.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. <?php
  2. /**
  3. * @file
  4. * Provides a demonstration session:// streamwrapper.
  5. *
  6. * This example is nearly fully functional, but has no known
  7. * practical use. It's an example and demonstration only.
  8. */
  9. /**
  10. * Example stream wrapper class to handle session:// streams.
  11. *
  12. * This is just an example, as it could have horrible results if much
  13. * information were placed in the $_SESSION variable. However, it does
  14. * demonstrate both the read and write implementation of a stream wrapper.
  15. *
  16. * A "stream" is an important Unix concept for the reading and writing of
  17. * files and other devices. Reading or writing a "stream" just means that you
  18. * open some device, file, internet site, or whatever, and you don't have to
  19. * know at all what it is. All the functions that deal with it are the same.
  20. * You can read/write more from/to the stream, seek a position in the stream,
  21. * or anything else without the code that does it even knowing what kind
  22. * of device it is talking to. This Unix idea is extended into PHP's
  23. * mindset.
  24. *
  25. * The idea of "stream wrapper" is that this can be extended indefinitely.
  26. * The classic example is HTTP: With PHP you can do a
  27. * file_get_contents("http://drupal.org/projects") as if it were a file,
  28. * because the scheme "http" is supported natively in PHP. So Drupal adds
  29. * the public:// and private:// schemes, and contrib modules can add any
  30. * scheme they want to. This example adds the session:// scheme, which allows
  31. * reading and writing the $_SESSION['file_example'] key as if it were a file.
  32. *
  33. * Note that because this implementation uses simple PHP arrays ($_SESSION)
  34. * it is limited to string values, so binary files will not work correctly.
  35. * Only text files can be used.
  36. *
  37. * @ingroup file_example
  38. */
  39. class FileExampleSessionStreamWrapper implements DrupalStreamWrapperInterface {
  40. /**
  41. * Stream context resource.
  42. *
  43. * @var Resource
  44. */
  45. public $context;
  46. /**
  47. * Instance URI (stream).
  48. *
  49. * These streams will be references as 'session://example_target'
  50. *
  51. * @var String
  52. */
  53. protected $uri;
  54. /**
  55. * The content of the stream.
  56. *
  57. * Since this trivial example just uses the $_SESSION variable, this is
  58. * simply a reference to the contents of the related part of
  59. * $_SESSION['file_example'].
  60. */
  61. protected $sessionContent;
  62. /**
  63. * Pointer to where we are in a directory read.
  64. */
  65. protected $directoryPointer;
  66. /**
  67. * List of keys in a given directory.
  68. */
  69. protected $directoryKeys;
  70. /**
  71. * The pointer to the next read or write within the session variable.
  72. */
  73. protected $streamPointer;
  74. /**
  75. * Constructor method.
  76. */
  77. public function __construct() {
  78. $_SESSION['file_example']['.isadir.txt'] = TRUE;
  79. }
  80. /**
  81. * Implements setUri().
  82. */
  83. public function setUri($uri) {
  84. $this->uri = $uri;
  85. }
  86. /**
  87. * Implements getUri().
  88. */
  89. public function getUri() {
  90. return $this->uri;
  91. }
  92. /**
  93. * Implements getTarget().
  94. *
  95. * The "target" is the portion of the URI to the right of the scheme.
  96. * So in session://example/test.txt, the target is 'example/test.txt'.
  97. */
  98. public function getTarget($uri = NULL) {
  99. if (!isset($uri)) {
  100. $uri = $this->uri;
  101. }
  102. list($scheme, $target) = explode('://', $uri, 2);
  103. // Remove erroneous leading or trailing, forward-slashes and backslashes.
  104. // In the session:// scheme, there is never a leading slash on the target.
  105. return trim($target, '\/');
  106. }
  107. /**
  108. * Implements getMimeType().
  109. */
  110. public static function getMimeType($uri, $mapping = NULL) {
  111. if (!isset($mapping)) {
  112. // The default file map, defined in file.mimetypes.inc is quite big.
  113. // We only load it when necessary.
  114. include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
  115. $mapping = file_mimetype_mapping();
  116. }
  117. $extension = '';
  118. $file_parts = explode('.', basename($uri));
  119. // Remove the first part: a full filename should not match an extension.
  120. array_shift($file_parts);
  121. // Iterate over the file parts, trying to find a match.
  122. // For my.awesome.image.jpeg, we try:
  123. // - jpeg
  124. // - image.jpeg, and
  125. // - awesome.image.jpeg
  126. while ($additional_part = array_pop($file_parts)) {
  127. $extension = drupal_strtolower($additional_part . ($extension ? '.' . $extension : ''));
  128. if (isset($mapping['extensions'][$extension])) {
  129. return $mapping['mimetypes'][$mapping['extensions'][$extension]];
  130. }
  131. }
  132. return 'application/octet-stream';
  133. }
  134. /**
  135. * Implements getDirectoryPath().
  136. *
  137. * In this case there is no directory string, so return an empty string.
  138. */
  139. public function getDirectoryPath() {
  140. return '';
  141. }
  142. /**
  143. * Overrides getExternalUrl().
  144. *
  145. * We have set up a helper function and menu entry to provide access to this
  146. * key via HTTP; normally it would be accessible some other way.
  147. */
  148. public function getExternalUrl() {
  149. $path = $this->getLocalPath();
  150. $url = url('examples/file_example/access_session/' . $path, array('absolute' => TRUE));
  151. return $url;
  152. }
  153. /**
  154. * We have no concept of chmod, so just return TRUE.
  155. */
  156. public function chmod($mode) {
  157. return TRUE;
  158. }
  159. /**
  160. * Implements realpath().
  161. */
  162. public function realpath() {
  163. return 'session://' . $this->getLocalPath();
  164. }
  165. /**
  166. * Returns the local path.
  167. *
  168. * Here we aren't doing anything but stashing the "file" in a key in the
  169. * $_SESSION variable, so there's not much to do but to create a "path"
  170. * which is really just a key in the $_SESSION variable. So something
  171. * like 'session://one/two/three.txt' becomes
  172. * $_SESSION['file_example']['one']['two']['three.txt'] and the actual path
  173. * is "one/two/three.txt".
  174. *
  175. * @param string $uri
  176. * Optional URI, supplied when doing a move or rename.
  177. */
  178. protected function getLocalPath($uri = NULL) {
  179. if (!isset($uri)) {
  180. $uri = $this->uri;
  181. }
  182. $path = str_replace('session://', '', $uri);
  183. $path = trim($path, '/');
  184. return $path;
  185. }
  186. /**
  187. * Opens a stream, as for fopen(), file_get_contents(), file_put_contents().
  188. *
  189. * @param string $uri
  190. * A string containing the URI to the file to open.
  191. * @param string $mode
  192. * The file mode ("r", "wb" etc.).
  193. * @param int $options
  194. * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
  195. * @param string &$opened_path
  196. * A string containing the path actually opened.
  197. *
  198. * @return bool
  199. * Returns TRUE if file was opened successfully. (Always returns TRUE).
  200. *
  201. * @see http://php.net/manual/en/streamwrapper.stream-open.php
  202. */
  203. public function stream_open($uri, $mode, $options, &$opened_path) {
  204. $this->uri = $uri;
  205. // We make $session_content a reference to the appropriate key in the
  206. // $_SESSION variable. So if the local path were
  207. // /example/test.txt it $session_content would now be a
  208. // reference to $_SESSION['file_example']['example']['test.txt'].
  209. $this->sessionContent = &$this->uri_to_session_key($uri);
  210. // Reset the stream pointer since this is an open.
  211. $this->streamPointer = 0;
  212. return TRUE;
  213. }
  214. /**
  215. * Return a reference to the correct $_SESSION key.
  216. *
  217. * @param string $uri
  218. * The uri: session://something
  219. * @param bool $create
  220. * If TRUE, create the key
  221. *
  222. * @return array|bool
  223. * A reference to the array at the end of the key-path, or
  224. * FALSE if the path doesn't map to a key-path (and $create is FALSE).
  225. */
  226. protected function &uri_to_session_key($uri, $create = TRUE) {
  227. // Since our uri_to_session_key() method returns a reference, we
  228. // have to set up a failure flag variable.
  229. $fail = FALSE;
  230. $path = $this->getLocalPath($uri);
  231. $path_components = explode('/', $path);
  232. // Set up a reference to the root session:// 'directory.'
  233. $var = &$_SESSION['file_example'];
  234. // Handle case of just session://.
  235. if (count($path_components) < 1) {
  236. return $var;
  237. }
  238. // Walk through the path components and create keys in $_SESSION,
  239. // unless we're told not to create them.
  240. foreach ($path_components as $component) {
  241. if ($create || isset($var[$component])) {
  242. $var = &$var[$component];
  243. }
  244. else {
  245. // This path doesn't exist as keys, either because the
  246. // key doesn't exist, or because we're told not to create it.
  247. return $fail;
  248. }
  249. }
  250. return $var;
  251. }
  252. /**
  253. * Support for flock().
  254. *
  255. * The $_SESSION variable has no locking capability, so return TRUE.
  256. *
  257. * @param int $operation
  258. * One of the following:
  259. * - LOCK_SH to acquire a shared lock (reader).
  260. * - LOCK_EX to acquire an exclusive lock (writer).
  261. * - LOCK_UN to release a lock (shared or exclusive).
  262. * - LOCK_NB if you don't want flock() to block while locking (not
  263. * supported on Windows).
  264. *
  265. * @return bool
  266. * Always returns TRUE at the present time. (no support)
  267. *
  268. * @see http://php.net/manual/en/streamwrapper.stream-lock.php
  269. */
  270. public function stream_lock($operation) {
  271. return TRUE;
  272. }
  273. /**
  274. * Support for fread(), file_get_contents() etc.
  275. *
  276. * @param int $count
  277. * Maximum number of bytes to be read.
  278. *
  279. * @return string
  280. * The string that was read, or FALSE in case of an error.
  281. *
  282. * @see http://php.net/manual/en/streamwrapper.stream-read.php
  283. */
  284. public function stream_read($count) {
  285. if (is_string($this->sessionContent)) {
  286. $remaining_chars = drupal_strlen($this->sessionContent) - $this->streamPointer;
  287. $number_to_read = min($count, $remaining_chars);
  288. if ($remaining_chars > 0) {
  289. $buffer = drupal_substr($this->sessionContent, $this->streamPointer, $number_to_read);
  290. $this->streamPointer += $number_to_read;
  291. return $buffer;
  292. }
  293. }
  294. return FALSE;
  295. }
  296. /**
  297. * Support for fwrite(), file_put_contents() etc.
  298. *
  299. * @param string $data
  300. * The string to be written.
  301. *
  302. * @return int
  303. * The number of bytes written (integer).
  304. *
  305. * @see http://php.net/manual/en/streamwrapper.stream-write.php
  306. */
  307. public function stream_write($data) {
  308. // Sanitize the data in a simple way since we're putting it into the
  309. // session variable.
  310. $data = check_plain($data);
  311. $this->sessionContent = substr_replace($this->sessionContent, $data, $this->streamPointer);
  312. $this->streamPointer += drupal_strlen($data);
  313. return drupal_strlen($data);
  314. }
  315. /**
  316. * Support for feof().
  317. *
  318. * @return bool
  319. * TRUE if end-of-file has been reached.
  320. *
  321. * @see http://php.net/manual/en/streamwrapper.stream-eof.php
  322. */
  323. public function stream_eof() {
  324. return FALSE;
  325. }
  326. /**
  327. * Support for fseek().
  328. *
  329. * @param int $offset
  330. * The byte offset to got to.
  331. * @param int $whence
  332. * SEEK_SET, SEEK_CUR, or SEEK_END.
  333. *
  334. * @return bool
  335. * TRUE on success.
  336. *
  337. * @see http://php.net/manual/en/streamwrapper.stream-seek.php
  338. */
  339. public function stream_seek($offset, $whence) {
  340. if (drupal_strlen($this->sessionContent) >= $offset) {
  341. $this->streamPointer = $offset;
  342. return TRUE;
  343. }
  344. return FALSE;
  345. }
  346. /**
  347. * Support for fflush().
  348. *
  349. * @return bool
  350. * TRUE if data was successfully stored (or there was no data to store).
  351. * This always returns TRUE, as this example provides and needs no
  352. * flush support.
  353. *
  354. * @see http://php.net/manual/en/streamwrapper.stream-flush.php
  355. */
  356. public function stream_flush() {
  357. return TRUE;
  358. }
  359. /**
  360. * Support for ftell().
  361. *
  362. * @return int
  363. * The current offset in bytes from the beginning of file.
  364. *
  365. * @see http://php.net/manual/en/streamwrapper.stream-tell.php
  366. */
  367. public function stream_tell() {
  368. return $this->streamPointer;
  369. }
  370. /**
  371. * Support for fstat().
  372. *
  373. * @return array
  374. * An array with file status, or FALSE in case of an error - see fstat()
  375. * for a description of this array.
  376. *
  377. * @see http://php.net/manual/en/streamwrapper.stream-stat.php
  378. */
  379. public function stream_stat() {
  380. return array(
  381. 'size' => drupal_strlen($this->sessionContent),
  382. );
  383. }
  384. /**
  385. * Support for fclose().
  386. *
  387. * @return bool
  388. * TRUE if stream was successfully closed.
  389. *
  390. * @see http://php.net/manual/en/streamwrapper.stream-close.php
  391. */
  392. public function stream_close() {
  393. $this->streamPointer = 0;
  394. // Unassign the reference.
  395. unset($this->sessionContent);
  396. return TRUE;
  397. }
  398. /**
  399. * Support for unlink().
  400. *
  401. * @param string $uri
  402. * A string containing the uri to the resource to delete.
  403. *
  404. * @return bool
  405. * TRUE if resource was successfully deleted.
  406. *
  407. * @see http://php.net/manual/en/streamwrapper.unlink.php
  408. */
  409. public function unlink($uri) {
  410. $path = $this->getLocalPath($uri);
  411. $path_components = preg_split('/\//', $path);
  412. $unset = '$_SESSION[\'file_example\']';
  413. foreach ($path_components as $component) {
  414. $unset .= '[\'' . $component . '\']';
  415. }
  416. // TODO: Is there a better way to delete from an array?
  417. // drupal_array_get_nested_value() doesn't work because it only returns
  418. // a reference; unsetting a reference only unsets the reference.
  419. eval("unset($unset);");
  420. return TRUE;
  421. }
  422. /**
  423. * Support for rename().
  424. *
  425. * @param string $from_uri
  426. * The uri to the file to rename.
  427. * @param string $to_uri
  428. * The new uri for file.
  429. *
  430. * @return bool
  431. * TRUE if file was successfully renamed.
  432. *
  433. * @see http://php.net/manual/en/streamwrapper.rename.php
  434. */
  435. public function rename($from_uri, $to_uri) {
  436. $from_key = &$this->uri_to_session_key($from_uri);
  437. $to_key = &$this->uri_to_session_key($to_uri);
  438. if (is_dir($to_key) || is_file($to_key)) {
  439. return FALSE;
  440. }
  441. $to_key = $from_key;
  442. unset($from_key);
  443. return TRUE;
  444. }
  445. /**
  446. * Gets the name of the directory from a given path.
  447. *
  448. * @param string $uri
  449. * A URI.
  450. *
  451. * @return string
  452. * A string containing the directory name.
  453. *
  454. * @see drupal_dirname()
  455. */
  456. public function dirname($uri = NULL) {
  457. list($scheme, $target) = explode('://', $uri, 2);
  458. $target = $this->getTarget($uri);
  459. if (strpos($target, '/')) {
  460. $dirname = preg_replace('@/[^/]*$@', '', $target);
  461. }
  462. else {
  463. $dirname = '';
  464. }
  465. return $scheme . '://' . $dirname;
  466. }
  467. /**
  468. * Support for mkdir().
  469. *
  470. * @param string $uri
  471. * A string containing the URI to the directory to create.
  472. * @param int $mode
  473. * Permission flags - see mkdir().
  474. * @param int $options
  475. * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
  476. *
  477. * @return bool
  478. * TRUE if directory was successfully created.
  479. *
  480. * @see http://php.net/manual/en/streamwrapper.mkdir.php
  481. */
  482. public function mkdir($uri, $mode, $options) {
  483. // If this already exists, then we can't mkdir.
  484. if (is_dir($uri) || is_file($uri)) {
  485. return FALSE;
  486. }
  487. // Create the key in $_SESSION;
  488. $this->uri_to_session_key($uri, TRUE);
  489. // Place a magic file inside it to differentiate this from an empty file.
  490. $marker_uri = $uri . '/.isadir.txt';
  491. $this->uri_to_session_key($marker_uri, TRUE);
  492. return TRUE;
  493. }
  494. /**
  495. * Support for rmdir().
  496. *
  497. * @param string $uri
  498. * A string containing the URI to the directory to delete.
  499. * @param int $options
  500. * A bit mask of STREAM_REPORT_ERRORS.
  501. *
  502. * @return bool
  503. * TRUE if directory was successfully removed.
  504. *
  505. * @see http://php.net/manual/en/streamwrapper.rmdir.php
  506. */
  507. public function rmdir($uri, $options) {
  508. $path = $this->getLocalPath($uri);
  509. $path_components = preg_split('/\//', $path);
  510. $unset = '$_SESSION[\'file_example\']';
  511. foreach ($path_components as $component) {
  512. $unset .= '[\'' . $component . '\']';
  513. }
  514. // TODO: I really don't like this eval.
  515. debug($unset, 'array element to be unset');
  516. eval("unset($unset);");
  517. return TRUE;
  518. }
  519. /**
  520. * Support for stat().
  521. *
  522. * This important function goes back to the Unix way of doing things.
  523. * In this example almost the entire stat array is irrelevant, but the
  524. * mode is very important. It tells PHP whether we have a file or a
  525. * directory and what the permissions are. All that is packed up in a
  526. * bitmask. This is not normal PHP fodder.
  527. *
  528. * @param string $uri
  529. * A string containing the URI to get information about.
  530. * @param int $flags
  531. * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
  532. *
  533. * @return array|bool
  534. * An array with file status, or FALSE in case of an error - see fstat()
  535. * for a description of this array.
  536. *
  537. * @see http://php.net/manual/en/streamwrapper.url-stat.php
  538. */
  539. public function url_stat($uri, $flags) {
  540. // Get a reference to the $_SESSION key for this URI.
  541. $key = $this->uri_to_session_key($uri, FALSE);
  542. // Default to fail.
  543. $return = FALSE;
  544. $mode = 0;
  545. // We will call an array a directory and the root is always an array.
  546. if (is_array($key) && array_key_exists('.isadir.txt', $key)) {
  547. // S_IFDIR means it's a directory.
  548. $mode = 0040000;
  549. }
  550. elseif ($key !== FALSE) {
  551. // S_IFREG, means it's a file.
  552. $mode = 0100000;
  553. }
  554. if ($mode) {
  555. $size = 0;
  556. if ($mode == 0100000) {
  557. $size = drupal_strlen($key);
  558. }
  559. // There are no protections on this, so all writable.
  560. $mode |= 0777;
  561. $return = array(
  562. 'dev' => 0,
  563. 'ino' => 0,
  564. 'mode' => $mode,
  565. 'nlink' => 0,
  566. 'uid' => 0,
  567. 'gid' => 0,
  568. 'rdev' => 0,
  569. 'size' => $size,
  570. 'atime' => 0,
  571. 'mtime' => 0,
  572. 'ctime' => 0,
  573. 'blksize' => 0,
  574. 'blocks' => 0,
  575. );
  576. }
  577. return $return;
  578. }
  579. /**
  580. * Support for opendir().
  581. *
  582. * @param string $uri
  583. * A string containing the URI to the directory to open.
  584. * @param int $options
  585. * Whether or not to enforce safe_mode (0x04).
  586. *
  587. * @return bool
  588. * TRUE on success.
  589. *
  590. * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
  591. */
  592. public function dir_opendir($uri, $options) {
  593. $var = &$this->uri_to_session_key($uri, FALSE);
  594. if ($var === FALSE || !array_key_exists('.isadir.txt', $var)) {
  595. return FALSE;
  596. }
  597. // We grab the list of key names, flip it so that .isadir.txt can easily
  598. // be removed, then flip it back so we can easily walk it as a list.
  599. $this->directoryKeys = array_flip(array_keys($var));
  600. unset($this->directoryKeys['.isadir.txt']);
  601. $this->directoryKeys = array_keys($this->directoryKeys);
  602. $this->directoryPointer = 0;
  603. return TRUE;
  604. }
  605. /**
  606. * Support for readdir().
  607. *
  608. * @return string|bool
  609. * The next filename, or FALSE if there are no more files in the directory.
  610. *
  611. * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
  612. */
  613. public function dir_readdir() {
  614. if ($this->directoryPointer < count($this->directoryKeys)) {
  615. $next = $this->directoryKeys[$this->directoryPointer];
  616. $this->directoryPointer++;
  617. return $next;
  618. }
  619. return FALSE;
  620. }
  621. /**
  622. * Support for rewinddir().
  623. *
  624. * @return bool
  625. * TRUE on success.
  626. *
  627. * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
  628. */
  629. public function dir_rewinddir() {
  630. $this->directoryPointer = 0;
  631. }
  632. /**
  633. * Support for closedir().
  634. *
  635. * @return bool
  636. * TRUE on success.
  637. *
  638. * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
  639. */
  640. public function dir_closedir() {
  641. $this->directoryPointer = 0;
  642. unset($this->directoryKeys);
  643. return TRUE;
  644. }
  645. }