<?php

class webdav{

	var $path;
	var $filename;
	var $filesize = 0;
	var $session_id = '';
	var $offset = 0;
	var $tmpsize = 0;
	var $tpfile = 'php://temp'; //php://memory not working on localhost
	var $mode = '';
	var $wp = NULL; // Memory Write Pointer
	var $credentials = array(); // The credentials
	var $url = ''; // The URL to make curl call
	var $chunk = 1048576;	//1MB
	var $range_lower_limit = 0;
	var $range_upper_limit = 0;
	var $chunk_url = ''; // The chunk URL to upload chunks
	var $chunk_counter = 0; // The chunk counter which will be used as file names
	
	function init($path){
		
		$stream = parse_url($path);
		
		$this->credentials = array(rawurldecode($stream['user']), rawurldecode($stream['pass']));
		
		$this->path = $stream['path'];
		
		$pathinfo = pathinfo($this->path);
		$this->filename = $pathinfo['basename'];
		//$dir = trim($pathinfo['dirname'], '/\\');
		//$this->backup_loc = (empty($dir) ? "" : $pathinfo['dirname']);
		//php://memory not working on localhost
		//$this->tpfile = 'php://temp';
		
		$_stream = $stream;
		
		unset($_stream['user']);
		unset($_stream['pass']);
		
		if($_stream['port'] == 80){
			$_stream['scheme'] = 'http';
		}else{
			$_stream['scheme'] = 'https';
		}
		
		$this->url = $this->soft_unparse_url($_stream);
		
		// Is this OwnCloud or Nextcloud ? 
		if(strpos($this->url, 'remote.php/dav/files')){
			$temp = explode('remote.php/dav/files', $this->url);
			$temp2 = explode('/', $temp[1]);
		
			// Can we form the chunk url ?
			if(!empty($temp[0]) && !empty($temp2[1])){
				// This chunk upload folder (when created) will have a .file file which will help to merge the chunk files
				$this->chunk_url = $temp[0].'remote.php/dav/uploads/'.$temp2[1].'/'.md5($this->url);
			}
		}
		
		return $this->url;
		
	}
	
	function stream_open($path, $mode, $options, &$opened_path){
		global $error;
		
		$this->init($path);
		
		$this->mode = $mode;
		
		$ret = false;
		
		if(preg_match('/w|a/is', $this->mode)){
			$this->offset = 0;
			$tfp = fopen($this->tpfile, $this->mode);
			$ret = $this->upload_start($tfp);
			fclose($tfp);
		}
		
		return $ret;
		
	}
	
	function upload_start($fp){
		global $error;

		$headers = array('Content-type: application/octet-stream');
		
		$resp = $this->request($this->url, $headers, 'PUT', $fp, 0);
		
		if(!in_array($resp['code'], array('201', '204'))){
			return false;
		}
		
		return true;
	}
	
	function stream_write($data){
		global $error;
		
		if(!is_resource($this->wp)){
			$this->wp = fopen($this->tpfile, 'w+');
		}
		
		//Initially store the data in a memory
		fwrite($this->wp, $data);		
		$this->tmpsize += strlen($data);
		$data_size = strlen($data);
		
		// Are we already more than 4 MB ?
		if($this->tmpsize >= 2000000){
			
			rewind($this->wp);
			
			//Call upload append function to write the data from PHP Memory stream
			$this->upload_append($this->wp, $this->tmpsize);
			
			// Close the temp file and reset the variables
			fclose($this->wp);
			$this->wp = NULL;
			$this->tmpsize = 0;
		}
		
		return $data_size;	
	}
	
	function upload_append($filep, $data_size){
		global $error;
		
		if(!empty($GLOBALS['init_pos'])){
			$this->offset = $GLOBALS['start_pos'];
		}
		
		$total_size = $this->offset + $data_size;
		$range_start = $this->offset;
		$range_end = $total_size - 1;
		
		// Do we need to create chunk files ? 
		if(!empty($this->chunk_url)){
			
			// cannot call $this->mkdir because it will overwrite $this->url by calling init
			$resp = $this->request($this->chunk_url, array(''), 'MKCOL');
			
			$headers = array('Content-type: application/octet-stream');
			$resp = $this->request($this->chunk_url.'/'.$this->chunk_counter, $headers, 'PUT', $filep, $data_size);
			
		}else{
		
			$headers = array('Content-type: application/octet-stream',
							'Content-Length: '.$data_size,
							'Content-Range: bytes '.$range_start.'-'.$range_end.'/'.$total_size);
			
			$resp = $this->request($this->url, $headers, 'PUT', $filep, $data_size);
			
		}
		
		if(in_array($resp['code'], array('201', '204'))){
			$this->offset += $data_size;
			$GLOBALS['start_pos'] = $this->offset;
			$this->chunk_counter++;
			return $data_size;
		}
		
		return false;
		
	}
	
	function stream_close(){
		global $error;
		
		if(preg_match('/w|a/is', $this->mode)){
			// Is there still some data left to be written ?
			if($this->tmpsize > 0){
				
				rewind($this->wp);
				
				//Call upload append function to write the data from PHP Memory stream
				$data_size = $this->upload_append($this->wp, $this->tmpsize);
				
				// Close the temp file and reset the variables
				fclose($this->wp);
				$this->wp = NULL;
				$this->tmpsize = 0;
			}
			
			// If we have written chunk files merge the files
			if(!empty($this->chunk_url)){
				// cannot call $this->rename because it will overwrite $this->url by calling init
				$headers = array('Destination: '.$this->url);
				$resp = $this->request($this->chunk_url.'/.file', $headers, 'MOVE');
				
				// Maybe timeout. But we can wait
				if($resp['code'] == 504){
					
					// Sometimes merge file can take time. Wait for the process to complete
					$max_time = time() + 60;
					do{
						sleep(1);
						$headers = array('Depth: 0');
						$resp = $this->request($this->chunk_url.'/.file', $headers, 'PROPFIND');
					}while($resp['code'] == 207 && time() < $max_time);
					
				}
			}
		}
		
		return true;
	}
	
