options = $options; $this->header = array(); $this->basePath = isset($options['basePath']) ? '/'.trim($options['basePath'],'/').'/' : '/'; $this->basePath = '/'.ltrim(KodIO::clear($this->basePath),'/'); $this->baseUrl = rtrim($options['host'],'/').'/'; $urlInfo = parse_url($this->baseUrl); $this->baseUrlPath = '/'.ltrim(KodIO::clear($urlInfo['path']),'/'); $this->basicAuth= "Basic ".base64_encode($options['user'].":".$options['password']); $this->plugin = Action('webdavPlugin'); $GLOBALS['requestFrom'] = 'webdav'; } public function check(){ $data = $this->propfind('/'); if(is_array($data['data'])){ $data['status'] = array_key_exists('response',$data['data']); }else{ $data['status'] = false; } // 不同CURLOPT_HTTPAUTH 账号密码加解密处理; CURLAUTH_DIGEST $authHeader = _get($data,'header.WWW-Authenticate');$authCheck = 'Digest'; // TODO header.WWW-Authenticate可能是个数组,有多种认证方式(Basic、Digest、Negotiate、NTLM等) if (is_array($authHeader)) $authHeader = $authHeader[0]; if(!$data['status'] && substr($authHeader,0,strlen($authCheck)) == $authCheck){ $this->lastRequest = false; $this->options['authType'] = 'Digest'; $data = $this->propfind('/'); if(is_array($data['data'])){ $data['status'] = array_key_exists('response',$data['data']); }else{ $data['status'] = false; } $GLOBALS['in']['config'] = json_encode($this->options,true); } // trace_log(array($data,$this->options('/'),$this->options)); $this->patchCheck(); return $data; } // 存储options返回头DAV字段;(标记支持项) private function patchCheck(){ $data = $this->options('/'); if(!$data['header'] || !$data['header']['DAV']) return; $this->options['dav'] = $data['header']['DAV']; $GLOBALS['in']['config'] = json_encode($this->options,true);// 修改配置; } /** * Overwrite已存在处理; * F=skip跳过,已存在时不执行copy或move; * T=覆盖;已存在则继续执行; 默认为T */ public function mkdir($path){ $this->setHeader('Overwrite','F'); $data = $this->send('MKCOL',$this->makeUrl($path)); return $data['status']; } public function move($from,$to,$lockToken=''){ $this->setHeader('Destination',$this->makeUrl($to)); $this->setHeader('Overwrite','T'); if($lockToken){ $this->setHeader('IF','<'.$lockToken .'>'); } return $this->send('MOVE',$this->makeUrl($from)); } public function copy($from,$to){ $this->setHeader('Destination',$this->makeUrl($to)); $this->setHeader('Overwrite','T'); return $this->send('COPY',$this->makeUrl($from)); } public function delete($path,$lockToken=''){ if($lockToken){ $this->setHeader('IF','<'.$lockToken .'>'); } $data = $this->send('DELETE',$this->makeUrl($path)); return $data['status']; } public function propfind($path,$depth='1',$header=''){ $this->setHeader('Depth',$depth);//遍历深度 $this->setHeader('Content-type','text/xml; charset=UTF-8'); if($header){$this->setHeader($header);} $body = ''; return $this->send('PROPFIND',$this->makeUrl($path),$body); } public function options($path){ return $this->send('OPTIONS',$this->makeUrl($path)); } public function get($path,$localFile,$range=''){ $getFileInfo = array('path'=>$localFile,'range'=>$range); $data = $this->send('GET',$this->makeUrl($path),false,false,$getFileInfo); return $data['status']; } public function put($path,$localFile,$lockToken=''){ if($lockToken){ $this->setHeader('IF','<'.$lockToken .'>'); } $this->setHeader('Overwrite','T'); $putFileInfo = array('name'=>get_path_this($path),'path'=>$localFile); return $this->send('PUT',$this->makeUrl($path),false,$putFileInfo); } public function uriToPath($uri){ if(substr($uri,0,strlen($this->baseUrl)) == $this->baseUrl){ $path = substr($uri,strlen($this->baseUrl)); }else{ $path = substr($uri,strlen($this->baseUrlPath)); } return '/'.ltrim(rawurldecode($path),'/'); } public function makeUrl($path){ $path = KodIO::clear($path); $path = $this->encodeUrl($path); return $this->baseUrl.ltrim($path,'/'); } public function encodeUrl($path){ if(!is_string($path) || !$path) return ''; $arr = explode('/',$path); for ($i=0; $i < count($arr); $i++) { $arr[$i] = rawurlencode($arr[$i]); } $path = implode('/',$arr); return $path; } public function setHeader($key,$value=false){ if($value === false){ $this->header[] = $key; }else{ $this->header[] = $key.': '.$value; } } // 缓存连续请求内容一致的内容;PROPFIND public function send($method,$requestUrl,$body=false,$putFileInfo=false,$getFileInfo=false,$timeout=3600){ $lastRequest = $method.';'.$requestUrl; if($method == 'PROPFIND' && $this->lastRequest == $lastRequest){ $this->header = array(); return $this->lastRequestData; } $this->lastRequest = $lastRequest; $result = $this->_send($method,$requestUrl,$body,$putFileInfo,$getFileInfo,$timeout); $this->lastRequestData = $result; return $result; } private function _send($method,$requestUrl,$body=false,$putFileInfo=false,$getFileInfo=false,$timeout=3600){ $this->cookieSet(); if($body){$body = ''.$body;} if(!request_url_safe($requestUrl)){$this->header = array();return false;} $ch = curl_init($requestUrl); if($this->options['authType'] == 'Digest'){ curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST); curl_setopt($ch, CURLOPT_USERPWD, $this->options['user'].":".$this->options['password']); }else{ $this->setHeader('Authorization',$this->basicAuth); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); } curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_HEADER,1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_setopt($ch, CURLINFO_HEADER_OUT, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_NOPROGRESS, false); curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,'curl_progress');curl_progress_start($ch); curl_setopt($ch, CURLOPT_USERAGENT,'webdav/kodbox 1.0'); curl_setopt($ch, CURLOPT_REFERER,get_url_link($requestUrl)); curl_setopt($ch, CURLOPT_TIMEOUT,$timeout); if($getFileInfo){$this->getFile($ch,$getFileInfo);} if($putFileInfo){$this->putFile($ch,$putFileInfo);} curl_setopt($ch, CURLOPT_HTTPHEADER, $this->header); $response = curl_exec($ch);curl_progress_end($ch,$response); $headerSize = curl_getinfo($ch,CURLINFO_HEADER_SIZE); $responseInfo = curl_getinfo($ch); $responseBody = substr($response, $headerSize); $responseHeader = substr($response, 0, $headerSize); $responseHeader = parse_headers($responseHeader); $headerSet = $this->header;$this->header = array(); $code = $responseInfo['http_code']; if($code == 0){ $errorMessage = curl_error($ch); $errorMessage = $errorMessage ? "\n".$errorMessage : 'Network error!'; return $this->parseResult(0,$errorMessage,$responseInfo,$headerSet); } curl_close($ch); $result = $this->parseResult($code,$responseBody,$responseHeader,$headerSet); $this->sendLog($result,$method,$requestUrl,$headerSet); $this->cookieSave($result); // trace_log([$requestUrl,$method,$result]); return $result; } private function parseResult($code,$body,$header,$headerSet){ $status = $code >= 200 && $code <= 299; $result = array('code'=>$code,'status'=>$status,'header'=>$header,'data'=>$body); if($code == 0){$result['error'] = $body;} if(!$body) return $result; $error = $status ? '':$header['0']; $contentType = is_array($header['content-type']) ? $header['content-type'][0]:$header['content-type']; if(!$contentType){ // 301跳转情况; $contentType = is_array($header['Content-Type']) ? $header['Content-Type'][0]:$header['Content-Type']; } if(strstr($contentType,'/json')){ $result['data'] = @json_decode($body,true); if( !$status && is_array($result['data']) && array_key_exists('code',$result['data']) && array_key_exists('data',$result['data']) && $result['data']['code'] != true ){ $error = _get($result['data'],'data',''); } } if(!is_array($result['data'])){ $result['data'] = webdavClient::xmlParse($body); if( !$status && is_array($result['data']) && array_key_exists('exception',$result['data']) ){ $exception = _get($result['data'],'exception',''); $message = _get($result['data'],'message',''); $error = $exception ? $exception.';'.$message : $message; } } if(is_array($result['data'])){$result['_data'] = $body;} if($error){$result['error'] = $error;} return $result; } // 请求日志; private function sendLog($result,$method,$requestUrl,$headerSet){ // $this->plugin->clientLog(array($method,$requestUrl,$headerSet,$result)); $message = $result['status'] ? $result['header']['0'] : $result['error']; $this->plugin->clientLog($method.':'.$requestUrl.';'.$message); if(!$result['status'] && $method != 'PROPFIND'){ IO::errorTips('[webdav error] '.$result['error']); } } private function getFile($curl,$fileInfo){ if(isset($fileInfo['range']) && $fileInfo['range']){ $this->setHeader('Range',$fileInfo['range']); } $fp = fopen ($fileInfo['path'],'w+'); curl_setopt($curl, CURLOPT_HTTPGET,1); curl_setopt($curl, CURLOPT_HEADER,0);//不输出头 curl_setopt($curl, CURLOPT_FILE, $fp); curl_setopt($curl, CURLOPT_TIMEOUT,3600*10); } private function putFile($curl,$fileInfo){ if(!$fileInfo['path']){ curl_setopt($curl, CURLOPT_PUT,true); return; } $path = $fileInfo['path']; /* $postData = array();$key = 'UPLOAD_FILE'; // post方式上传; $filename = $fileInfo['name']; $mime = get_file_mime(get_path_ext($filename)); if (class_exists('\CURLFile')){ $postData[$key] = new CURLFile(realpath($path),$mime,$filename); }else{ $postData[$key] = "@".realpath($path).";type=".$mime.";filename=".$filename; } // post方式上传; 默认会加上header: multipart/form-data; boundary=-----xxxx-- //$this->setHeader('Content-Type','application/octet-stream');//"application/x-www-form-urlencoded" //$this->setHeader('Content-Length',@filesize($path)); curl_setopt($curl, CURLOPT_POSTFIELDS,$postData); */ curl_setopt($curl, CURLOPT_PUT,true); curl_setopt($curl, CURLOPT_INFILE,@fopen($path,'r')); curl_setopt($curl, CURLOPT_INFILESIZE,@filesize($path)); curl_setopt($curl, CURLOPT_TIMEOUT,3600*10); if(class_exists('\CURLFile')){ curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true); }else if(defined('CURLOPT_SAFE_UPLOAD')) { curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false); } } // 请求前,设置上次保存的cookie; private function cookieSet(){ $key = 'webdav_cookie_'.md5($this->basicAuth); $cookie = Cache::get($key); if(!$cookie || !is_array($cookie)) return; $cookieItem = array(); foreach($cookie as $key => $val){ $cookieItem[] = $key.'='.rawurlencode($val); } $setCookie = implode('; ',$cookieItem); if(!$cookieItem || !$setCookie) return; $this->setHeader('Cookie',$setCookie); } // 请求完成保存cookie; private function cookieSave($data){ if(!is_array($data['header'])) return; if(!is_array($data['header']['set-cookie'])) return; $cookie = $data['header']['set-cookie']; $data = array(); foreach ($cookie as $item) { $value = substr($item,0,strpos($item,';')); if(!$value || !strpos($value,'=')) continue; $keyValue = explode('=',trim($value)); if(count($keyValue) != 2) continue; if(!$keyValue[0] || !$keyValue[1]) continue; if($keyValue[1] == 'deleted') continue; $data[$keyValue[0]] = rawurldecode($keyValue[1]); } if(count($data) == 0) return; $key = 'webdav_cookie_'.md5($this->basicAuth); Cache::set($key,$data); } public static function xmlParse($xml){ $doc = new DOMDocument(); $doc->loadXML($xml); $root = $doc->documentElement; $result = self::domNodeToArray($root); $result = self::xmlParseKey($result); return $result; } public static function domNodeToArray($node){ $output = array(); switch ($node->nodeType) { case 4: // XML_CDATA_SECTION_NODE case 3: // XML_TEXT_NODE $output = trim($node->textContent); break; case 1: // XML_ELEMENT_NODE for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) { $child = $node->childNodes->item($i); $v = self::domNodeToArray($child); if (isset($child->tagName)) { $t = $child->tagName; if (!isset($output[$t])) { $output[$t] = array(); } if (is_array($v) && empty($v)) {$v = '';} $output[$t][] = $v; } elseif ($v || $v === '0') { $output = is_array($v) ? json_encode($v) : $v; } } if ($node->attributes->length && !is_array($output)) { // has attributes but isn't an array $output = array('@content' => $output); // change output into an array. } if (is_array($output)) { if ($node->attributes->length) { $a = array(); foreach ($node->attributes as $attrName => $attrNode) { $a[$attrName] = (string) $attrNode->value; } $output['@attributes'] = $a; } foreach ($output as $t => $v) { if ($t !== '@attributes' && is_array($v) && count($v) === 1) { $output[$t] = $v[0]; } } } break; } return $output; } private static function xmlParseKey($arr){ $result = array(); foreach ($arr as $key => $value) { if(is_string($key) && strstr($key,':')){ $keyArr = explode(':',$key); $key = $keyArr[count($keyArr) - 1]; } if(is_array($value)){$value = self::xmlParseKey($value);} $result[$key] = $value; } return $result; } public static function xmlParse2($xml){ $parse = simplexml_load_string($xml); if($parse === false) return array(); $namespace = $parse->getNamespaces(true); if(!$namespace){$namespace = array();} $namespace[''] = ''; $result = array(); foreach($namespace as $key=>$v){ // $children = $parse->children($key,true); // 多个命名空间会丢失部分的情况 $children = $parse->children($v); self::objectToArr($children,$result); } //$parse->children($v) 第一项多一层情况处理; if(isset($result['response'][0][0])){ unset($result['response'][0]); $result['response'] = array_values($result['response']); } return $result; } private static function objectToArr($obj,&$arr){ $arrCount = 0; // 相同key,重复出现则归并为数组; foreach($obj as $key => $val){ if(count($val) == 0){$arr[$key] = (string)$val;continue;} if(!isset($arr[$key])){$arr[$key] = array();} if(!$arr[$key]){self::objectToArr($val,$arr[$key]);continue;} if(!$arrCount){$arr[$key] = array($arr[$key]);$arrCount++;} if(!isset($arr[$key][$arrCount])){$arr[$key][$arrCount] = array();} self::objectToArr($val,$arr[$key][$arrCount]);$arrCount++; } } }