mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-27 18:33:39 +01:00
1120 lines
31 KiB
PHP
1120 lines
31 KiB
PHP
<?php
|
|
/**************************************************************************\
|
|
* phpGroupWare API - WebDAV *
|
|
* This file written by Jonathon Sim (for Zeald Ltd) <jsim@free.net.nz> *
|
|
* Provides methods for manipulating an RFC 2518 DAV repository *
|
|
* Copyright (C) 2002 Zeald Ltd *
|
|
* -------------------------------------------------------------------------*
|
|
* This library is part of the phpGroupWare API *
|
|
* http://www.phpgroupware.org/api *
|
|
* ------------------------------------------------------------------------ *
|
|
* This library is free software; you can redistribute it and/or modify it *
|
|
* under the terms of the GNU Lesser General Public License as published by *
|
|
* the Free Software Foundation; either version 2.1 of the License, *
|
|
* or any later version. *
|
|
* This library is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* See the GNU Lesser General Public License for more details. *
|
|
* You should have received a copy of the GNU Lesser General Public License *
|
|
* along with this library; if not, write to the Free Software Foundation, *
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
|
\**************************************************************************/
|
|
|
|
/* $Id$ */
|
|
|
|
/*At the moment much of this is simply a wrapper around the NET_HTTP_Client class,
|
|
with some other methods for parsing the returned XML etc
|
|
Ideally this will eventually use groupware's inbuilt HTTP class
|
|
*/
|
|
|
|
define ('DEBUG_DAV_CLIENT', 0);
|
|
define ('DEBUG_DAV_XML', 0);
|
|
define ('DEBUG_CACHE', 0);
|
|
|
|
|
|
##############################################################
|
|
# 'Private' classes - these are only used internally and should
|
|
# not be used by external code
|
|
##############################################################
|
|
|
|
/*
|
|
PHP STILL doesnt have any sort of stable DOM parser. So lets make our
|
|
own XML parser, that parses XML into a tree of arrays (I know, it could do
|
|
something resembling DOM, but it doesnt!)
|
|
*/
|
|
class xml_tree_parser
|
|
{
|
|
var $namespaces;
|
|
var $current_element;
|
|
var $num = 1;
|
|
var $tree = NULL;
|
|
|
|
/*
|
|
This is the only end-user function in the class. Call parse with an XML string, and
|
|
you will get back the tree of 'element' arrays
|
|
*/
|
|
function parse($xml_string)
|
|
{
|
|
$this->xml_parser = xml_parser_create();
|
|
xml_set_element_handler($this->xml_parser,array(&$this,"start_element"),array(&$this,"end_element"));
|
|
xml_set_character_data_handler($this->xml_parser,array(&$this,"parse_data"));
|
|
|
|
$this->parser_result=array();
|
|
$this->xml = $xml_string;
|
|
xml_parse($this->xml_parser,$xml_string);
|
|
xml_parser_free($this->xml_parser);
|
|
if (DEBUG_DAV_XML)
|
|
{
|
|
echo '<pre>'.htmlentities($xml_string).'</pre>';
|
|
echo 'parsed to:' ; $this->print_tree();
|
|
}
|
|
return $this->tree;
|
|
}
|
|
|
|
//a useful function for debug output - will print the tree after parsing
|
|
function print_tree($element=NULL, $prefix='|', $processed=array())
|
|
{
|
|
|
|
if ($processed[$element['id']])
|
|
{
|
|
echo '<b>***RECURSION!!***</b></br>';
|
|
die();
|
|
}
|
|
else
|
|
{
|
|
$processed[$element['id']] = true;
|
|
}
|
|
|
|
if ($element == NULL)
|
|
{
|
|
$element = $this->tree;
|
|
}
|
|
echo $prefix.$element['namespace'].':'.$element['name'].' '.$element['start'].'->'.$element['end'].'<br>';
|
|
$prefix .= '-->';
|
|
if ($element['data'])
|
|
{
|
|
echo $prefix.$element['data'].'<br>';
|
|
}
|
|
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->print_tree($child, $prefix, &$processed);
|
|
}
|
|
|
|
}
|
|
|
|
//called by the xml parser at the start of an element, it creates that elements node in the tree
|
|
function start_element($parser,$name,$attr)
|
|
{
|
|
|
|
if (preg_match('/(.*):(.*)/', $name, $matches))
|
|
{
|
|
$ns = $this->namespaces[$matches[1]];
|
|
$element = $matches[2];
|
|
}
|
|
else
|
|
{
|
|
$element = $name;
|
|
}
|
|
|
|
if ($this->tree == NULL)
|
|
{
|
|
$this->tree = array(
|
|
'namespace'=>$ns,
|
|
'name' => $element,
|
|
'attributes' => $attr,
|
|
'data' => '',
|
|
'children' => array(),
|
|
'parent' => NULL,
|
|
'type' => 'xml_element',
|
|
'id' => $this->num
|
|
);
|
|
$this->current_element = &$this->tree;
|
|
}
|
|
else
|
|
{
|
|
$parent = &$this->current_element;
|
|
$parent['children'][$this->num]=array(
|
|
'namespace'=>$ns,
|
|
'name' => $element,
|
|
'attributes' => $attr,
|
|
'data' => '',
|
|
'children' => array(),
|
|
'parent' => &$parent,
|
|
'type' => 'xml_element',
|
|
'id' => $this->num
|
|
);
|
|
$this->current_element = &$parent['children'][$this->num];
|
|
}
|
|
$this->num++;
|
|
$this->current_element['start'] = xml_get_current_byte_index($parser);
|
|
foreach ($attr as $name => $value)
|
|
{
|
|
if (ereg('^XMLNS:(.*)', $name, $matches) )
|
|
{
|
|
$this->namespaces[$matches[1]] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
//at the end of an element, stores the start and end positions in the xml stream, and moves up the tree
|
|
function end_element($parser,$name)
|
|
{
|
|
$curr = xml_get_current_byte_index($parser);
|
|
$this->current_element['end'] =strpos($this->xml, '>', $curr);
|
|
$this->current_element = &$this->current_element['parent'];
|
|
|
|
}
|
|
|
|
//if there is a CDATA element, puts it into the parent elements node
|
|
function parse_data($parser,$data)
|
|
{
|
|
$this->current_element['data']=$data;
|
|
}
|
|
|
|
}
|
|
|
|
/*This class uses a bunch of recursive functions to process the DAV XML tree
|
|
digging out the relevent information and putting it into an array
|
|
*/
|
|
class dav_processor
|
|
{
|
|
function dav_processor($xml_string)
|
|
{
|
|
$this->xml = $xml_string;
|
|
$this->dav_parser = new xml_tree_parser();
|
|
$this->tree = $this->dav_parser->parse($xml_string);
|
|
|
|
}
|
|
|
|
function process_tree(&$element, &$result_array)
|
|
{
|
|
|
|
//This lets us mark a node as 'done' and provides protection against infinite loops
|
|
if ($this->processed[$element['id']])
|
|
{
|
|
return $result_array;
|
|
}
|
|
else
|
|
{
|
|
$this->processed[$element['id']] = true;
|
|
}
|
|
|
|
if ( $element['namespace'] == 'DAV:')
|
|
{
|
|
if ($element['name'] == 'RESPONSE')
|
|
{
|
|
$result = array(
|
|
'size' => 0,
|
|
'getcontenttype' => 'application/octet-stream',
|
|
'is_dir' => 0
|
|
);
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $result);
|
|
}
|
|
$result_array[$result['full_name']] = $result;
|
|
|
|
}
|
|
}
|
|
// ->recursion
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_tree($child, $result_array);
|
|
}
|
|
return $result_array;
|
|
}
|
|
|
|
function process_properties($element, &$result_array)
|
|
{
|
|
if ($this->processed[$element['id']])
|
|
{
|
|
return $result_array;
|
|
}
|
|
else
|
|
{
|
|
$this->processed[$element['id']] = true;
|
|
}
|
|
|
|
if ( $element['namespace'] == 'DAV:')
|
|
{
|
|
switch ($element['name'])
|
|
{
|
|
case 'HREF':
|
|
$string = $element['data'];
|
|
$idx=strrpos($string,SEP);
|
|
if($idx && $idx==strlen($string)-1)
|
|
{
|
|
$this->current_ref=substr($string,0,$idx);
|
|
}
|
|
else
|
|
{
|
|
$this->current_ref=$string;
|
|
}
|
|
$result_array['name']=basename($string);
|
|
$result_array['directory']=dirname($string);
|
|
$result_array['full_name'] = $this->current_ref;
|
|
break;
|
|
case 'SUPPORTEDLOCK':
|
|
if (count($element['children'])) //There are active locks
|
|
{
|
|
$result_array['supported_locks'] = array();
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $result_array['supported_locks']);
|
|
}
|
|
}
|
|
break;
|
|
case 'LOCKDISCOVERY':
|
|
if (count($element['children'])) //There are active locks
|
|
{
|
|
$result_array['locks'] = array();
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $result_array['locks']);
|
|
}
|
|
}
|
|
break;
|
|
case 'LOCKENTRY':
|
|
if (count($element['children']))
|
|
{
|
|
$result_array[$element['id']] = array();
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $result_array[$element['id']] );
|
|
}
|
|
}
|
|
break;
|
|
case 'ACTIVELOCK':
|
|
if (count($element['children']))
|
|
{
|
|
$result_array[$element['id']] = array();
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $result_array[$element['id']] );
|
|
}
|
|
}
|
|
break;
|
|
case 'OWNER':
|
|
$result_array['owner'] = array();
|
|
|
|
foreach ($element['children'] as $child)
|
|
{
|
|
$this->process_verbatim($child, &$result_array['owner'][]);
|
|
}
|
|
|
|
//print_r($element);die();
|
|
//die();
|
|
$result_array['owner_xml'] = substr($this->xml, $element['start'], $element['end']-$element['start']+1);
|
|
return $result_array; //No need to process this branch further
|
|
break;
|
|
case 'LOCKTOKEN':
|
|
if (count($element['children']))
|
|
{
|
|
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $tmp_result , $processed);
|
|
$result_array['lock_tokens'][$tmp_result['full_name']] = $tmp_result;
|
|
}
|
|
}
|
|
break;
|
|
case 'LOCKTYPE':
|
|
$child = end($element['children']);
|
|
if ($child)
|
|
{
|
|
$this->processed[$child['id']] = true;
|
|
$result_array['locktype'] = $child['name'];
|
|
}
|
|
break;
|
|
case 'LOCKSCOPE':
|
|
$child = end($element['children']);
|
|
if ($child)
|
|
{
|
|
$this->processed[$child['id']] = true;
|
|
$result_array['lockscope'] = $child['name'];
|
|
}
|
|
break;
|
|
default:
|
|
if (trim($element['data']))
|
|
{
|
|
$result_array[strtolower($element['name'])] = $element['data'];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (trim($element['data']))
|
|
{
|
|
$result_array[strtolower($element['name'])] = $element['data'];
|
|
}
|
|
}
|
|
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
$this->process_properties($child, $result_array);
|
|
}
|
|
|
|
return $result_array;
|
|
}
|
|
|
|
function process_verbatim($element, &$result_array)
|
|
{
|
|
if ($this->processed[$element['id']])
|
|
{
|
|
return $result_array;
|
|
}
|
|
else
|
|
{
|
|
$this->processed[$element['id']] = true;
|
|
}
|
|
|
|
foreach ( $element as $key => $value)
|
|
{
|
|
//The parent link is death to naive programmers (eg me) :)
|
|
if (!( $key == 'children' || $key == 'parent') )
|
|
{
|
|
$result_array[$key] = $value;
|
|
}
|
|
}
|
|
$result_array['children'] = array();
|
|
foreach ($element['children'] as $id=>$child)
|
|
{
|
|
echo 'processing child:';
|
|
$this->process_verbatim($child, $result_array['children']);
|
|
}
|
|
return $result_array;
|
|
}
|
|
}
|
|
|
|
#####################################################
|
|
#This is the actual public interface of this class
|
|
#####################################################
|
|
class http_dav_client
|
|
{
|
|
var $attributes=array();
|
|
var $vfs_property_map = array();
|
|
var $cached_props = array();
|
|
function http_dav_client()
|
|
{
|
|
$this->http_client = CreateObject('phpgwapi.net_http_client');
|
|
$this->set_debug(0);
|
|
}
|
|
|
|
//TODO: Get rid of this
|
|
//A quick, temporary debug output function
|
|
function debug($info) {
|
|
|
|
if (DEBUG_DAV_CLIENT)
|
|
{
|
|
echo '<b> http_dav_client debug:<em> ';
|
|
if (is_array($info))
|
|
{
|
|
print_r($info);
|
|
}
|
|
else
|
|
{
|
|
echo $info;
|
|
}
|
|
echo '</em></b><br>';
|
|
}
|
|
}
|
|
/*!
|
|
@function glue_url
|
|
@abstract glues a parsed url (ie parsed using PHP's parse_url) back
|
|
together
|
|
@param $url The parsed url (its an array)
|
|
*/
|
|
|
|
function glue_url ($url){
|
|
if (!is_array($url))
|
|
{
|
|
return false;
|
|
}
|
|
// scheme
|
|
$uri = (!empty($url['scheme'])) ? $url['scheme'].'://' : '';
|
|
// user & pass
|
|
if (!empty($url['user']))
|
|
{
|
|
$uri .= $url['user'];
|
|
if (!empty($url['pass']))
|
|
{
|
|
$uri .=':'.$url['pass'];
|
|
}
|
|
$uri .='@';
|
|
}
|
|
// host
|
|
$uri .= $url['host'];
|
|
// port
|
|
$port = (!empty($url['port'])) ? ':'.$url['port'] : '';
|
|
$uri .= $port;
|
|
// path
|
|
$uri .= $url['path'];
|
|
// fragment or query
|
|
if (isset($url['fragment']))
|
|
{
|
|
$uri .= '#'.$url['fragment'];
|
|
} elseif (isset($url['query']))
|
|
{
|
|
$uri .= '?'.$url['query'];
|
|
}
|
|
return $uri;
|
|
}
|
|
|
|
/*!
|
|
@function encodeurl
|
|
@abstract encodes a url from its "display name" to something the dav server will accept
|
|
@param uri The unencoded uri
|
|
@discussion
|
|
Deals with "url"s which may contain spaces and other unsavoury characters,
|
|
by using appropriate %20s
|
|
*/
|
|
function encodeurl($uri)
|
|
{
|
|
$parsed_uri = parse_url($uri);
|
|
if (empty($parsed_uri['scheme']))
|
|
{
|
|
$path = $uri;
|
|
}
|
|
else
|
|
{
|
|
$path = $parsed_uri['path'];
|
|
}
|
|
$fixed_array = array();
|
|
foreach (explode('/', $path) as $name)
|
|
{
|
|
$fixed_array[] = rawurlencode($name);
|
|
}
|
|
$fixed_path = implode('/', $fixed_array);
|
|
if (!empty($parsed_uri['scheme']))
|
|
{
|
|
$parsed_uri['path'] = $fixed_path;
|
|
$newuri = $this->glue_url($parsed_uri);
|
|
}
|
|
else
|
|
{
|
|
$newuri = $fixed_path;
|
|
}
|
|
return $newuri;
|
|
|
|
}
|
|
/*!
|
|
@function decodeurl
|
|
@abstract decodes a url to its "display name"
|
|
@param uri The encoded uri
|
|
@discussion
|
|
Deals with "url"s which may contain spaces and other unsavoury characters,
|
|
by using appropriate %20s
|
|
*/
|
|
function decodeurl($uri)
|
|
{
|
|
$parsed_uri = parse_url($uri);
|
|
if (empty($parsed_uri['scheme']))
|
|
{
|
|
$path = $uri;
|
|
}
|
|
else
|
|
{
|
|
$path = $parsed_uri['path'];
|
|
}
|
|
$fixed_array = array();
|
|
foreach (explode('/', $path) as $name)
|
|
{
|
|
$fixed_array[] = rawurldecode($name);
|
|
}
|
|
$fixed_path = implode('/', $fixed_array);
|
|
if (!empty($parsed_uri['scheme']))
|
|
{
|
|
$parsed_uri['path'] = $fixed_path;
|
|
$newuri = $this->glue_url($parsed_uri);
|
|
}
|
|
else
|
|
{
|
|
$newuri = $fixed_path;
|
|
}
|
|
return $newuri;
|
|
|
|
}
|
|
/*!
|
|
@function set_attributes
|
|
@abstract Sets the "attribute map"
|
|
@param attributes Attributes to extract "as-is" from the DAV properties
|
|
@param dav_map A mapping of dav_property_name => attribute_name for attributes
|
|
with different names in DAV and the desired name space.
|
|
@discussion
|
|
This is mainly for use by VFS, where the VFS attributes (eg size) differ
|
|
from the corresponding DAV ones ("getcontentlength")
|
|
*/
|
|
function set_attributes($attributes, $dav_map)
|
|
{
|
|
$this->vfs_property_map = $dav_map;
|
|
$this->attributes = $attributes;
|
|
}
|
|
|
|
/*!
|
|
@function set_credentials
|
|
@abstract Sets authentication credentials for HTTP AUTH
|
|
@param username The username to connect with
|
|
@param password The password to connect with
|
|
@discussion
|
|
The only supported authentication type is "basic"
|
|
*/
|
|
function set_credentials( $username, $password )
|
|
{
|
|
$this->http_client->setCredentials($username, $password );
|
|
}
|
|
|
|
/*!
|
|
@function connect
|
|
@abstract connects to the server
|
|
@param dav_host The host to connect to
|
|
@param dav_port The port to connect to
|
|
@discussion
|
|
If the server requires authentication you will need to set credentials
|
|
with set_credentials first
|
|
*/
|
|
|
|
function connect($dav_host,$dav_port)
|
|
{
|
|
$this->dav_host = $dav_host;
|
|
$this->dav_port = $dav_port;
|
|
$this->http_client->addHeader('Host',$this->dav_host);
|
|
$this->http_client->addHeader('Connection','close');
|
|
//$this->http_client->addHeader('transfer-encoding','identity');
|
|
// $this->http_client->addHeader('Connection','keep-alive');
|
|
// $this->http_client->addHeader('Keep-Alive','timeout=20, state="Accept,Accept-Language"');
|
|
$this->http_client->addHeader('Accept-Encoding','chunked');
|
|
$this->http_client->setProtocolVersion( '1.1' );
|
|
$this->http_client->addHeader( 'user-agent', 'Mozilla/5.0 (compatible; PHPGroupware dav_client/1; Linux)');
|
|
return $this->http_client->Connect($dav_host,$dav_port);
|
|
}
|
|
function set_debug($debug)
|
|
{
|
|
$this->http_client->setDebug($debug);
|
|
}
|
|
|
|
/*!
|
|
@function disconnect
|
|
@abstract disconnect from the server
|
|
@discussion
|
|
When doing HTTP 1.1 we frequently close/reopen the connection
|
|
anyway, so this function needs to be called after any other DAV calls
|
|
(since if they find the connection closed, they just reopen it)
|
|
*/
|
|
|
|
function disconnect()
|
|
{
|
|
$this->http_client->Disconnect();
|
|
}
|
|
|
|
/*!
|
|
@function get_properties
|
|
@abstract a high-level method of getting DAV properties
|
|
@param url The URL to get properties for
|
|
@param scope the 'depth' to recuse subdirectories (default 1)
|
|
@param sorted whether we should sort the rsulting array (default True)
|
|
@result array of file->property arra
|
|
@discussion
|
|
This function performs all the necessary XML parsing etc to convert DAV properties (ie XML nodes)
|
|
into associative arrays of properties - including doing mappings
|
|
from DAV property names to any desired property name format (eg the VFS one)
|
|
This is controlled by the attribute arrays set in the set_attributes function.
|
|
*/
|
|
function get_properties($url,$scope=1){
|
|
$request_id = $url.'//'.$scope.'//'.$sorted; //A unique id for this request (for caching)
|
|
if ($this->cached_props[$request_id])
|
|
{
|
|
if (DEBUG_CACHE) echo'Cache hit : cache id:'.$request_id;
|
|
return $this->cached_props[$request_id];
|
|
}
|
|
else if (! $sorted && $this->cached_props[$url.'//'.$scope.'//1'])
|
|
{
|
|
if (DEBUG_CACHE) echo ' Cache hit : cache id: '.$request_id;
|
|
return $this->cached_props[$url.'//'.$scope.'//1'];
|
|
}
|
|
if (DEBUG_CACHE)
|
|
{
|
|
echo ' <b>Cache miss </b>: cache id: '.$request_id;
|
|
/* echo " cache:<pre>";
|
|
print_r($this->cached_props);
|
|
echo '</pre>';*/
|
|
}
|
|
|
|
|
|
if($this->propfind($url,$scope) != 207)
|
|
{
|
|
if($this->propfind($url.'/',$scope) != 207)
|
|
{
|
|
return array();
|
|
}
|
|
}
|
|
$xml_result=$this->http_client->getBody();
|
|
$result_array = array();
|
|
$dav_processor = new dav_processor($xml_result);
|
|
$tmp_list = $dav_processor->process_tree($dav_processor->tree, $result_array);
|
|
|
|
foreach($tmp_list as $name=>$item) {
|
|
$fixed_name = $this->decodeurl($name);
|
|
$newitem = $item;
|
|
$newitem['is_dir']= ($item['getcontenttype'] =='httpd/unix-directory' ? 1 : 0);
|
|
$item['directory'] = $this->decodeurl($item['directory']);
|
|
//Since above we sawed off the protocol and host portions of the url, lets readd them.
|
|
if (strlen($item['directory'])) {
|
|
$path = $item['directory'];
|
|
$host = $this->dav_host;
|
|
$newitem['directory'] = $host.$path;
|
|
}
|
|
|
|
//Get any extra properties that may share the vfs name
|
|
foreach ($this->attributes as $num=>$vfs_name)
|
|
{
|
|
if ($item[$vfs_name])
|
|
{
|
|
$newitem[$vfs_name] = $item[$vfs_name];
|
|
}
|
|
}
|
|
|
|
//Map some DAV properties onto VFS ones.
|
|
foreach ($this->vfs_property_map as $dav_name=>$vfs_name)
|
|
{
|
|
if ($item[$dav_name])
|
|
{
|
|
$newitem[$vfs_name] = $item[$dav_name];
|
|
}
|
|
}
|
|
|
|
if ($newitem['is_dir'] == 1)
|
|
{
|
|
$newitem['mime_type']='Directory';
|
|
}
|
|
|
|
$this->debug('<br><br>properties:<br>');
|
|
$this->debug($newitem);
|
|
$newitem['name'] = $this->decodeurl($newitem['name']);
|
|
$result[$fixed_name]=$newitem;
|
|
if ($newitem['is_dir']==1)
|
|
{
|
|
$this->cached_props[$name.'//0//1'] = array($fixed_name=>$newitem);
|
|
}
|
|
else
|
|
{
|
|
$this->cached_props[$name.'//1//1'] = array($fixed_name=>$newitem);
|
|
}
|
|
}
|
|
if ($sorted)
|
|
{
|
|
ksort($result);
|
|
}
|
|
$this->cached_props[$request_id] = $result;
|
|
return $result;
|
|
}
|
|
|
|
function get($uri)
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
return $this->http_client->Get($uri);
|
|
}
|
|
|
|
/*!
|
|
@function get_body
|
|
@abstract return the response body
|
|
@result string body content
|
|
@discussion
|
|
invoke it after a Get() call for instance, to retrieve the response
|
|
*/
|
|
function get_body()
|
|
{
|
|
return $this->http_client->getBody();
|
|
}
|
|
|
|
/*!
|
|
@function get_headers
|
|
@abstract return the response headers
|
|
@result array headers received from server in the form headername => value
|
|
@discussion
|
|
to be called after a Get() or Head() call
|
|
*/
|
|
function get_headers()
|
|
{
|
|
return $this->http_client->getHeaders();
|
|
}
|
|
|
|
/*!
|
|
@function copy
|
|
@abstract PUT is the method to sending a file on the server.
|
|
@param uri the location of the file on the server. dont forget the heading "/"
|
|
@param data the content of the file. binary content accepted
|
|
@result string response status code 201 (Created) if ok
|
|
*/
|
|
function put($uri, $data, $token='')
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
if (strlen($token))
|
|
{
|
|
$this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
|
|
}
|
|
|
|
$this->cached_props = array();
|
|
$result = $this->http_client->Put($uri, $data);
|
|
$this->http_client->removeHeader('If');
|
|
return $result;
|
|
}
|
|
|
|
/*!
|
|
@function copy
|
|
@abstract Copy a file -allready on the server- into a new location
|
|
@param srcUri the current file location on the server. dont forget the heading "/"
|
|
@param destUri the destination location on the server. this is *not* a full URL
|
|
@param overwrite boolean - true to overwrite an existing destination - overwrite by default
|
|
@result Returns the HTTP status code
|
|
@discussion
|
|
returns response status code 204 (Unchanged) if ok
|
|
*/
|
|
function copy( $srcUri, $destUri, $overwrite=true, $scope=0, $token='')
|
|
{
|
|
$srcUri = $this->encodeurl($srcUri);
|
|
$destUri = $this->encodeurl($destUri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
if (strlen($token))
|
|
{
|
|
$this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
|
|
}
|
|
$this->cached_props = array();
|
|
$result = $this->http_client->Copy( $srcUri, $destUri, $overwrite, $scope);
|
|
$this->http_client->removeHeader('If');
|
|
return $result;
|
|
}
|
|
|
|
/*!
|
|
@function move
|
|
@abstract Moves a WEBDAV resource on the server
|
|
@param srcUri the current file location on the server. dont forget the heading "/"
|
|
@param destUri the destination location on the server. this is *not* a full URL
|
|
@param overwrite boolean - true to overwrite an existing destination (default is yes)
|
|
@result Returns the HTTP status code
|
|
@discussion
|
|
returns response status code 204 (Unchanged) if ok
|
|
*/
|
|
function move( $srcUri, $destUri, $overwrite=true, $scope=0, $token='' )
|
|
{
|
|
$srcUri = $this->encodeurl($srcUri);
|
|
$destUri = $this->encodeurl($destUri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
if (strlen($token))
|
|
{
|
|
$this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
|
|
}
|
|
$this->cached_props = array();
|
|
$result = $this->http_client->Move( $srcUri, $destUri, $overwrite, $scope);
|
|
$this->http_client->removeHeader('If');
|
|
return $result;
|
|
}
|
|
|
|
/*!
|
|
@function delete
|
|
@abstract Deletes a WEBDAV resource
|
|
@param uri The URI we are deleting
|
|
@result Returns the HTTP status code
|
|
@discussion
|
|
returns response status code 204 (Unchanged) if ok
|
|
*/
|
|
function delete( $uri, $scope=0, $token='')
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
if (strlen($token))
|
|
{
|
|
$this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
|
|
}
|
|
|
|
$this->cached_props = array();
|
|
$result = $this->http_client->Delete( $uri, $scope);
|
|
$this->http_client->removeHeader('If');
|
|
return $result;
|
|
}
|
|
|
|
/*!
|
|
@function mkcol
|
|
@abstract Creates a WEBDAV collection (AKA a directory)
|
|
@param uri The URI to create
|
|
@result Returns the HTTP status code
|
|
*/
|
|
function mkcol( $uri, $token='' )
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
if (strlen($token))
|
|
{
|
|
$this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
|
|
}
|
|
$this->cached_props = array();
|
|
return $this->http_client->MkCol( $uri );
|
|
$this->http_client->removeHeader('If');
|
|
}
|
|
|
|
/*!
|
|
@function propfind
|
|
@abstract Queries WEBDAV properties
|
|
@param uri uri of resource whose properties we are changing
|
|
@param scope Specifies how "deep" to search (0=just this file/dir 1=subfiles/dirs etc)
|
|
@result Returns the HTTP status code
|
|
@discussion
|
|
to get the result XML call get_body()
|
|
*/
|
|
function propfind( $uri, $scope=0 )
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
return $this->http_client->PropFind( $uri, $scope);
|
|
}
|
|
/*!
|
|
@function proppatch
|
|
@abstract Sets DAV properties
|
|
@param uri uri of resource whose properties we are changing
|
|
@param attributes An array of attributes and values.
|
|
@param namespaces Extra namespace definitions that apply to the properties
|
|
@result Returns the HTTP status code
|
|
@discussion
|
|
To make DAV properties useful it helps to use a well established XML dialect
|
|
such as the "Dublin Core"
|
|
|
|
*/
|
|
function proppatch($uri, $attributes, $namespaces='', $token='')
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
if (strlen($token))
|
|
{
|
|
$this->http_client->addHeader('If', '<'.$uri.'>'.' (<'.$token.'>)');
|
|
}
|
|
$this->cached_props = array();
|
|
//Begin evil nastiness
|
|
$davxml = '<?xml version="1.0" encoding="utf-8" ?>
|
|
<D:propertyupdate xmlns:D="DAV:"';
|
|
|
|
if ($namespaces)
|
|
{
|
|
$davxml .= ' ' . $namespaces;
|
|
}
|
|
$davxml .= ' >';
|
|
foreach ($attributes as $name => $value)
|
|
{
|
|
$davxml .= '
|
|
<D:set>
|
|
<D:prop>
|
|
<'.$name.'>'.utf8_encode(htmlspecialchars($value)).'</'.$name.'>
|
|
</D:prop>
|
|
</D:set>
|
|
';
|
|
}
|
|
$davxml .= '
|
|
</D:propertyupdate>';
|
|
|
|
if (DEBUG_DAV_XML) {
|
|
echo '<b>send</b><pre>'.htmlentities($davxml).'</pre>';
|
|
}
|
|
$this->http_client->requestBody = $davxml;
|
|
if( $this->http_client->sendCommand( 'PROPPATCH '.$uri.' HTTP/1.1' ) )
|
|
{
|
|
$this->http_client->processReply();
|
|
}
|
|
|
|
if (DEBUG_DAV_XML) {
|
|
echo '<b>Recieve</b><pre>'.htmlentities($this->http_client->getBody()).'</pre>';
|
|
}
|
|
$this->http_client->removeHeader('If');
|
|
return $this->http_client->reply;
|
|
}
|
|
|
|
/*!
|
|
@function unlock
|
|
@abstract unlocks a locked resource on the DAV server
|
|
@param uri uri of the resource we are unlocking
|
|
@param a 'token' for the lock (to get the token, do a propfind)
|
|
@result true if successfull
|
|
@discussion
|
|
Not all DAV servers support locking (its in the RFC, but many common
|
|
DAV servers only implement "DAV class 1" (no locking)
|
|
*/
|
|
|
|
function unlock($uri, $token)
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
$this->cached_props = array();
|
|
$this->http_client->addHeader('Lock-Token', '<'.$token.'>');
|
|
$this->http_client->sendCommand( 'UNLOCK '.$uri.' HTTP/1.1');
|
|
$this->http_client->removeHeader('Lock-Token');
|
|
$this->http_client->processReply();
|
|
if ( $this->http_client->reply == '204')
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
$headers = $this->http_client->getHeaders();
|
|
echo $this->http_client->getBody();
|
|
if ($headers['Content-Type'] == 'text/html')
|
|
{
|
|
echo $this->http_client->getBody();
|
|
die();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@function lock
|
|
@abstract locks a resource on the DAV server
|
|
@param uri uri of the resource we are locking
|
|
@param owner the 'owner' information for the lock (purely informative)
|
|
@param depth the depth to which we lock collections
|
|
@result true if successfull
|
|
@discussion
|
|
Not all DAV servers support locking (its in the RFC, but many common
|
|
DAV servers only implement "DAV class 1" (no locking)
|
|
*/
|
|
function lock($uri, $owner, $depth=0, $timeout='infinity')
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if (DEBUG_CACHE) echo '<b>cache cleared</b>';
|
|
$this->cached_props = array();
|
|
$body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>
|
|
<D:lockinfo xmlns:D='DAV:'>
|
|
<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>
|
|
<D:owner><D:href>$owner</D:href></D:owner>
|
|
</D:lockinfo>\n";
|
|
|
|
$this->http_client->requestBody = utf8_encode( $body );
|
|
$this->http_client->addHeader('Depth', $depth);
|
|
if (! (strtolower(trim($timeout)) == 'infinite'))
|
|
{
|
|
$timeout = 'Second-'.$timeout;
|
|
}
|
|
$this->http_client->addHeader('Timeout', $timeout);
|
|
|
|
if( $this->http_client->sendCommand( "LOCK $uri HTTP/1.1" ) )
|
|
$this->http_client->processReply();
|
|
$this->http_client->removeHeader('timeout');
|
|
if ( $this->http_client->reply == '200')
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
$headers = $this->http_client->getHeaders();
|
|
echo $this->http_client->getBody();
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
/*!
|
|
@function options
|
|
@abstract determines the optional HTTP features supported by a server
|
|
@param uri uri of the resource we are seeking options for (or * for the whole server)
|
|
@result Returns an array of option values
|
|
@discussion
|
|
Interesting options include "ACCESS" (whether you can read a file) and
|
|
DAV (DAV features)
|
|
|
|
*/
|
|
function options($uri)
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
if( $this->http_client->sendCommand( 'OPTIONS '.$uri.' HTTP/1.1' ) == '200' )
|
|
{
|
|
$this->http_client->processReply();
|
|
$headers = $this->http_client->getHeaders();
|
|
return $headers;
|
|
}
|
|
else
|
|
{
|
|
return False;
|
|
}
|
|
}
|
|
/*!
|
|
@function dav_features
|
|
@abstract determines the features of a DAV server
|
|
@param uri uri of resource whose properties we are changing
|
|
@result Returns an array of option values
|
|
@discussion
|
|
Likely return codes include NULL (this isnt a dav server!), 1
|
|
(This is a dav server, supporting all standard DAV features except locking)
|
|
2, (additionally supports locking (should also return 1)) and
|
|
'version-control' (this server supports versioning extensions for this resource)
|
|
*/
|
|
function dav_features($uri)
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
$options = $this->options($uri);
|
|
$dav_options = $options['DAV'];
|
|
if ($dav_options)
|
|
{
|
|
$features=explode(',', $dav_options);
|
|
}
|
|
else
|
|
{
|
|
$features = NULL;
|
|
}
|
|
return $features;
|
|
}
|
|
/**************************************************************
|
|
RFC 3253 DeltaV versioning extensions
|
|
**************************************************************
|
|
These are 100% untested, and almost certainly dont work yet...
|
|
eventually they will be made to work with subversion...
|
|
*/
|
|
|
|
/*!
|
|
@function report
|
|
@abstract Report is a kind of extended PROPFIND - it queries properties accros versions etc
|
|
@param uri uri of resource whose properties we are changing
|
|
@param report the type of report desired eg DAV:version-tree, DAV:expand-property etc (see http://greenbytes.de/tech/webdav/rfc3253.html#METHOD_REPORT)
|
|
@param namespace any extra XML namespaces needed for the specified properties
|
|
@result Returns an array of option values
|
|
@discussion
|
|
From the relevent RFC:
|
|
"A REPORT request is an extensible mechanism for obtaining information about
|
|
a resource. Unlike a resource property, which has a single value, the value
|
|
of a report can depend on additional information specified in the REPORT
|
|
request body and in the REPORT request headers."
|
|
*/
|
|
function report($uri, $report, $properties, $namespaces='')
|
|
{
|
|
$uri = $this->encodeurl($uri);
|
|
$davxml = '<?xml version="1.0" encoding="utf-8" ?>
|
|
<D:'.$report . 'xmlns:D="DAV:"';
|
|
if ($namespaces)
|
|
{
|
|
$davxml .= ' ' . $namespaces;
|
|
}
|
|
$davxml .= ' >
|
|
<D:prop>';
|
|
foreach($properties as $property)
|
|
{
|
|
$davxml .= '<'.$property.'/>\n';
|
|
}
|
|
$davxml .= '\t<D:/prop>\n<D:/'.$report.'>';
|
|
if (DEBUG_DAV_XML) {
|
|
echo '<b>send</b><pre>'.htmlentities($davxml).'</pre>';
|
|
}
|
|
$this->http_client->requestBody = $davxml;
|
|
if( $this->http_client->sendCommand( 'REPORT '.$uri.' HTTP/1.1' ) )
|
|
{
|
|
$this->http_client->processReply();
|
|
}
|
|
|
|
if (DEBUG_DAV_XML) {
|
|
echo '<b>Recieve</b><pre>'.htmlentities($this->http_client->getBody()).'</pre>';
|
|
}
|
|
return $this->http_client->reply;
|
|
}
|
|
|
|
}
|
|
|