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