PageRenderTime 61ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/public/external/pydio/plugins/access.hpcloud/HPCloud_Patch/Storage/ObjectStorage/StreamWrapper.php

https://github.com/costinu/cms
PHP | 1775 lines | 691 code | 167 blank | 917 comment | 101 complexity | 12171a3b25efe5428e89b6a45da418f6 MD5 | raw file
Possible License(s): BSD-2-Clause, Apache-2.0, LGPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause, LGPL-2.0, AGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /* ============================================================================
  3. (c) Copyright 2012 Hewlett-Packard Development Company, L.P.
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights to
  7. use, copy, modify, merge,publish, distribute, sublicense, and/or sell copies of
  8. the Software, and to permit persons to whom the Software is furnished to do so,
  9. subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. ============================================================================ */
  20. /**
  21. * @file
  22. * Contains the stream wrapper for `swift://` URLs.
  23. */
  24. namespace HPCloud\Storage\ObjectStorage;
  25. use \HPCloud\Bootstrap;
  26. use \HPCloud\Storage\ObjectStorage;
  27. /**
  28. * Provides stream wrapping for Swift.
  29. *
  30. * This provides a full stream wrapper to expose `swift://` URLs to the
  31. * PHP stream system.
  32. *
  33. * Swift streams provide authenticated and priviledged access to the
  34. * swift data store. These URLs are not generally used for granting
  35. * unauthenticated access to files (which can be done using the HTTP
  36. * stream wrapper -- no need for swift-specific logic).
  37. *
  38. * <b>URL Structure</b>
  39. *
  40. * This takes URLs of the following form:
  41. *
  42. * <tt>swift://CONTAINER/FILE</tt>
  43. *
  44. * Example:
  45. *
  46. * <tt>swift://public/example.txt</tt>
  47. *
  48. * The example above would access the `public` container and attempt to
  49. * retrieve the file named `example.txt`.
  50. *
  51. * Slashes are legal in Swift filenames, so a pathlike URL can be constructed
  52. * like this:
  53. *
  54. * <tt>swift://public/path/like/file/name.txt</tt>
  55. *
  56. * The above would attempt to find a file in object storage named
  57. * `path/like/file/name.txt`.
  58. *
  59. * A note on UTF-8 and URLs: PHP does not yet natively support many UTF-8
  60. * characters in URLs. Thus, you ought to rawurlencode() your container name
  61. * and object name (path) if there is any possibility that it will contain
  62. * UTF-8 characters.
  63. *
  64. * <b>Locking</b>
  65. *
  66. * This library does not support locking (e.g. flock()). This is because the
  67. * OpenStack Object Storage implementation does not support locking. But there
  68. * are a few related things you should keep in mind:
  69. *
  70. * - Working with a stream is essentially working with a COPY OF a remote file.
  71. * Local locking is not an issue.
  72. * - If you open two streams for the same object, you will be working with
  73. * TWO COPIES of the object. This can, of course, lead to nasty race
  74. * conditions if each copy is modified.
  75. *
  76. * <b>Usage</b>
  77. *
  78. * The principle purpose of this wrapper is to make it easy to access and
  79. * manipulate objects on a remote object storage instance. Managing
  80. * containers is a secondary concern (and can often better be managed using
  81. * the HPCloud API). Consequently, almost all actions done through the
  82. * stream wrapper are focused on objects, not containers, servers, etc.
  83. *
  84. * <b>Retrieving an Existing Object</b>
  85. *
  86. * Retrieving an object is done by opening a file handle to that object.
  87. *
  88. * <b>Writing an Object</b>
  89. *
  90. * Nothing is written to the remote storage until the file is closed. This
  91. * keeps network traffic at a minimum, and respects the more-or-less stateless
  92. * nature of ObjectStorage.
  93. *
  94. * <b>USING FILE/STREAM RESOURCES</b>
  95. *
  96. * In general, you should access files like this:
  97. *
  98. * @code
  99. * <?php
  100. * \HPCloud\Bootstrap::useStreamWrappers();
  101. * // Set up the context.
  102. * $context = stream_context_create(
  103. * array('swift' => array(
  104. * 'account' => ACCOUNT_NUMBER,
  105. * 'key' => SECRET_KEY,
  106. * 'tenantId' => TENANT_ID
  107. * 'endpoint' => AUTH_ENDPOINT_URL,
  108. * )
  109. * )
  110. * );
  111. * // Open the file.
  112. * $handle = fopen('swift://mycontainer/myobject.txt', 'r+', FALSE, $context);
  113. *
  114. * // You can get the entire file, or use fread() to loop through the file.
  115. * $contents = stream_get_contents($handle);
  116. *
  117. * fclose($handle);
  118. * ?>
  119. * @endcode
  120. *
  121. * @remarks
  122. *
  123. * - file_get_contents() works fine.
  124. * - You can write to a stream, too. Nothing is pushed to the server until
  125. * fflush() or fclose() is called.
  126. * - Mode strings (w, r, w+, r+, c, c+, a, a+, x, x+) all work, though certain
  127. * constraints are slightly relaxed to accomodate efficient local buffering.
  128. * - Files are buffered locally.
  129. *
  130. * <b>USING FILE-LEVEL FUNCTIONS</b>
  131. *
  132. * PHP provides a number of file-level functions that stream wrappers can
  133. * optionally support. Here are a few such functions:
  134. *
  135. * - file_exists()
  136. * - is_readable()
  137. * - stat()
  138. * - filesize()
  139. * - fileperms()
  140. *
  141. * The HPCloud stream wrapper provides support for these file-level functions.
  142. * But there are a few things you should know:
  143. *
  144. * - Each call to one of these functions generates at least one request. It may
  145. * be as many as three:
  146. * * An auth request
  147. * * A request for the container (to get container permissions)
  148. * * A request for the object
  149. * - <em>IMPORTANT:</em> Unlike the fopen()/fclose()... functions NONE of these functions
  150. * retrieves the body of the file. If you are working with large files, using
  151. * these functions may be orders of magnitude faster than using fopen(), etc.
  152. * (The crucial detail: These kick off a HEAD request, will fopen() does a
  153. * GET request).
  154. * - You must use Bootstrap::setConfiguration() to pass in all of the values you
  155. * would normally pass into a stream context:
  156. * * endpoint
  157. * * account
  158. * * key
  159. * - Most of the information from this family of calls can also be obtained using
  160. * fstat(). If you were going to open a stream anyway, you might as well use
  161. * fopen()/fstat().
  162. * - stat() and fstat() fake the permissions and ownership as follows:
  163. * * uid/gid are always sset to the current user. This basically assumes that if
  164. * the current user can access the object, the current user has ownership over
  165. * the file. As the OpenStack ACL system developers, this may change.
  166. * * Mode is faked. Swift uses ACLs, not UNIX mode strings. So we fake the string:
  167. * - 770: The ACL has the object marked as private.
  168. * - 775: The ACL has the object marked as public.
  169. * - ACLs are actually set on the container, so every file in a public container
  170. * will return 775.
  171. * - stat/fstat provide only one timestamp. Swift only tracks mtime, so mtime, atime,
  172. * and ctime are all set to the last modified time.
  173. *
  174. * <b>DIRECTORIES</b>
  175. *
  176. * OpenStack Swift does not really have directories. Rather, it allows
  177. * characters such as '/' to be used to designate namespaces on object
  178. * names. (For simplicity, this library uses only '/' as a separator).
  179. *
  180. * This allows for simulated directory listings. Requesting
  181. * `scandir('swift://foo/bar/')` is really a request to "find all of the items
  182. * in the 'foo' container whose names start with 'bar/'".
  183. *
  184. * Because of this...
  185. *
  186. * - Directory reading functions like scandir(), opendir(), readdir()
  187. * and so forth are supported.
  188. * - Functions to create or remove directories (mkdir() and rmdir()) are
  189. * meaningless, and thus not supported.
  190. *
  191. * Swift still has support for "directory markers" (special zero-byte files
  192. * that act like directories). However, since there are no standards for how
  193. * said markers ought to be created, they are not supported by the stream
  194. * wrapper.
  195. *
  196. * As usual, the underlying HPCloud::Storage::ObjectStorage::Container class
  197. * supports the full range of Swift features.
  198. *
  199. * <b>SUPPORTED CONTEXT PARAMETERS</b>
  200. *
  201. * This section details paramters that can be passed <i>either</i>
  202. * through a stream context <i>or</i> through
  203. * HPCloud::Bootstrap::setConfiguration().
  204. *
  205. * @attention
  206. * PHP functions that do not allow you to pass a context may still be supported
  207. * here <em>IF</em> you have set options using Bootstrap::setConfiguration().
  208. *
  209. * You are <i>required</i> to pass in authentication information. This
  210. * comes in one of three forms:
  211. *
  212. * -# API keys: acccount, key, tenantid, endpoint
  213. * -# User login: username, password, tenantid, endpoint
  214. * -# Existing (valid) token: token, swift_endpoint
  215. *
  216. * The third method (token) can be used when the application has already
  217. * authenticated. In this case, a token has been generated and assigneet
  218. * to an account and tenant ID.
  219. *
  220. * The following parameters may be set either in the stream context
  221. * or through HPCloud::Bootstrap::setConfiguration():
  222. *
  223. * - token: An auth token. If this is supplied, authentication is skipped and
  224. * this token is used. NOTE: You MUST set swift_endpoint if using this
  225. * option.
  226. * - swift_endpoint: The URL to the swift instance. This is only necessary if
  227. * 'token' is set. Otherwise it is ignored.
  228. * - username: A username. MUST be accompanied by 'password' and 'tenantid'.
  229. * - password: A password. MUST be accompanied by 'username' and 'tenantid'.
  230. * - account: An account ID. MUST be accompanied by a 'key' and 'tenantid'.
  231. * - key: A secret key. MUST be accompanied by an 'account' and 'tenantid'.
  232. * - endpoint: The URL to the authentication endpoint. Necessary if you are not
  233. * using a 'token' and 'swift_endpoint'.
  234. * - use_swift_auth: If this is set to TRUE, it will force the app to use
  235. * the deprecated swiftAuth instead of IdentityServices authentication.
  236. * In general, you should avoid using this.
  237. * - content_type: This is effective only when writing files. It will
  238. * set the Content-Type of the file during upload.
  239. * - use_cdn: If this is set to TRUE, then whenever possible assets will be
  240. * loaded from CDN instead of from the object store. The following
  241. * conditions must obtain for this to work:
  242. * - The container must allow public reading (ACL)
  243. * - The container must have CDN enabled
  244. * - The CDN container must be active ("cdn-enabled")
  245. * - Authentication info must be accessible to the stream wrapper.
  246. * - cdn_require_ssl: If this is set to FALSE, then CDN-based requests
  247. * may use plain HTTP instead of HTTPS. This will spead up CDN
  248. * fetches at the cost of security.
  249. *
  250. * @attention
  251. * ADVANCED: You can also pass an HPCloud::Storage::CDN object in use_cdn instead of
  252. * a boolean.
  253. *
  254. * @see http://us3.php.net/manual/en/class.streamwrapper.php
  255. *
  256. * @todo The service catalog should be cached in the context like the token so that
  257. * it can be retrieved later.
  258. */
  259. class StreamWrapper
  260. {
  261. const DEFAULT_SCHEME = 'swift';
  262. /**
  263. * Cache of auth token -> service catalog.
  264. *
  265. * This will eventually be replaced by a better system, but for a system of
  266. * moderate complexity, many, many file operations may be run during the
  267. * course of a request. Caching the catalog can prevent numerous calls
  268. * to identity services.
  269. */
  270. protected static $serviceCatalogCache = array();
  271. protected static $statCache = array();
  272. protected static $statCacheData = array();
  273. /**
  274. * The stream context.
  275. *
  276. * This is set automatically when the stream wrapper is created by
  277. * PHP. Note that it is not set through a constructor.
  278. */
  279. public $context;
  280. protected $contextArray = array();
  281. protected $schemeName = self::DEFAULT_SCHEME;
  282. protected $authToken;
  283. // File flags. These should probably be replaced by O_ const's at some point.
  284. protected $isBinary = FALSE;
  285. protected $isText = TRUE;
  286. protected $isWriting = FALSE;
  287. protected $isReading = FALSE;
  288. protected $isTruncating = FALSE;
  289. protected $isAppending = FALSE;
  290. protected $noOverwrite = FALSE;
  291. protected $createIfNotFound = TRUE;
  292. /**
  293. * If this is TRUE, no data is ever sent to the remote server.
  294. */
  295. protected $isNeverDirty = FALSE;
  296. protected $triggerErrors = FALSE;
  297. /**
  298. * Indicate whether the local differs from remote.
  299. *
  300. * When the file is modified in such a way that
  301. * it needs to be written remotely, the isDirty flag
  302. * is set to TRUE.
  303. */
  304. protected $isDirty = FALSE;
  305. /**
  306. * Object storage instance.
  307. */
  308. protected $store;
  309. /**
  310. * The Container.
  311. */
  312. protected $container;
  313. /**
  314. * The Object.
  315. */
  316. protected $obj;
  317. /**
  318. * The IO stream for the Object.
  319. */
  320. protected $objStream;
  321. /**
  322. * Directory listing.
  323. *
  324. * Used for directory methods.
  325. */
  326. protected $dirListing = array();
  327. protected $dirIndex = 0;
  328. protected $dirPrefix = '';
  329. /**
  330. * Close a directory.
  331. *
  332. * This closes a directory handle, freeing up the resources.
  333. *
  334. * @code
  335. * <?php
  336. *
  337. * // Assuming a valid context in $cxt...
  338. *
  339. * // Get the container as if it were a directory.
  340. * $dir = opendir('swift://mycontainer', $cxt);
  341. *
  342. * // Do something with $dir
  343. *
  344. * closedir($dir);
  345. * ?>
  346. * @endcode
  347. *
  348. * NB: Some versions of PHP 5.3 don't clear all buffers when
  349. * closing, and the handle can occasionally remain accessible for
  350. * some period of time.
  351. */
  352. public function dir_closedir()
  353. {
  354. $this->dirIndex = 0;
  355. $this->dirListing = array();
  356. //syslog(LOG_WARNING, "CLOSEDIR called.");
  357. return TRUE;
  358. }
  359. /**
  360. * Open a directory for reading.
  361. *
  362. * @code
  363. * <?php
  364. *
  365. * // Assuming a valid context in $cxt...
  366. *
  367. * // Get the container as if it were a directory.
  368. * $dir = opendir('swift://mycontainer', $cxt);
  369. *
  370. * // Do something with $dir
  371. *
  372. * closedir($dir);
  373. * ?>
  374. * @endcode
  375. *
  376. * See opendir() and scandir().
  377. *
  378. * @param string $path
  379. * The URL to open.
  380. * @param int $options
  381. * Unused.
  382. * @retval boolean
  383. * TRUE if the directory is opened, FALSE otherwise.
  384. */
  385. public function dir_opendir($path, $options)
  386. {
  387. \AJXP_Logger::debug("OPENDIR ".$path);
  388. $url = $this->parseUrl($path);
  389. if (isSet(self::$statCacheData["HPC_MAIN_PATH"]) && self::$statCacheData["HPC_MAIN_PATH"] == $path) {
  390. \AJXP_Logger::debug(">> listing from cache");
  391. $this->listFromCache = true;
  392. return TRUE;
  393. }
  394. $this->listFromCache = false;
  395. self::$statCache = array();
  396. self::$statCacheData = array();
  397. if (empty($url['host'])) {
  398. trigger_error('Container name is required.' , E_USER_WARNING);
  399. return FALSE;
  400. }
  401. try {
  402. $this->initializeObjectStorage();
  403. $container = $this->store->container($url['host']);
  404. self::$statCacheData["HPC_CONTAINER"] = $container;
  405. self::$statCacheData["HPC_MAIN_PATH"] = $path;
  406. if (empty($url['path'])) {
  407. $this->dirPrefix = '';
  408. } else {
  409. $this->dirPrefix = $url['path'];
  410. }
  411. $sep = '/';
  412. $this->dirListing = $container->objectsWithPrefix($this->dirPrefix, $sep);
  413. } catch (\HPCloud\Exception $e) {
  414. trigger_error('Directory could not be opened: ' . $e->getMessage(), E_USER_WARNING);
  415. return FALSE;
  416. }
  417. return TRUE;
  418. }
  419. /**
  420. * Read an entry from the directory.
  421. *
  422. * This gets a single line from the directory.
  423. * @code
  424. * <?php
  425. *
  426. * // Assuming a valid context in $cxt...
  427. *
  428. * // Get the container as if it were a directory.
  429. * $dir = opendir('swift://mycontainer', $cxt);
  430. *
  431. * while (($entry = readdir($dir)) !== FALSE) {
  432. * print $entry . PHP_EOL;
  433. * }
  434. *
  435. * closedir($dir);
  436. * ?>
  437. * @endcode
  438. *
  439. * @retval string
  440. * The name of the resource or FALSE when the directory has no more
  441. * entries.
  442. */
  443. public function dir_readdir()
  444. {
  445. if ($this->listFromCache) {
  446. $values = array_keys(self::$statCache);
  447. if (count($values) <= $this->dirIndex) {
  448. return FALSE;
  449. }
  450. $fullpath = $values[$this->dirIndex];
  451. if (!empty($this->dirPrefix)) {
  452. $len = strlen($this->dirPrefix);
  453. $fullpath = substr($fullpath, $len);
  454. }
  455. $this->dirIndex ++;
  456. return $fullpath;
  457. }
  458. // If we are at the end of the listing, return FALSE.
  459. if (count($this->dirListing) <= $this->dirIndex) {
  460. return FALSE;
  461. }
  462. $curr = $this->dirListing[$this->dirIndex];
  463. $this->dirIndex++;
  464. if ($curr instanceof \HPCloud\Storage\ObjectStorage\Subdir) {
  465. $fullpath = $curr->path();
  466. \AJXP_Logger::debug("Full path should be dir ".$fullpath);
  467. $fullpath = rtrim($fullpath, "/");
  468. self::$statCache[basename($fullpath)] = $this->fakeStat(true);
  469. } else {
  470. $fullpath = $curr->name();
  471. self::$statCache[basename($fullpath)] = $this->generateStat($curr, self::$statCacheData["HPC_CONTAINER"], $curr->contentLength() );
  472. }
  473. if (!empty($this->dirPrefix)) {
  474. $len = strlen($this->dirPrefix);
  475. $fullpath = substr($fullpath, $len);
  476. }
  477. return $fullpath;
  478. }
  479. /**
  480. * Fake stat data.
  481. *
  482. * Under certain conditions we have to return totally trumped-up
  483. * stats. This generates those.
  484. */
  485. protected function fakeStat($dir = FALSE)
  486. {
  487. $request_time = time();
  488. // Set inode type to directory or file.
  489. $type = $dir ? 040000 : 0100000;
  490. // Fake world-readible
  491. $mode = $type + $this->cxt('swiftfs_fake_stat_mode', 0777);
  492. $values = array(
  493. 'dev' => 0,
  494. 'ino' => 0,
  495. 'mode' => $mode,
  496. 'nlink' => 0,
  497. 'uid' => posix_getuid(),
  498. 'gid' => posix_getgid(),
  499. 'rdev' => 0,
  500. 'size' => 0,
  501. 'atime' => $request_time,
  502. 'mtime' => $request_time,
  503. 'ctime' => $request_time,
  504. 'blksize' => -1,
  505. 'blocks' => -1,
  506. );
  507. $final = array_values($values) + $values;
  508. return $final;
  509. }
  510. /**
  511. * Rewind to the beginning of the listing.
  512. *
  513. * This repositions the read pointer at the first entry in the directory.
  514. * @code
  515. * <?php
  516. *
  517. * // Assuming a valid context in $cxt...
  518. *
  519. * // Get the container as if it were a directory.
  520. * $dir = opendir('swift://mycontainer', $cxt);
  521. *
  522. * while (($entry = readdir($dir)) !== FALSE) {
  523. * print $entry . PHP_EOL;
  524. * }
  525. *
  526. * rewinddir($dir);
  527. *
  528. * $first = readdir($dir);
  529. *
  530. * closedir($dir);
  531. * ?>
  532. * @endcode
  533. */
  534. public function dir_rewinddir()
  535. {
  536. $this->dirIndex = 0;
  537. }
  538. /*
  539. public function mkdir($path, $mode, $options)
  540. {
  541. }
  542. public function rmdir($path, $options)
  543. {
  544. }
  545. */
  546. /**
  547. * Rename a swift object.
  548. *
  549. * This works by copying the object (metadata) and
  550. * then removing the original version.
  551. *
  552. * This DOES support cross-container renaming.
  553. *
  554. * See Container::copy().
  555. *
  556. * @code
  557. * <?php
  558. * Bootstrap::setConfiguration(array(
  559. * 'tenantid' => '1234',
  560. * 'account' => '1234',
  561. * 'secret' => '4321',
  562. * 'endpoint' => 'https://auth.example.com',
  563. * ));
  564. *
  565. * $from = 'swift://containerOne/file.txt';
  566. * $to = 'swift://containerTwo/file.txt';
  567. *
  568. * // Rename can also take a context as a third param.
  569. * rename($from, $to);
  570. *
  571. * ?>
  572. * @endcode
  573. *
  574. * @param string $path_from
  575. * A swift URL that exists on the remote.
  576. * @param string $path_to
  577. * A swift URL to another path.
  578. * @retval boolean
  579. * TRUE on success, FALSE otherwise.
  580. */
  581. public function rename($path_from, $path_to)
  582. {
  583. $this->initializeObjectStorage();
  584. $src = $this->parseUrl($path_from);
  585. $dest = $this->parseUrl($path_to);
  586. if ($src['scheme'] != $dest['scheme']) {
  587. trigger_error("I'm too stupid to copy across protocols.", E_USER_WARNING);
  588. }
  589. if ( empty($src['host']) || empty($src['path'])
  590. || empty($dest['host']) || empty($dest['path'])) {
  591. trigger_error('Container and path are required for both source and destination URLs.', E_USER_WARNING);
  592. return FALSE;
  593. }
  594. try {
  595. $container = $this->store->container($src['host']);
  596. $object = $container->remoteObject($src['path']);
  597. $ret = $container->copy($object, $dest['path'], $dest['host']);
  598. if ($ret) {
  599. return $container->delete($src['path']);
  600. }
  601. } catch (\HPCloud\Exception $e) {
  602. trigger_error('Rename was not completed: ' . $e->getMessage(), E_USER_WARNING);
  603. return FALSE;
  604. }
  605. }
  606. /*
  607. public function copy($path_from, $path_to)
  608. {
  609. throw new \Exception("UNDOCUMENTED.");
  610. }
  611. */
  612. /**
  613. * Cast stream into a lower-level stream.
  614. *
  615. * This is used for stream_select() and perhaps others.Because it exposes
  616. * the lower-level buffer objects, this function can have unexpected
  617. * side effects.
  618. *
  619. * @retval resource
  620. * this returns the underlying stream.
  621. */
  622. public function stream_cast($cast_as)
  623. {
  624. return $this->objStream;
  625. }
  626. /**
  627. * Close a stream, writing if necessary.
  628. *
  629. * @code
  630. * <?php
  631. *
  632. * // Assuming $cxt has a valid context.
  633. *
  634. * $file = fopen('swift://container/file.txt', 'r', FALSE, $cxt);
  635. *
  636. * fclose($file);
  637. *
  638. * ?>
  639. * @endcode
  640. *
  641. * This will close the present stream. Importantly,
  642. * this will also write to the remote object storage if
  643. * any changes have been made locally.
  644. *
  645. * See stream_open().
  646. */
  647. public function stream_close()
  648. {
  649. try {
  650. $this->writeRemote();
  651. } catch (\HPCloud\Exception $e) {
  652. trigger_error('Error while closing: ' . $e->getMessage(), E_USER_NOTICE);
  653. return FALSE;
  654. }
  655. // Force-clear the memory hogs.
  656. unset($this->obj);
  657. fclose($this->objStream);
  658. }
  659. /**
  660. * Check whether the stream has reached its end.
  661. *
  662. * This checks whether the stream has reached the
  663. * end of the object's contents.
  664. *
  665. * Called when \c feof() is called on a stream.
  666. *
  667. * See stream_seek().
  668. *
  669. * @retval boolean
  670. * TRUE if it has reached the end, FALSE otherwise.
  671. */
  672. public function stream_eof()
  673. {
  674. return feof($this->objStream);
  675. }
  676. /**
  677. * Initiate saving data on the remote object storage.
  678. *
  679. * If the local copy of this object has been modified,
  680. * it is written remotely.
  681. *
  682. * Called when \c fflush() is called on a stream.
  683. */
  684. public function stream_flush()
  685. {
  686. try {
  687. $this->writeRemote();
  688. } catch (\HPCloud\Exception $e) {
  689. syslog(LOG_WARNING, $e);
  690. trigger_error('Error while flushing: ' . $e->getMessage(), E_USER_NOTICE);
  691. return FALSE;
  692. }
  693. }
  694. /**
  695. * Write data to the remote object storage.
  696. *
  697. * Internally, this is used by flush and close.
  698. */
  699. protected function writeRemote()
  700. {
  701. $contentType = $this->cxt('content_type');
  702. if (!empty($contentType)) {
  703. $this->obj->setContentType($contentType);
  704. }
  705. // Skip debug streams.
  706. if ($this->isNeverDirty) {
  707. return;
  708. }
  709. // Stream is dirty and needs a write.
  710. if ($this->isDirty) {
  711. $position = ftell($this->objStream);
  712. rewind($this->objStream);
  713. $this->container->save($this->obj, $this->objStream);
  714. fseek($this->objStream, SEEK_SET, $position);
  715. }
  716. $this->isDirty = FALSE;
  717. }
  718. /*
  719. * Locking is currently unsupported.
  720. *
  721. * There is no remote support for locking a
  722. * file.
  723. public function stream_lock($operation)
  724. {
  725. }
  726. */
  727. /**
  728. * Open a stream resource.
  729. *
  730. * This opens a given stream resource and prepares it for reading or writing.
  731. *
  732. * @code
  733. * <?php
  734. * $cxt = stream_context_create(array(
  735. * 'account' => '1bc123456',
  736. * 'tenantid' => '987654321',
  737. * 'secret' => 'eieio',
  738. * 'endpoint' => 'https://auth.example.com',
  739. * ));
  740. * ?>
  741. *
  742. * $file = fopen('swift://myContainer/myObject.csv', 'rb', FALSE, $cxt);
  743. * while ($bytes = fread($file, 8192)) {
  744. * print $bytes;
  745. * }
  746. * fclose($file);
  747. * ?>
  748. * @endcode
  749. *
  750. * If a file is opened in write mode, its contents will be retrieved from the
  751. * remote storage and cached locally for manipulation. If the file is opened
  752. * in a write-only mode, the contents will be created locally and then pushed
  753. * remotely as necessary.
  754. *
  755. * During this operation, the remote host may need to be contacted for
  756. * authentication as well as for file retrieval.
  757. *
  758. * @param string $path
  759. * The URL to the resource. See the class description for details, but
  760. * typically this expects URLs in the form <tt>swift://CONTAINER/OBJECT</tt>.
  761. * @param string $mode
  762. * Any of the documented mode strings. See fopen(). For any file that is
  763. * in a writing mode, the file will be saved remotely on flush or close.
  764. * Note that there is an extra mode: 'nope'. It acts like 'c+' except
  765. * that it is never written remotely. This is useful for debugging the
  766. * stream locally without sending that data to object storage. (Note that
  767. * data is still fetched -- just never written.)
  768. * @param int $options
  769. * An OR'd list of options. Only STREAM_REPORT_ERRORS has any meaning
  770. * to this wrapper, as it is not working with local files.
  771. * @param string $opened_path
  772. * This is not used, as this wrapper deals only with remote objects.
  773. */
  774. public function stream_open($path, $mode, $options, &$opened_path)
  775. {
  776. //syslog(LOG_WARNING, "I received this URL: " . $path);
  777. // If STREAM_REPORT_ERRORS is set, we are responsible for
  778. // all error handling while opening the stream.
  779. if (STREAM_REPORT_ERRORS & $options) {
  780. $this->triggerErrors = TRUE;
  781. }
  782. // Using the mode string, set the internal mode.
  783. $this->setMode($mode);
  784. // Parse the URL.
  785. $url = $this->parseUrl($path);
  786. //syslog(LOG_WARNING, print_r($url, TRUE));
  787. // Container name is required.
  788. if (empty($url['host'])) {
  789. //if ($this->triggerErrors) {
  790. trigger_error('No container name was supplied in ' . $path, E_USER_WARNING);
  791. //}
  792. return FALSE;
  793. }
  794. // A path to an object is required.
  795. if (empty($url['path'])) {
  796. //if ($this->triggerErrors) {
  797. trigger_error('No object name was supplied in ' . $path, E_USER_WARNING);
  798. //}
  799. return FALSE;
  800. }
  801. // We set this because it is possible to bind another scheme name,
  802. // and we need to know that name if it's changed.
  803. //$this->schemeName = isset($url['scheme']) ? $url['scheme'] : self::DEFAULT_SCHEME;
  804. if (isset($url['scheme'])) {
  805. $this->schemeName == $url['scheme'];
  806. }
  807. // Now we find out the container name. We walk a fine line here, because we don't
  808. // create a new container, but we don't want to incur heavy network
  809. // traffic, either. So we have to assume that we have a valid container
  810. // until we issue our first request.
  811. $containerName = $url['host'];
  812. // Object name.
  813. $objectName = $url['path'];
  814. // XXX: We reserve the query string for passing additional params.
  815. try {
  816. $this->initializeObjectStorage();
  817. } catch (\HPCloud\Exception $e) {
  818. trigger_error('Failed to init object storage: ' . $e->getMessage(), E_USER_WARNING);
  819. return FALSE;
  820. }
  821. //syslog(LOG_WARNING, "Container: " . $containerName);
  822. // EXPERIMENTAL:
  823. // If we can get the resource from CDN, we do so now. Note that we try to sidestep
  824. // the Container creation, which saves us an HTTP request.
  825. $cdnUrl = $this->store->cdnUrl($containerName, FALSE);
  826. $cdnSslUrl = $this->store->cdnUrl($containerName, TRUE);
  827. if (!empty($cdnUrl) && !$this->isWriting && !$this->isAppending) {
  828. $requireSSL = (boolean) $this->cxt('cdn_require_ssl', TRUE);
  829. try {
  830. $newUrl = $this->store->url() . '/' . $containerName;
  831. $token = $this->store->token();
  832. $this->container = new \HPCloud\Storage\ObjectStorage\Container($containerName, $newUrl, $token);
  833. $this->container->useCDN($cdnUrl, $cdnSslUrl);
  834. $this->obj = $this->container->object($objectName, $requireSSL);
  835. $this->objStream = $this->obj->stream();
  836. return TRUE;
  837. }
  838. // If there is an error, fall back to regular handling.
  839. catch (\HPCloud\Exception $e) {}
  840. }
  841. // End EXPERIMENTAL section.
  842. // Now we need to get the container. Doing a server round-trip here gives
  843. // us the peace of mind that we have an actual container.
  844. // XXX: Should we make it possible to get a container blindly, without the
  845. // server roundtrip?
  846. \AJXP_Logger::debug(">> Getting the container $containerName");
  847. try {
  848. if (isSet(self::$statCacheData["HPC_CONTAINER"]) && self::$statCacheData["HPC_MAIN_PATH"] == $containerName) {
  849. $this->container = self::$statCacheData["HPC_CONTAINER"];
  850. } else {
  851. $this->container = $this->store->container($containerName);
  852. self::$statCacheData["HPC_MAIN_PATH"] = $containerName;
  853. self::$statCacheData["HPC_CONTAINER"] = $this->container;
  854. }
  855. } catch (\HPCloud\Transport\FileNotFoundException $e) {
  856. trigger_error('Container not found.', E_USER_WARNING);
  857. return FALSE;
  858. }
  859. try {
  860. \AJXP_Logger::debug(">> Now fetching the file");
  861. // Now we fetch the file. Only under certain circumstances do we generate
  862. // an error if the file is not found.
  863. // FIXME: We should probably allow a context param that can be set to
  864. // mark the file as lazily fetched.
  865. $this->obj = $this->container->object($objectName);
  866. $stream = $this->obj->stream();
  867. $streamMeta = stream_get_meta_data($stream);
  868. // Support 'x' and 'x+' modes.
  869. if ($this->noOverwrite) {
  870. //if ($this->triggerErrors) {
  871. trigger_error('File exists and cannot be overwritten.', E_USER_WARNING);
  872. //}
  873. return FALSE;
  874. }
  875. // If we need to write to it, we need a writable
  876. // stream. Also, if we need to block reading, this
  877. // will require creating an alternate stream.
  878. if ($this->isWriting && ($streamMeta['mode'] == 'r' || !$this->isReading)) {
  879. $newMode = $this->isReading ? 'rb+' : 'wb';
  880. $tmpStream = fopen('php://temp', $newMode);
  881. stream_copy_to_stream($stream, $tmpStream);
  882. // Skip rewinding if we can.
  883. if (!$this->isAppending) {
  884. rewind($tmpStream);
  885. }
  886. $this->objStream = $tmpStream;
  887. } else {
  888. $this->objStream = $this->obj->stream();
  889. }
  890. // Append mode requires seeking to the end.
  891. if ($this->isAppending) {
  892. fseek($this->objStream, -1, SEEK_END);
  893. }
  894. }
  895. // If a 404 is thrown, we need to determine whether
  896. // or not a new file should be created.
  897. catch (\HPCloud\Transport\FileNotFoundException $nf) {
  898. // For many modes, we just go ahead and create.
  899. if ($this->createIfNotFound) {
  900. $this->obj = new Object($objectName);
  901. $this->objStream = fopen('php://temp', 'rb+');
  902. $this->isDirty = TRUE;
  903. } else {
  904. //if ($this->triggerErrors) {
  905. trigger_error($nf->getMessage(), E_USER_WARNING);
  906. //}
  907. return FALSE;
  908. }
  909. }
  910. // All other exceptions are fatal.
  911. catch (\HPCloud\Exception $e) {
  912. //if ($this->triggerErrors) {
  913. trigger_error('Failed to fetch object: ' . $e->getMessage(). $e->getTraceAsString() , E_USER_WARNING);
  914. //}
  915. return FALSE;
  916. }
  917. // At this point, we have a file that may be read-only. It also may be
  918. // reading off of a socket. It will be positioned at the beginning of
  919. // the stream.
  920. return TRUE;
  921. }
  922. /**
  923. * Read N bytes from the stream.
  924. *
  925. * This will read up to the requested number of bytes. Or, upon
  926. * hitting the end of the file, it will return NULL.
  927. *
  928. * See fread(), fgets(), and so on for examples.
  929. *
  930. * @code
  931. * <?php
  932. * $cxt = stream_context_create(array(
  933. * 'tenantid' => '12345',
  934. * 'username' => 'me@example.com',
  935. * 'password' => 'secret',
  936. * 'endpoint' => 'https://auth.example.com',
  937. * ));
  938. *
  939. * $content = file_get_contents('swift://public/myfile.txt', FALSE, $cxt);
  940. * ?>
  941. * @endcode
  942. *
  943. * @param int $count
  944. * The number of bytes to read (usually 8192).
  945. * @retval string
  946. * The data read.
  947. */
  948. public function stream_read($count)
  949. {
  950. return fread($this->objStream, $count);
  951. }
  952. /**
  953. * Perform a seek.
  954. *
  955. * This is called whenever \c fseek() or \c rewind() is called on a
  956. * Swift stream.
  957. *
  958. * @attention
  959. * IMPORTANT: Unlike the PHP core, this library
  960. * allows you to fseek() inside of a file opened
  961. * in append mode ('a' or 'a+').
  962. */
  963. public function stream_seek($offset, $whence)
  964. {
  965. $ret = fseek($this->objStream, $offset, $whence);
  966. // fseek returns 0 for success, -1 for failure.
  967. // We need to return TRUE for success, FALSE for failure.
  968. return $ret === 0;
  969. }
  970. /**
  971. * Set options on the underlying stream.
  972. *
  973. * The settings here do not trickle down to the network socket, which is
  974. * left open for only a brief period of time. Instead, they impact the middle
  975. * buffer stream, where the file is read and written to between flush/close
  976. * operations. Thus, tuning these will not have any impact on network
  977. * performance.
  978. *
  979. * See stream_set_blocking(), stream_set_timeout(), and stream_write_buffer().
  980. */
  981. public function stream_set_option($option, $arg1, $arg2)
  982. {
  983. switch ($option) {
  984. case STREAM_OPTION_BLOCKING:
  985. return stream_set_blocking($this->objStream, $arg1);
  986. case STREAM_OPTION_READ_TIMEOUT:
  987. // XXX: Should this have any effect on the lower-level
  988. // socket, too? Or just the buffered tmp storage?
  989. return stream_set_timeout($this->objStream, $arg1, $arg2);
  990. case STREAM_OPTION_WRITE_BUFFER:
  991. return stream_set_write_buffer($this->objStream, $arg2);
  992. }
  993. }
  994. /**
  995. * Perform stat()/lstat() operations.
  996. *
  997. * @code
  998. * <?php
  999. * $file = fopen('swift://foo/bar', 'r+', FALSE, $cxt);
  1000. * $stats = fstat($file);
  1001. * ?>
  1002. * @endcode
  1003. *
  1004. * To use standard \c stat() on a Swift stream, you will
  1005. * need to set account information (tenant ID, account ID, secret,
  1006. * etc.) through HPCloud::Bootstrap::setConfiguration().
  1007. *
  1008. * @retval array
  1009. * The stats array.
  1010. */
  1011. public function stream_stat()
  1012. {
  1013. $stat = fstat($this->objStream);
  1014. // FIXME: Need to calculate the length of the $objStream.
  1015. //$contentLength = $this->obj->contentLength();
  1016. $contentLength = $stat['size'];
  1017. return $this->generateStat($this->obj, $this->container, $contentLength);
  1018. }
  1019. /**
  1020. * Get the current position in the stream.
  1021. *
  1022. * See ftell() and fseek().
  1023. *
  1024. * @retval int
  1025. * The current position in the stream.
  1026. */
  1027. public function stream_tell()
  1028. {
  1029. return ftell($this->objStream);
  1030. }
  1031. /**
  1032. * Write data to stream.
  1033. *
  1034. * This writes data to the local stream buffer. Data
  1035. * is not pushed remotely until stream_close() or
  1036. * stream_flush() is called.
  1037. *
  1038. * @param string $data
  1039. * Data to write to the stream.
  1040. * @retval int
  1041. * The number of bytes written. 0 indicates and error.
  1042. */
  1043. public function stream_write($data)
  1044. {
  1045. $this->isDirty = TRUE;
  1046. return fwrite($this->objStream, $data);
  1047. }
  1048. /**
  1049. * Unlink a file.
  1050. *
  1051. * This removes the remote copy of the file. Like a normal unlink operation,
  1052. * it does not destroy the (local) file handle until the file is closed.
  1053. * Therefore you can continue accessing the object locally.
  1054. *
  1055. * Note that OpenStack Swift does not draw a distinction between file objects
  1056. * and "directory" objects (where the latter is a 0-byte object). This will
  1057. * delete either one. If you are using directory markers, not that deleting
  1058. * a marker will NOT delete the contents of the "directory".
  1059. *
  1060. * @attention
  1061. * You will need to use HPCloud::Bootstrap::setConfiguration() to set the
  1062. * necessary stream configuration, since \c unlink() does not take a context.
  1063. *
  1064. * @param string $path
  1065. * The URL.
  1066. * @retval boolean
  1067. * TRUE if the file was deleted, FALSE otherwise.
  1068. */
  1069. public function unlink($path)
  1070. {
  1071. $url = $this->parseUrl($path);
  1072. // Host is required.
  1073. if (empty($url['host'])) {
  1074. trigger_error('Container name is required.', E_USER_WARNING);
  1075. return FALSE;
  1076. }
  1077. // I suppose we could allow deleting containers,
  1078. // but that isn't really the purpose of the
  1079. // stream wrapper.
  1080. if (empty($url['path'])) {
  1081. trigger_error('Path is required.', E_USER_WARNING);
  1082. return FALSE;
  1083. }
  1084. try {
  1085. $this->initializeObjectStorage();
  1086. // $container = $this->store->container($url['host']);
  1087. $name = $url['host'];
  1088. $token = $this->store->token();
  1089. $endpoint_url = $this->store->url() . '/' . rawurlencode($name);
  1090. $container = new \HPCloud\Storage\ObjectStorage\Container($name, $endpoint_url, $token);
  1091. return $container->delete($url['path']);
  1092. } catch (\HPCLoud\Exception $e) {
  1093. trigger_error('Error during unlink: ' . $e->getMessage(), E_USER_WARNING);
  1094. return FALSE;
  1095. }
  1096. }
  1097. /**
  1098. * @see stream_stat().
  1099. */
  1100. public function url_stat($path, $flags)
  1101. {
  1102. $url = $this->parseUrl($path);
  1103. \AJXP_Logger::debug("STATING ".$path);
  1104. $base = $url["host"]; //dirname($path)."/";
  1105. $name = basename($path);
  1106. if (isSet(self::$statCacheData["HPC_MAIN_PATH"]) && $path."/" == self::$statCacheData["HPC_MAIN_PATH"]) {
  1107. return $this->fakeStat(true);
  1108. }
  1109. if (isset(self::$statCache[$name]) && self::$statCacheData["HPC_MAIN_PATH"] == $base) {
  1110. return self::$statCache[$name];
  1111. }
  1112. if (is_array(self::$statCache) && isSet(self::$statCache[$path])) {
  1113. \AJXP_Logger::debug("Cached path!");
  1114. return self::$statCache[$path];
  1115. }
  1116. if (empty($url['host']) || empty($url['path'])) {
  1117. if ($flags & STREAM_URL_STAT_QUIET) {
  1118. trigger_error('Container name (host) and path are required.', E_USER_WARNING);
  1119. }
  1120. return FALSE;
  1121. }
  1122. try {
  1123. $this->initializeObjectStorage();
  1124. // Since we are throwing the $container away without really using its
  1125. // internals, we create an unchecked container. It may not actually
  1126. // exist on the server, which will cause a 404 error.
  1127. //$container = $this->store->container($url['host']);
  1128. $name = $url['host'];
  1129. try {
  1130. if (isSet(self::$statCacheData["HPC_CONTAINER"]) && self::$statCacheData["HPC_MAIN_PATH"] == $name) {
  1131. \AJXP_Logger::debug("URL_STAT Getting container from cache");
  1132. $container = self::$statCacheData["HPC_CONTAINER"];
  1133. } else {
  1134. \AJXP_Logger::debug("URL_STAT Instanciating container ".$name);
  1135. $token = $this->store->token();
  1136. $endpoint_url = $this->store->url() . '/' . rawurlencode($name);
  1137. $container = new \HPCloud\Storage\ObjectStorage\Container($name, $endpoint_url, $token);
  1138. self::$statCacheData["HPC_MAIN_PATH"] = $name;
  1139. self::$statCacheData["HPC_CONTAINER"] = $container;
  1140. }
  1141. } catch (\HPCloud\Transport\FileNotFoundException $e) {
  1142. trigger_error('Container not found.', E_USER_WARNING);
  1143. return FALSE;
  1144. }
  1145. $obj = $container->remoteObject($url['path']);
  1146. } catch (\HPCloud\Exception $e) {
  1147. // Apparently file_exists does not set STREAM_URL_STAT_QUIET.
  1148. //if ($flags & STREAM_URL_STAT_QUIET) {
  1149. //trigger_error('Could not stat remote file: ' . $e->getMessage(), E_USER_WARNING);
  1150. //}
  1151. return FALSE;
  1152. }
  1153. if(!is_array(self::$statCache)) self::$statCache = array();
  1154. if ($flags & STREAM_URL_STAT_QUIET) {
  1155. try {
  1156. $s = @$this->generateStat($obj, $container, $obj->contentLength());
  1157. \AJXP_Logger::debug("CACHING1 ".$path, array_keys(self::$statCache));
  1158. self::$statCache[$path] = $s;
  1159. return $s;
  1160. } catch (\HPCloud\Exception $e) {
  1161. return FALSE;
  1162. }
  1163. }
  1164. $s = $this->generateStat($obj, $container, $obj->contentLength());
  1165. self::$statCache[$path] = $s;
  1166. \AJXP_Logger::debug("CACHING2 ".$path, array_keys(self::$statCache));
  1167. return $s;
  1168. }
  1169. /**
  1170. * Get the Object.
  1171. *
  1172. * This provides low-level access to the
  1173. * PHCloud::Storage::ObjectStorage::Object instance in which the content
  1174. * is stored.
  1175. *
  1176. * Accessing the object's payload (Object::content()) is strongly
  1177. * discouraged, as it will modify the pointers in the stream that the
  1178. * stream wrapper is using.
  1179. *
  1180. * HOWEVER, accessing the Object's metadata properties, content type,
  1181. * and so on is okay. Changes to this data will be written on the next
  1182. * flush, provided that the file stream data has also been changed.
  1183. *
  1184. * To access this:
  1185. *
  1186. * @code
  1187. * <?php
  1188. * $handle = fopen('swift://container/test.txt', 'rb', $cxt);
  1189. * $md = stream_get_meta_data($handle);
  1190. * $obj = $md['wrapper_data']->object();
  1191. * ?>
  1192. * @endcode
  1193. */
  1194. public function object()
  1195. {
  1196. return $this->obj;
  1197. }
  1198. /**
  1199. * EXPERT: Get the ObjectStorage for this wrapper.
  1200. *
  1201. * @retval object HPCloud::ObjectStorage
  1202. * An ObjectStorage object.
  1203. * @see object()
  1204. */
  1205. public function objectStorage()
  1206. {
  1207. return $this->store;
  1208. }
  1209. /**
  1210. * EXPERT: Get the auth token for this wrapper.
  1211. *
  1212. * @retval string
  1213. * A token.
  1214. * @see object()
  1215. */
  1216. public function token()
  1217. {
  1218. return $this->store->token();
  1219. }
  1220. /**
  1221. * EXPERT: Get the service catalog (IdentityServices) for this wrapper.
  1222. *
  1223. * This is only available when a file is opened via fopen().
  1224. *
  1225. * @retval array
  1226. * A service catalog.
  1227. * @see object()
  1228. */
  1229. public function serviceCatalog()
  1230. {
  1231. return self::$serviceCatalogCache[$this->token()];
  1232. }
  1233. /**
  1234. * Generate a reasonably accurate STAT array.
  1235. *
  1236. * Notes on mode:
  1237. * - All modes are of the (octal) form 100XXX, where
  1238. * XXX is replaced by the permission string. Thus,
  1239. * this always reports that the type is "file" (100).
  1240. * - Currently, only two permission sets are generated:
  1241. * - 770: Represents the ACL::makePrivate() perm.
  1242. * - 775: Represents the ACL::makePublic() perm.
  1243. *
  1244. * Notes on mtime/atime/ctime:
  1245. * - For whatever reason, Swift only stores one timestamp.
  1246. * We use that for mtime, atime, and ctime.
  1247. *
  1248. * Notes on size:
  1249. * - Size must be calculated externally, as it will sometimes
  1250. * be the remote's Content-Length, and it will sometimes be
  1251. * the cached stat['size'] for the underlying buffer.
  1252. */
  1253. protected function generateStat($object, $container, $size)
  1254. {
  1255. // This is not entirely accurate. Basically, if the
  1256. // file is marked public, it gets 100775, and if
  1257. // it is private, it gets 100770.
  1258. //
  1259. // Mode is always set to file (100XXX) because there
  1260. // is no alternative that is more sensible. PHP docs
  1261. // do not recommend an alternative.
  1262. //
  1263. // octdec(100770) == 33272
  1264. // octdec(100775) == 33277
  1265. $mode = $container->acl()->isPublic() ? 33277 : 33272;
  1266. // We have to fake the UID value in order for is_readible()/is_writable()
  1267. // to work. Note that on Windows systems, stat does not look for a UID.
  1268. if (function_exists('posix_geteuid')) {
  1269. $uid = posix_geteuid();
  1270. $gid = posix_getegid();
  1271. } else {
  1272. $uid = 0;
  1273. $gid = 0;
  1274. }
  1275. if ($object instanceof \HPCloud\Storage\ObjectStorage\RemoteObject) {
  1276. $modTime = $object->lastModified();
  1277. } else {
  1278. $modTime = 0;
  1279. }
  1280. $values = array(
  1281. 'dev' => 0,
  1282. 'ino' => 0,
  1283. 'mode' => $mode,
  1284. 'nlink' => 0,
  1285. 'uid' => $uid,
  1286. 'gid' => $gid,
  1287. 'rdev' => 0,
  1288. 'size' => $size,
  1289. 'atime' => $modTime,
  1290. 'mtime' => $modTime,
  1291. 'ctime' => $modTime,
  1292. 'blksize' => -1,
  1293. 'blocks' => -1,
  1294. );
  1295. $final = array_values($values) + $values;
  1296. return $final;
  1297. }
  1298. ///////////////////////////////////////////////////////////////////
  1299. // INTERNAL METHODS
  1300. // All methods beneath this line are not part of the Stream API.
  1301. ///////////////////////////////////////////////////////////////////
  1302. /**
  1303. * Set the fopen mode.
  1304. *
  1305. * @param string $mode
  1306. * The mode string, e.g. `r+` or `wb`.
  1307. */
  1308. protected function setMode($mode)
  1309. {
  1310. $mode = strtolower($mode);
  1311. // These are largely ignored, as the remote
  1312. // object storage does not distinguish between
  1313. // text and binary files. Per the PHP recommendation
  1314. // files are treated as binary.
  1315. $this->isBinary = strpos($mode, 'b') !== FALSE;
  1316. $this->isText = strpos($mode, 't') !== FALSE;
  1317. // Rewrite mode to remove b or t:
  1318. $mode = preg_replace('/[bt]?/', '', $mode);
  1319. switch ($mode) {
  1320. case 'r+':
  1321. $this->isWriting = TRUE;
  1322. case 'r':
  1323. $this->isReading = TRUE;
  1324. $this->createIfNotFound = FALSE;
  1325. break;
  1326. case 'w+':
  1327. $this->isReading = TRUE;
  1328. case 'w':
  1329. $this->isTruncating = TRUE;
  1330. $this->isWriting = TRUE;
  1331. break;
  1332. case 'a+':
  1333. $this->isReading = TRUE;
  1334. case 'a':
  1335. $this->isAppending = TRUE;
  1336. $this->isWriting = TRUE;
  1337. break;
  1338. case 'x+':
  1339. $this->isReading = TRUE;
  1340. case 'x':
  1341. $this->isWriting = TRUE;
  1342. $this->noOverwrite = TRUE;
  1343. break;
  1344. case 'c+':
  1345. $this->isReading = TRUE;
  1346. case 'c':
  1347. $this->isWriting = TRUE;
  1348. break;
  1349. // nope mode: Mock read/write support,
  1350. // but never write to the remote server.
  1351. // (This is accomplished by never marking
  1352. // the stream as dirty.)
  1353. case 'nope':
  1354. $this->isReading = TRUE;
  1355. $this->isWriting = TRUE;
  1356. $this->isNeverDirty = TRUE;
  1357. break;
  1358. // Default case is read/write
  1359. // like c+.
  1360. default:
  1361. $this->isReading = TRUE;
  1362. $this->isWriting = TRUE;
  1363. break;
  1364. }
  1365. }
  1366. /**
  1367. * Get an item out of the context.
  1368. *
  1369. * @todo Should there be an option to NOT query the Bootstrap::conf()?
  1370. *
  1371. * @param string $name
  1372. * The name to look up. First look it up in the context, then look
  1373. * it up in the Bootstrap config.
  1374. * @param mixed $default
  1375. * The default value to return if no config param was found.
  1376. * @retval mixed
  1377. * The discovered result, or $default if specified, or NULL if
  1378. * no $default is specified.
  1379. */
  1380. protected function cxt($name, $default = NULL)
  1381. {
  1382. // Lazilly populate the context array.
  1383. if (is_resource($this->context) && empty($this->contextArray)) {
  1384. $cxt = stream_context_get_options($this->context);
  1385. // If a custom scheme name has been set, use that.
  1386. if (!empty($cxt[$this->schemeName])) {
  1387. $this->contextArray = $cxt[$this->schemeName];
  1388. }
  1389. // We fall back to this just in case.
  1390. elseif (!empty($cxt[self::DEFAULT_SCHEME])) {
  1391. $this->contextArray = $cxt[self::DEFAULT_SCHEME];
  1392. }
  1393. }
  1394. // Should this be array_key_exists()?
  1395. if (isset($this->contextArray[$name])) {
  1396. return $this->contextArray[$name];
  1397. }
  1398. // Check to see if the value can be gotten from
  1399. // \HPCloud\Bootstrap.
  1400. $val = \HPCloud\Bootstrap::config($name, NULL);
  1401. if (isset($val)) {
  1402. return $val;
  1403. }
  1404. return $default;
  1405. }
  1406. /**
  1407. * Parse a URL.
  1408. *
  1409. * In order to provide full UTF-8 support, URLs must be
  1410. * rawurlencoded before they are passed into the stream wrapper.
  1411. *
  1412. * This parses the URL and urldecodes the container name and
  1413. * the object name.
  1414. *
  1415. * @param string $url
  1416. * A Swift URL.
  1417. * @retval array
  1418. * An array as documented in parse_url().
  1419. */
  1420. protected function parseUrl($url)
  1421. {
  1422. $res = parse_url($url);
  1423. // These have to be decode because they will later
  1424. // be encoded.
  1425. foreach ($res as $key => $val) {
  1426. if ($key == 'host') {
  1427. $res[$key] = urldecode($val);
  1428. } elseif ($key == 'path') {
  1429. if (strpos($val, '/') === 0) {
  1430. $val = substr($val, 1);
  1431. }
  1432. $res[$key] = urldecode($val);
  1433. }
  1434. }
  1435. return $res;
  1436. }
  1437. /**
  1438. * Based on the context, initialize the ObjectStorage.
  1439. *
  1440. * The following parameters may be set either in the stream context
  1441. * or through HPCloud::Bootstrap::setConfiguration():
  1442. *
  1443. * - token: An auth token. If this is supplied, authentication is skipped and
  1444. * this token is used. NOTE: You MUST set swift_endpoint if using this
  1445. * option.
  1446. * - swift_endpoint: The URL to the swift instance. This is only necessary if
  1447. * 'token' is set. Otherwise it is ignored.
  1448. * - username: A username. MUST be accompanied by 'password' and 'tenantid'.
  1449. * - password: A password. MUST be accompanied by 'username' and 'tenantid'.
  1450. * - account: An account ID. MUST be accompanied by a 'key' and 'tenantid'.
  1451. * - key: A secret key. MUST be accompanied by an 'account' and 'tenantid'.
  1452. * - endpoint: The URL to the authentication endpoint. Necessary if you are not
  1453. * using a 'token' and 'swift_endpoint'.
  1454. * - use_swift_auth: If this is set to TRUE, it will force the app to use
  1455. * the deprecated swiftAuth instead of IdentityServices authentication.
  1456. * In general, you should avoid using this.
  1457. *
  1458. * To find these params, the method first checks the supplied context. If the
  1459. * key is not found there, it checks the Bootstrap::conf().
  1460. *
  1461. * @fixme This should be rewritten to use ObjectStorage::newFromServiceCatalog().
  1462. */
  1463. protected function initializeObjectStorage()
  1464. {
  1465. $token = $this->cxt('token');
  1466. if (empty($token) && isSet($_SESSION["HPCLOUD_TOKEN"])) {
  1467. $t

Large files files are truncated, but you can click here to view the full file