upyun.class.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <?php
  2. class UpYunException extends Exception {/*{{{*/
  3. public function __construct($message, $code, Exception $previous = null) {
  4. parent::__construct($message, $code); // For PHP 5.2.x
  5. }
  6. public function __toString() {
  7. return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
  8. }
  9. }/*}}}*/
  10. class UpYunAuthorizationException extends UpYunException {/*{{{*/
  11. public function __construct($message, $code = 0, Exception $previous = null) {
  12. parent::__construct($message, 401, $previous);
  13. }
  14. }/*}}}*/
  15. class UpYunForbiddenException extends UpYunException {/*{{{*/
  16. public function __construct($message, $code = 0, Exception $previous = null) {
  17. parent::__construct($message, 403, $previous);
  18. }
  19. }/*}}}*/
  20. class UpYunNotFoundException extends UpYunException {/*{{{*/
  21. public function __construct($message, $code = 0, Exception $previous = null) {
  22. parent::__construct($message, 404, $previous);
  23. }
  24. }/*}}}*/
  25. class UpYunNotAcceptableException extends UpYunException {/*{{{*/
  26. public function __construct($message, $code = 0, Exception $previous = null) {
  27. parent::__construct($message, 406, $previous);
  28. }
  29. }/*}}}*/
  30. class UpYunServiceUnavailable extends UpYunException {/*{{{*/
  31. public function __construct($message, $code = 0, Exception $previous = null) {
  32. parent::__construct($message, 503, $previous);
  33. }
  34. }/*}}}*/
  35. class UpYun {
  36. const VERSION = '2.0';
  37. /*{{{*/
  38. const ED_AUTO = 'v0.api.upyun.com';
  39. const ED_TELECOM = 'v1.api.upyun.com';
  40. const ED_CNC = 'v2.api.upyun.com';
  41. const ED_CTT = 'v3.api.upyun.com';
  42. const CONTENT_TYPE = 'Content-Type';
  43. const CONTENT_MD5 = 'Content-MD5';
  44. const CONTENT_SECRET = 'Content-Secret';
  45. // 缩略图
  46. const X_GMKERL_THUMBNAIL = 'x-gmkerl-thumbnail';
  47. const X_GMKERL_TYPE = 'x-gmkerl-type';
  48. const X_GMKERL_VALUE = 'x-gmkerl-value';
  49. const X_GMKERL_QUALITY = 'x­gmkerl-quality';
  50. const X_GMKERL_UNSHARP = 'x­gmkerl-unsharp';
  51. /*}}}*/
  52. private $_bucket_name;
  53. private $_username;
  54. private $_password;
  55. private $_timeout = 30;
  56. /**
  57. * @deprecated
  58. */
  59. private $_content_md5 = NULL;
  60. /**
  61. * @deprecated
  62. */
  63. private $_file_secret = NULL;
  64. /**
  65. * @deprecated
  66. */
  67. private $_file_infos= NULL;
  68. protected $endpoint;
  69. /**
  70. * 初始化 UpYun 存储接口
  71. * @param $bucketname 空间名称
  72. * @param $username 操作员名称
  73. * @param $password 密码
  74. *
  75. * @return object
  76. */
  77. public function __construct($bucketname, $username, $password, $endpoint = NULL, $timeout = 30) {/*{{{*/
  78. $this->_bucketname = $bucketname;
  79. $this->_username = $username;
  80. $this->_password = md5($password);
  81. $this->_timeout = $timeout;
  82. $this->endpoint = is_null($endpoint) ? self::ED_AUTO : $endpoint;
  83. }/*}}}*/
  84. /**
  85. * 获取当前SDK版本号
  86. */
  87. public function version() {
  88. return self::VERSION;
  89. }
  90. /**
  91. * 创建目录
  92. * @param $path 路径
  93. * @param $auto_mkdir 是否自动创建父级目录,最多10层次
  94. *
  95. * @return void
  96. */
  97. public function makeDir($path, $auto_mkdir = false) {/*{{{*/
  98. $headers = array('Folder' => 'true');
  99. if ($auto_mkdir) $headers['Mkdir'] = 'true';
  100. return $this->_do_request('PUT', $path, $headers);
  101. }/*}}}*/
  102. /**
  103. * 删除目录和文件
  104. * @param string $path 路径
  105. *
  106. * @return boolean
  107. */
  108. public function delete($path) {/*{{{*/
  109. return $this->_do_request('DELETE', $path);
  110. }/*}}}*/
  111. /**
  112. * 上传文件
  113. * @param string $path 存储路径
  114. * @param mixed $file 需要上传的文件,可以是文件流或者文件内容
  115. * @param boolean $auto_mkdir 自动创建目录
  116. * @param array $opts 可选参数
  117. */
  118. public function writeFile($path, $file, $auto_mkdir = False, $opts = NULL) {/*{{{*/
  119. if (is_null($opts)) $opts = array();
  120. if (!is_null($this->_content_md5) || !is_null($this->_file_secret)) {
  121. //if (!is_null($this->_content_md5)) array_push($opts, self::CONTENT_MD5 . ": {$this->_content_md5}");
  122. //if (!is_null($this->_file_secret)) array_push($opts, self::CONTENT_SECRET . ": {$this->_file_secret}");
  123. if (!is_null($this->_content_md5)) $opts[self::CONTENT_MD5] = $this->_content_md5;
  124. if (!is_null($this->_file_secret)) $opts[self::CONTENT_SECRET] = $this->_file_secret;
  125. }
  126. // 如果设置了缩略版本或者缩略图类型,则添加默认压缩质量和锐化参数
  127. //if (isset($opts[self::X_GMKERL_THUMBNAIL]) || isset($opts[self::X_GMKERL_TYPE])) {
  128. // if (!isset($opts[self::X_GMKERL_QUALITY])) $opts[self::X_GMKERL_QUALITY] = 95;
  129. // if (!isset($opts[self::X_GMKERL_UNSHARP])) $opts[self::X_GMKERL_UNSHARP] = 'true';
  130. //}
  131. if ($auto_mkdir === True) $opts['Mkdir'] = 'true';
  132. $this->_file_infos = $this->_do_request('PUT', $path, $opts, $file);
  133. return $this->_file_infos;
  134. }/*}}}*/
  135. /**
  136. * 下载文件
  137. * @param string $path 文件路径
  138. * @param mixed $file_handle
  139. *
  140. * @return mixed
  141. */
  142. public function readFile($path, $file_handle = NULL) {/*{{{*/
  143. return $this->_do_request('GET', $path, NULL, NULL, $file_handle);
  144. }/*}}}*/
  145. /**
  146. * 获取目录文件列表
  147. *
  148. * @param string $path 查询路径
  149. *
  150. * @return mixed
  151. */
  152. public function getList($path = '/') {/*{{{*/
  153. $rsp = $this->_do_request('GET', $path);
  154. $list = array();
  155. if ($rsp) {
  156. $rsp = explode("\n", $rsp);
  157. foreach($rsp as $item) {
  158. @list($name, $type, $size, $time) = explode("\t", trim($item));
  159. if (!empty($time)) {
  160. $type = $type == 'N' ? 'file' : 'folder';
  161. }
  162. $item = array(
  163. 'name' => $name,
  164. 'type' => $type,
  165. 'size' => intval($size),
  166. 'time' => intval($time),
  167. );
  168. array_push($list, $item);
  169. }
  170. }
  171. return $list;
  172. }/*}}}*/
  173. /**
  174. * 获取目录空间使用情况
  175. *
  176. * @param string $path 目录路径
  177. *
  178. * @return mixed
  179. */
  180. public function getFolderUsage($path) {/*{{{*/
  181. $rsp = $this->_do_request('GET', $path . '?usage');
  182. return floatval($rsp);
  183. }/*}}}*/
  184. /**
  185. * 获取文件、目录信息
  186. *
  187. * @param string $path 路径
  188. *
  189. * @return mixed
  190. */
  191. public function getFileInfo($path) {/*{{{*/
  192. $rsp = $this->_do_request('HEAD', $path);
  193. return $rsp;
  194. }/*}}}*/
  195. /**
  196. * 连接签名方法
  197. * @param $method 请求方式 {GET, POST, PUT, DELETE}
  198. * return 签名字符串
  199. */
  200. private function sign($method, $uri, $date, $length){/*{{{*/
  201. //$uri = urlencode($uri);
  202. $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->_password}";
  203. return 'UpYun '.$this->_username.':'.md5($sign);
  204. }/*}}}*/
  205. /**
  206. * HTTP REQUEST 封装
  207. * @param string $method HTTP REQUEST方法,包括PUT、POST、GET、OPTIONS、DELETE
  208. * @param string $path 除Bucketname之外的请求路径,包括get参数
  209. * @param array $headers 请求需要的特殊HTTP HEADERS
  210. * @param array $body 需要POST发送的数据
  211. *
  212. * @return mixed
  213. */
  214. protected function _do_request($method, $path, $headers = NULL, $body= NULL, $file_handle= NULL) {/*{{{*/
  215. $uri = "/{$this->_bucketname}{$path}";
  216. $ch = curl_init("http://{$this->endpoint}{$uri}");
  217. $_headers = array('Expect:');
  218. if (!is_null($headers) && is_array($headers)){
  219. foreach($headers as $k => $v) {
  220. array_push($_headers, "{$k}: {$v}");
  221. }
  222. }
  223. $length = 0;
  224. $date = gmdate('D, d M Y H:i:s \G\M\T');
  225. if (!is_null($body)) {
  226. if(is_resource($body)){
  227. fseek($body, 0, SEEK_END);
  228. $length = ftell($body);
  229. fseek($body, 0);
  230. array_push($_headers, "Content-Length: {$length}");
  231. curl_setopt($ch, CURLOPT_INFILE, $body);
  232. curl_setopt($ch, CURLOPT_INFILESIZE, $length);
  233. }
  234. else {
  235. $length = @strlen($body);
  236. array_push($_headers, "Content-Length: {$length}");
  237. curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
  238. }
  239. }
  240. else {
  241. array_push($_headers, "Content-Length: {$length}");
  242. }
  243. array_push($_headers, "Authorization: {$this->sign($method, $uri, $date, $length)}");
  244. array_push($_headers, "Date: {$date}");
  245. curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
  246. curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout);
  247. curl_setopt($ch, CURLOPT_HEADER, 1);
  248. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  249. //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  250. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  251. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  252. if ($method == 'PUT' || $method == 'POST') {
  253. curl_setopt($ch, CURLOPT_POST, 1);
  254. }
  255. else {
  256. curl_setopt($ch, CURLOPT_POST, 0);
  257. }
  258. if ($method == 'GET' && is_resource($file_handle)) {
  259. curl_setopt($ch, CURLOPT_HEADER, 0);
  260. curl_setopt($ch, CURLOPT_FILE, $file_handle);
  261. }
  262. if ($method == 'HEAD') {
  263. curl_setopt($ch, CURLOPT_NOBODY, true);
  264. }
  265. $response = curl_exec($ch);
  266. $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  267. if ($http_code == 0) throw new UpYunException('Connection Failed', $http_code);
  268. curl_close($ch);
  269. $header_string = '';
  270. $body = '';
  271. if ($method == 'GET' && is_resource($file_handle)) {
  272. $header_string = '';
  273. $body = $response;
  274. }
  275. else {
  276. list($header_string, $body) = explode("\r\n\r\n", $response, 2);
  277. }
  278. //var_dump($http_code);
  279. if ($http_code == 200) {
  280. if ($method == 'GET' && is_null($file_handle)) {
  281. return $body;
  282. }
  283. else {
  284. $data = $this->_getHeadersData($header_string);
  285. return count($data) > 0 ? $data : true;
  286. }
  287. //elseif ($method == 'HEAD') {
  288. // //return $this->_get_headers_data(substr($response, 0 , $header_size));
  289. // return $this->_getHeadersData($header_string);
  290. //}
  291. //return True;
  292. }
  293. else {
  294. $message = $this->_getErrorMessage($header_string);
  295. if (is_null($message) && $method == 'GET' && is_resource($file_handle)) {
  296. $message = 'File Not Found';
  297. }
  298. switch($http_code) {
  299. case 401:
  300. throw new UpYunAuthorizationException($message);
  301. break;
  302. case 403:
  303. throw new UpYunForbiddenException($message);
  304. break;
  305. case 404:
  306. throw new UpYunNotFoundException($message);
  307. break;
  308. case 406:
  309. throw new UpYunNotAcceptableException($message);
  310. break;
  311. case 503:
  312. throw new UpYunServiceUnavailable($message);
  313. break;
  314. default:
  315. throw new UpYunException($message, $http_code);
  316. }
  317. }
  318. }/*}}}*/
  319. /**
  320. * 处理HTTP HEADERS中返回的自定义数据
  321. *
  322. * @param string $text header字符串
  323. *
  324. * @return array
  325. */
  326. private function _getHeadersData($text) {/*{{{*/
  327. $headers = explode("\r\n", $text);
  328. $items = array();
  329. foreach($headers as $header) {
  330. $header = trim($header);
  331. if(strpos($header, 'x-upyun') !== False){
  332. list($k, $v) = explode(':', $header);
  333. $items[trim($k)] = in_array(substr($k,8,5), array('width','heigh','frame')) ? intval($v) : trim($v);
  334. }
  335. }
  336. return $items;
  337. }/*}}}*/
  338. /**
  339. * 获取返回的错误信息
  340. *
  341. * @param string $header_string
  342. *
  343. * @return mixed
  344. */
  345. private function _getErrorMessage($header_string) {
  346. list($status, $stash) = explode("\r\n", $header_string, 2);
  347. list($v, $code, $message) = explode(" ", $status, 3);
  348. return $message;
  349. }
  350. /**
  351. * 删除目录
  352. * @deprecated
  353. * @param $path 路径
  354. *
  355. * @return void
  356. */
  357. public function rmDir($path) {/*{{{*/
  358. $this->_do_request('DELETE', $path);
  359. }/*}}}*/
  360. /**
  361. * 删除文件
  362. *
  363. * @deprecated
  364. * @param string $path 要删除的文件路径
  365. *
  366. * @return boolean
  367. */
  368. public function deleteFile($path) {/*{{{*/
  369. $rsp = $this->_do_request('DELETE', $path);
  370. }/*}}}*/
  371. /**
  372. * 获取目录文件列表
  373. * @deprecated
  374. *
  375. * @param string $path 要获取列表的目录
  376. *
  377. * @return array
  378. */
  379. public function readDir($path) {/*{{{*/
  380. return $this->getList($path);
  381. }/*}}}*/
  382. /**
  383. * 获取空间使用情况
  384. *
  385. * @deprecated 推荐直接使用 getFolderUsage('/')来获取
  386. * @return mixed
  387. */
  388. public function getBucketUsage() {/*{{{*/
  389. return $this->getFolderUsage('/');
  390. }/*}}}*/
  391. /**
  392. * 获取文件信息
  393. *
  394. * #deprecated
  395. * @param $file 文件路径(包含文件名)
  396. * return array('type'=> file | folder, 'size'=> file size, 'date'=> unix time) 或 null
  397. */
  398. //public function getFileInfo($file){/*{{{*/
  399. // $result = $this->head($file);
  400. // if(is_null($r))return null;
  401. // return array('type'=> $this->tmp_infos['x-upyun-file-type'], 'size'=> @intval($this->tmp_infos['x-upyun-file-size']), 'date'=> @intval($this->tmp_infos['x-upyun-file-date']));
  402. //}/*}}}*/
  403. /**
  404. * 切换 API 接口的域名
  405. *
  406. * @deprecated
  407. * @param $domain {默然 v0.api.upyun.com 自动识别, v1.api.upyun.com 电信, v2.api.upyun.com 联通, v3.api.upyun.com 移动}
  408. * return null;
  409. */
  410. public function setApiDomain($domain){/*{{{*/
  411. $this->endpoint = $domain;
  412. }/*}}}*/
  413. /**
  414. * 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误)
  415. *
  416. * @deprecated
  417. * @param $str (文件 MD5 校验码)
  418. * return null;
  419. */
  420. public function setContentMD5($str){/*{{{*/
  421. $this->_content_md5 = $str;
  422. }/*}}}*/
  423. /**
  424. * 设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问)
  425. * 如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac
  426. *
  427. * @deprecated
  428. * @param $str (文件 MD5 校验码)
  429. * return null;
  430. */
  431. public function setFileSecret($str){/*{{{*/
  432. $this->_file_secret = $str;
  433. }/*}}}*/
  434. /**
  435. * @deprecated
  436. * 获取上传文件后的信息(仅图片空间有返回数据)
  437. * @param $key 信息字段名(x-upyun-width、x-upyun-height、x-upyun-frames、x-upyun-file-type)
  438. * return value or NULL
  439. */
  440. public function getWritedFileInfo($key){/*{{{*/
  441. if(!isset($this->_file_infos))return NULL;
  442. return $this->_file_infos[$key];
  443. }/*}}}*/
  444. }