Storage.php 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371
  1. <?php
  2. namespace sinacloud\sae;
  3. /**
  4. * 新浪云 Storage PHP 客户端
  5. *
  6. * @copyright Copyright (c) 2015, SINA, All rights reserved.
  7. *
  8. * ```php
  9. * <?php
  10. * use sinacloud\sae\Storage as Storage;
  11. *
  12. * **类初始化**
  13. *
  14. * // 方法一:在新浪云运行环境中时可以不传认证信息,默认会从应用的环境变量中取
  15. * $s = new Storage();
  16. *
  17. * // 方法二:如果不在新浪云运行环境或者要连非本应用的 storage,需要传入所连应用的"应用名:应用 AccessKey"和"应用 SecretKey"
  18. * $s = new Storage("$AppName:$AccessKey", $SecretKey);
  19. *
  20. * **Bucket 操作**
  21. *
  22. * // 创建一个 Bucket test
  23. * $s->putBucket("test");
  24. *
  25. * // 获取 Bucket 列表
  26. * $s->listBuckets();
  27. *
  28. * // 获取 Bucket 列表及 Bucket 中 Object 数量和 Bucket 的大小
  29. * $s->listBuckets(true);
  30. *
  31. * // 获取 test 这个 Bucket 中的 Object 对象列表,默认返回前 1000 个,如果需要返回大于 1000 个 Object 的列表,可以通过 limit 参数来指定。
  32. * $s->getBucket("test");
  33. *
  34. * // 获取 test 这个 Bucket 中所有以 a/ 为前缀的 Objects 列表
  35. * $s->getBucket("test", 'a/');
  36. *
  37. * // 获取 test 这个 Bucket 中所有以 a/ 为前缀的 Objects 列表,只显示 a/N 这个 Object 之后的列表(不包含 a/N 这个 Object)。
  38. * $s->getBucket("test", 'a/', 'a/N');
  39. *
  40. * // Storage 也可以当成一个伪文件系统来使用,比如获取 a/ 目录下的 Object(不显示其下的子目录的具体 Object 名称,只显示目录名)
  41. * $s->getBucket("test", 'a/', null, 10000, '/');
  42. *
  43. * // 删除一个空的 Bucket test
  44. * $s->deleteBucket("test");
  45. *
  46. * **Object 上传操作**
  47. *
  48. * // 把 $_FILES 全局变量中的缓存文件上传到 test 这个 Bucket,设置此 Object 名为 1.txt
  49. * $s->putObjectFile($_FILES['uploaded']['tmp_name'], "test", "1.txt");
  50. *
  51. * // 把 $_FILES 全局变量中的缓存文件上传到 test 这个 Bucket,设置此 Object 名为 sae/1.txt
  52. * $s->putObjectFile($_FILES['uploaded']['tmp_name'], "test", "sae/1.txt");
  53. *
  54. * // 上传一个字符串到 test 这个 Bucket 中,设置此 Object 名为 string.txt,并且设置其 Content-type
  55. * $s->putObject("This is string.", "test", "string.txt", array(), array('Content-Type' => 'text/plain'));
  56. *
  57. * // 上传一个文件句柄(必须是 buffer 或者一个文件,文件会被自动 fclose 掉)到 test 这个 Bucket 中,设置此 Object 名为 file.txt
  58. * $s->putObject(Storage::inputResource(fopen($_FILES['uploaded']['tmp_name'], 'rb'), filesize($_FILES['uploaded']['tmp_name']), "test", "file.txt", Storage::ACL_PUBLIC_READ);
  59. *
  60. * **Object 下载操作**
  61. *
  62. * // 从 test 这个 Bucket 读取 Object 1.txt,输出为此次请求的详细信息,包括状态码和 1.txt 的内容等
  63. * var_dump($s->getObject("test", "1.txt"));
  64. *
  65. * // 从 test 这个 Bucket 读取 Object 1.txt,把 1.txt 的内容保存在 SAE_TMP_PATH 变量指定的 TmpFS 中,savefile.txt 为保存的文件名;SAE_TMP_PATH 路径具有写权限,用户可以往这个目录下写文件,但文件的生存周期等同于 PHP 请求,也就是当该 PHP 请求完成执行时,所有写入 SAE_TMP_PATH 的文件都会被销毁
  66. * $s->getObject("test", "1.txt", SAE_TMP_PATH."savefile.txt");
  67. *
  68. * // 从 test 这个 Bucket 读取 Object 1.txt,把 1.txt 的内容保存在打开的文件句柄中
  69. * $s->getObject("test", "1.txt", fopen(SAE_TMP_PATH."savefile.txt", 'wb'));
  70. *
  71. * **Object 删除操作**
  72. *
  73. * // 从 test 这个 Bucket 删除 Object 1.txt
  74. * $s->deleteObject("test", "1.txt");
  75. *
  76. * **Object 复制操作**
  77. *
  78. * // 把 test 这个 Bucket 的 Object 1.txt 内容复制到 newtest 这个 Bucket 的 Object 1.txt
  79. * $s->copyObject("test", "1.txt", "newtest", "1.txt");
  80. *
  81. * // 把 test 这个 Bucket 的 Object 1.txt 内容复制到 newtest 这个 Bucket 的 Object 1.txt,并设置 Object 的浏览器缓存过期时间为 10s 和 Content-Type 为 text/plain
  82. * $s->copyObject("test", "1.txt", "newtest", "1.txt", array('expires' => '10s'), array('Content-Type' => 'text/plain'));
  83. *
  84. * **生成一个外网能够访问的 url**
  85. *
  86. * // 为私有 Bucket test 中的 Object 1.txt 生成一个能够在外网用 GET 方法临时访问的 URL,次 URL 过期时间为 600s
  87. * $s->getTempUrl("test", "1.txt", "GET", 600);
  88. *
  89. * // 为 test 这个 Bucket 中的 Object 1.txt 生成一个能用 CDN 访问的 URL
  90. * $s->getCdnUrl("test", "1.txt");
  91. *
  92. * **调试模式**
  93. *
  94. * // 开启调试模式,出问题的时候方便定位问题,设置为 true 后遇到错误的时候会抛出异常而不是写一条 warning 信息到日志。
  95. * $s->setExceptions(true);
  96. * ?>
  97. * ```
  98. */
  99. if (defined('SAE_APPNAME')) {
  100. define('DEFAULT_STORAGE_ENDPOINT', 'api.i.sinas3.com:81');
  101. define('DEFAULT_USE_SSL', false);
  102. } else {
  103. define('DEFAULT_STORAGE_ENDPOINT', 'api.sinas3.com');
  104. define('DEFAULT_USE_SSL', true);
  105. }
  106. class Storage
  107. {
  108. // ACL flags
  109. const ACL_PRIVATE = '';
  110. const ACL_PUBLIC_READ = '.r:*';
  111. private static $__accessKey = null;
  112. private static $__secretKey = null;
  113. private static $__account = null;
  114. /**
  115. * 默认使用的分隔符,getBucket() 等用到
  116. *
  117. * @var string
  118. * @access public
  119. * @static
  120. */
  121. public static $defDelimiter = null;
  122. public static $endpoint = DEFAULT_STORAGE_ENDPOINT;
  123. public static $proxy = null;
  124. /**
  125. * 使用 SSL 连接?
  126. *
  127. * @var bool
  128. * @access public
  129. * @static
  130. */
  131. public static $useSSL = DEFAULT_USE_SSL;
  132. /**
  133. * 是否验证 SSL 证书
  134. *
  135. * @var bool
  136. * @access public
  137. * @static
  138. */
  139. public static $useSSLValidation = false;
  140. /**
  141. * 使用的 SSL 版本
  142. *
  143. * @var const
  144. * @access public
  145. * @static
  146. */
  147. public static $useSSLVersion = 1;
  148. /**
  149. * 出现错误的时候是否使用 PHP Exception(默认使用 trigger_error 纪录错误)
  150. *
  151. * @var bool
  152. * @access public
  153. * @static
  154. */
  155. public static $useExceptions = false;
  156. /**
  157. * 构造函数
  158. *
  159. * @param string $accessKey 此处需要使用"应用名:Accesskey"
  160. * @param string $secretKey 应用 Secretkey
  161. * @param boolean $useSSL 是否使用 SSL
  162. * @param string $endpoint 新浪云 Storage 的 endpoint
  163. * @return void
  164. */
  165. public function __construct($accessKey = null, $secretKey = null,
  166. $useSSL = DEFAULT_USE_SSL, $endpoint = DEFAULT_STORAGE_ENDPOINT)
  167. {
  168. if ($accessKey !== null && $secretKey !== null) {
  169. self::setAuth($accessKey, $secretKey);
  170. } else if (defined('SAE_APPNAME')) {
  171. // We are in 新浪云运行环境
  172. self::setAuth(SAE_APPNAME.':'.SAE_ACCESSKEY, SAE_SECRETKEY);
  173. }
  174. self::$useSSL = $useSSL;
  175. self::$endpoint = $endpoint;
  176. }
  177. /**
  178. * 设置新浪云的 Storage 的 endpoint
  179. *
  180. * @param string $host 新浪云 Storage 的 hostname
  181. * @return void
  182. */
  183. public function setEndpoint($host)
  184. {
  185. self::$endpoint = $host;
  186. }
  187. /**
  188. * 设置访问的 Accesskey 和 Secretkey
  189. *
  190. * @param string $accessKey 此处需要使用"应用名:应用 Accesskey"
  191. * @param string $secretKey 应用 Secretkey
  192. * @return void
  193. */
  194. public static function setAuth($accessKey, $secretKey)
  195. {
  196. $e = explode(':', $accessKey);
  197. self::$__account = $e[0];
  198. self::$__accessKey = $e[1];
  199. self::$__secretKey = $secretKey;
  200. }
  201. public static function hasAuth() {
  202. return (self::$__accessKey !== null && self::$__secretKey !== null);
  203. }
  204. /**
  205. * 开启或者关闭 SSL
  206. *
  207. * @param boolean $enabled 是否启用 SSL
  208. * @param boolean $validate 是否验证 SSL 证书
  209. * @return void
  210. */
  211. public static function setSSL($enabled, $validate = true)
  212. {
  213. self::$useSSL = $enabled;
  214. self::$useSSLValidation = $validate;
  215. }
  216. /**
  217. * 设置代理信息
  218. *
  219. * @param string $host 代理的 hostname 和端口 (localhost:1234)
  220. * @param string $user 代理的 username
  221. * @param string $pass 代理的 password
  222. * @param constant $type CURL 代理类型
  223. * @return void
  224. */
  225. public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
  226. {
  227. self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass);
  228. }
  229. /**
  230. * 设置是否使用 PHP Exception,默认使用 trigger_error
  231. *
  232. * @param boolean $enabled Enable exceptions
  233. * @return void
  234. */
  235. public static function setExceptions($enabled = true)
  236. {
  237. self::$useExceptions = $enabled;
  238. }
  239. private static function __triggerError($message, $file, $line, $code = 0)
  240. {
  241. if (self::$useExceptions)
  242. throw new StorageException($message, $file, $line, $code);
  243. else
  244. trigger_error($message, E_USER_WARNING);
  245. }
  246. /**
  247. * 获取 bucket 列表
  248. *
  249. * @param boolean $detailed 设置为 true 时返回 bucket 的详细信息
  250. * @return array | false
  251. */
  252. public static function listBuckets($detailed = false)
  253. {
  254. $rest = new StorageRequest('GET', self::$__account, '', '', self::$endpoint);
  255. $rest->setParameter('format', 'json');
  256. $rest = $rest->getResponse();
  257. if ($rest->error === false && $rest->code !== 200)
  258. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  259. if ($rest->error !== false)
  260. {
  261. self::__triggerError(sprintf("Storage::listBuckets(): [%s] %s", $rest->error['code'],
  262. $rest->error['message']), __FILE__, __LINE__);
  263. return false;
  264. }
  265. $buckets = json_decode($rest->body, True);
  266. if ($buckets === False) {
  267. self::__triggerError(sprintf("Storage::listBuckets(): invalid body: %s", $rest->body),
  268. __FILE__, __LINE__);
  269. return false;
  270. }
  271. if ($detailed) {
  272. return $buckets;
  273. }
  274. $results = array();
  275. foreach ($buckets as $b) $results[] = (string)$b['name'];
  276. return $results;
  277. }
  278. /**
  279. * 获取 bucket 中的 object 列表
  280. *
  281. * @param string $bucket Bucket 名称
  282. * @param string $prefix Object 名称的前缀
  283. * @param string $marker Marker (返回 marker 之后的 object 列表,不包含 marker)
  284. * @param string $limit 最大返回的 Object 数目
  285. * @param string $delimiter 分隔符
  286. * @return array | false
  287. */
  288. public static function getBucket($bucket, $prefix = null, $marker = null, $limit = 1000, $delimiter = null, $keyword = null)
  289. {
  290. $result = array();
  291. do {
  292. $rest = new StorageRequest('GET', self::$__account, $bucket, '', self::$endpoint);
  293. $rest->setParameter('format', 'json');
  294. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  295. if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
  296. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  297. if ($keyword !== null && $keyword !== '') $rest->setParameter('keyword', $keyword);
  298. else if (!empty(self::$defDelimiter)) $rest->setParameter('delimiter', self::$defDelimiter);
  299. if ($limit > 1000) {
  300. $max_keys = 1000;
  301. } else {
  302. $max_keys = $limit;
  303. }
  304. $rest->setParameter("limit", $max_keys);
  305. $limit -= 1000;
  306. $response = $rest->getResponse();
  307. if ($response->error === false && $response->code !== 200)
  308. $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
  309. if ($response->error !== false)
  310. {
  311. self::__triggerError(sprintf("Storage::getBucket(): [%s] %s", $response->error['code'],
  312. $response->error['message']), __FILE__, __LINE__);
  313. return false;
  314. }
  315. $objects = json_decode($response->body, True);
  316. if ($objects === False) {
  317. self::__triggerError(sprintf("Storage::getBucket(): invalid body: %s", $response->body),
  318. __FILE__, __LINE__);
  319. return false;
  320. }
  321. if ($objects) {
  322. $result = array_merge($result, $objects);
  323. $marker = end($objects);
  324. $marker = $marker['name'];
  325. }
  326. } while ($objects && count($objects) == $max_keys && $limit > 0);
  327. return $result;
  328. }
  329. /**
  330. * 创建一个 Bucket
  331. *
  332. * @param string $bucket Bucket 名称
  333. * @param constant $acl Bucket 的 ACL
  334. * @param array $metaHeaders x-sws-container-meta-* header 数组
  335. * @return boolean
  336. */
  337. public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $metaHeaders=array())
  338. {
  339. $rest = new StorageRequest('PUT', self::$__account, $bucket, '', self::$endpoint);
  340. if ($acl) {
  341. $rest->setSwsHeader('x-sws-container-read', $acl);
  342. }
  343. foreach ($metaHeaders as $k => $v) {
  344. $rest->setSwsHeader('x-sws-container-meta-'.$k, $v);
  345. }
  346. $rest = $rest->getResponse();
  347. if ($rest->error === false && ($rest->code !== 201 && $rest->code != 202 && $rest->code !== 204))
  348. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  349. if ($rest->error !== false)
  350. {
  351. self::__triggerError(sprintf("Storage::putBucket({$bucket}, {$acl}): [%s] %s",
  352. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  353. return false;
  354. }
  355. return true;
  356. }
  357. /**
  358. * 获取一个 Bucket 的属性
  359. * @param string $bucket Bucket 名称
  360. * @param boolean $returnInfo 是否返回 Bucket 的信息
  361. * @return mixed
  362. */
  363. public static function getBucketInfo($bucket, $returnInfo=True) {
  364. $rest = new StorageRequest('HEAD', self::$__account, $bucket, '', self::$endpoint);
  365. $rest = $rest->getResponse();
  366. if ($rest->error === false && ($rest->code !== 204 && $rest->code !== 404))
  367. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  368. if ($rest->error !== false)
  369. {
  370. self::__triggerError(sprintf("Storage::getBucketInfo({$bucket}): [%s] %s",
  371. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  372. return false;
  373. }
  374. return $rest->code !== 404 ? $returnInfo ? $rest->headers : true : false;
  375. }
  376. /**
  377. * 修改一个 Bucket 的属性
  378. *
  379. * @param string $bucket Bucket 名称
  380. * @param constant $acl Bucket 的 ACL,null 表示不变
  381. * @param array $metaHeaders x-sws-container-meta-* header 数组
  382. * @return boolean
  383. */
  384. public static function postBucket($bucket, $acl = null, $metaHeaders=array())
  385. {
  386. $rest = new StorageRequest('POST', self::$__account, $bucket, '', self::$endpoint);
  387. if ($acl) {
  388. $rest->setSwsHeader('x-sws-container-read', $acl);
  389. }
  390. foreach ($metaHeaders as $k => $v) {
  391. $rest->setSwsHeader('x-sws-container-meta-'.$k, $v);
  392. }
  393. $rest = $rest->getResponse();
  394. if ($rest->error === false && ($rest->code !== 201 && $rest->code !== 204))
  395. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  396. if ($rest->error !== false)
  397. {
  398. self::__triggerError(sprintf("Storage::postBucket({$bucket}, {$acl}): [%s] %s",
  399. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  400. return false;
  401. }
  402. return true;
  403. }
  404. /**
  405. * 删除一个空的 Bucket
  406. *
  407. * @param string $bucket Bucket 名称
  408. * @return boolean
  409. */
  410. public static function deleteBucket($bucket)
  411. {
  412. $rest = new StorageRequest('DELETE', self::$__account, $bucket, '', self::$endpoint);
  413. $rest = $rest->getResponse();
  414. if ($rest->error === false && $rest->code !== 204)
  415. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  416. if ($rest->error !== false)
  417. {
  418. self::__triggerError(sprintf("Storage::deleteBucket({$bucket}): [%s] %s",
  419. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  420. return false;
  421. }
  422. return true;
  423. }
  424. /**
  425. * 为本地文件路径创建一个可以用于 putObject() 上传的 array
  426. *
  427. * @param string $file 文件路径
  428. * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
  429. * @return array | false
  430. */
  431. public static function inputFile($file, $md5sum = false)
  432. {
  433. if (!file_exists($file) || !is_file($file) || !is_readable($file))
  434. {
  435. self::__triggerError('Storage::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
  436. return false;
  437. }
  438. clearstatcache(false, $file);
  439. return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
  440. (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
  441. }
  442. /**
  443. * 为打开的文件句柄创建一个可以用于 putObject() 上传的 array
  444. *
  445. * @param string $resource Input resource to read from
  446. * @param integer $bufferSize Input byte size
  447. * @param string $md5sum MD5 hash to send (optional)
  448. * @return array | false
  449. */
  450. public static function inputResource(&$resource, $bufferSize = false, $md5sum = '')
  451. {
  452. if (!is_resource($resource) || (int)$bufferSize < 0)
  453. {
  454. self::__triggerError('Storage::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
  455. return false;
  456. }
  457. // Try to figure out the bytesize
  458. if ($bufferSize === false)
  459. {
  460. if (fseek($resource, 0, SEEK_END) < 0 || ($bufferSize = ftell($resource)) === false)
  461. {
  462. self::__triggerError('Storage::inputResource(): Unable to obtain resource size', __FILE__, __LINE__);
  463. return false;
  464. }
  465. fseek($resource, 0);
  466. }
  467. $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
  468. $input['fp'] =& $resource;
  469. return $input;
  470. }
  471. /**
  472. * 上传一个 object
  473. *
  474. * @param mixed $input Input data
  475. * @param string $bucket Bucket name
  476. * @param string $uri Object URI
  477. * @param array $metaHeaders x-sws-object-meta-* header 数组
  478. * @param array $requestHeaders Array of request headers or content type as a string
  479. * @return boolean
  480. */
  481. public static function putObject($input, $bucket, $uri, $metaHeaders = array(), $requestHeaders = array())
  482. {
  483. if ($input === false) return false;
  484. $rest = new StorageRequest('PUT', self::$__account, $bucket, $uri, self::$endpoint);
  485. if (!is_array($input)) $input = array(
  486. 'data' => $input, 'size' => strlen($input),
  487. 'md5sum' => base64_encode(md5($input, true))
  488. );
  489. // Data
  490. if (isset($input['fp']))
  491. $rest->fp =& $input['fp'];
  492. elseif (isset($input['file']))
  493. $rest->fp = @fopen($input['file'], 'rb');
  494. elseif (isset($input['data']))
  495. $rest->data = $input['data'];
  496. // Content-Length (required)
  497. if (isset($input['size']) && $input['size'] >= 0)
  498. $rest->size = $input['size'];
  499. else {
  500. if (isset($input['file'])) {
  501. clearstatcache(false, $input['file']);
  502. $rest->size = filesize($input['file']);
  503. }
  504. elseif (isset($input['data']))
  505. $rest->size = strlen($input['data']);
  506. }
  507. // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
  508. if (is_array($requestHeaders))
  509. foreach ($requestHeaders as $h => $v)
  510. strpos($h, 'x-') === 0 ? $rest->setSwsHeader($h, $v) : $rest->setHeader($h, $v);
  511. if (is_string($requestHeaders)) // Support for legacy contentType parameter
  512. $rest->setHeader('content-type', $requestHeaders);
  513. else if (!isset($requestHeaders['content-type']) && !isset($requestHeaders['Content-Type']))
  514. $rest->setHeader('content-type', self::__getMIMEType($uri));
  515. // We need to post with Content-Length and Content-Type, MD5 is optional
  516. if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
  517. {
  518. if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
  519. foreach ($metaHeaders as $h => $v) $rest->setSwsHeader('x-sws-object-meta-'.$h, $v);
  520. $rest->getResponse();
  521. } else
  522. $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
  523. if ($rest->response->error === false && $rest->response->code !== 201)
  524. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  525. if ($rest->response->error !== false)
  526. {
  527. self::__triggerError(sprintf("Storage::putObject(): [%s] %s",
  528. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  529. return false;
  530. }
  531. return true;
  532. }
  533. /**
  534. * Put an object from a file (legacy function)
  535. *
  536. * @param string $file Input file path
  537. * @param string $bucket Bucket name
  538. * @param string $uri Object URI
  539. * @param constant $acl ACL constant
  540. * @param array $metaHeaders Array of x-meta-* headers
  541. * @param string $contentType Content type
  542. * @return boolean
  543. */
  544. public static function putObjectFile($file, $bucket, $uri, $metaHeaders = array(), $contentType = null)
  545. {
  546. return self::putObject(self::inputFile($file), $bucket, $uri, $metaHeaders, $contentType);
  547. }
  548. /**
  549. * Put an object from a string (legacy function)
  550. *
  551. * @param string $string Input data
  552. * @param string $bucket Bucket name
  553. * @param string $uri Object URI
  554. * @param constant $acl ACL constant
  555. * @param array $metaHeaders Array of x-sws-meta-* headers
  556. * @param string $contentType Content type
  557. * @return boolean
  558. */
  559. public static function putObjectString($string, $bucket, $uri, $metaHeaders = array(), $contentType = false)
  560. {
  561. return self::putObject($string, $bucket, $uri, $metaHeaders, $contentType);
  562. }
  563. /**
  564. * 修改一个 Object 的属性
  565. *
  566. * @param string $bucket Bucket 名称
  567. * @param constant $uri Object 名称
  568. * @param array $metaHeaders x-sws-container-meta-* header 数组
  569. * @param array $requestHeaders 其它 header 属性
  570. * @return boolean
  571. */
  572. public static function postObject($bucket, $uri, $metaHeaders=array(), $requestHeaders=Array())
  573. {
  574. $rest = new StorageRequest('POST', self::$__account, $bucket, $uri, self::$endpoint);
  575. foreach ($metaHeaders as $k => $v) {
  576. $rest->setSwsHeader('x-sws-object-meta-'.$k, $v);
  577. }
  578. foreach ($requestHeaders as $k => $v) {
  579. $rest->setHeader('x-sws-object-meta-'.$k, $v);
  580. }
  581. $rest = $rest->getResponse();
  582. if ($rest->error === false && ($rest->code !== 202))
  583. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  584. if ($rest->error !== false)
  585. {
  586. self::__triggerError(sprintf("Storage::postObject({$bucket}, {$uri}): [%s] %s",
  587. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  588. return false;
  589. }
  590. return true;
  591. }
  592. /**
  593. * 获取一个 Object 的内容
  594. *
  595. * @param string $bucket Bucket 名称
  596. * @param string $uri Object 名称
  597. * @param mixed $saveTo 文件保存到的文件名或者句柄
  598. * @return mixed 返回服务端返回的 response,其中 headers 为 Object 的属性信息,body 为 Object 的内容
  599. */
  600. public static function getObject($bucket, $uri, $saveTo = false)
  601. {
  602. $rest = new StorageRequest('GET', self::$__account, $bucket, $uri, self::$endpoint);
  603. if ($saveTo !== false)
  604. {
  605. if (is_resource($saveTo))
  606. $rest->fp =& $saveTo;
  607. else
  608. if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
  609. $rest->file = realpath($saveTo);
  610. else
  611. $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
  612. }
  613. if ($rest->response->error === false) $rest->getResponse();
  614. if ($rest->response->error === false && $rest->response->code !== 200)
  615. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  616. if ($rest->response->error !== false)
  617. {
  618. self::__triggerError(sprintf("Storage::getObject({$bucket}, {$uri}): [%s] %s",
  619. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  620. return false;
  621. }
  622. return $rest->response;
  623. }
  624. /**
  625. * 获取一个 Object 的信息
  626. *
  627. * @param string $bucket Bucket 名称
  628. * @param string $uri Object 名称
  629. * @param boolean $returnInfo 是否返回 Object 的详细信息
  630. * @return mixed | false
  631. */
  632. public static function getObjectInfo($bucket, $uri, $returnInfo = true)
  633. {
  634. $rest = new StorageRequest('HEAD', self::$__account, $bucket, $uri, self::$endpoint);
  635. $rest = $rest->getResponse();
  636. if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
  637. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  638. if ($rest->error !== false)
  639. {
  640. self::__triggerError(sprintf("Storage::getObjectInfo({$bucket}, {$uri}): [%s] %s",
  641. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  642. return false;
  643. }
  644. return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
  645. }
  646. /**
  647. * 从一个 Bucket 复制一个 Object 到另一个 Bucket
  648. *
  649. * @param string $srcBucket 源 Bucket 名称
  650. * @param string $srcUri 源 Object 名称
  651. * @param string $bucket 目标 Bucket 名称
  652. * @param string $uri 目标 Object 名称
  653. * @param array $metaHeaders Optional array of x-sws-meta-* headers
  654. * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
  655. * @return mixed | false
  656. */
  657. public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $metaHeaders = array(), $requestHeaders = array())
  658. {
  659. $rest = new StorageRequest('PUT', self::$__account, $bucket, $uri, self::$endpoint);
  660. $rest->setHeader('Content-Length', 0);
  661. foreach ($requestHeaders as $h => $v)
  662. strpos($h, 'x-sws-') === 0 ? $rest->setSwsHeader($h, $v) : $rest->setHeader($h, $v);
  663. foreach ($metaHeaders as $h => $v) $rest->setSwsHeader('x-sws-object-meta-'.$h, $v);
  664. $rest->setSwsHeader('x-sws-copy-from', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
  665. $rest->setSwsHeader('x-sws-newest', '1');
  666. $rest = $rest->getResponse();
  667. if ($rest->error === false && $rest->code !== 201)
  668. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  669. if ($rest->error !== false)
  670. {
  671. self::__triggerError(sprintf("Storage::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
  672. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  673. return false;
  674. }
  675. return isset($rest->headers['time'], $rest->headers['hash']) ? $rest->headers : false;
  676. }
  677. /**
  678. * Set object or bucket Access Control Policy
  679. *
  680. * @param string $bucket Bucket name
  681. * @param string $uri Object URI
  682. * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
  683. * @return boolean
  684. */
  685. public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
  686. {
  687. }
  688. /**
  689. * 删除一个 Object
  690. *
  691. * @param string $bucket Bucket 名称
  692. * @param string $uri Object 名称
  693. * @return boolean
  694. */
  695. public static function deleteObject($bucket, $uri)
  696. {
  697. $rest = new StorageRequest('DELETE', self::$__account, $bucket, $uri, self::$endpoint);
  698. $rest = $rest->getResponse();
  699. if ($rest->error === false && $rest->code !== 204)
  700. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  701. if ($rest->error !== false)
  702. {
  703. self::__triggerError(sprintf("Storage::deleteObject(): [%s] %s",
  704. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  705. return false;
  706. }
  707. return true;
  708. }
  709. /**
  710. * 获取一个 Object 的外网直接访问 URL
  711. *
  712. * @param string $bucket Bucket 名称
  713. * @param string $uri Object 名称
  714. * @return string
  715. */
  716. public static function getUrl($bucket, $uri)
  717. {
  718. return "http://" . self::$__account . '-' . $bucket . '.stor.sinaapp.com/' . self::__encodeURI($uri);
  719. }
  720. /**
  721. * 获取一个 Object 的外网临时访问 URL
  722. *
  723. * @param string $bucket Bucket 名称
  724. * @param string $uri Object 名称
  725. * @param string $method Http 请求的方法,有 GET, PUT, DELETE 等
  726. * @param int $seconds 设置这个此 URL 的过期时间,单位是秒
  727. */
  728. public static function getTempUrl($bucket, $uri, $method, $seconds)
  729. {
  730. $expires = (int)(time() + $seconds);
  731. $path = "/v1/SAE_" . self::$__account . "/" . $bucket . "/" . $uri;
  732. $hmac_body = $method . "\n" . $expires . "\n" . $path;
  733. $sig = hash_hmac('sha1', $hmac_body, self::$__secretKey);
  734. $parameter = http_build_query(array("temp_url_sig" => $sig, "temp_url_expires" => $expires));
  735. return "http://" . self::$__account . '-' . $bucket . '.stor.sinaapp.com/' . self::__encodeURI($uri) . "?" . $parameter;
  736. }
  737. /**
  738. * 获取一个 Object 的 CDN 访问 URL
  739. * @param string $bucket Bucket 名称
  740. * @param string $uri Object 名称
  741. * @return string
  742. */
  743. public static function getCdnUrl($bucket, $uri)
  744. {
  745. return "http://". self::$__account . '.sae.sinacn.com/.app-stor/' . $bucket . '/' . self::__encodeURI($uri);
  746. }
  747. /**
  748. * 修改账户的属性(for internal use onley)
  749. *
  750. * @param array $metaHeaders x-sws-account-meta-* header 数组
  751. * @return boolean
  752. */
  753. public static function postAccount($metaHeaders=array())
  754. {
  755. $rest = new StorageRequest('POST', self::$__account, '', '', self::$endpoint);
  756. foreach ($metaHeaders as $k => $v) {
  757. $rest->setSwsHeader('x-sws-account-meta-'.$k, $v);
  758. }
  759. $rest = $rest->getResponse();
  760. if ($rest->error === false && ($rest->code !== 201 && $rest->code !== 204))
  761. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  762. if ($rest->error !== false)
  763. {
  764. self::__triggerError(sprintf("Storage::postAccount({$bucket}, {$acl}): [%s] %s",
  765. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  766. return false;
  767. }
  768. return true;
  769. }
  770. /**
  771. * 获取账户的属性(for internal use only)
  772. *
  773. * @param string $bucket Bucket 名称
  774. * @return mixed
  775. */
  776. public static function getAccountInfo() {
  777. $rest = new StorageRequest('HEAD', self::$__account, '', '', self::$endpoint);
  778. $rest = $rest->getResponse();
  779. if ($rest->error === false && ($rest->code !== 204 && $rest->code !== 404))
  780. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  781. if ($rest->error !== false)
  782. {
  783. self::__triggerError(sprintf("Storage::getAccountInfo({$bucket}): [%s] %s",
  784. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  785. return false;
  786. }
  787. return $rest->code !== 404 ? $rest->headers : false;
  788. }
  789. private static function __getMIMEType(&$file)
  790. {
  791. static $exts = array(
  792. 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
  793. 'png' => 'image/png', 'ico' => 'image/x-icon', 'pdf' => 'application/pdf',
  794. 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'svg' => 'image/svg+xml',
  795. 'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash',
  796. 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
  797. 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
  798. 'bz2' => 'application/x-bzip2', 'rar' => 'application/x-rar-compressed',
  799. 'exe' => 'application/x-msdownload', 'msi' => 'application/x-msdownload',
  800. 'cab' => 'application/vnd.ms-cab-compressed', 'txt' => 'text/plain',
  801. 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
  802. 'css' => 'text/css', 'js' => 'text/javascript',
  803. 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
  804. 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
  805. 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
  806. 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
  807. );
  808. $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
  809. if (isset($exts[$ext])) return $exts[$ext];
  810. // Use fileinfo if available
  811. if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
  812. ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
  813. {
  814. if (($type = finfo_file($finfo, $file)) !== false)
  815. {
  816. // Remove the charset and grab the last content-type
  817. $type = explode(' ', str_replace('; charset=', ';charset=', $type));
  818. $type = array_pop($type);
  819. $type = explode(';', $type);
  820. $type = trim(array_shift($type));
  821. }
  822. finfo_close($finfo);
  823. if ($type !== false && strlen($type) > 0) return $type;
  824. }
  825. return 'application/octet-stream';
  826. }
  827. private static function __encodeURI($uri)
  828. {
  829. return str_replace('%2F', '/', rawurlencode($uri));
  830. }
  831. public static function __getTime()
  832. {
  833. return time() + self::$__timeOffset;
  834. }
  835. public static function __getSignature($string)
  836. {
  837. //var_dump("sign:", $string);
  838. return 'SWS '.self::$__accessKey.':'.self::__getHash($string);
  839. }
  840. private static function __getHash($string)
  841. {
  842. return base64_encode(extension_loaded('hash') ?
  843. hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
  844. (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
  845. pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
  846. (str_repeat(chr(0x36), 64))) . $string)))));
  847. }
  848. }
  849. /**
  850. * @ignore
  851. */
  852. final class StorageRequest
  853. {
  854. private $endpoint;
  855. private $verb;
  856. private $uri;
  857. private $parameters = array();
  858. private $swsHeaders = array();
  859. private $headers = array(
  860. 'Host' => '', 'Date' => '', 'Content-MD5' => ''
  861. );
  862. public $fp = false;
  863. public $size = 0;
  864. public $data = false;
  865. public $response;
  866. function __construct($verb, $account, $bucket = '', $uri = '', $endpoint = DEFAULT_STORAGE_ENDPOINT)
  867. {
  868. $this->endpoint = $endpoint;
  869. $this->verb = $verb;
  870. $this->uri = "/v1/SAE_" . rawurlencode($account);
  871. $this->resource = "/$account";
  872. if ($bucket !== '') {
  873. $this->uri = $this->uri . '/' . rawurlencode($bucket);
  874. $this->resource = $this->resource . '/'. $bucket;
  875. }
  876. if ($uri !== '') {
  877. $this->uri .= '/'.str_replace('%2F', '/', rawurlencode($uri));
  878. $this->resource = $this->resource . '/'. str_replace(' ', '%20', $uri);
  879. }
  880. $this->headers['Host'] = $this->endpoint;
  881. $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
  882. $this->response = new \STDClass;
  883. $this->response->error = false;
  884. $this->response->body = null;
  885. $this->response->headers = array();
  886. }
  887. public function setParameter($key, $value)
  888. {
  889. $this->parameters[$key] = $value;
  890. }
  891. public function setHeader($key, $value)
  892. {
  893. $this->headers[$key] = $value;
  894. }
  895. public function setSwsHeader($key, $value)
  896. {
  897. $this->swsHeaders[$key] = $value;
  898. }
  899. public function getResponse()
  900. {
  901. $query = '';
  902. if (sizeof($this->parameters) > 0)
  903. {
  904. $query = substr($this->uri, -1) !== '?' ? '?' : '&';
  905. foreach ($this->parameters as $var => $value)
  906. if ($value == null || $value == '') $query .= $var.'&';
  907. else $query .= $var.'='.rawurlencode($value).'&';
  908. $query = substr($query, 0, -1);
  909. $this->uri .= $query;
  910. $this->resource .= $query;
  911. }
  912. $url = (Storage::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
  913. //var_dump('uri: ' . $this->uri, 'url: ' . $url, 'resource: ' . $this->resource);
  914. // Basic setup
  915. $curl = curl_init();
  916. curl_setopt($curl, CURLOPT_USERAGENT, 'Storage/php');
  917. if (Storage::$useSSL)
  918. {
  919. // Set protocol version
  920. curl_setopt($curl, CURLOPT_SSLVERSION, Storage::$useSSLVersion);
  921. // SSL Validation can now be optional for those with broken OpenSSL installations
  922. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, Storage::$useSSLValidation ? 2 : 0);
  923. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, Storage::$useSSLValidation ? 1 : 0);
  924. }
  925. curl_setopt($curl, CURLOPT_URL, $url);
  926. if (Storage::$proxy != null && isset(Storage::$proxy['host']))
  927. {
  928. curl_setopt($curl, CURLOPT_PROXY, Storage::$proxy['host']);
  929. curl_setopt($curl, CURLOPT_PROXYTYPE, Storage::$proxy['type']);
  930. if (isset(Storage::$proxy['user'], Storage::$proxy['pass']) && Storage::$proxy['user'] != null && Storage::$proxy['pass'] != null)
  931. curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', Storage::$proxy['user'], Storage::$proxy['pass']));
  932. }
  933. // Headers
  934. $headers = array(); $sae = array();
  935. foreach ($this->swsHeaders as $header => $value)
  936. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  937. foreach ($this->headers as $header => $value)
  938. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  939. foreach ($this->swsHeaders as $header => $value)
  940. if (strlen($value) > 0) $sae[] = strtolower($header).':'.$value;
  941. if (sizeof($sae) > 0)
  942. {
  943. usort($sae, array(&$this, '__sortMetaHeadersCmp'));
  944. $sae= "\n".implode("\n", $sae);
  945. } else $sae = '';
  946. if (Storage::hasAuth())
  947. {
  948. $headers[] = 'Authorization: ' . Storage::__getSignature(
  949. $this->verb."\n".
  950. $this->headers['Date'].$sae."\n".
  951. $this->resource
  952. );
  953. }
  954. //var_dump("headers:", $headers);
  955. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  956. curl_setopt($curl, CURLOPT_HEADER, false);
  957. curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
  958. curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
  959. curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
  960. // Request types
  961. switch ($this->verb)
  962. {
  963. case 'GET': break;
  964. case 'PUT': case 'POST':
  965. if ($this->fp !== false)
  966. {
  967. curl_setopt($curl, CURLOPT_PUT, true);
  968. curl_setopt($curl, CURLOPT_INFILE, $this->fp);
  969. if ($this->size >= 0)
  970. curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
  971. }
  972. elseif ($this->data !== false)
  973. {
  974. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
  975. curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
  976. }
  977. else
  978. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
  979. break;
  980. case 'HEAD':
  981. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
  982. curl_setopt($curl, CURLOPT_NOBODY, true);
  983. break;
  984. case 'DELETE':
  985. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
  986. break;
  987. default: break;
  988. }
  989. // Execute, grab errors
  990. if (curl_exec($curl))
  991. $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  992. else
  993. $this->response->error = array(
  994. 'code' => curl_errno($curl),
  995. 'message' => curl_error($curl),
  996. );
  997. @curl_close($curl);
  998. // Clean up file resources
  999. if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
  1000. //var_dump("response:", $this->response);
  1001. return $this->response;
  1002. }
  1003. private function __sortMetaHeadersCmp($a, $b)
  1004. {
  1005. $lenA = strpos($a, ':');
  1006. $lenB = strpos($b, ':');
  1007. $minLen = min($lenA, $lenB);
  1008. $ncmp = strncmp($a, $b, $minLen);
  1009. if ($lenA == $lenB) return $ncmp;
  1010. if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
  1011. return $ncmp;
  1012. }
  1013. private function __responseWriteCallback(&$curl, &$data)
  1014. {
  1015. if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
  1016. return fwrite($this->fp, $data);
  1017. else
  1018. $this->response->body .= $data;
  1019. return strlen($data);
  1020. }
  1021. private function __responseHeaderCallback($curl, $data)
  1022. {
  1023. if (($strlen = strlen($data)) <= 2) return $strlen;
  1024. if (substr($data, 0, 4) == 'HTTP')
  1025. $this->response->code = (int)substr($data, 9, 3);
  1026. else
  1027. {
  1028. $data = trim($data);
  1029. if (strpos($data, ': ') === false) return $strlen;
  1030. list($header, $value) = explode(': ', $data, 2);
  1031. if ($header == 'Last-Modified')
  1032. $this->response->headers['time'] = strtotime($value);
  1033. elseif ($header == 'Date')
  1034. $this->response->headers['date'] = strtotime($value);
  1035. elseif ($header == 'Content-Length')
  1036. $this->response->headers['size'] = (int)$value;
  1037. elseif ($header == 'Content-Type')
  1038. $this->response->headers['type'] = $value;
  1039. elseif ($header == 'ETag' || $header == 'Etag')
  1040. $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
  1041. elseif (preg_match('/^x-sws-(?:account|container|object)-read$/i', $header))
  1042. $this->response->headers['acl'] = $value;
  1043. elseif (preg_match('/^x-sws-(?:account|container|object)-meta-(.*)$/i', $header))
  1044. $this->response->headers[strtolower($header)] = $value;
  1045. elseif (preg_match('/^x-sws-(?:account|container|object)-(.*)$/i', $header, $m))
  1046. $this->response->headers[strtolower($m[1])] = $value;
  1047. }
  1048. return $strlen;
  1049. }
  1050. }
  1051. /**
  1052. * Storage 异常类
  1053. */
  1054. class StorageException extends \Exception {
  1055. /**
  1056. * 构造函数
  1057. *
  1058. * @param string $message 异常信息
  1059. * @param string $file 抛出异常的文件
  1060. * @param string $line 抛出异常的代码行
  1061. * @param int $code 异常码
  1062. */
  1063. function __construct($message, $file, $line, $code = 0)
  1064. {
  1065. parent::__construct($message, $code);
  1066. $this->file = $file;
  1067. $this->line = $line;
  1068. }
  1069. }
  1070. /**
  1071. * A PHP wrapper for Storage
  1072. *
  1073. * @ignore
  1074. */
  1075. final class StorageWrapper extends Storage {
  1076. private $position = 0, $mode = '', $buffer;
  1077. public function url_stat($path, $flags) {
  1078. self::__getURL($path);
  1079. return (($info = self::getObjectInfo($this->url['host'], $this->url['path'])) !== false) ?
  1080. array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']) : false;
  1081. }
  1082. public function unlink($path) {
  1083. self::__getURL($path);
  1084. return self::deleteObject($this->url['host'], $this->url['path']);
  1085. }
  1086. public function mkdir($path, $mode, $options) {
  1087. self::__getURL($path);
  1088. return self::putBucket($this->url['host'], self::__translateMode($mode));
  1089. }
  1090. public function rmdir($path) {
  1091. self::__getURL($path);
  1092. return self::deleteBucket($this->url['host']);
  1093. }
  1094. public function dir_opendir($path, $options) {
  1095. self::__getURL($path);
  1096. if (($contents = self::getBucket($this->url['host'], $this->url['path'])) !== false) {
  1097. $pathlen = strlen($this->url['path']);
  1098. if (substr($this->url['path'], -1) == '/') $pathlen++;
  1099. $this->buffer = array();
  1100. foreach ($contents as $file) {
  1101. if ($pathlen > 0) $file['name'] = substr($file['name'], $pathlen);
  1102. $this->buffer[] = $file;
  1103. }
  1104. return true;
  1105. }
  1106. return false;
  1107. }
  1108. public function dir_readdir() {
  1109. return (isset($this->buffer[$this->position])) ? $this->buffer[$this->position++]['name'] : false;
  1110. }
  1111. public function dir_rewinddir() {
  1112. $this->position = 0;
  1113. }
  1114. public function dir_closedir() {
  1115. $this->position = 0;
  1116. unset($this->buffer);
  1117. }
  1118. public function stream_close() {
  1119. if ($this->mode == 'w') {
  1120. self::putObject($this->buffer, $this->url['host'], $this->url['path']);
  1121. }
  1122. $this->position = 0;
  1123. unset($this->buffer);
  1124. }
  1125. public function stream_stat() {
  1126. if (is_object($this->buffer) && isset($this->buffer->headers))
  1127. return array(
  1128. 'size' => $this->buffer->headers['size'],
  1129. 'mtime' => $this->buffer->headers['time'],
  1130. 'ctime' => $this->buffer->headers['time']
  1131. );
  1132. elseif (($info = self::getObjectInfo($this->url['host'], $this->url['path'])) !== false)
  1133. return array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']);
  1134. return false;
  1135. }
  1136. public function stream_flush() {
  1137. $this->position = 0;
  1138. return true;
  1139. }
  1140. public function stream_open($path, $mode, $options, &$opened_path) {
  1141. if (!in_array($mode, array('r', 'rb', 'w', 'wb'))) return false; // Mode not supported
  1142. $this->mode = substr($mode, 0, 1);
  1143. self::__getURL($path);
  1144. $this->position = 0;
  1145. if ($this->mode == 'r') {
  1146. if (($this->buffer = self::getObject($this->url['host'], $this->url['path'])) !== false) {
  1147. if (is_object($this->buffer->body)) $this->buffer->body = (string)$this->buffer->body;
  1148. } else return false;
  1149. }
  1150. return true;
  1151. }
  1152. public function stream_read($count) {
  1153. if ($this->mode !== 'r' && $this->buffer !== false) return false;
  1154. $data = substr(is_object($this->buffer) ? $this->buffer->body : $this->buffer, $this->position, $count);
  1155. $this->position += strlen($data);
  1156. return $data;
  1157. }
  1158. public function stream_write($data) {
  1159. if ($this->mode !== 'w') return 0;
  1160. $left = substr($this->buffer, 0, $this->position);
  1161. $right = substr($this->buffer, $this->position + strlen($data));
  1162. $this->buffer = $left . $data . $right;
  1163. $this->position += strlen($data);
  1164. return strlen($data);
  1165. }
  1166. public function stream_tell() {
  1167. return $this->position;
  1168. }
  1169. public function stream_eof() {
  1170. return $this->position >= strlen(is_object($this->buffer) ? $this->buffer->body : $this->buffer);
  1171. }
  1172. public function stream_seek($offset, $whence) {
  1173. switch ($whence) {
  1174. case SEEK_SET:
  1175. if ($offset < strlen($this->buffer->body) && $offset >= 0) {
  1176. $this->position = $offset;
  1177. return true;
  1178. } else return false;
  1179. break;
  1180. case SEEK_CUR:
  1181. if ($offset >= 0) {
  1182. $this->position += $offset;
  1183. return true;
  1184. } else return false;
  1185. break;
  1186. case SEEK_END:
  1187. $bytes = strlen($this->buffer->body);
  1188. if ($bytes + $offset >= 0) {
  1189. $this->position = $bytes + $offset;
  1190. return true;
  1191. } else return false;
  1192. break;
  1193. default: return false;
  1194. }
  1195. }
  1196. private function __getURL($path) {
  1197. $this->url = parse_url($path);
  1198. if (!isset($this->url['scheme']) || $this->url['scheme'] !== 'storage') return $this->url;
  1199. if (isset($this->url['user'], $this->url['pass'])) self::setAuth($this->url['user'], $this->url['pass']);
  1200. $this->url['path'] = isset($this->url['path']) ? substr($this->url['path'], 1) : '';
  1201. }
  1202. private function __translateMode($mode) {
  1203. $acl = self::ACL_PRIVATE;
  1204. if (($mode & 0x0020) || ($mode & 0x0004))
  1205. $acl = self::ACL_PUBLIC_READ;
  1206. // You probably don't want to enable public write access
  1207. if (($mode & 0x0010) || ($mode & 0x0008) || ($mode & 0x0002) || ($mode & 0x0001))
  1208. $acl = self::ACL_PUBLIC_READ; //$acl = self::ACL_PUBLIC_READ_WRITE;
  1209. return $acl;
  1210. }
  1211. } stream_wrapper_register('storage', 'sinacloud\sae\StorageWrapper');