PageRenderTime 36ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/administrator/components/com_akeeba/akeeba/plugins/utils/cloudfiles.php

https://bitbucket.org/kraymitchell/saiu
PHP | 2137 lines | 1226 code | 155 blank | 756 comment | 246 complexity | 27458181cb8938e511d402a6818d90e0 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, BSD-3-Clause, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. Copyright (C) 2008 Rackspace US, Inc.
  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
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all 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. Except as contained in this notice, the name of Rackspace US, Inc. shall not
  20. be used in advertising or otherwise to promote the sale, use or other dealings
  21. in this Software without prior written authorization from Rackspace US, Inc.
  22. *
  23. * <code>
  24. * $auth = new AEUtilCFAuthentication($username, $api_key);
  25. * $auth->authenticate();
  26. * $conn = new AEUtilCFConnection($auth);
  27. * $images = $conn->create_container("photos");
  28. * $bday = $images->create_object("first_birthday.jpg");
  29. * $bday->load_from_filename("/home/user/photos/birthdays/birthday1.jpg");
  30. * </code>
  31. *
  32. * @author Eric "EJ" Johnson <ej@racklabs.com>
  33. * @copyright Copyright (c) 2008, Rackspace US, Inc.
  34. * @package php-cloudfiles
  35. */
  36. class AEUtilCloudfiles {}
  37. define("DEFAULT_CF_API_VERSION", 1);
  38. define("MAX_CONTAINER_NAME_LEN", 256);
  39. define("MAX_OBJECT_NAME_LEN", 1024);
  40. define("MAX_OBJECT_SIZE", 5*1024*1024*1024+1); # bigger than S3! ;-)
  41. define("PHP_CF_VERSION", "1.7.0");
  42. define("USER_AGENT", sprintf("PHP-CloudFiles/%s", PHP_CF_VERSION));
  43. define("ACCOUNT_CONTAINER_COUNT", "X-Account-Container-Count");
  44. define("ACCOUNT_BYTES_USED", "X-Account-Bytes-Used");
  45. define("CONTAINER_OBJ_COUNT", "X-Container-Object-Count");
  46. define("CONTAINER_BYTES_USED", "X-Container-Bytes-Used");
  47. define("METADATA_HEADER", "X-Object-Meta-");
  48. define("CDN_URI", "X-CDN-URI");
  49. define("CDN_ENABLED", "X-CDN-Enabled");
  50. define("CDN_LOG_RETENTION", "X-Log-Retention");
  51. define("CDN_ACL_USER_AGENT", "X-User-Agent-ACL");
  52. define("CDN_ACL_REFERRER", "X-Referrer-ACL");
  53. define("CDN_TTL", "X-TTL");
  54. define("CDNM_URL", "X-CDN-Management-Url");
  55. define("STORAGE_URL", "X-Storage-Url");
  56. define("AUTH_TOKEN", "X-Auth-Token");
  57. define("AUTH_USER_HEADER", "X-Auth-User");
  58. define("AUTH_KEY_HEADER", "X-Auth-Key");
  59. define("AUTH_USER_HEADER_LEGACY", "X-Storage-User");
  60. define("AUTH_KEY_HEADER_LEGACY", "X-Storage-Pass");
  61. define("AUTH_TOKEN_LEGACY", "X-Storage-Token");
  62. class SyntaxException extends Exception { }
  63. class AuthenticationException extends Exception { }
  64. class InvalidResponseException extends Exception { }
  65. class NonEmptyContainerException extends Exception { }
  66. class NoSuchObjectException extends Exception { }
  67. class NoSuchContainerException extends Exception { }
  68. class NoSuchAccountException extends Exception { }
  69. class MisMatchedChecksumException extends Exception { }
  70. class IOException extends Exception { }
  71. class CDNNotEnabledException extends Exception { }
  72. class BadContentTypeException extends Exception { }
  73. class InvalidUTF8Exception extends Exception { }
  74. class ConnectionNotOpenException extends Exception { }
  75. /**
  76. * HTTP/cURL wrapper for Cloud Files
  77. *
  78. * This class should not be used directly. It's only purpose is to abstract
  79. * out the HTTP communication from the main API.
  80. *
  81. * @package php-cloudfiles-http
  82. */
  83. class AEUtilCfhttp
  84. {
  85. private $error_str;
  86. private $dbug;
  87. private $cabundle_path;
  88. private $api_version;
  89. # Authentication instance variables
  90. #
  91. private $storage_url;
  92. private $cdnm_url;
  93. private $auth_token;
  94. # Request/response variables
  95. #
  96. private $response_status;
  97. private $response_reason;
  98. private $connections;
  99. # Variables used for content/header callbacks
  100. #
  101. private $_user_read_progress_callback_func;
  102. private $_user_write_progress_callback_func;
  103. private $_write_callback_type;
  104. private $_text_list;
  105. private $_account_container_count;
  106. private $_account_bytes_used;
  107. private $_container_object_count;
  108. private $_container_bytes_used;
  109. private $_obj_etag;
  110. private $_obj_last_modified;
  111. private $_obj_content_type;
  112. private $_obj_content_length;
  113. private $_obj_metadata;
  114. private $_obj_write_resource;
  115. private $_obj_write_string;
  116. private $_cdn_enabled;
  117. private $_cdn_uri;
  118. private $_cdn_ttl;
  119. private $_cdn_log_retention;
  120. private $_cdn_acl_user_agent;
  121. private $_cdn_acl_referrer;
  122. private $isUKAccount = false;
  123. function __construct($api_version, $isUKAccount = false)
  124. {
  125. $this->dbug = False;
  126. $this->cabundle_path = NULL;
  127. $this->api_version = $api_version;
  128. $this->error_str = NULL;
  129. $this->storage_url = NULL;
  130. $this->cdnm_url = NULL;
  131. $this->auth_token = NULL;
  132. $this->response_status = NULL;
  133. $this->response_reason = NULL;
  134. # Curl connections array - since there is no way to "re-set" the
  135. # connection paramaters for a cURL handle, we keep an array of
  136. # the unique use-cases and funnel all of those same type
  137. # requests through the appropriate curl connection.
  138. #
  139. $this->connections = array(
  140. "GET_CALL" => NULL, # GET objects/containers/lists
  141. "PUT_OBJ" => NULL, # PUT object
  142. "HEAD" => NULL, # HEAD requests
  143. "PUT_CONT" => NULL, # PUT container
  144. "DEL_POST" => NULL, # DELETE containers/objects, POST objects
  145. );
  146. $this->_user_read_progress_callback_func = NULL;
  147. $this->_user_write_progress_callback_func = NULL;
  148. $this->_write_callback_type = NULL;
  149. $this->_text_list = array();
  150. $this->_return_list = NULL;
  151. $this->_account_container_count = 0;
  152. $this->_account_bytes_used = 0;
  153. $this->_container_object_count = 0;
  154. $this->_container_bytes_used = 0;
  155. $this->_obj_write_resource = NULL;
  156. $this->_obj_write_string = "";
  157. $this->_obj_etag = NULL;
  158. $this->_obj_last_modified = NULL;
  159. $this->_obj_content_type = NULL;
  160. $this->_obj_content_length = NULL;
  161. $this->_obj_metadata = array();
  162. $this->_cdn_enabled = NULL;
  163. $this->_cdn_uri = NULL;
  164. $this->_cdn_ttl = NULL;
  165. $this->_cdn_log_retention = NULL;
  166. $this->_cdn_acl_user_agent = NULL;
  167. $this->_cdn_acl_referrer = NULL;
  168. $this->isUKAccount = $isUKAccount;
  169. }
  170. # Uses separate cURL connection to authenticate
  171. #
  172. function authenticate($user, $pass, $acct=NULL, $host=NULL)
  173. {
  174. $path = array();
  175. if (isset($acct) || isset($host)) {
  176. $headers = array(
  177. sprintf("%s: %s", AUTH_USER_HEADER_LEGACY, $user),
  178. sprintf("%s: %s", AUTH_KEY_HEADER_LEGACY, $pass),
  179. );
  180. $path[] = $host;
  181. $path[] = rawurlencode(sprintf("v%d",$this->api_version));
  182. $path[] = rawurlencode($acct);
  183. } else {
  184. $headers = array(
  185. sprintf("%s: %s", AUTH_USER_HEADER, $user),
  186. sprintf("%s: %s", AUTH_KEY_HEADER, $pass),
  187. );
  188. if(!$this->isUKAccount) {
  189. $path[] = "https://auth.api.rackspacecloud.com";
  190. } else {
  191. $path[] = "https://lon.auth.api.rackspacecloud.com";
  192. }
  193. }
  194. $path[] = "v1.0";
  195. $url = implode("/", $path);
  196. $curl_ch = curl_init();
  197. @curl_setopt($curl_ch, CURLOPT_CAINFO, AKEEBA_CACERT_PEM);
  198. curl_setopt($curl_ch, CURLOPT_VERBOSE, $this->dbug);
  199. curl_setopt($curl_ch, CURLOPT_FOLLOWLOCATION, 1);
  200. curl_setopt($curl_ch, CURLOPT_MAXREDIRS, 4);
  201. curl_setopt($curl_ch, CURLOPT_HEADER, 0);
  202. curl_setopt($curl_ch, CURLOPT_HTTPHEADER, $headers);
  203. curl_setopt($curl_ch, CURLOPT_USERAGENT, USER_AGENT);
  204. curl_setopt($curl_ch, CURLOPT_RETURNTRANSFER, TRUE);
  205. curl_setopt($curl_ch, CURLOPT_HEADERFUNCTION,array(&$this,'_auth_hdr_cb'));
  206. curl_setopt($curl_ch, CURLOPT_SSL_VERIFYPEER, (stristr(PHP_OS, 'WIN') ? false : true));
  207. curl_setopt($curl_ch, CURLOPT_URL, $url);
  208. curl_exec($curl_ch);
  209. curl_close($curl_ch);
  210. return array($this->response_status, $this->response_reason,
  211. $this->storage_url, $this->cdnm_url, $this->auth_token);
  212. }
  213. # HEAD /v1/Account
  214. #
  215. function head_account()
  216. {
  217. $conn_type = "HEAD";
  218. $url_path = $this->_make_path();
  219. $return_code = $this->_send_request($conn_type,$url_path);
  220. if (!$return_code) {
  221. $this->error_str .= ": Failed to obtain valid HTTP response.";
  222. array(0,$this->error_str,0,0);
  223. }
  224. if ($return_code == 404) {
  225. return array($return_code,"Account not found.",0,0);
  226. }
  227. if ($return_code == 204) {
  228. return array($return_code,$this->response_reason,
  229. $this->_account_container_count, $this->_account_bytes_used);
  230. }
  231. return array($return_code,$this->response_reason,0,0);
  232. }
  233. # HEAD /v1/Account/Container
  234. #
  235. function head_container($container_name)
  236. {
  237. if ($container_name == "") {
  238. $this->error_str = "Container name not set.";
  239. return False;
  240. }
  241. if ($container_name != "0" and !isset($container_name)) {
  242. $this->error_str = "Container name not set.";
  243. return False;
  244. }
  245. $conn_type = "HEAD";
  246. $url_path = $this->_make_path("STORAGE", $container_name);
  247. $return_code = $this->_send_request($conn_type,$url_path);
  248. if (!$return_code) {
  249. $this->error_str .= ": Failed to obtain valid HTTP response.";
  250. array(0,$this->error_str,0,0);
  251. }
  252. if ($return_code == 404) {
  253. return array($return_code,"Container not found.",0,0);
  254. }
  255. if ($return_code == 204 or 200) {
  256. return array($return_code,$this->response_reason,
  257. $this->_container_object_count, $this->_container_bytes_used);
  258. }
  259. return array($return_code,$this->response_reason,0,0);
  260. }
  261. # GET /v1/Account/Container/Object
  262. #
  263. function get_object_to_stream(&$obj, &$resource=NULL, $hdrs=array())
  264. {
  265. if (!is_object($obj) || get_class($obj) != "AEUtilCFObject") {
  266. throw new SyntaxException(
  267. "Method argument is not a valid AEUtilCFObject.");
  268. }
  269. if (!is_resource($resource)) {
  270. throw new SyntaxException(
  271. "Resource argument not a valid PHP resource.");
  272. }
  273. $conn_type = "GET_CALL";
  274. $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
  275. $this->_obj_write_resource = $resource;
  276. $this->_write_callback_type = "OBJECT_STREAM";
  277. $return_code = $this->_send_request($conn_type,$url_path,$hdrs);
  278. if (!$return_code) {
  279. $this->error_str .= ": Failed to obtain valid HTTP response.";
  280. return array($return_code,$this->error_str);
  281. }
  282. if ($return_code == 404) {
  283. $this->error_str = "Object not found.";
  284. return array($return_code,$this->error_str);
  285. }
  286. if (($return_code < 200) || ($return_code > 299
  287. && $return_code != 412 && $return_code != 304)) {
  288. $this->error_str = "Unexpected HTTP return code: $return_code";
  289. return array($return_code,$this->error_str);
  290. }
  291. return array($return_code,$this->response_reason);
  292. }
  293. # PUT /v1/Account/Container/Object
  294. #
  295. function put_object(&$obj, &$fp)
  296. {
  297. if (!is_object($obj) || get_class($obj) != "AEUtilCFObject") {
  298. throw new SyntaxException(
  299. "Method argument is not a valid AEUtilCFObject.");
  300. }
  301. if (!is_resource($fp)) {
  302. throw new SyntaxException(
  303. "File pointer argument is not a valid resource.");
  304. }
  305. $conn_type = "PUT_OBJ";
  306. $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
  307. $hdrs = $this->_metadata_headers($obj);
  308. $etag = $obj->getETag();
  309. if (isset($etag)) {
  310. $hdrs[] = "ETag: " . $etag;
  311. }
  312. if (!$obj->content_type) {
  313. $hdrs[] = "Content-Type: application/octet-stream";
  314. } else {
  315. $hdrs[] = "Content-Type: " . $obj->content_type;
  316. }
  317. $this->_init($conn_type);
  318. curl_setopt($this->connections[$conn_type],
  319. CURLOPT_INFILE, $fp);
  320. if (!$obj->content_length) {
  321. # We don''t know the Content-Length, so assumed "chunked" PUT
  322. #
  323. curl_setopt($this->connections[$conn_type], CURLOPT_UPLOAD, True);
  324. $hdrs[] = 'Transfer-Encoding: chunked';
  325. } else {
  326. # We know the Content-Length, so use regular transfer
  327. #
  328. curl_setopt($this->connections[$conn_type],
  329. CURLOPT_INFILESIZE, $obj->content_length);
  330. }
  331. $return_code = $this->_send_request($conn_type,$url_path,$hdrs);
  332. if (!$return_code) {
  333. $this->error_str .= ": Failed to obtain valid HTTP response.";
  334. return array(0,$this->error_str,NULL);
  335. }
  336. if ($return_code == 412) {
  337. $this->error_str = "Missing Content-Type header";
  338. return array($return_code,$this->error_str,NULL);
  339. }
  340. if ($return_code == 422) {
  341. $this->error_str = "Derived and computed checksums do not match.";
  342. return array($return_code,$this->error_str,NULL);
  343. }
  344. if ($return_code != 201) {
  345. $this->error_str = "Unexpected HTTP return code: $return_code";
  346. return array($return_code,$this->error_str,NULL);
  347. }
  348. return array($return_code,$this->response_reason,$this->_obj_etag);
  349. }
  350. # HEAD /v1/Account/Container/Object
  351. #
  352. function head_object(&$obj)
  353. {
  354. if (!is_object($obj) || get_class($obj) != "AEUtilCFObject") {
  355. throw new SyntaxException(
  356. "Method argument is not a valid AEUtilCFObject.");
  357. }
  358. $conn_type = "HEAD";
  359. $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
  360. $return_code = $this->_send_request($conn_type,$url_path);
  361. if (!$return_code) {
  362. $this->error_str .= ": Failed to obtain valid HTTP response.";
  363. return array(0, $this->error_str." ".$this->response_reason,
  364. NULL, NULL, NULL, NULL, array());
  365. }
  366. if ($return_code == 404) {
  367. return array($return_code, $this->response_reason,
  368. NULL, NULL, NULL, NULL, array());
  369. }
  370. if ($return_code == 204 or 200) {
  371. return array($return_code,$this->response_reason,
  372. $this->_obj_etag,
  373. $this->_obj_last_modified,
  374. $this->_obj_content_type,
  375. $this->_obj_content_length,
  376. $this->_obj_metadata);
  377. }
  378. $this->error_str = "Unexpected HTTP return code: $return_code";
  379. return array($return_code, $this->error_str." ".$this->response_reason,
  380. NULL, NULL, NULL, NULL, array());
  381. }
  382. # GET /v1/Account/Container?format=json
  383. #
  384. function get_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL)
  385. {
  386. if (!$cname) {
  387. $this->error_str = "Container name not set.";
  388. return array(0, $this->error_str, array());
  389. }
  390. $url_path = $this->_make_path("STORAGE", $cname);
  391. $limit = intval($limit);
  392. $params = array();
  393. $params[] = "format=json";
  394. if ($limit > 0) {
  395. $params[] = "limit=$limit";
  396. }
  397. if ($marker) {
  398. $params[] = "marker=".rawurlencode($marker);
  399. }
  400. if ($prefix) {
  401. $params[] = "prefix=".rawurlencode($prefix);
  402. }
  403. if ($path) {
  404. $params[] = "path=".rawurlencode($path);
  405. }
  406. if (!empty($params)) {
  407. $url_path .= "?" . implode("&", $params);
  408. }
  409. $conn_type = "GET_CALL";
  410. $this->_write_callback_type = "OBJECT_STRING";
  411. $return_code = $this->_send_request($conn_type,$url_path);
  412. if (!$return_code) {
  413. $this->error_str .= ": Failed to obtain valid HTTP response.";
  414. return array(0,$this->error_str,array());
  415. }
  416. if ($return_code == 204) {
  417. $this->error_str = "Container has no Objects.";
  418. return array($return_code,$this->error_str,array());
  419. }
  420. if ($return_code == 404) {
  421. $this->error_str = "Container has no Objects.";
  422. return array($return_code,$this->error_str,array());
  423. }
  424. if ($return_code == 200) {
  425. $json_body = json_decode($this->_obj_write_string, True);
  426. return array($return_code,$this->response_reason, $json_body);
  427. }
  428. $this->error_str = "Unexpected HTTP response code: $return_code";
  429. return array(0,$this->error_str,array());
  430. }
  431. function get_error()
  432. {
  433. return $this->error_str;
  434. }
  435. function setDebug($bool)
  436. {
  437. $this->dbug = $bool;
  438. foreach ($this->connections as $k => $v) {
  439. if (!is_null($v)) {
  440. curl_setopt($this->connections[$k], CURLOPT_VERBOSE, $this->dbug);
  441. }
  442. }
  443. }
  444. function getStorageUrl()
  445. {
  446. return $this->storage_url;
  447. }
  448. function getAuthToken()
  449. {
  450. return $this->auth_token;
  451. }
  452. function setCFAuth($cfs_auth, $servicenet=False)
  453. {
  454. if ($servicenet) {
  455. $this->storage_url = "https://snet-" . substr($cfs_auth->storage_url, 8);
  456. } else {
  457. $this->storage_url = $cfs_auth->storage_url;
  458. }
  459. $this->auth_token = $cfs_auth->auth_token;
  460. $this->cdnm_url = $cfs_auth->cdnm_url;
  461. }
  462. function setReadProgressFunc($func_name)
  463. {
  464. $this->_user_read_progress_callback_func = $func_name;
  465. }
  466. function setWriteProgressFunc($func_name)
  467. {
  468. $this->_user_write_progress_callback_func = $func_name;
  469. }
  470. function getCDNMUrl()
  471. {
  472. return $this->cdnm_url;
  473. }
  474. # DELETE /v1/Account/Container/Object
  475. #
  476. function delete_object($container_name, $object_name)
  477. {
  478. if ($container_name == "") {
  479. $this->error_str = "Container name not set.";
  480. return 0;
  481. }
  482. if ($container_name != "0" and !isset($container_name)) {
  483. $this->error_str = "Container name not set.";
  484. return 0;
  485. }
  486. if (!$object_name) {
  487. $this->error_str = "Object name not set.";
  488. return 0;
  489. }
  490. $url_path = $this->_make_path("STORAGE", $container_name,$object_name);
  491. $return_code = $this->_send_request("DEL_POST",$url_path,NULL,"DELETE");
  492. switch ($return_code) {
  493. case 204:
  494. break;
  495. case 0:
  496. $this->error_str .= ": Failed to obtain valid HTTP response.";
  497. $return_code = 0;
  498. break;
  499. case 404:
  500. $this->error_str = "Specified container did not exist to delete.";
  501. break;
  502. default:
  503. $this->error_str = "Unexpected HTTP return code: $return_code.";
  504. }
  505. return $return_code;
  506. }
  507. private function _header_cb($ch, $header)
  508. {
  509. preg_match("/^HTTP\/1\.[01] (\d{3}) (.*)/", $header, $matches);
  510. if (isset($matches[1])) {
  511. $this->response_status = $matches[1];
  512. }
  513. if (isset($matches[2])) {
  514. $this->response_reason = $matches[2];
  515. }
  516. if (stripos($header, ACCOUNT_CONTAINER_COUNT) === 0) {
  517. $this->_account_container_count = (float) trim(substr($header,
  518. strlen(ACCOUNT_CONTAINER_COUNT)+1))+0;
  519. return strlen($header);
  520. }
  521. if (stripos($header, ACCOUNT_BYTES_USED) === 0) {
  522. $this->_account_bytes_used = (float) trim(substr($header,
  523. strlen(ACCOUNT_BYTES_USED)+1))+0;
  524. return strlen($header);
  525. }
  526. if (stripos($header, CONTAINER_OBJ_COUNT) === 0) {
  527. $this->_container_object_count = (float) trim(substr($header,
  528. strlen(CONTAINER_OBJ_COUNT)+1))+0;
  529. return strlen($header);
  530. }
  531. if (stripos($header, CONTAINER_BYTES_USED) === 0) {
  532. $this->_container_bytes_used = (float) trim(substr($header,
  533. strlen(CONTAINER_BYTES_USED)+1))+0;
  534. return strlen($header);
  535. }
  536. if (stripos($header, METADATA_HEADER) === 0) {
  537. # $header => X-Object-Meta-Foo: bar baz
  538. $temp = substr($header, strlen(METADATA_HEADER));
  539. # $temp => Foo: bar baz
  540. $parts = explode(":", $temp);
  541. # $parts[0] => Foo
  542. $val = substr(strstr($temp, ":"), 1);
  543. # $val => bar baz
  544. $this->_obj_metadata[$parts[0]] = trim($val);
  545. return strlen($header);
  546. }
  547. if (stripos($header, "ETag:") === 0) {
  548. # $header => ETag: abc123def456...
  549. $val = substr(strstr($header, ":"), 1);
  550. # $val => abc123def456...
  551. $this->_obj_etag = trim($val);
  552. return strlen($header);
  553. }
  554. if (stripos($header, "Last-Modified:") === 0) {
  555. $val = substr(strstr($header, ":"), 1);
  556. $this->_obj_last_modified = trim($val);
  557. return strlen($header);
  558. }
  559. if (stripos($header, "Content-Type:") === 0) {
  560. $val = substr(strstr($header, ":"), 1);
  561. $this->_obj_content_type = trim($val);
  562. return strlen($header);
  563. }
  564. if (stripos($header, "Content-Length:") === 0) {
  565. $val = substr(strstr($header, ":"), 1);
  566. $this->_obj_content_length = (float) trim($val)+0;
  567. return strlen($header);
  568. }
  569. return strlen($header);
  570. }
  571. private function _read_cb($ch, $fd, $length)
  572. {
  573. $data = fread($fd, $length);
  574. $len = strlen($data);
  575. if (isset($this->_user_write_progress_callback_func)) {
  576. call_user_func($this->_user_write_progress_callback_func, $len);
  577. }
  578. return $data;
  579. }
  580. private function _write_cb($ch, $data)
  581. {
  582. $dlen = strlen($data);
  583. switch ($this->_write_callback_type) {
  584. case "TEXT_LIST":
  585. $this->_return_list = $this->_return_list . $data;
  586. //= explode("\n",$data); # keep tab,space
  587. //his->_text_list[] = rtrim($data,"\n\r\x0B"); # keep tab,space
  588. break;
  589. case "OBJECT_STREAM":
  590. fwrite($this->_obj_write_resource, $data, $dlen);
  591. break;
  592. case "OBJECT_STRING":
  593. $this->_obj_write_string .= $data;
  594. break;
  595. }
  596. if (isset($this->_user_read_progress_callback_func)) {
  597. call_user_func($this->_user_read_progress_callback_func, $dlen);
  598. }
  599. return $dlen;
  600. }
  601. private function _auth_hdr_cb($ch, $header)
  602. {
  603. preg_match("/^HTTP\/1\.[01] (\d{3}) (.*)/", $header, $matches);
  604. if (isset($matches[1])) {
  605. $this->response_status = $matches[1];
  606. }
  607. if (isset($matches[2])) {
  608. $this->response_reason = $matches[2];
  609. }
  610. if (stripos($header, STORAGE_URL) === 0) {
  611. $this->storage_url = trim(substr($header, strlen(STORAGE_URL)+1));
  612. }
  613. if (stripos($header, AUTH_TOKEN) === 0) {
  614. $this->auth_token = trim(substr($header, strlen(AUTH_TOKEN)+1));
  615. }
  616. if (stripos($header, AUTH_TOKEN_LEGACY) === 0) {
  617. $this->auth_token = trim(substr($header,strlen(AUTH_TOKEN_LEGACY)+1));
  618. }
  619. return strlen($header);
  620. }
  621. private function _make_headers($hdrs=NULL)
  622. {
  623. $new_headers = array();
  624. $has_stoken = False;
  625. $has_uagent = False;
  626. if (is_array($hdrs)) {
  627. foreach ($hdrs as $h => $v) {
  628. if (is_int($h)) {
  629. $parts = explode(":", $v);
  630. $header = $parts[0];
  631. $value = trim(substr(strstr($v, ":"), 1));
  632. } else {
  633. $header = $h;
  634. $value = trim($v);
  635. }
  636. if (stripos($header, AUTH_TOKEN) === 0) {
  637. $has_stoken = True;
  638. }
  639. if (stripos($header, "user-agent") === 0) {
  640. $has_uagent = True;
  641. }
  642. $new_headers[] = $header . ": " . $value;
  643. }
  644. }
  645. if (!$has_stoken) {
  646. $new_headers[] = AUTH_TOKEN . ": " . $this->auth_token;
  647. }
  648. if (!$has_uagent) {
  649. $new_headers[] = "User-Agent: " . USER_AGENT;
  650. }
  651. return $new_headers;
  652. }
  653. private function _init($conn_type, $force_new=False)
  654. {
  655. if (!array_key_exists($conn_type, $this->connections)) {
  656. $this->error_str = "Invalid CURL_XXX connection type";
  657. return False;
  658. }
  659. if (is_null($this->connections[$conn_type]) || $force_new) {
  660. $ch = curl_init();
  661. @curl_setopt($ch, CURLOPT_CAINFO, AKEEBA_CACERT_PEM);
  662. } else {
  663. return;
  664. }
  665. if ($this->dbug) { curl_setopt($ch, CURLOPT_VERBOSE, 1); }
  666. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (stristr(PHP_OS, 'WIN') ? false : true));
  667. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  668. curl_setopt($ch, CURLOPT_MAXREDIRS, 4);
  669. curl_setopt($ch, CURLOPT_HEADER, 0);
  670. curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this, '_header_cb'));
  671. if ($conn_type == "GET_CALL") {
  672. curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(&$this, '_write_cb'));
  673. }
  674. if ($conn_type == "PUT_OBJ") {
  675. curl_setopt($ch, CURLOPT_PUT, 1);
  676. curl_setopt($ch, CURLOPT_READFUNCTION, array(&$this, '_read_cb'));
  677. }
  678. if ($conn_type == "HEAD") {
  679. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD");
  680. curl_setopt($ch, CURLOPT_NOBODY, 1);
  681. }
  682. if ($conn_type == "PUT_CONT") {
  683. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
  684. curl_setopt($ch, CURLOPT_INFILESIZE, 0);
  685. curl_setopt($ch, CURLOPT_NOBODY, 1);
  686. }
  687. if ($conn_type == "DEL_POST") {
  688. curl_setopt($ch, CURLOPT_NOBODY, 1);
  689. }
  690. $this->connections[$conn_type] = $ch;
  691. return;
  692. }
  693. private function _reset_callback_vars()
  694. {
  695. $this->_text_list = array();
  696. $this->_return_list = NULL;
  697. $this->_account_container_count = 0;
  698. $this->_account_bytes_used = 0;
  699. $this->_container_object_count = 0;
  700. $this->_container_bytes_used = 0;
  701. $this->_obj_etag = NULL;
  702. $this->_obj_last_modified = NULL;
  703. $this->_obj_content_type = NULL;
  704. $this->_obj_content_length = NULL;
  705. $this->_obj_metadata = array();
  706. $this->_obj_write_string = "";
  707. $this->_cdn_enabled = NULL;
  708. $this->_cdn_uri = NULL;
  709. $this->_cdn_ttl = NULL;
  710. $this->response_status = 0;
  711. $this->response_reason = "";
  712. }
  713. private function _make_path($t="STORAGE",$c=NULL,$o=NULL)
  714. {
  715. $path = array();
  716. switch ($t) {
  717. case "STORAGE":
  718. $path[] = $this->storage_url; break;
  719. }
  720. if ($c == "0")
  721. $path[] = rawurlencode($c);
  722. if ($c) {
  723. $path[] = rawurlencode($c);
  724. }
  725. if ($o) {
  726. # mimic Python''s urllib.quote() feature of a "safe" '/' character
  727. #
  728. $path[] = str_replace("%2F","/",rawurlencode($o));
  729. }
  730. return implode("/",$path);
  731. }
  732. private function _metadata_headers(&$obj)
  733. {
  734. $hdrs = array();
  735. foreach ($obj->metadata as $k => $v) {
  736. if (strpos($k,":") !== False) {
  737. throw new SyntaxException(
  738. "Metadata keys cannot contain a ':' character.");
  739. }
  740. $k = trim($k);
  741. $key = sprintf("%s%s", METADATA_HEADER, $k);
  742. if (!array_key_exists($key, $hdrs)) {
  743. if (strlen($k) > 128 || strlen($v) > 256) {
  744. $this->error_str = "Metadata key or value exceeds ";
  745. $this->error_str .= "maximum length: ($k: $v)";
  746. return 0;
  747. }
  748. $hdrs[] = sprintf("%s%s: %s", METADATA_HEADER, $k, trim($v));
  749. }
  750. }
  751. return $hdrs;
  752. }
  753. private function _send_request($conn_type, $url_path, $hdrs=NULL, $method="GET")
  754. {
  755. $this->_init($conn_type);
  756. $this->_reset_callback_vars();
  757. $headers = $this->_make_headers($hdrs);
  758. if (gettype($this->connections[$conn_type]) == "unknown type")
  759. throw new ConnectionNotOpenException (
  760. "Connection is not open."
  761. );
  762. switch ($method) {
  763. case "DELETE":
  764. curl_setopt($this->connections[$conn_type],
  765. CURLOPT_CUSTOMREQUEST, "DELETE");
  766. break;
  767. case "POST":
  768. curl_setopt($this->connections[$conn_type],
  769. CURLOPT_CUSTOMREQUEST, "POST");
  770. default:
  771. break;
  772. }
  773. curl_setopt($this->connections[$conn_type],
  774. CURLOPT_HTTPHEADER, $headers);
  775. curl_setopt($this->connections[$conn_type],
  776. CURLOPT_URL, $url_path);
  777. if (!curl_exec($this->connections[$conn_type])) {
  778. $this->error_str = "(curl error: "
  779. . curl_errno($this->connections[$conn_type]) . ") ";
  780. $this->error_str .= curl_error($this->connections[$conn_type]);
  781. return False;
  782. }
  783. return curl_getinfo($this->connections[$conn_type], CURLINFO_HTTP_CODE);
  784. }
  785. function close()
  786. {
  787. foreach ($this->connections as $cnx) {
  788. if (isset($cnx)) {
  789. curl_close($cnx);
  790. $this->connections[$cnx] = NULL;
  791. }
  792. }
  793. }
  794. private function create_array()
  795. {
  796. $this->_text_list = explode("\n",rtrim($this->_return_list,"\n\x0B"));
  797. return True;
  798. }
  799. }
  800. /**
  801. * Class for handling Cloud Files Authentication, call it's {@link authenticate()}
  802. * method to obtain authorized service urls and an authentication token.
  803. *
  804. * Example:
  805. * <code>
  806. * # Create the authentication instance
  807. * #
  808. * $auth = new AEUtilCFAuthentication("username", "api_key");
  809. * # Perform authentication request
  810. * #
  811. * $auth->authenticate();
  812. * </code>
  813. *
  814. * @package php-cloudfiles
  815. */
  816. class AEUtilCFAuthentication
  817. {
  818. public $dbug;
  819. public $username;
  820. public $api_key;
  821. public $auth_host;
  822. public $account;
  823. public $isUKAccount = false;
  824. /**
  825. * Instance variables that are set after successful authentication
  826. */
  827. public $storage_url;
  828. public $cdnm_url;
  829. public $auth_token;
  830. /**
  831. * Class constructor (PHP 5 syntax)
  832. *
  833. * @param string $username Mosso username
  834. * @param string $api_key Mosso API Access Key
  835. * @param string $account <b>Deprecated</b> <i>Account name</i>
  836. * @param string $auth_host <b>Deprecated</b> <i>Authentication service URI</i>
  837. */
  838. function __construct($username=NULL, $api_key=NULL, $account=NULL, $auth_host=NULL, $isUKAccount = false)
  839. {
  840. $this->dbug = False;
  841. $this->username = $username;
  842. $this->api_key = $api_key;
  843. $this->account_name = $account;
  844. $this->auth_host = $auth_host;
  845. $this->storage_url = NULL;
  846. $this->cdnm_url = NULL;
  847. $this->auth_token = NULL;
  848. $this->isUKAccount = $isUKAccount;
  849. $this->cfs_http = new AEUtilCfhttp(DEFAULT_CF_API_VERSION, $this->isUKAccount);
  850. }
  851. /**
  852. * Attempt to validate Username/API Access Key
  853. *
  854. * Attempts to validate credentials with the authentication service. It
  855. * either returns <kbd>True</kbd> or throws an Exception. Accepts a single
  856. * (optional) argument for the storage system API version.
  857. *
  858. * Example:
  859. * <code>
  860. * # Create the authentication instance
  861. * #
  862. * $auth = new AEUtilCFAuthentication("username", "api_key");
  863. *
  864. * # Perform authentication request
  865. * #
  866. * $auth->authenticate();
  867. * </code>
  868. *
  869. * @param string $version API version for Auth service (optional)
  870. * @return boolean <kbd>True</kbd> if successfully authenticated
  871. * @throws AuthenticationException invalid credentials
  872. * @throws InvalidResponseException invalid response
  873. */
  874. function authenticate($version=DEFAULT_CF_API_VERSION)
  875. {
  876. list($status,$reason,$surl,$curl,$atoken) =
  877. $this->cfs_http->authenticate($this->username, $this->api_key,
  878. $this->account_name, $this->auth_host);
  879. if ($status == 401) {
  880. throw new AuthenticationException("Invalid username or access key.");
  881. }
  882. if ($status != 204) {
  883. throw new InvalidResponseException(
  884. "Unexpected response (".$status."): ".$reason);
  885. }
  886. if (!($surl || $curl) || !$atoken) {
  887. throw new InvalidResponseException(
  888. "Expected headers missing from auth service.");
  889. }
  890. $this->storage_url = $surl;
  891. $this->cdnm_url = $curl;
  892. $this->auth_token = $atoken;
  893. return True;
  894. }
  895. /**
  896. * Use Cached Token and Storage URL's rather then grabbing from the Auth System
  897. *
  898. * Example:
  899. * <code>
  900. * #Create an Auth instance
  901. * $auth = new AEUtilCFAuthentication();
  902. * #Pass Cached URL's and Token as Args
  903. * $auth->load_cached_credentials("auth_token", "storage_url", "cdn_management_url");
  904. * </code>
  905. *
  906. * @param string $auth_token A Cloud Files Auth Token (Required)
  907. * @param string $storage_url The Cloud Files Storage URL (Required)
  908. * @param string $cdnm_url CDN Management URL (Required)
  909. * @return boolean <kbd>True</kbd> if successful
  910. * @throws SyntaxException If any of the Required Arguments are missing
  911. */
  912. function load_cached_credentials($auth_token, $storage_url, $cdnm_url)
  913. {
  914. if(!$storage_url || !$cdnm_url)
  915. {
  916. throw new SyntaxException("Missing Required Interface URL's!");
  917. return False;
  918. }
  919. if(!$auth_token)
  920. {
  921. throw new SyntaxException("Missing Auth Token!");
  922. return False;
  923. }
  924. $this->storage_url = $storage_url;
  925. $this->cdnm_url = $cdnm_url;
  926. $this->auth_token = $auth_token;
  927. return True;
  928. }
  929. /**
  930. * Grab Cloud Files info to be Cached for later use with the load_cached_credentials method.
  931. *
  932. * Example:
  933. * <code>
  934. * #Create an Auth instance
  935. * $auth = new AEUtilCFAuthentication("UserName","API_Key");
  936. * $auth->authenticate();
  937. * $array = $auth->export_credentials();
  938. * </code>
  939. *
  940. * @return array of url's and an auth token.
  941. */
  942. function export_credentials()
  943. {
  944. $arr = array();
  945. $arr['storage_url'] = $this->storage_url;
  946. $arr['cdnm_url'] = $this->cdnm_url;
  947. $arr['auth_token'] = $this->auth_token;
  948. return $arr;
  949. }
  950. /**
  951. * Make sure the AEUtilCFAuthentication instance has authenticated.
  952. *
  953. * Ensures that the instance variables necessary to communicate with
  954. * Cloud Files have been set from a previous authenticate() call.
  955. *
  956. * @return boolean <kbd>True</kbd> if successfully authenticated
  957. */
  958. function authenticated()
  959. {
  960. if (!($this->storage_url || $this->cdnm_url) || !$this->auth_token) {
  961. return False;
  962. }
  963. return True;
  964. }
  965. /**
  966. * Toggle debugging - set cURL verbose flag
  967. */
  968. function setDebug($bool)
  969. {
  970. $this->dbug = $bool;
  971. $this->cfs_http->setDebug($bool);
  972. }
  973. }
  974. /**
  975. * Class for establishing connections to the Cloud Files storage system.
  976. * Connection instances are used to communicate with the storage system at
  977. * the account level; listing and deleting Containers and returning Container
  978. * instances.
  979. *
  980. * Example:
  981. * <code>
  982. * # Create the authentication instance
  983. * #
  984. * $auth = new AEUtilCFAuthentication("username", "api_key");
  985. *
  986. * # Perform authentication request
  987. * #
  988. * $auth->authenticate();
  989. *
  990. * # Create a connection to the storage/cdn system(s) and pass in the
  991. * # validated AEUtilCFAuthentication instance.
  992. * #
  993. * $conn = new AEUtilCFConnection($auth);
  994. * </code>
  995. *
  996. * @package php-cloudfiles
  997. */
  998. class AEUtilCFConnection
  999. {
  1000. public $dbug;
  1001. public $cfs_http;
  1002. public $cfs_auth;
  1003. /**
  1004. * Pass in a previously authenticated AEUtilCFAuthentication instance.
  1005. *
  1006. * Example:
  1007. * <code>
  1008. * # Create the authentication instance
  1009. * #
  1010. * $auth = new AEUtilCFAuthentication("username", "api_key");
  1011. *
  1012. * # Perform authentication request
  1013. * #
  1014. * $auth->authenticate();
  1015. *
  1016. * # Create a connection to the storage/cdn system(s) and pass in the
  1017. * # validated AEUtilCFAuthentication instance.
  1018. * #
  1019. * $conn = new AEUtilCFConnection($auth);
  1020. *
  1021. * # If you are connecting via Rackspace servers and have access
  1022. * # to the servicenet network you can set the $servicenet to True
  1023. * # like this.
  1024. *
  1025. * $conn = new AEUtilCFConnection($auth, $servicenet=True);
  1026. *
  1027. * </code>
  1028. *
  1029. * If the environement variable RACKSPACE_SERVICENET is defined it will
  1030. * force to connect via the servicenet.
  1031. *
  1032. * @param obj $cfs_auth previously authenticated AEUtilCFAuthentication instance
  1033. * @param boolean $servicenet enable/disable access via Rackspace servicenet.
  1034. * @throws AuthenticationException not authenticated
  1035. */
  1036. function __construct($cfs_auth, $servicenet=False)
  1037. {
  1038. if (isset($_ENV['RACKSPACE_SERVICENET']))
  1039. $servicenet=True;
  1040. $this->cfs_http = new AEUtilCfhttp(DEFAULT_CF_API_VERSION);
  1041. $this->cfs_auth = $cfs_auth;
  1042. if (!$this->cfs_auth->authenticated()) {
  1043. $e = "Need to pass in a previously authenticated ";
  1044. $e .= "AEUtilCFAuthentication instance.";
  1045. throw new AuthenticationException($e);
  1046. }
  1047. $this->cfs_http->setCFAuth($this->cfs_auth, $servicenet=$servicenet);
  1048. $this->dbug = False;
  1049. }
  1050. /**
  1051. * Toggle debugging of instance and back-end HTTP module
  1052. *
  1053. * @param boolean $bool enable/disable cURL debugging
  1054. */
  1055. function setDebug($bool)
  1056. {
  1057. $this->dbug = (boolean) $bool;
  1058. $this->cfs_http->setDebug($this->dbug);
  1059. }
  1060. /**
  1061. * Close a connection
  1062. *
  1063. * Example:
  1064. * <code>
  1065. *
  1066. * $conn->close();
  1067. *
  1068. * </code>
  1069. *
  1070. * Will close all current cUrl active connections.
  1071. *
  1072. */
  1073. public function close()
  1074. {
  1075. $this->cfs_http->close();
  1076. }
  1077. /**
  1078. * Return a Container instance
  1079. *
  1080. * For the given name, return a Container instance if the remote Container
  1081. * exists, otherwise throw a Not Found exception.
  1082. *
  1083. * Example:
  1084. * <code>
  1085. * # ... authentication code excluded (see previous examples) ...
  1086. * #
  1087. * $conn = new AEUtilCFAuthentication($auth);
  1088. *
  1089. * $images = $conn->get_container("my photos");
  1090. * print "Number of Objects: " . $images->count . "\n";
  1091. * print "Bytes stored in container: " . $images->bytes . "\n";
  1092. * </code>
  1093. *
  1094. * @param string $container_name name of the remote Container
  1095. * @return container AEUtilCFContainer instance
  1096. * @throws NoSuchContainerException thrown if no remote Container
  1097. * @throws InvalidResponseException unexpected response
  1098. */
  1099. function get_container($container_name=NULL)
  1100. {
  1101. list($status, $reason, $count, $bytes) =
  1102. $this->cfs_http->head_container($container_name);
  1103. if ($status == 404) {
  1104. throw new NoSuchContainerException("Container not found.");
  1105. }
  1106. if ($status < 200 || $status > 299) {
  1107. throw new InvalidResponseException(
  1108. "Invalid response: ".$this->cfs_http->get_error());
  1109. }
  1110. return new AEUtilCFContainer($this->cfs_auth, $this->cfs_http,
  1111. $container_name, $count, $bytes);
  1112. }
  1113. /**
  1114. * Set a user-supplied callback function to report upload progress
  1115. *
  1116. * The callback function is used to report incremental progress of a data
  1117. * upload functions (e.g. $obj->write() call). The specified function will
  1118. * be periodically called with the number of bytes transferred until the
  1119. * entire upload is complete. This callback function can be useful
  1120. * for implementing "progress bars" for large uploads/downloads.
  1121. *
  1122. * The specified callback function should take a single integer parameter.
  1123. *
  1124. * <code>
  1125. * function write_callback($bytes_transferred) {
  1126. * print ">> uploaded " . $bytes_transferred . " bytes.\n";
  1127. * # ... do other things ...
  1128. * return;
  1129. * }
  1130. *
  1131. * $conn = new AEUtilCFConnection($auth_obj);
  1132. * $conn->set_write_progress_function("write_callback");
  1133. * $container = $conn->create_container("stuff");
  1134. * $obj = $container->create_object("foo");
  1135. * $obj->write("The callback function will be called during upload.");
  1136. *
  1137. * # output would look like this:
  1138. * # >> uploaded 51 bytes.
  1139. * #
  1140. * </code>
  1141. *
  1142. * @param string $func_name the name of the user callback function
  1143. */
  1144. function set_write_progress_function($func_name)
  1145. {
  1146. $this->cfs_http->setWriteProgressFunc($func_name);
  1147. }
  1148. }
  1149. /**
  1150. * Container operations
  1151. *
  1152. * Containers are storage compartments where you put your data (objects).
  1153. * A container is similar to a directory or folder on a conventional filesystem
  1154. * with the exception that they exist in a flat namespace, you can not create
  1155. * containers inside of containers.
  1156. *
  1157. * You also have the option of marking a Container as "public" so that the
  1158. * Objects stored in the Container are publicly available via the CDN.
  1159. *
  1160. * @package php-cloudfiles
  1161. */
  1162. class AEUtilCFContainer
  1163. {
  1164. public $cfs_auth;
  1165. public $cfs_http;
  1166. public $name;
  1167. public $object_count;
  1168. public $bytes_used;
  1169. public $cdn_enabled;
  1170. public $cdn_uri;
  1171. public $cdn_ttl;
  1172. public $cdn_log_retention;
  1173. public $cdn_acl_user_agent;
  1174. public $cdn_acl_referrer;
  1175. /**
  1176. * Class constructor
  1177. *
  1178. * Constructor for Container
  1179. *
  1180. * @param obj $cfs_auth AEUtilCFAuthentication instance
  1181. * @param obj $cfs_http HTTP connection manager
  1182. * @param string $name name of Container
  1183. * @param int $count number of Objects stored in this Container
  1184. * @param int $bytes number of bytes stored in this Container
  1185. * @throws SyntaxException invalid Container name
  1186. */
  1187. function __construct(&$cfs_auth, &$cfs_http, $name, $count=0,
  1188. $bytes=0, $docdn=True)
  1189. {
  1190. if (strlen($name) > MAX_CONTAINER_NAME_LEN) {
  1191. throw new SyntaxException("Container name exceeds "
  1192. . "maximum allowed length.");
  1193. }
  1194. if (strpos($name, "/") !== False) {
  1195. throw new SyntaxException(
  1196. "Container names cannot contain a '/' character.");
  1197. }
  1198. $this->cfs_auth = $cfs_auth;
  1199. $this->cfs_http = $cfs_http;
  1200. $this->name = $name;
  1201. $this->object_count = $count;
  1202. $this->bytes_used = $bytes;
  1203. $this->cdn_enabled = NULL;
  1204. $this->cdn_uri = NULL;
  1205. $this->cdn_ttl = NULL;
  1206. $this->cdn_log_retention = NULL;
  1207. $this->cdn_acl_user_agent = NULL;
  1208. $this->cdn_acl_referrer = NULL;
  1209. if ($this->cfs_http->getCDNMUrl() != NULL && $docdn) {
  1210. $this->_cdn_initialize();
  1211. }
  1212. }
  1213. /**
  1214. * String representation of Container
  1215. *
  1216. * Pretty print the Container instance.
  1217. *
  1218. * @return string Container details
  1219. */
  1220. function __toString()
  1221. {
  1222. $me = sprintf("name: %s, count: %.0f, bytes: %.0f",
  1223. $this->name, $this->object_count, $this->bytes_used);
  1224. if ($this->cfs_http->getCDNMUrl() != NULL) {
  1225. $me .= sprintf(", cdn: %s, cdn uri: %s, cdn ttl: %.0f, logs retention: %s",
  1226. $this->is_public() ? "Yes" : "No",
  1227. $this->cdn_uri, $this->cdn_ttl,
  1228. $this->cdn_log_retention ? "Yes" : "No"
  1229. );
  1230. if ($this->cdn_acl_user_agent != NULL) {
  1231. $me .= ", cdn acl user agent: " . $this->cdn_acl_user_agent;
  1232. }
  1233. if ($this->cdn_acl_referrer != NULL) {
  1234. $me .= ", cdn acl referrer: " . $this->cdn_acl_referrer;
  1235. }
  1236. }
  1237. return $me;
  1238. }
  1239. function create_object($obj_name=NULL)
  1240. {
  1241. return new AEUtilCFObject($this, $obj_name);
  1242. }
  1243. /**
  1244. * Helper function to create "path" elements for a given Object name
  1245. *
  1246. * Given an Object whos name contains '/' path separators, this function
  1247. * will create the "directory marker" Objects of one byte with the
  1248. * Content-Type of "application/folder".
  1249. *
  1250. * It assumes the last element of the full path is the "real" Object
  1251. * and does NOT create a remote storage Object for that last element.
  1252. */
  1253. function create_paths($path_name)
  1254. {
  1255. if ($path_name[0] == '/') {
  1256. $path_name = mb_substr($path_name, 0, 1);
  1257. }
  1258. $elements = explode('/', $path_name, -1);
  1259. $build_path = "";
  1260. foreach ($elements as $idx => $val) {
  1261. if (!$build_path) {
  1262. $build_path = $val;
  1263. } else {
  1264. $build_path .= "/" . $val;
  1265. }
  1266. $obj = new AEUtilCFObject($this, $build_path);
  1267. $obj->content_type = "application/directory";
  1268. $obj->write(".", 1);
  1269. }
  1270. }
  1271. /**
  1272. * Delete a remote storage Object
  1273. *
  1274. * Given an Object instance or name, permanently remove the remote Object
  1275. * and all associated metadata.
  1276. *
  1277. * Example:
  1278. * <code>
  1279. * # ... authentication code excluded (see previous examples) ...
  1280. * #
  1281. * $conn = new CF_Authentication($auth);
  1282. *
  1283. * $images = $conn->get_container("my photos");
  1284. *
  1285. * # Delete specific object
  1286. * #
  1287. * $images->delete_object("disco_dancing.jpg");
  1288. * </code>
  1289. *
  1290. * @param obj $obj name or instance of Object to delete
  1291. * @return boolean <kbd>True</kbd> if successfully removed
  1292. * @throws SyntaxException invalid Object name
  1293. * @throws NoSuchObjectException remote Object does not exist
  1294. * @throws InvalidResponseException unexpected response
  1295. */
  1296. function delete_object($obj)
  1297. {
  1298. $obj_name = NULL;
  1299. if (is_object($obj)) {
  1300. if (get_class($obj) == "AEUtilCFObject") {
  1301. $obj_name = $obj->name;
  1302. }
  1303. }
  1304. if (is_string($obj)) {
  1305. $obj_name = $obj;
  1306. }
  1307. if (!$obj_name) {
  1308. throw new SyntaxException("Object name not set.");
  1309. }
  1310. $status = $this->cfs_http->delete_object($this->name, $obj_name);
  1311. #if ($status == 401 && $this->_re_auth()) {
  1312. # return $this->delete_object($obj);
  1313. #}
  1314. if ($status == 404) {
  1315. $m = "Specified object '".$this->name."/".$obj_name;
  1316. $m.= "' did not exist to delete.";
  1317. throw new NoSuchObjectException($m);
  1318. }
  1319. if ($status != 204) {
  1320. throw new InvalidResponseException(
  1321. "Invalid response (".$status."): ".$this->cfs_http->get_error());
  1322. }
  1323. return True;
  1324. }
  1325. /**
  1326. * Return an Object instance for the remote storage Object
  1327. *
  1328. * Given a name, return a Object instance representing the
  1329. * remote storage object.
  1330. *
  1331. * Example:
  1332. * <code>
  1333. * # ... authentication code excluded (see previous examples) ...
  1334. * #
  1335. * $conn = new CF_Authentication($auth);
  1336. *
  1337. * $public_container = $conn->get_container("public");
  1338. *
  1339. * # This call only fetches header information and not the content of
  1340. * # the storage object. Use the Object's read() or stream() methods
  1341. * # to obtain the object's data.
  1342. * #
  1343. * $pic = $public_container->get_object("baby.jpg");
  1344. * </code>
  1345. *
  1346. * @param string $obj_name name of storage Object
  1347. * @return obj CF_Object instance
  1348. */
  1349. function get_object($obj_name=NULL)
  1350. {
  1351. return new AEUtilCFObject($this, $obj_name, True);
  1352. }
  1353. /**
  1354. * Return a list of Objects
  1355. *
  1356. * Return an array of strings listing the Object names in this Container.
  1357. *
  1358. * Example:
  1359. * <code>
  1360. * # ... authentication code excluded (see previous examples) ...
  1361. * #
  1362. * $images = $conn->get_container("my photos");
  1363. *
  1364. * # Grab the list of all storage objects
  1365. * #
  1366. * $all_objects = $images->list_objects();
  1367. *
  1368. * # Grab subsets of all storage objects
  1369. * #
  1370. * $first_ten = $images->list_objects(10);
  1371. *
  1372. * # Note the use of the previous result's last object name being
  1373. * # used as the 'marker' parameter to fetch the next 10 objects
  1374. * #
  1375. * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]);
  1376. *
  1377. * # Grab images starting with "birthday_party" and default limit/marker
  1378. * # to match all photos with that prefix
  1379. * #
  1380. * $prefixed = $images->list_objects(0, NULL, "birthday");
  1381. *
  1382. * # Assuming you have created the appropriate directory marker Objects,
  1383. * # you can traverse your pseudo-hierarchical containers
  1384. * # with the "path" argument.
  1385. * #
  1386. * $animals = $images->list_objects(0,NULL,NULL,"pictures/animals");
  1387. * $dogs = $images->list_objects(0,NULL,NULL,"pictures/animals/dogs");
  1388. * </code>
  1389. *
  1390. * @param int $limit <i>optional</i> only return $limit names
  1391. * @param int $marker <i>optional</i> subset of names starting at $marker
  1392. * @param string $prefix <i>optional</i> Objects whose names begin with $prefix
  1393. * @param string $path <i>optional</i> only return results under "pathname"
  1394. * @return array array of strings
  1395. * @throws InvalidResponseException unexpected response
  1396. */
  1397. function list_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL)
  1398. {
  1399. list($status, $reason, $obj_list) =
  1400. $this->cfs_http->list_objects($this->name, $limit,
  1401. $marker, $prefix, $path);
  1402. #if ($status == 401 && $this->_re_auth()) {
  1403. # return $this->list_objects($limit, $marker, $prefix, $path);
  1404. #}
  1405. if ($status < 200 || $status > 299) {
  1406. throw new InvalidResponseException(
  1407. "Invalid response (".$status."): ".$this->cfs_http->get_error());
  1408. }
  1409. return $obj_list;
  1410. }
  1411. }
  1412. /**
  1413. * Object operations
  1414. *
  1415. * An Object is analogous to a file on a conventional filesystem. You can
  1416. * read data from, or write data to your Objects. You can also associate
  1417. * arbitrary metadata with them.
  1418. *
  1419. * @package php-cloudfiles
  1420. */
  1421. class AEUtilCFObject
  1422. {
  1423. public $container;
  1424. public $name;
  1425. public $last_modified;
  1426. public $content_type;
  1427. public $content_length;
  1428. public $metadata;
  1429. private $etag;
  1430. /**
  1431. * Class constructor
  1432. *
  1433. * @param obj $container AEUtilCFContainer instance
  1434. * @param string $name name of Object
  1435. * @param boolean $force_exists if set, throw an error if Object doesn't exist
  1436. */
  1437. function __construct(&$container, $name, $force_exists=False, $dohead=True)
  1438. {
  1439. if ($name[0] == "/") {
  1440. $r = "Object name '".$name;
  1441. $r .= "' cannot contain begin with a '/' character.";
  1442. throw new SyntaxException($r);
  1443. }
  1444. if (strlen($name) > MAX_OBJECT_NAME_LEN) {
  1445. throw new SyntaxException("Object name exceeds "
  1446. . "maximum allowed length.");
  1447. }
  1448. $this->container = $container;
  1449. $this->name = $name;
  1450. $this->etag = NULL;
  1451. $this->_etag_override = False;
  1452. $this->last_modified = NULL;
  1453. $this->content_type = NULL;
  1454. $this->content_length = 0;
  1455. $this->metadata = array();
  1456. if ($dohead) {
  1457. if (!$this->_initialize() && $force_exists) {
  1458. throw new NoSuchObjectException("No such object '".$name."'");
  1459. }
  1460. }
  1461. }
  1462. /**
  1463. * String representation of Object
  1464. *
  1465. * Pretty print the Object's location and name
  1466. *
  1467. * @return string Object information
  1468. */
  1469. function __toString()
  1470. {
  1471. return $this->container->name . "/" . $this->name;
  1472. }
  1473. /**
  1474. * Internal check to get the proper mimetype.
  1475. *
  1476. * This function would go over the available PHP methods to get
  1477. * the MIME type.
  1478. *
  1479. * By default it will try to use the PHP fileinfo library which is
  1480. * available from PHP 5.3 or as an PECL extension
  1481. * (http://pecl.php.net/package/Fileinfo).
  1482. *
  1483. * It will get the magic file by default from the system wide file
  1484. * which is usually available in /usr/share/magic on Unix or try
  1485. * to use the file specified in the source directory of the API
  1486. * (share directory).
  1487. *
  1488. * if fileinfo is not available it will try to use the internal
  1489. * mime_content_type function.
  1490. *
  1491. * @param string $handle name of file or buffer to guess the type from
  1492. * @return boolean <kbd>True</kbd> if successful
  1493. * @throws BadContentTypeException
  1494. */
  1495. function _guess_content_type($handle) {
  1496. if ($this->content_type)
  1497. return;
  1498. if (function_exists("finfo_open")) {
  1499. $local_magic = dirname(__FILE__) . "/share/magic";
  1500. $finfo = @finfo_open(FILEINFO_MIME, $local_magic);
  1501. if (!$finfo)
  1502. $finfo = @finfo_open(FILEINFO_MIME);
  1503. if ($finfo) {
  1504. if (is_file((string)$handle))
  1505. $ct = @finfo_file($finfo, $handle);
  1506. else
  1507. $ct = @finfo_buffer($finfo, $handle);
  1508. /* PHP 5.3 fileinfo display extra information like
  1509. charset so we remove everything after the ; since
  1510. we are not into that stuff */
  1511. if ($ct) {
  1512. $extra_content_type_info = strpos($ct, "; ");
  1513. if ($extra_content_type_info)
  1514. $ct = substr($ct, 0, $extra_content_type_info);
  1515. }
  1516. if ($ct && $ct != 'application/octet-stream')
  1517. $this->content_type = $ct;
  1518. @finfo_close($finfo);
  1519. }
  1520. }
  1521. if (!$this->content_type && (string)is_file($handle) && function_exists("mime_content_type")) {
  1522. $this->content_type = @mime_content_type($handle);
  1523. }
  1524. if (!$this->content_type) {
  1525. throw new BadContentTypeException("Required Content-Type not set");
  1526. }
  1527. return True;
  1528. }
  1529. /**
  1530. * String representation of the Object's public URI
  1531. *
  1532. * A string representing the Object's public URI assuming that it's
  1533. * parent Container is CDN-enabled.
  1534. *
  1535. * Example:
  1536. * <code>
  1537. * # ... authentication/connection/container code excluded
  1538. * # ... see previous examples
  1539. *
  1540. * # Print out the Object's CDN URI (if it has one) in an HTML img-tag
  1541. * #
  1542. * print "<img src='$pic->public_uri()' />\n";
  1543. * </code>
  1544. *
  1545. * @return string Object's public URI or NULL
  1546. */
  1547. function public_uri()
  1548. {
  1549. if ($this->container->cdn_enabled) {
  1550. return $this->container->cdn_uri . "/" . $this->name;
  1551. }
  1552. return NULL;
  1553. }
  1554. /**
  1555. * String representation of the Object's public SSL URI
  1556. *
  1557. * A string representing the Object's public SSL URI assuming that it's
  1558. * parent Container is CDN-enabled.
  1559. *
  1560. * Example:
  1561. * <code>
  1562. * # ... authentication/connection/container code excluded
  1563. * # ... see previous examples
  1564. *
  1565. * # Print out the Object's CDN SSL URI (if it has one) in an HTML img-tag
  1566. * #
  1567. * print "<img src='$pic->public_ssl_uri()' />\n";
  1568. * </code>
  1569. *
  1570. * @return string Object's public SSL URI or NULL
  1571. */
  1572. function public_ssl_uri()
  1573. {
  1574. if ($this->container->cdn_enabled) {
  1575. return $this->container->cdn_ssl_uri . "/" . $this->name;
  1576. }
  1577. return NULL;
  1578. }
  1579. /**
  1580. * Read the remote Object's data
  1581. *
  1582. * Returns the Object's data. This is useful for smaller Objects such
  1583. * as images or office documents. Object's with larger content should use
  1584. * the stream() method below.
  1585. *
  1586. * Pass in $hdrs array to set specific custom HTTP headers such as
  1587. * If-Match, If-None-Match, If-Modified-Since, Range, etc.
  1588. *
  1589. * Example:
  1590. * <code>
  1591. * # ... authentication/connection/container code excluded
  1592. * # ... see previous examples
  1593. *
  1594. * $my_docs = $conn->get_container("documents");
  1595. * $doc = $my_docs->get_object("README");
  1596. * $data = $doc->read(); # read image content into a string variable
  1597. * print $data;
  1598. *
  1599. * # Or see stream() below for a different example.
  1600. * #
  1601. * </code>
  1602. *
  1603. * @param array $hdrs user-defined headers (Range, If-Match, etc.)
  1604. * @return string Object's data
  1605. * @throws InvalidResponseException unexpected response
  1606. */
  1607. function read($hdrs=array())
  1608. {
  1609. list($status, $reason, $data) =
  1610. $this->container->cfs_http->get_object_to_string($this, $hdrs);
  1611. #if ($status == 401 && $this->_re_auth()) {
  1612. # return $this->read($hdrs);
  1613. #}
  1614. if (($status < 200) || ($status > 299
  1615. && $status != 412 && $status != 304)) {
  1616. throw new InvalidResponseException("Invalid response (".$status."): "
  1617. . $this->container->cfs_http->get_error());
  1618. }
  1619. return $data;
  1620. }
  1621. /**
  1622. * Streaming read of Object's data
  1623. *
  1624. * Given an open PHP resource (see PHP's fopen() method), fetch the Object's
  1625. * data and write it to the open resource handle. This is useful for
  1626. * streaming an Object's content to the browser (videos, images) or for
  1627. * fetching content to a local file.
  1628. *
  1629. * Pass in $hdrs array to set specific custom HTTP headers such as
  1630. * If-Match, If-None-Match, If-Modified-Since, Range, etc.
  1631. *
  1632. * Example:
  1633. * <code>
  1634. * # ... authentication/connection/container code excluded
  1635. * # ... see previous examples
  1636. *
  1637. * # Assuming this is a web script to display the README to the
  1638. * # user's browser:
  1639. * #
  1640. * <?php
  1641. * // grab README from storage system
  1642. * //
  1643. * $my_docs = $conn->get_container("documents");
  1644. * $doc = $my_docs->get_object("README");
  1645. *
  1646. * // Hand it back to user's browser with appropriate content-type
  1647. * //
  1648. * header("Content-Type: " . $doc->content_type);
  1649. * $output = fopen("php://output", "w");
  1650. * $doc->stream($output); # stream object content to PHP's output buffer
  1651. * fclose($output);
  1652. * ?>
  1653. *
  1654. * # See read() above for a more simple example.
  1655. * #
  1656. * </code>
  1657. *
  1658. * @param resource $fp open resource for writing data to
  1659. * @param array $hdrs user-defined headers (Range, If-Match, etc.)
  1660. * @return string Object's data
  1661. * @throws InvalidResponseException unexpected response
  1662. */
  1663. function stream(&$fp, $hdrs=array())
  1664. {
  1665. list($status, $reason) =
  1666. $this->container->cfs_http->get_object_to_stream($this,$fp,$hdrs);
  1667. #if ($status == 401 && $this->_re_auth()) {
  1668. # return $this->stream($fp, $hdrs);
  1669. #}
  1670. if (($status < 200) || ($status > 299
  1671. && $status != 412 && $status != 304)) {
  1672. throw new InvalidResponseException("Invalid response (".$status."): "
  1673. .$reason);
  1674. }
  1675. return True;
  1676. }
  1677. /**
  1678. * Store new Object metadata
  1679. *
  1680. * Write's an Object's metadata to the remote Object. This will overwrite
  1681. * an prior Object metadata.
  1682. *
  1683. * Example:
  1684. * <code>
  1685. * # ... authentication/connection/container code excluded
  1686. * # ... see previous examples
  1687. *
  1688. * $my_docs = $conn->get_container("documents");
  1689. * $doc = $my_docs->get_object("README");
  1690. *
  1691. * # Define new metadata for the object
  1692. * #
  1693. * $doc->metadata = array(
  1694. * "Author" => "EJ",
  1695. * "Subject" => "How to use the PHP tests",
  1696. * "Version" => "1.2.2"
  1697. * );
  1698. *
  1699. * # Push the new metadata up to the storage system
  1700. * #
  1701. * $doc->sync_metadata();
  1702. * </code>
  1703. *
  1704. * @return boolean <kbd>True</kbd> if successful, <kbd>False</kbd> otherwise
  1705. * @throws InvalidResponseException unexpected response
  1706. */
  1707. function sync_metadata()
  1708. {
  1709. if (!empty($this->metadata)) {
  1710. $status = $this->container->cfs_http->update_object($this);
  1711. #if ($status == 401 && $this->_re_auth()) {
  1712. # return $this->sync_metadata();
  1713. #}
  1714. if ($status != 202) {
  1715. throw new InvalidResponseException("Invalid response ("
  1716. .$status."): ".$this->container->cfs_http->get_error());
  1717. }
  1718. return True;
  1719. }
  1720. return False;
  1721. }
  1722. /**
  1723. * Upload Object's data to Cloud Files
  1724. *
  1725. * Write data to the remote Object. The $data argument can either be a
  1726. * PHP resource open for reading (see PHP's fopen() method) or an in-memory
  1727. * variable. If passing in a PHP resource, you must also include the $bytes
  1728. * parameter.
  1729. *
  1730. * Example:
  1731. * <code>
  1732. * # ... authentication/connection/container code excluded
  1733. * # ... see previous examples
  1734. *
  1735. * $my_docs = $conn->get_container("documents");
  1736. * $doc = $my_docs->get_object("README");
  1737. *
  1738. * # Upload placeholder text in my README
  1739. * #
  1740. * $doc->write("This is just placeholder text for now...");
  1741. * </code>
  1742. *
  1743. * @param string|resource $data string or open resource
  1744. * @param float $bytes amount of data to upload (required for resources)
  1745. * @param boolean $verify generate, send, and compare MD5 checksums
  1746. * @return boolean <kbd>True</kbd> when data uploaded successfully
  1747. * @throws SyntaxException missing required parameters
  1748. * @throws BadContentTypeException if no Content-Type was/could be set
  1749. * @throws MisMatchedChecksumException $verify is set and checksums unequal
  1750. * @throws InvalidResponseException unexpected response
  1751. */
  1752. function write($data=NULL, $bytes=0, $verify=True)
  1753. {
  1754. if (!$data) {
  1755. throw new SyntaxException("Missing data source.");
  1756. }
  1757. if ($bytes > MAX_OBJECT_SIZE) {
  1758. throw new SyntaxException("Bytes exceeds maximum object size.");
  1759. }
  1760. if ($verify) {
  1761. if (!$this->_etag_override) {
  1762. $this->etag = $this->compute_md5sum($data);
  1763. }
  1764. } else {
  1765. $this->etag = NULL;
  1766. }
  1767. $close_fh = False;
  1768. if (!is_resource($data)) {
  1769. # A hack to treat string data as a file handle. php://memory feels
  1770. # like a better option, but it seems to break on Windows so use
  1771. # a temporary file instead.
  1772. #
  1773. $fp = fopen("php://temp", "wb+");
  1774. #$fp = fopen("php://memory", "wb+");
  1775. fwrite($fp, $data, strlen($data));
  1776. rewind($fp);
  1777. $close_fh = True;
  1778. $this->content_length = (float) strlen($data);
  1779. if ($this->content_length > MAX_OBJECT_SIZE) {
  1780. throw new SyntaxException("Data exceeds maximum object size");
  1781. }
  1782. $ct_data = substr($data, 0, 64);
  1783. } else {
  1784. $this->content_length = $bytes;
  1785. $fp = $data;
  1786. $ct_data = fread($data, 64);
  1787. rewind($data);
  1788. }
  1789. $this->_guess_content_type($ct_data);
  1790. list($status, $reason, $etag) =
  1791. $this->container->cfs_http->put_object($this, $fp);
  1792. #if ($status == 401 && $this->_re_auth()) {
  1793. # return $this->write($data, $bytes, $verify);
  1794. #}
  1795. if ($status == 412) {
  1796. if ($close_fh) { fclose($fp); }
  1797. throw new SyntaxException("Missing Content-Type header");
  1798. }
  1799. if ($status == 422) {
  1800. if ($close_fh) { fclose($fp); }
  1801. throw new MisMatchedChecksumException(
  1802. "Supplied and computed checksums do not match.");
  1803. }
  1804. if ($status != 201) {
  1805. if ($close_fh) { fclose($fp); }
  1806. throw new InvalidResponseException("Invalid response (".$status."): "
  1807. . $this->container->cfs_http->get_error());
  1808. }
  1809. if (!$verify) {
  1810. $this->etag = $etag;
  1811. }
  1812. if ($close_fh) { fclose($fp); }
  1813. return True;
  1814. }
  1815. /**
  1816. * Upload Object data from local filename
  1817. *
  1818. * This is a convenience function to upload the data from a local file. A
  1819. * True value for $verify will cause the method to compute the Object's MD5
  1820. * checksum prior to uploading.
  1821. *
  1822. * Example:
  1823. * <code>
  1824. * # ... authentication/connection/container code excluded
  1825. * # ... see previous examples
  1826. *
  1827. * $my_docs = $conn->get_container("documents");
  1828. * $doc = $my_docs->get_object("README");
  1829. *
  1830. * # Upload my local README's content
  1831. * #
  1832. * $doc->load_from_filename("/home/ej/cloudfiles/readme");
  1833. * </code>
  1834. *
  1835. * @param string $filename full path to local file
  1836. * @param boolean $verify enable local/remote MD5 checksum validation
  1837. * @return boolean <kbd>True</kbd> if data uploaded successfully
  1838. * @throws SyntaxException missing required parameters
  1839. * @throws BadContentTypeException if no Content-Type was/could be set
  1840. * @throws MisMatchedChecksumException $verify is set and checksums unequal
  1841. * @throws InvalidResponseException unexpected response
  1842. * @throws IOException error opening file
  1843. */
  1844. function load_from_filename($filename, $verify=True)
  1845. {
  1846. $fp = @fopen($filename, "r");
  1847. if (!$fp) {
  1848. throw new IOException("Could not open file for reading: ".$filename);
  1849. }
  1850. clearstatcache();
  1851. $size = (float) sprintf("%u", filesize($filename));
  1852. if ($size > MAX_OBJECT_SIZE) {
  1853. throw new SyntaxException("File size exceeds maximum object size.");
  1854. }
  1855. $this->_guess_content_type($filename);
  1856. $this->write($fp, $size, $verify);
  1857. fclose($fp);
  1858. return True;
  1859. }
  1860. /**
  1861. * Save Object's data to local filename
  1862. *
  1863. * Given a local filename, the Object's data will be written to the newly
  1864. * created file.
  1865. *
  1866. * Example:
  1867. * <code>
  1868. * # ... authentication/connection/container code excluded
  1869. * # ... see previous examples
  1870. *
  1871. * # Whoops! I deleted my local README, let me download/save it
  1872. * #
  1873. * $my_docs = $conn->get_container("documents");
  1874. * $doc = $my_docs->get_object("README");
  1875. *
  1876. * $doc->save_to_filename("/home/ej/cloudfiles/readme.restored");
  1877. * </code>
  1878. *
  1879. * @param string $filename name of local file to write data to
  1880. * @param array $hdrs extra headers to pass to the request
  1881. * @return boolean <kbd>True</kbd> if successful
  1882. * @throws IOException error opening file
  1883. * @throws InvalidResponseException unexpected response
  1884. */
  1885. function save_to_filename($filename, $hdrs=array())
  1886. {
  1887. $fp = @fopen($filename, "wb");
  1888. if (!$fp) {
  1889. throw new IOException("Could not open file for writing: ".$filename);
  1890. }
  1891. $result = $this->stream($fp, $hdrs);
  1892. fclose($fp);
  1893. return $result;
  1894. }
  1895. /**
  1896. * Set Object's MD5 checksum
  1897. *
  1898. * Manually set the Object's ETag. Including the ETag is mandatory for
  1899. * Cloud Files to perform end-to-end verification. Omitting the ETag forces
  1900. * the user to handle any data integrity checks.
  1901. *
  1902. * @param string $etag MD5 checksum hexidecimal string
  1903. */
  1904. function set_etag($etag)
  1905. {
  1906. $this->etag = $etag;
  1907. $this->_etag_override = True;
  1908. }
  1909. /**
  1910. * Object's MD5 checksum
  1911. *
  1912. * Accessor method for reading Object's private ETag attribute.
  1913. *
  1914. * @return string MD5 checksum hexidecimal string
  1915. */
  1916. function getETag()
  1917. {
  1918. return $this->etag;
  1919. }
  1920. /**
  1921. * Compute the MD5 checksum
  1922. *
  1923. * Calculate the MD5 checksum on either a PHP resource or data. The argument
  1924. * may either be a local filename, open resource for reading, or a string.
  1925. *
  1926. * <b>WARNING:</b> if you are uploading a big file over a stream
  1927. * it could get very slow to compute the md5 you probably want to
  1928. * set the $verify parameter to False in the write() method and
  1929. * compute yourself the md5 before if you have it.
  1930. *
  1931. * @param filename|obj|string $data filename, open resource, or string
  1932. * @return string MD5 checksum hexidecimal string
  1933. */
  1934. function compute_md5sum(&$data)
  1935. {
  1936. if (function_exists("hash_init") && is_resource($data)) {
  1937. $ctx = hash_init('md5');
  1938. while (!feof($data)) {
  1939. $buffer = fgets($data, 65536);
  1940. hash_update($ctx, $buffer);
  1941. }
  1942. $md5 = hash_final($ctx, false);
  1943. rewind($data);
  1944. } elseif ((string)is_file($data)) {
  1945. $md5 = md5_file($data);
  1946. } else {
  1947. $md5 = md5($data);
  1948. }
  1949. return $md5;
  1950. }
  1951. /**
  1952. * PRIVATE: fetch information about the remote Object if it exists
  1953. */
  1954. private function _initialize()
  1955. {
  1956. list($status, $reason, $etag, $last_modified, $content_type,
  1957. $content_length, $metadata) =
  1958. $this->container->cfs_http->head_object($this);
  1959. #if ($status == 401 && $this->_re_auth()) {
  1960. # return $this->_initialize();
  1961. #}
  1962. if ($status == 404) {
  1963. return False;
  1964. }
  1965. if ($status < 200 || $status > 299) {
  1966. throw new InvalidResponseException("Invalid response (".$status."): "
  1967. . $this->container->cfs_http->get_error());
  1968. }
  1969. $this->etag = $etag;
  1970. $this->last_modified = $last_modified;
  1971. $this->content_type = $content_type;
  1972. $this->content_length = $content_length;
  1973. $this->metadata = $metadata;
  1974. return True;
  1975. }
  1976. }