COSAPI_PARAMS_ERROR, 'message' => 'file ' . $srcPath .' not exists', 'data' => array() ); } $dstPath = self::normalizerPath($dstPath, false); //文件大于20M则使用分片传输 if (filesize($srcPath) < self::MAX_UNSLICE_FILE_SIZE ) { return self::uploadFile($bucket, $srcPath, $dstPath,$appid,$secretId,$secretKey,$bizAttr, $insertOnly); } else { $sliceSize = self::getSliceSize($sliceSize); return self::uploadBySlicing($bucket,$appid,$secretId,$secretKey,$srcPath, $dstPath, $bizAttr, $sliceSize, $insertOnly); } } /* * 创建目录 * @param string $bucket bucket名称 * @param string $folder 目录路径 * @param string $bizAttr 目录属性 */ public static function createFolder($bucket, $folder, $bizAttr = null) { if (!self::isValidPath($folder)) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'folder ' . $path . ' is not a valid folder name', 'data' => array() ); } $folder = self::normalizerPath($folder, True); $folder = self::cosUrlEncode($folder); $expired = time() + self::EXPIRED_SECONDS; $url = self::generateResUrl($bucket, $folder); $signature = Auth::createReusableSignature($expired, $bucket); $data = array( 'op' => 'create', 'biz_attr' => (isset($bizAttr) ? $bizAttr : ''), ); $data = json_encode($data); $req = array( 'url' => $url, 'method' => 'post', 'timeout' => self::$timeout, 'data' => $data, 'header' => array( 'Authorization: ' . $signature, 'Content-Type: application/json', ), ); return self::sendRequest($req); } /* * 目录列表 * @param string $bucket bucket名称 * @param string $path 目录路径,sdk会补齐末尾的 '/' * @param int $num 拉取的总数 * @param string $pattern eListBoth,ListDirOnly,eListFileOnly 默认both * @param int $order 默认正序(=0), 填1为反序, * @param string $offset 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 */ public static function listFolder( $bucket, $folder, $num = 20, $pattern = 'eListBoth', $order = 0, $context = null) { $folder = self::normalizerPath($folder, True); return self::listBase($bucket, $folder, $num, $pattern, $order, $context); } /* * 目录列表(前缀搜索) * @param string $bucket bucket名称 * @param string $prefix 列出含此前缀的所有文件 * @param int $num 拉取的总数 * @param string $pattern eListBoth(默认),ListDirOnly,eListFileOnly * @param int $order 默认正序(=0), 填1为反序, * @param string $offset 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 */ public static function prefixSearch( $bucket, $prefix, $num = 20, $pattern = 'eListBoth', $order = 0, $context = null) { $path = self::normalizerPath($prefix); return self::listBase($bucket, $prefix, $num, $pattern, $order, $context); } /* * 目录更新 * @param string $bucket bucket名称 * @param string $folder 文件夹路径,SDK会补齐末尾的 '/' * @param string $bizAttr 目录属性 */ public static function updateFolder($bucket, $folder, $bizAttr = null) { $folder = self::normalizerPath($folder, True); return self::updateBase($bucket, $folder, $bizAttr); } /* * 查询目录信息 * @param string $bucket bucket名称 * @param string $folder 目录路径 */ public static function statFolder($bucket, $folder) { $folder = self::normalizerPath($folder, True); return self::statBase($bucket, $folder); } /* * 删除目录 * @param string $bucket bucket名称 * @param string $folder 目录路径 * 注意不能删除bucket下根目录/ */ public static function delFolder($bucket, $folder) { if (empty($bucket) || empty($folder)) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'bucket or path is empty'); } $folder = self::normalizerPath($folder, True); return self::delBase($bucket, $folder); } /* * 更新文件 * @param string $bucket bucket名称 * @param string $path 文件路径 * @param string $authority: eInvalid(继承Bucket的读写权限)/eWRPrivate(私有读写)/eWPrivateRPublic(公有读私有写) * @param array $customer_headers_array 携带的用户自定义头域,包括 * 'Cache-Control' => '*' * 'Content-Type' => '*' * 'Content-Disposition' => '*' * 'Content-Language' => '*' * 'x-cos-meta-自定义内容' => '*' */ public static function update($bucket, $path, $bizAttr = null, $authority=null,$customer_headers_array=null) { $path = self::normalizerPath($path); return self::updateBase($bucket, $path, $bizAttr, $authority, $customer_headers_array); } /* * 查询文件信息 * @param string $bucket bucket名称 * @param string $path 文件路径 */ public static function stat($bucket, $path) { $path = self::normalizerPath($path); return self::statBase($bucket, $path); } /* * 删除文件 * @param string $bucket * @param string $path 文件路径 */ public static function delFile($bucket, $path,$appId,$secretId,$secretKey) { if (empty($bucket) || empty($path)) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'path is empty'); } $path = self::normalizerPath($path); return self::delBase($bucket, $path,$appId,$secretId,$secretKey); } /** * 内部方法, 上传文件 * @param string $bucket bucket名称 * @param string $srcPath 本地文件路径 * @param string $dstPath 上传的文件路径 * @param string $bizAttr 文件属性 * @param int $insertOnly 是否覆盖同名文件:0 覆盖,1:不覆盖 * @return [type] [description] */ private static function uploadFile($bucket, $srcPath, $dstPath,$appid,$secretId,$secretKey,$bizAttr = null, $insertOnly = null) { $srcPath = realpath($srcPath); $dstPath = self::cosUrlEncode($dstPath); if (filesize($srcPath) >= self::MAX_UNSLICE_FILE_SIZE ) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'file '.$srcPath.' larger then 20M, please use uploadBySlicing interface', 'data' => array() ); } $expired = time() + self::EXPIRED_SECONDS; $url = self::generateResUrl($bucket, $dstPath,$appid); $signature = Auth::createReusableSignature($expired, $bucket,$appid,$secretId,$secretKey); $fileSha = hash_file('sha1', $srcPath); $data = array( 'op' => 'upload', 'sha' => $fileSha, 'biz_attr' => (isset($bizAttr) ? $bizAttr : ''), ); $data['filecontent'] = file_get_contents($srcPath); if (isset($insertOnly) && strlen($insertOnly) > 0) { $data['insertOnly'] = (($insertOnly == 0 || $insertOnly == '0' ) ? 0 : 1); } $req = array( 'url' => $url, 'method' => 'post', 'timeout' => self::$timeout, 'data' => $data, 'header' => array( 'Authorization: ' . $signature, ), ); return self::sendRequest($req); } /** * 内部方法,上传文件 * @param string $bucket bucket名称 * @param string $srcPath 本地文件路径 * @param string $dstPath 上传的文件路径 * @param string $bizAttr 文件属性 * @param string $sliceSize 分片大小 * @param int $insertOnly 是否覆盖同名文件:0 覆盖,1:不覆盖 * @return [type] [description] */ private static function uploadBySlicing( $bucket,$appid,$secretId,$secretKey,$srcFpath, $dstFpath, $bizAttr=null, $sliceSize=null, $insertOnly=null) { $srcFpath = realpath($srcFpath); $fileSize = filesize($srcFpath); $dstFpath = self::cosUrlEncode($dstFpath); $url = self::generateResUrl($bucket, $dstFpath,$appid); $sliceCount = ceil($fileSize / $sliceSize); // expiration seconds for one slice mutiply by slice count // will be the expired seconds for whole file $expiration = time() + (self::EXPIRED_SECONDS * $sliceCount); if ($expiration >= (time() + 10 * 24 * 60 * 60)) { $expiration = time() + 10 * 24 * 60 * 60; } $signature = Auth::createReusableSignature($expiration, $bucket,$appid,$secretId,$secretKey); $sliceUploading = new SliceUploading(self::$timeout * 1000, self::MAX_RETRY_TIMES); for ($tryCount = 0; $tryCount < self::MAX_RETRY_TIMES; ++$tryCount) { if ($sliceUploading->initUploading( $signature, $srcFpath, $url, $fileSize, $sliceSize, $bizAttr, $insertOnly)) { break; } $errorCode = $sliceUploading->getLastErrorCode(); if ($errorCode === -4019) { // Delete broken file and retry again on _ERROR_FILE_NOT_FINISH_UPLOAD error. Cosapi::delFile($bucket, $dstFpath); continue; } if ($tryCount === self::MAX_RETRY_TIMES - 1) { return array( 'code' => $sliceUploading->getLastErrorCode(), 'message' => $sliceUploading->getLastErrorMessage(), 'request_id' => $sliceUploading->getRequestId(), ); } } if (!$sliceUploading->performUploading()) { return array( 'code' => $sliceUploading->getLastErrorCode(), 'message' => $sliceUploading->getLastErrorMessage(), 'request_id' => $sliceUploading->getRequestId(), ); } if (!$sliceUploading->finishUploading()) { return array( 'code' => $sliceUploading->getLastErrorCode(), 'message' => $sliceUploading->getLastErrorMessage(), 'request_id' => $sliceUploading->getRequestId(), ); } return array( 'code' => 0, 'message' => 'success', 'request_id' => $sliceUploading->getRequestId(), 'data' => array( 'access_url' => $sliceUploading->getAccessUrl(), 'resource_path' => $sliceUploading->getResourcePath(), 'source_url' => $sliceUploading->getSourceUrl(), ), ); } /* * 内部公共函数 * @param string $bucket bucket名称 * @param string $path 文件夹路径 * @param int $num 拉取的总数 * @param string $pattern eListBoth(默认),ListDirOnly,eListFileOnly * @param int $order 默认正序(=0), 填1为反序, * @param string $context 在翻页查询时候用到 */ private static function listBase( $bucket, $path, $num = 20, $pattern = 'eListBoth', $order = 0, $context = null) { $path = self::cosUrlEncode($path); $expired = time() + self::EXPIRED_SECONDS; $url = self::generateResUrl($bucket, $path); $signature = Auth::createReusableSignature($expired, $bucket); $data = array( 'op' => 'list', ); if (self::isPatternValid($pattern) == false) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'parameter pattern invalid', ); } $data['pattern'] = $pattern; if ($order != 0 && $order != 1) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'parameter order invalid', ); } $data['order'] = $order; if ($num < 0 || $num > 199) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'parameter num invalid, num need less then 200', ); } $data['num'] = $num; if (isset($context)) { $data['context'] = $context; } $url = $url . '?' . http_build_query($data); $req = array( 'url' => $url, 'method' => 'get', 'timeout' => self::$timeout, 'header' => array( 'Authorization: ' . $signature, ), ); return self::sendRequest($req); } /* * 内部公共方法(更新文件和更新文件夹) * @param string $bucket bucket名称 * @param string $path 路径 * @param string $bizAttr 文件/目录属性 * @param string $authority: eInvalid/eWRPrivate(私有)/eWPrivateRPublic(公有读写) * @param array $customer_headers_array 携带的用户自定义头域,包括 * 'Cache-Control' => '*' * 'Content-Type' => '*' * 'Content-Disposition' => '*' * 'Content-Language' => '*' * 'x-cos-meta-自定义内容' => '*' */ private static function updateBase( $bucket, $path, $bizAttr = null, $authority = null, $custom_headers_array = null) { $path = self::cosUrlEncode($path); $expired = time() + self::EXPIRED_SECONDS; $url = self::generateResUrl($bucket, $path); $signature = Auth::createNonreusableSignature($bucket, $path); $data = array('op' => 'update'); if (isset($bizAttr)) { $data['biz_attr'] = $bizAttr; } if (isset($authority) && strlen($authority) > 0) { if(self::isAuthorityValid($authority) == false) { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'parameter authority invalid'); } $data['authority'] = $authority; } if (isset($custom_headers_array)) { $data['custom_headers'] = array(); self::add_customer_header($data['custom_headers'], $custom_headers_array); } $data = json_encode($data); $req = array( 'url' => $url, 'method' => 'post', 'timeout' => self::$timeout, 'data' => $data, 'header' => array( 'Authorization: ' . $signature, 'Content-Type: application/json', ), ); return self::sendRequest($req); } /* * 内部方法 * @param string $bucket bucket名称 * @param string $path 文件/目录路径 */ private static function statBase($bucket, $path) { $path = self::cosUrlEncode($path); $expired = time() + self::EXPIRED_SECONDS; $url = self::generateResUrl($bucket, $path); $signature = Auth::createReusableSignature($expired, $bucket); $data = array('op' => 'stat'); $url = $url . '?' . http_build_query($data); $req = array( 'url' => $url, 'method' => 'get', 'timeout' => self::$timeout, 'header' => array( 'Authorization: ' . $signature, ), ); return self::sendRequest($req); } /* * 内部私有方法 * @param string $bucket bucket名称 * @param string $path 文件/目录路径路径 */ private static function delBase($bucket, $path,$appId,$secretId,$secretKey) { if ($path == "/") { return array( 'code' => COSAPI_PARAMS_ERROR, 'message' => 'can not delete bucket using api! go to ' . 'http://console.qcloud.com/cos to operate bucket'); } $path = self::cosUrlEncode($path); $expired = time() + self::EXPIRED_SECONDS; $url = self::generateResUrl($bucket,$path,$appId); $signature = Auth::createNonreusableSignature($bucket,$path,$appId,$secretId,$secretKey); $data = array('op' => 'delete'); $data = json_encode($data); $req = array( 'url' => $url, 'method' => 'post', 'timeout' => self::$timeout, 'data' => $data, 'header' => array( 'Authorization: ' . $signature, 'Content-Type: application/json', ), ); return self::sendRequest($req); } /* * 内部公共方法, 路径编码 * @param string $path 待编码路径 */ private static function cosUrlEncode($path) { return str_replace('%2F', '/', rawurlencode($path)); } /* * 内部公共方法, 构造URL * @param string $bucket * @param string $dstPath */ private static function generateResUrl($bucket, $dstPath,$appid) { $endPoint = Conf::API_COSAPI_END_POINT; $endPoint = str_replace('region', self::$region, $endPoint); return $endPoint . $appid . '/' . $bucket . $dstPath; } /* * 内部公共方法, 发送消息 * @param string $req */ private static function sendRequest($req) { $rsp = HttpClient::sendRequest($req); if ($rsp === false) { return array( 'code' => COSAPI_NETWORK_ERROR, 'message' => 'network error', ); } $info = HttpClient::info(); $ret = json_decode($rsp, true); if ($ret === NULL) { return array( 'code' => COSAPI_NETWORK_ERROR, 'message' => $rsp, 'data' => array() ); } return $ret; } /** * Get slice size. */ private static function getSliceSize($sliceSize) { // Fix slice size to 1MB. return self::SLICE_SIZE_1M; } /* * 内部方法, 规整文件路径 * @param string $path 文件路径 * @param string $isfolder 是否为文件夹 */ private static function normalizerPath($path, $isfolder = False) { if (preg_match('/^\//', $path) == 0) { $path = '/' . $path; } if ($isfolder == True) { if (preg_match('/\/$/', $path) == 0) { $path = $path . '/'; } } // Remove unnecessary slashes. $path = preg_replace('#/+#', '/', $path); return $path; } /** * 判断authority值是否正确 * @param string $authority * @return [type] bool */ private static function isAuthorityValid($authority) { if ($authority == 'eInvalid' || $authority == 'eWRPrivate' || $authority == 'eWPrivateRPublic') { return true; } return false; } /** * 判断pattern值是否正确 * @param string $authority * @return [type] bool */ private static function isPatternValid($pattern) { if ($pattern == 'eListBoth' || $pattern == 'eListDirOnly' || $pattern == 'eListFileOnly') { return true; } return false; } /** * 判断是否符合自定义属性 * @param string $key * @return [type] bool */ private static function isCustomer_header($key) { if ($key == 'Cache-Control' || $key == 'Content-Type' || $key == 'Content-Disposition' || $key == 'Content-Language' || $key == 'Content-Encoding' || substr($key,0,strlen('x-cos-meta-')) == 'x-cos-meta-') { return true; } return false; } /** * 增加自定义属性到data中 * @param array $data * @param array $customer_headers_array * @return [type] void */ private static function add_customer_header(&$data, &$customer_headers_array) { if (count($customer_headers_array) < 1) { return; } foreach($customer_headers_array as $key=>$value) { if(self::isCustomer_header($key)) { $data[$key] = $value; } } } // Check |$path| is a valid file path. // Return true on success, otherwise return false. private static function isValidPath($path) { if (strpos($path, '?') !== false) { return false; } if (strpos($path, '*') !== false) { return false; } if (strpos($path, ':') !== false) { return false; } if (strpos($path, '|') !== false) { return false; } if (strpos($path, '\\') !== false) { return false; } if (strpos($path, '<') !== false) { return false; } if (strpos($path, '>') !== false) { return false; } if (strpos($path, '"') !== false) { return false; } return true; } /** * Copy a file. * @param $bucket bucket name. * @param $srcFpath source file path. * @param $dstFpath destination file path. * @param $overwrite if the destination location is occupied, overwrite it or not? * @return array|mixed. */ public static function copyFile($bucket, $srcFpath, $dstFpath, $overwrite = false) { $url = self::generateResUrl($bucket, $srcFpath); $sign = Auth::createNonreusableSignature($bucket, $srcFpath); $data = array( 'op' => 'copy', 'dest_fileid' => $dstFpath, 'to_over_write' => $overwrite ? 1 : 0, ); $req = array( 'url' => $url, 'method' => 'post', 'timeout' => self::$timeout, 'data' => $data, 'header' => array( 'Authorization: ' . $sign, ), ); return self::sendRequest($req); } /** * Move a file. * @param $bucket bucket name. * @param $srcFpath source file path. * @param $dstFpath destination file path. * @param $overwrite if the destination location is occupied, overwrite it or not? * @return array|mixed. */ public static function moveFile($bucket, $srcFpath, $dstFpath, $overwrite = false) { $url = self::generateResUrl($bucket, $srcFpath); $sign = Auth::createNonreusableSignature($bucket, $srcFpath); $data = array( 'op' => 'move', 'dest_fileid' => $dstFpath, 'to_over_write' => $overwrite ? 1 : 0, ); $req = array( 'url' => $url, 'method' => 'post', 'timeout' => self::$timeout, 'data' => $data, 'header' => array( 'Authorization: ' . $sign, ), ); return self::sendRequest($req); } }