	function mkdir($path){
		global $error;
		
		$this->init($path);
		
		$headers = array('');
		
		$resp = $this->request($this->url, $headers, 'MKCOL');
		
		if($resp['code'] == 201 || $resp['code'] == 405){
			return true;
		}
		
		return false;
	}
	
	//In response to file_exists(), is_file(), is_dir()
	function url_stat($path , $flags){
		global $error;
		
		$this->init($path);
		
		$headers = array('Depth: 0');
		
		$resp = $this->request($this->url, $headers, 'PROPFIND');
		
		if($resp['code'] != 207){
			return false;
		}
		
		soft_preg_replace('/<(.*?):creationdate>(.*?)<\/(.*?):creationdate>/is', $resp['response'], $creation_date, 2);
		soft_preg_replace('/<(.*?):getlastmodified>(.*?)<\/(.*?):getlastmodified>/is', $resp['response'], $last_modified, 2);
		soft_preg_replace('/<(.*?):getcontentlength>(.*?)<\/(.*?):getcontentlength>/is', $resp['response'], $size, 2);

		if(preg_match('/<D:getcontenttype>(.*?)directory(.*?)<\/D:getcontenttype>/is', $resp['response'])){
			$mode = 0040000;	//For DIR
		}else{
			$mode = 0100000;	//For File
		}
		
		if(!empty($resp['response'])){
			$stat = array('dev' => 0,
						'ino' => 0,
						'mode' => $mode,
						'nlink' => 0,
						'uid' => 0,
						'gid' => 0,
						'rdev' => 0,
						'size' => $size,
						'atime' => strtotime($last_modified),
						'mtime' => strtotime($last_modified),
						'ctime' => strtotime($creation_date),
						'blksize' => 0,
						'blocks' => 0);
				
			$this->filesize = $stat['size'];
			return $stat;
		}
		
		return false;
	}
	
	/*// AS of now not used
	function stream_read($count){
	
		if($count == ''){
			return false;
		}
				
		$data = json_encode(array("path" => $this->path));
		$url = 'https://content.dropboxapi.com/2/files/download';
		$headers = array('Authorization: Bearer '.$this->access_token,
						'Dropbox-API-Arg: '.$data,
						'Range:bytes = 0-'.$count,
						'Content-Type:');
		
		$sfp = fopen($GLOBALS['backup_local_path'], 'wb');
		$resp = $this->__curl($url, $headers, '', 0, '', $sfp);
		fclose($sfp);
		
		return $resp;
		
	}*/
	
	function unlink($path){
		global $error;
		
		$this->init($path);
		
		$headers = array('');
		
		$resp = $this->request($this->url, $headers, 'DELETE');
		
		if($resp['code'] == 204 || $resp['code'] == 404){
			return true;
		}
		
		return false;
	}	
	
	function rename($from, $to){
		global $error;
		
		$dest_url = $this->init($to);
		$this->init($from);
		
		$headers = array('Destination: '.$dest_url);
		
		$resp = $this->request($this->url, $headers, 'MOVE');
		
		if($resp['code'] == 201){
			return true;
		}
		
		return false;
	}
	
	//Download Backup File from WebDAV to local server
	function download_file_loop($source, $dest, $startpos = 0){
		
		global $error;
		
		$this->init($source);
		
		$sfp = fopen($dest, 'ab');
		$resp = $this->request($this->url, array(), 'GET', '', 0, '', $sfp);
		fclose($sfp);
		
		if($resp['code'] != 200){
			return false;
		}
		
		return true;
	}
	
	function request($url, $headers = array(), $method = '', $filepointer = '', $upload_size = 0, $post = '', $download_file = '', $retry = 0){
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_FAILONERROR, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_HEADER, false);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($ch, CURLOPT_USERPWD, implode(':', $this->credentials));

		if(!empty($this->authtype)){
			curl_setopt($ch, CURLOPT_HTTPAUTH, $this->authtype);
		}

		if(!empty($headers)){
			curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		}
		
		if(!empty($method)){
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
		}
		
		if(!empty($filepointer)){
			curl_setopt($ch, CURLOPT_PUT, true);
			curl_setopt($ch, CURLOPT_INFILE, $filepointer);
			curl_setopt($ch, CURLOPT_INFILESIZE, $upload_size);
		}
		
		if(!empty($download_file)){
			curl_setopt($ch, CURLOPT_FILE, $download_file);
		}

		$response = curl_exec($ch);
		$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		$curl_error = curl_error($ch);
		$curl_info = curl_getinfo($ch);

		curl_close($ch);
		
		// Some servers do not follow redirect so we need to make another call to the redirect URL
		if($statusCode == 301 && !empty($curl_info['redirect_url']) && empty($retry)){
			return $this->request($curl_info['redirect_url'], $headers, $method, $filepointer, $upload_size, $post, $download_file, 1);
		}

		$result = array();
		$result['response'] = $response;
		$result['code'] = $statusCode;
		$result['curl_error'] = $curl_error;
		$result['curl_info'] = $curl_info;
		//r_print($result);

		return $result;
	}
	
	function soft_unparse_url($parsed_url){
		$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
		$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
		$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
		$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
		$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass']  : '';
		$pass = ($user || $pass) ? $pass.'@' : '';
		$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
		$query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
		$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';

		return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
	}
}
