root = $root; $this->initPath($DAV_PRE_PATH); $this->start(); } public function initPath($DAV_PRE_PATH){ $GLOBALS['requestFrom'] = 'webdav'; $this->method = 'http'.HttpHeader::method(); $uri = rtrim($_SERVER['REQUEST_URI'],'/').'/'; //带有后缀的从domain之后部分; if(!$this->pathCheck($uri)){//路径长度限制 $this->lastError = LNG('common.lengthLimit'); $this->response(array("code"=>404));exit; } $this->urlBase = substr($uri,0,strpos($uri,$DAV_PRE_PATH)+1); //$find之前; $this->urlBase = rtrim($this->urlBase,'/').$DAV_PRE_PATH; $this->uri = $this->pathGet(); $this->path = $this->parsePath($this->uri); if(strpos($uri,$DAV_PRE_PATH) === false){ $this->lastError = LNG('common.noPermission'); $this->response(array("code"=>404));exit; } } public function checkUser(){ $user = HttpAuth::get(); if($user['user'] == 'admin' && $user['pass'] == '123'){ return true; } HttpAuth::error(); } private function pathCheck($path){ $PATH_LENGTH_MAX = 4096;//路径最长限制; return strlen($path) >= $PATH_LENGTH_MAX ? false:true; } public function start(){ $method = 'http'.HttpHeader::method(); if(!method_exists($this,$method)){ return HttpAuth::error(); } if($method == 'httpOPTIONS'){ return self::response($this->httpOPTIONS()); } $this->checkUser(); $notCheck = array('httpMKCOL','httpPUT'); if( !in_array($method,$notCheck) && !$this->pathExists($this->path,true) ){ $result = array('code' => 404); }else{ $result = $this->$method(); } if(!$result) return;//文件下载; self::response($result); } public function pathGet($dest=false){ $path = $dest ? $_SERVER['HTTP_DESTINATION'] : $_SERVER['REQUEST_URI']; $path = KodIO::clear(rawurldecode($path)); if(!strstr($path,KodIO::clear($this->urlBase))) return false; return substr($path,strpos($path,$this->urlBase)+ strlen($this->urlBase) ); } public function pathExists($path,$allowInRecycle=false){ return file_exists($path); } public function pathMkdir($path){ return mkdir($path,DEFAULT_PERRMISSIONS,true); } public function pathInfo($path){ return path_info($path); } public function pathList($path){ return path_list($path); } // range支持; public function pathOut($path){ echo file_get_contents($path); } public function pathPut($path,$tempFile=''){ if(!$tempFile){ return file_put_contents($path,''); } return move_path($tempFile,$path); } public function pathRemove($path){ if(is_file($path)){ return @unlink($this->path); }else{ return del_dir($this->path); } } public function pathMove($path,$dest){ return move_path($path,$dest); } public function pathCopy($path,$dest){ return copy_dir($path,$dest); } public function parsePath($path){ return $path; } public function parseItem($item,$isInfo){ $pathAdd = $this->pathGet().'/'.$item['name']; $pathAdd = '/'.str_replace('%2F','/',rawurlencode($pathAdd)); if($isInfo){ $pathAdd = '/'.str_replace('%2F','/',rawurlencode($this->pathGet())); } if(!trim($item['modifyTime'])){$item['modifyTime'] = time();} if(!trim($item['createTime'])){$item['createTime'] = time();} $result = array( 'href' => KodIO::clear($this->urlBase.$pathAdd), 'modifyTime' => @gmdate("D, d M Y H:i:s",$item['modifyTime']).' GMT', 'createTime' => @gmdate("Y-m-d\\TH:i:s\\Z",$item['createTime']), 'size' => $item['size'] ? $item['size']:0, ); return $result; } public function parseItemXml($itemFile,$isInfo){ $item = $this->parseItem($itemFile,$isInfo); if ($itemFile['type'] == 'folder') {//getetag $xmlAdd = ""; $xmlAdd.= "httpd/unix-directory"; $item['href'] = rtrim($item['href'],'/').'/'; if(isset($_SERVER['HTTP_DATE']) && isset($_SERVER['HTTP_DEPTH'])){ // goodsync同步处理;HTTP_DATE/HTTP_DEPTH; 首次列表展开失败问题处理; $result['modifyTime'] = $_SERVER['HTTP_DATE']; } }else{ $ext = $itemFile['ext'] ? $itemFile['ext']:get_path_ext($itemFile['name']); $mime = get_file_mime($ext); $xmlAdd = ''; $xmlAdd.= "{$mime}"; } $infoMore = array(); $picker = array( 'hasFile','hasFolder','fileInfo','fileInfoMore','oexeContent','parentID','isTruePath', 'isReadable','isWriteable','sourceRoot','icon','iconClassName','children', 'listAllChildren','fileThumb','fileThumbCover','fileShowView', ); foreach ($picker as $key){ if(array_key_exists($key,$itemFile)){$infoMore[$key] = $itemFile[$key];} } if($itemFile['type'] == 'file'){ $param = array('path'=>$itemFile['path']); $infoMore['fileOutLink'] = Action('user.index')->apiSignMake('explorer/index/fileOut',$param); } if($infoMore){ $xmlAdd.= "".base64_encode(json_encode($infoMore)).""; } return " {$item['href']} {$item['modifyTime']} {$item['createTime']} {$item['size']} {$xmlAdd} HTTP/1.1 200 OK "; } public function pathListMerge($listData){ if(!$listData) return $listData; $keyList = array('fileList','folderList','groupList'); $list = array(); foreach ($listData as $key=>$typeList){ if(!in_array($key,$keyList) || !is_array($typeList)) continue; $list = array_merge($list,$typeList); } //去除名称中的/分隔; 兼容存储挂载 foreach ($list as &$item) { $item['name'] = str_replace('/','@',$item['name']); } return $list; } public function httpPROPFIND(){ $listFile = $this->pathList($this->path); $list = $this->pathListMerge($listFile); $pathInfo = $listFile['current']; if(!is_array($list) || (isset($pathInfo['exists']) && $pathInfo['exists'] === false) ){//不存在; return array("code" => 404,"body" => $this->errorBody('ObjectNotFound','not exist')); } if(isset($listFile['folderList'])){ $pathInfo['type'] = 'folder'; } //只显示属性; $isInfo = $pathInfo['type'] == 'file' || HttpHeader::get('Depth') == '0'; // kodbox webdav挂载获取文件夹属性; if( $pathInfo['type'] == 'folder' && HttpHeader::get('X_DAV_ACTION') == 'infoChildren'){ $pathInfo = IO::infoWithChildren($pathInfo['path']); } // kodbox 挂载kod存储; listAll请求优化; if( $pathInfo['type'] == 'folder' && isset($_SERVER['HTTP_X_DAV_ACTION']) && $_SERVER['HTTP_X_DAV_ACTION'] == 'kodListAll'){ $pathInfo['listAllChildren'] = IO::listAllSimple($this->path,true); } if($isInfo){ $list = array($pathInfo); }else{ $pathInfo['name'] = ''; $list = array_merge(array($pathInfo),$list); } $out = ''; foreach ($list as $itemFile){ $out .= $this->parseItemXml($itemFile,$isInfo); } // write_log([$this->pathGet(),$this->path,$pathInfo],'webdav'); $code = 207;//207 => 200; if(strstr($this->uri,'.xbel')){$code = 200;} // 兼容floccus // 扩展kod内容; $infoMore = array(); $picker = array('groupShow','pageInfo','targetSpace','listTypePhoto','listTypeSet','pageSizeArray'); foreach ($picker as $key){ if(array_key_exists($key,$listFile)){$infoMore[$key] = $listFile[$key];} } $infoMoreData = $infoMore ? base64_encode(json_encode($infoMore)):''; return array( "code" => $code, "headers" => array('X-extendFileList: '.$infoMoreData), "body" => "\n{$out}\n" ); } public function httpHEAD() { $info = $this->pathInfo($this->path); if(!$info || $info['type'] == 'folder'){ return array( 'code' => 200, 'headers' => array( 'Content-Type: text/html; charset=utf8', 'Last-Modified: '.gmdate("D, d M Y H:i:s ",time())."GMT", 'ETag: "'.md5(time()).'"', ) ); } return array( 'code'=> 200, 'headers' => array( 'Vary: Range', 'Accept-Ranges: bytes', 'Content-length: '.$info['size'], 'Content-type: '.get_file_mime($info['ext']), 'Last-Modified: '.gmdate("D, d M Y H:i:s ", $info['mtime'])."GMT", 'Cache-Control: max-age=86400,must-revalidate', 'ETag: "'.md5($info['mtime'].$info['size']).'"', ) ); } public function httpOPTIONS() { return array( 'code' => 200, 'headers' => array( 'DAV: 1, 2, 3, extended-kodbox', 'MS-Author-Via: DAV', 'Allow: OPTIONS, PROPFIND, PROPPATCH, MKCOL, GET, PUT, DELETE, COPY, MOVE, LOCK, UNLOCK, HEAD', 'Accept-Ranges: bytes', 'Content-Length: 0', ) ); } public function httpPROPPATCH(){ $out = ' '.$_SERVER['REQUEST_URI'].' HTTP/1.1 200 OK '; return array( "code" => 207, "body" => "\n{$out}\n" ); } public function httpGET() { $this->pathOut($this->path); } // 分片支持; X-Expected-Entity-Length public function httpPUT() { $tempFile = $this->uploadFileLocal(); if($tempFile){ $code = 204; }else{ $tempFile = ''; $code = 201; } $result = $this->pathPut($this->path,$tempFile); @unlink($tempFile); if($result == false){$code = 404;} return array("code"=>$code); } public function uploadFileTemp(){ return TEMP_FILES; } // 兼容move_uploaded_file 和 流的方式上传 public function uploadFileLocal(){ $dest = TEMP_FILES.'upload_dav_'.rand_string(32);mk_dir(TEMP_FILES); $outFp = @fopen($dest, "wb"); $in = @fopen("php://input","rb"); if(!$in || !$outFp){@unlink($dest);return false;} while(!feof($in)) { fwrite($outFp, fread($in, 1024*200)); } fclose($in);fclose($outFp); if(@filesize($dest) > 0) return $dest; @unlink($dest);return false; } /** * 新建文件夹 */ public function httpMKCOL() { if ($this->pathExists($this->path)) { return array('code' => 201); // alist 等程序可能额外调用; // return array('code' => 409); } $res = $this->pathMkdir($this->path); return array('code' => $res?201:403); } public function httpMOVE() { $dest = $this->parsePath($this->pathGet(true)); if (isset($_SERVER["HTTP_OVERWRITE"])) { $options["overwrite"] = $_SERVER["HTTP_OVERWRITE"] == "T"; } $res = $this->pathMove($this->path,$dest); return array('code' => $res?201:404); } public function httpCOPY() { $dest = $this->parsePath($this->pathGet(true)); $res = $this->pathCopy($this->path,$dest); return array('code' => $res?201:404); } public function httpDELETE() { $res = $this->pathRemove($this->path); return array('code' => $res?200:503); } public function httpLOCK() { $this->fileLock($this->path); $token = md5($this->path); $lockInfo = ' infinity '.$this->xmlGet('lockinfo/owner/href').' Infinite opaquelocktoken:'.$token.' '; return array( 'code' => 200, 'headers' => array( 'Lock-Token: '.$token, 'Connection: keep-alive', ), 'body' => $lockInfo, ); } public function httpUNLOCK() { $this->fileUnLock($this->path); return array('code' => 204); } public function fileLock($path){} public function fileUnLock($path){} public function xmlGet($key){ static $xml = false; if(!$xml){ // 禁用xml实体,避免xxe攻击; php8以上已废弃 if(PHP_VERSION_ID < 80000) {libxml_disable_entity_loader(true);} $body = file_get_contents('php://input'); if(!$body) return ''; $xml = new DOMDocument(); $xml->loadXML($body); } $tag = array_shift(explode('/', $key)); $objData = $xml->getElementsByTagNameNS('DAV:', $tag); if($objData) return $objData[0]->nodeValue; return ''; } public function getLastError(){return $this->lastError;} public function errorBody($title='',$desc=''){ if(!$desc){$desc = $this->getLastError();} return ' '.htmlentities($title).' '.htmlentities($desc).' '; } /** * 输出应答信息 * @param array $data [header:array,code:int,body:string] */ public function response($data) { $headers = is_array($data['headers']) ? $data['headers'] :array(); $headers[] = HttpHeader::code($data['code']); $headers[] = 'Pragma: no-cache'; $headers[] = 'Cache-Control: no-cache'; $headers[] = 'X-DAV-BY: kodbox'; foreach ($headers as $header) { header($header); } if($data['code'] >= 400 && !$data['body']){ $data['body'] = $this->errorBody(); } if (is_string($data['body'])) { header('Content-Type: text/xml; charset=UTF-8'); echo ''."\n".$data['body']; } // write_log(array($_SERVER['REQUEST_URI'],$headers,$GLOBALS['_SERVER'],$data),'webdav'); } }