├── .htaccess ├── README.md ├── auth.php ├── chunk.php ├── chunks └── .gitignore ├── config.php ├── file.php ├── index.php ├── lib └── Thumb.php ├── manager.php ├── mkfile.php ├── object.php ├── thumbs └── .gitignore ├── upload.php └── uploads └── .gitignore /.htaccess: -------------------------------------------------------------------------------- 1 | SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudreve-remote-server -------------------------------------------------------------------------------- /auth.php: -------------------------------------------------------------------------------- 1 | sign = $sign; 9 | } 10 | 11 | public function check($policy,$method){ 12 | $signingKey = hash_hmac("sha256",json_encode($policy),$method.ACCESS_KEY); 13 | return ($signingKey==$this->sign); 14 | } 15 | 16 | public function sign($content,$method = null){ 17 | return hash_hmac("sha256",base64_encode(json_encode($content)),$method.ACCESS_KEY); 18 | } 19 | 20 | public function checkPost(){ 21 | $signingKey = hash_hmac("sha256",$_POST["object"],$_POST["action"].ACCESS_KEY); 22 | if($signingKey != $this->sign){ 23 | die("auth failed"); 24 | } 25 | } 26 | 27 | public function checkGET(){ 28 | $urlNow = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; 29 | $signingKey = hash_hmac("sha256",str_replace("&auth=".$this->sign,"",$urlNow),"GET".ACCESS_KEY); 30 | if($signingKey != $this->sign || $_GET["expires"] < time()){ 31 | die("auth failed"); 32 | } 33 | } 34 | 35 | } 36 | 37 | ?> -------------------------------------------------------------------------------- /chunk.php: -------------------------------------------------------------------------------- 1 | chunkInit(); 7 | ?> -------------------------------------------------------------------------------- /chunks/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /file.php: -------------------------------------------------------------------------------- 1 | thumb($picInfo[1], $picInfo[0]); 17 | if(!is_dir(dirname("thumbs/".$fname))){ 18 | mkdir(dirname("thumbs/".$fname),0777,true); 19 | } 20 | $thumbImg->out("thumbs/".$fname."_thumb"); 21 | self::outputThumb("thumbs/".$fname."_thumb"); 22 | } 23 | 24 | static function outputThumb($path){ 25 | ob_end_clean(); 26 | header("Cache-Control: max-age=10800"); 27 | header('Content-Type: '.self::getMimetype($path)); 28 | $fileObj = fopen($path,"r"); 29 | echo fread($fileObj,filesize($path)); 30 | fclose($file); 31 | } 32 | 33 | static function update($fname,$content){ 34 | $filePath = 'uploads/' . $fname; 35 | file_put_contents($filePath, ""); 36 | file_put_contents($filePath, $content); 37 | } 38 | 39 | static function getThumbSize($width,$height){ 40 | $rate = $width/$height; 41 | $maxWidth = 90; 42 | $maxHeight = 39; 43 | $changeWidth = 39*$rate; 44 | $changeHeight = 90/$rate; 45 | if($changeWidth>=$maxWidth){ 46 | return [(int)$changeHeight,90]; 47 | } 48 | return [39,(int)$changeWidth]; 49 | } 50 | 51 | static function delete($fileList){ 52 | $fileList = json_decode(base64_decode($fileList),true); 53 | foreach ($fileList as $key => $value) { 54 | @unlink("uploads/".$value); 55 | if(file_exists('thumbs/'.$value."_thumb")){ 56 | @unlink('thumbs/'.$value."_thumb"); 57 | } 58 | } 59 | } 60 | 61 | static function preview($fname,$download = false){ 62 | $filePath = 'uploads/' . $fname; 63 | @set_time_limit(0); 64 | session_write_close(); 65 | $file_size = filesize($filePath); 66 | $ranges = self::getRange($file_size); 67 | if($ranges!=null){ 68 | header('HTTP/1.1 206 Partial Content'); 69 | header('Accept-Ranges:bytes'); 70 | header(sprintf('content-length:%u',$ranges['end']-$ranges['start'])); 71 | header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end']-1, $file_size)); 72 | } 73 | if($download){ 74 | header('Cache-control: private'); 75 | header('Content-Type: application/octet-stream'); 76 | header('Content-Length: '.filesize($filePath)); 77 | header('Content-Disposition: filename='.str_replace(",","",isset($_GET["attaname"]) ? urldecode($_GET["attaname"]) : "download")); 78 | ob_flush(); 79 | flush(); 80 | } 81 | if(file_exists($filePath)){ 82 | if(!$download){ 83 | header('Content-Type: '.self::getMimetype($filePath)); 84 | ob_flush(); 85 | flush(); 86 | } 87 | $fileObj = fopen($filePath,"rb"); 88 | fseek($fileObj, sprintf('%u', $ranges['start'])); 89 | while(!feof($fileObj)){ 90 | echo fread($fileObj,10240); 91 | ob_flush(); 92 | flush(); 93 | } 94 | fclose($fileObj); 95 | } 96 | } 97 | 98 | static function getMimetype($path){ 99 | $finfoObj = finfo_open(FILEINFO_MIME); 100 | $mimetype = finfo_file($finfoObj, $path); 101 | finfo_close($finfoObj); 102 | return $mimetype; 103 | } 104 | 105 | static function getRange($file_size){ 106 | if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])){ 107 | $range = $_SERVER['HTTP_RANGE']; 108 | $range = preg_replace('/[\s|,].*/', '', $range); 109 | $range = explode('-', substr($range, 6)); 110 | if(count($range)<2){ 111 | $range[1] = $file_size; 112 | } 113 | $range = array_combine(array('start','end'), $range); 114 | if(empty($range['start'])){ 115 | $range['start'] = 0; 116 | } 117 | if(empty($range['end'])){ 118 | $range['end'] = $file_size; 119 | } 120 | return $range; 121 | } 122 | return null; 123 | } 124 | 125 | static function clean(){ 126 | @unlink("chunks"); 127 | @mkdir("chunks"); 128 | } 129 | 130 | } 131 | ?> -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | init(); 7 | ?> -------------------------------------------------------------------------------- /lib/Thumb.php: -------------------------------------------------------------------------------- 1 | file = $_file; 14 | list($this->width, $this->height, $this->img_type) = getimagesize($this->file); 15 | $this->img = $this->getFromImg($this->file, $this->img_type); 16 | } 17 | //缩略图(固定长高容器,图像等比例,扩容填充,裁剪)[固定了大小,不失真,不变形] 18 | public function thumb($new_width = 0,$new_height = 0) { 19 | 20 | if (empty($new_width) && empty($new_height)) { 21 | $new_width = $this->width; 22 | $new_height = $this->height; 23 | } 24 | 25 | if (!is_numeric($new_width) || !is_numeric($new_height)) { 26 | $new_width = $this->width; 27 | $new_height = $this->height; 28 | } 29 | 30 | //创建一个容器 31 | $_n_w = $new_width; 32 | $_n_h = $new_height; 33 | 34 | //创建裁剪点 35 | $_cut_width = 0; 36 | $_cut_height = 0; 37 | 38 | if ($this->width < $this->height) { 39 | $new_width = ($new_height / $this->height) * $this->width; 40 | } else { 41 | $new_height = ($new_width / $this->width) * $this->height; 42 | } 43 | if ($new_width < $_n_w) { //如果新高度小于新容器高度 44 | $r = $_n_w / $new_width; //按长度求出等比例因子 45 | $new_width *= $r; //扩展填充后的长度 46 | $new_height *= $r; //扩展填充后的高度 47 | $_cut_height = ($new_height - $_n_h) / 2; //求出裁剪点的高度 48 | } 49 | 50 | if ($new_height < $_n_h) { //如果新高度小于容器高度 51 | $r = $_n_h / $new_height; //按高度求出等比例因子 52 | $new_width *= $r; //扩展填充后的长度 53 | $new_height *= $r; //扩展填充后的高度 54 | $_cut_width = ($new_width - $_n_w) / 2; //求出裁剪点的长度 55 | } 56 | 57 | $this->new = imagecreatetruecolor($_n_w,$_n_h); 58 | imagecopyresampled($this->new,$this->img,0,0,$_cut_width,$_cut_height,$new_width,$new_height,$this->width,$this->height); 59 | } 60 | 61 | //加载图片,各种类型,返回图片的资源句柄 62 | private function getFromImg($_file, $_type) { 63 | switch ($_type) { 64 | case 1 : 65 | $img = imagecreatefromgif($_file); 66 | break; 67 | case 2 : 68 | $img = imagecreatefromjpeg($_file); 69 | break; 70 | case 3 : 71 | $img = imagecreatefrompng($_file); 72 | break; 73 | default: 74 | $img = ''; 75 | } 76 | return $img; 77 | } 78 | 79 | //图像输出 80 | public function out($name) { 81 | imagepng($this->new,$name);//第二个参数为新生成的图片名 82 | imagedestroy($this->img); 83 | imagedestroy($this->new); 84 | } 85 | } 86 | ?> -------------------------------------------------------------------------------- /manager.php: -------------------------------------------------------------------------------- 1 | checkPost(); 8 | switch ($_POST["action"]) { 9 | case 'DELETE': 10 | File::delete($_POST["object"]); 11 | break; 12 | 13 | case 'UPDATE': 14 | $reqInfo = json_decode(base64_decode($_POST["object"]),true); 15 | File::update($reqInfo["fname"],$reqInfo["content"]); 16 | break; 17 | default: 18 | # code... 19 | break; 20 | } 21 | 22 | ?> -------------------------------------------------------------------------------- /mkfile.php: -------------------------------------------------------------------------------- 1 | mkfile(); 10 | } 11 | ?> -------------------------------------------------------------------------------- /object.php: -------------------------------------------------------------------------------- 1 | checkGet(); 9 | switch ($_GET["action"]) { 10 | case 'preview': 11 | File::preview(urldecode($_GET["name"])); 12 | break; 13 | case 'download': 14 | File::preview(urldecode($_GET["name"]),true); 15 | break; 16 | case 'thumb': 17 | File::thumb(urldecode($_GET["name"]),true); 18 | break; 19 | case 'clean': 20 | File::clean(); 21 | break; 22 | default: 23 | echo "none"; 24 | break; 25 | } 26 | 27 | ?> -------------------------------------------------------------------------------- /thumbs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /upload.php: -------------------------------------------------------------------------------- 1 | showMsg = $msg; 21 | $this->policy = isset($_POST["token"]) ? $_POST["token"] : $this->quit(); 22 | $this->policy = empty($this->policy) ? $_SERVER['HTTP_AUTHORIZATION'] :$this->policy; 23 | } 24 | 25 | public function chunkInit(){ 26 | header("access-control-allow-headers:authorization,content-type"); 27 | header("access-control-allow-methods:OPTIONS, HEAD, POST"); 28 | $policyInfo = explode(":", $this->policy); 29 | $this->signKey = $policyInfo[0]; 30 | $this->policy = json_decode(base64_decode($policyInfo[1]),true); 31 | $this->auth = new Auth($this->signKey); 32 | if(!$this->auth->check($this->policy,"UPLOAD") && $_SERVER['REQUEST_METHOD'] !="OPTIONS"){ 33 | self::setError("auth failed."); 34 | } 35 | if($_SERVER['REQUEST_METHOD'] !="OPTIONS"){ 36 | $this->chunkData = file_get_contents("php://input"); 37 | $this->fileSize = strlen($this->chunkData); 38 | if(!$this->validCheck(true)){ 39 | self::setError($this->errMsg); 40 | } 41 | $this->saveChunk(); 42 | } 43 | } 44 | 45 | public function init(){ 46 | header("Content-Type:text/json"); 47 | $policyInfo = explode(":", $this->policy); 48 | $this->signKey = $policyInfo[0]; 49 | $this->policy = json_decode(base64_decode($policyInfo[1]),true); 50 | $this->frontPath = isset($_POST["path"]) ? $_POST["path"] : ""; 51 | $this->auth = new Auth($this->signKey); 52 | if(!$this->auth->check($this->policy,"UPLOAD")){ 53 | self::setError("auth failed."); 54 | } 55 | $this->fileSize = $_FILES["file"]["size"]; 56 | if(!$this->validCheck(false)){ 57 | self::setError($this->errMsg); 58 | } 59 | $this->saveFile(); 60 | } 61 | 62 | private function quit(){ 63 | if($this->showMsg){ 64 | die ("Cloudreve Remote Server"); 65 | } 66 | } 67 | 68 | private function validCheck($chunk=false){ 69 | if($chunk && $this->fileSize > 4194350){ 70 | $this->errMsg = "File is to large."; 71 | return false; 72 | } 73 | if($this->fileSize > $this->policy["fsizeLimit"]){ 74 | $this->errMsg = "文件太大"; 75 | return false; 76 | } 77 | return true; 78 | 79 | } 80 | 81 | public function saveChunk(){ 82 | $this->fileName=md5(uniqid()); 83 | if(!is_dir("chunks/" . $this->policy["uid"])){ 84 | mkdir("chunks/" . $this->policy["uid"],0777,true); 85 | } 86 | if(function_exists("scandir")){ 87 | $chunkList = scandir("chunks/" . $this->policy["uid"]); 88 | if(count($chunkList)*4194304 >= CHUNK_BUFFER_TIMES*$this->policy["fsizeLimit"]){ 89 | self::setError("分片缓冲区已满,请等待系统回收"); 90 | } 91 | } 92 | if(!file_put_contents("chunks/" .$this->policy["uid"] ."/".$this->fileName,$this->chunkData)){ 93 | self::setError("文件转移失败"); 94 | }; 95 | echo json_encode(["ctx" => $this->fileName]); 96 | } 97 | 98 | public function saveFile(){ 99 | $this->fileName = str_replace('$(fname)', $_FILES["file"]["name"], $this->policy["saveKey"]); 100 | if (file_exists("uploads/" . $this->fileName)){ 101 | self::setError("文件冲突"); 102 | } 103 | if(!is_dir("uploads/" . dirname($this->fileName))){ 104 | mkdir("uploads/" . dirname($this->fileName),0777,true); 105 | } 106 | $this->originName = $_FILES["file"]["name"]; 107 | if(!move_uploaded_file($_FILES["file"]["tmp_name"],"uploads/" .$this->fileName)){ 108 | self::setError("文件转移失败"); 109 | }; 110 | $this->filePath = "uploads/" .$this->fileName; 111 | $this->sendCallback(); 112 | } 113 | 114 | public static function setError($msg){ 115 | header("HTTP/1.1 401 Unauthorized"); 116 | die(json_encode(["error"=> $msg])); 117 | } 118 | 119 | static function urlsafe_b64decode($string) { 120 | $data = str_replace(array('-','_'),array('+','/'),$string); 121 | $mod4 = strlen($data) % 4; 122 | if ($mod4) { 123 | $data .= substr('====', $mod4); 124 | } 125 | return base64_decode($data); 126 | } 127 | 128 | public function mkfile(){ 129 | $policyInfo = explode(":", $this->policy); 130 | $this->signKey = $policyInfo[0]; 131 | $this->policy = json_decode(base64_decode($policyInfo[1]),true); 132 | $this->auth = new Auth($this->signKey); 133 | if(!$this->auth->check($this->policy,"UPLOAD")){ 134 | self::setError("auth failed."); 135 | } 136 | $this->frontPath = isset($_GET["path"]) ? self::urlsafe_b64decode($_GET["path"]) : ""; 137 | $this->originName = self::urlsafe_b64decode($_GET["fname"]); 138 | $chunkList = explode(",",file_get_contents("php://input")); 139 | $this->combineChunk($chunkList); 140 | } 141 | 142 | public function combineChunk($chunkList){ 143 | $fileName = "file_".md5(uniqid()); 144 | $fileObj=fopen ('chunks/'.$this->policy["uid"]."/".$fileName,"a+"); 145 | foreach ($chunkList as $key => $value) { 146 | $chunkObj = fopen('chunks/'.$this->policy["uid"]."/".$value, "rb"); 147 | if(!$fileObj || !$chunkObj){ 148 | self::setError("文件创建失败"); 149 | } 150 | $content = fread($chunkObj, 4195304); 151 | fwrite($fileObj, $content, 4195304); 152 | unset($content); 153 | fclose($chunkObj); 154 | unlink('chunks/'.$this->policy["uid"]."/".$value); 155 | } 156 | fclose($fileObj); 157 | $this->generateFile($fileName); 158 | } 159 | 160 | private function generateFile($fileName){ 161 | $this->fileSize = filesize('chunks/'.$this->policy["uid"]."/".$fileName); 162 | if(!$this->validCheck(false)){ 163 | unlink('chunks/'.$this->policy["uid"]."/".$fileName); 164 | self::setError($this->errMsg); 165 | } 166 | $this->fileName = str_replace('$(fname)', $this->originName, $this->policy["saveKey"]); 167 | if (file_exists("uploads/" . $this->fileName)){ 168 | unlink('chunks/'.$this->policy["uid"]."/".$fileName); 169 | self::setError("文件冲突"); 170 | } 171 | if(!is_dir("uploads/" . dirname($this->fileName))){ 172 | mkdir("uploads/" . dirname($this->fileName),0777,true); 173 | } 174 | if(!@rename('chunks/'.$this->policy["uid"]."/".$fileName,"uploads/" .$this->fileName)){ 175 | @unlink('chunks/'.$this->policy["uid"]."/".$fileName); 176 | self::setError("文件转移失败"); 177 | }; 178 | $this->filePath = "uploads/" .$this->fileName; 179 | $this->sendCallback(); 180 | } 181 | 182 | public function sendCallback(){ 183 | @list($width, $height, $img_type, $attr) = getimagesize($this->filePath); 184 | $picInfo = empty($width)?" ":$width.",".$height; 185 | $callbackBody = [ 186 | "fname" => $this->originName, 187 | "objname" => $this->fileName, 188 | "path" => $this->frontPath, 189 | "fsize" => $this->fileSize, 190 | "callbackkey" => $this->policy["callbackKey"], 191 | "picinfo" => $picInfo, 192 | ]; 193 | $session = curl_init($this->policy["callbackUrl"]); 194 | $headers = array(); 195 | $headers[] = "Authorization: " . $this->auth->sign($callbackBody); 196 | curl_setopt($session, CURLOPT_HTTPHEADER, $headers); 197 | curl_setopt($session, CURLOPT_POST, 1); 198 | curl_setopt($session, CURLOPT_POSTFIELDS, base64_encode(json_encode($callbackBody))); 199 | curl_setopt($session, CURLOPT_RETURNTRANSFER, 1); 200 | curl_setopt($session, CURLOPT_SSL_VERIFYPEER, false); 201 | curl_setopt($session, CURLOPT_SSL_VERIFYHOST, false); 202 | $server_output = curl_exec($session); 203 | $httpCode = curl_getinfo($session,CURLINFO_HTTP_CODE); 204 | curl_close ($session); 205 | if(!$server_output){ 206 | unlink("uploads/" .$this->fileName); 207 | self::setError("回调无法发起,错误代码:".curl_errno($session)); 208 | } 209 | if($httpCode != 200){ 210 | $resutltData = json_decode($server_output,true); 211 | $errorMsg = isset($resutltData["error"]) ? $resutltData["error"] : "回调失败,未知错误"; 212 | unlink("uploads/" .$this->fileName); 213 | self::setError($errorMsg); 214 | }else{ 215 | echo '{"key":"success"}'; 216 | } 217 | } 218 | 219 | } 220 | 221 | ?> 222 | -------------------------------------------------------------------------------- /uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore --------------------------------------------------------------------------------