<?php
require_once "HTTP/Request2.php";
require "HTTP/WebDAV/Tools/_parse_propfind_response.php";
require "HTTP/WebDAV/Tools/_parse_lock_response.php";
// WebDAV defines some addition HTTP methods
define('HTTP_REQUEST_METHOD_COPY', 'COPY');
define('HTTP_REQUEST_METHOD_MOVE', 'MOVE');
define('HTTP_REQUEST_METHOD_MKCOL', 'MKCOL');
define('HTTP_REQUEST_METHOD_PROPFIND', 'PROPFIND');
define('HTTP_REQUEST_METHOD_PROPPATCH', 'PROPPATCH');
define('HTTP_REQUEST_METHOD_LOCK', 'LOCK');
define('HTTP_REQUEST_METHOD_UNLOCK', 'UNLOCK');
/**
* A stream wrapper class for WebDAV access
*
* @access public
*/
class HTTP_WebDAV_Client_Stream
{
/**
* User-Agent: header string
*
* @access private
* @var string
*/
var $userAgent = "PEAR::HTTP_WebDAV_Client";
/**
* Content-type: header string
*
* @access private
* @var string
*/
var $contentType = "application/octet-stream";
/**
* The http or https resource URL
*
* @access private
* @var string url
*/
var $url = false;
/**
* The resource URL path
*
* @access private
* @var string path
*/
var $path = false;
/**
* File position indicator
*
* @access private
* @var int offset in bytes
*/
var $position = 0;
/**
* File status information cache
*
* @access private
* @var array stat information
*/
var $stat = array();
/**
* User name for authentication
*
* @access private
* @var string name
*/
var $user = false;
/**
* Password for authentication
*
* @access private
* @var string password
*/
var $pass = false;
/**
* WebDAV protocol levels supported by the server
*
* @access private
* @var array level entries
*/
var $dav_level = array();
/**
* HTTP methods supported by the server
*
* @access private
* @var array method entries
*/
var $dav_allow = array();
/**
* Directory content cache
*
* @access private
* @var array filename entries
*/
var $dirfiles = false;
/**
* Current readdir() position
*
* @access private
* @var int
*/
var $dirpos = 0;
/**
* Remember if end of file was reached
*
* @access private
* @var bool
*/
var $eof = false;
/**
* Lock token
*
* @access private
* @var string
*/
var $locktoken = false;
var $stream_write_returned_recoverable_error = false;
var $stream_write_final = false;
var $stream_write_returned_for_final_write = false;
/**
* Stream wrapper interface open() method
*
* @access public
* @var string resource URL
* @var string mode flags
* @var array not used here
* @var string return real path here if suitable
* @return bool true on success
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
global $updraftplus;
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
$writing = preg_match('|[aw\+]|', $mode);
// query server for WebDAV options
if (!$this->_check_options()) {
if ($writing) {
// Retry on the directory instead of on the file itself
$old_url = $this->url;
$this->url = dirname($this->url);
if (!$this->_check_options()) {
$this->url = $old_url;
$updraftplus->log('Failed to check WebDAV server options');
return false;
}
$this->url = $old_url;
} else {
$updraftplus->log('Failed to check WebDAV server options');
return false;
}
}
try {
// now get the file metadata
// we only need type, size, creation and modification date
$req = $this->_startRequest(HTTP_REQUEST_METHOD_PROPFIND);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
$req->setHeader('Depth', "0");
$req->setHeader('Content-type', 'text/xml');
$req->setBody('<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<resourcetype/>
<getcontentlength/>
<getlastmodified />
<creationdate/>
</prop>
</propfind>
');
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
// check the response code, anything but 207 indicates a problem
switch ($result->getStatus()) {
case 207: // OK
// now we have to parse the result to get the status info items
#$propinfo = &new HTTP_WebDAV_Client_parse_propfind_response($result->getBody());
$propinfo = new HTTP_WebDAV_Client_parse_propfind_response($result->getBody());
$this->stat = $propinfo->stat();
unset($propinfo);
break;
case 404: // not found is ok in write modes
if (preg_match('|[aw\+]|', $mode)) {
break; // write
}
$this->eof = true;
// else fallthru
// case 405: // method disabled. In write mode, try to carry on.
// if (preg_match('|[aw\+]|', $mode)) {
// break; // write
// }
// $this->eof = true;
// N.B. Some 404s drop also through to here
default:
// Log only if the condition was not expected
global $updraftplus_404_should_be_logged;
if ((isset($updraftplus_404_should_be_logged) && $updraftplus_404_should_be_logged) || !isset($updraftplus_404_should_be_logged)) {
trigger_error("file not found: ".$result->getStatus());
}
return false;
}
// 'w' -> open for writing, truncate existing files
if (strpos($mode, "w") !== false) {
try {
$req = $this->_startRequest(HTTP_Request2::METHOD_PUT);
$req->setHeader('Content-length', 0);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
$req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
}
// 'a' -> open for appending
if (strpos($mode, "a") !== false) {
$this->position = (!empty($this->stat['size'])) ? $this->stat['size'] : 0;
$this->eof = true;
}
// we are done :)
return true;
}
/**
* Stream wrapper interface close() method
*
* @access public
*/
public function stream_close()
{
global $updraftplus, $updraftplus_webdav_filepath;
if ((defined('UPDRAFTPLUS_WEBDAV_NEVER_CHUNK') && UPDRAFTPLUS_WEBDAV_NEVER_CHUNK && true === $this->stream_write_returned_for_final_write) || $this->stream_write_returned_recoverable_error) {
if (!empty($updraftplus_webdav_filepath) && is_readable($updraftplus_webdav_filepath)) {
$this->position = 0;
$this->stream_write_returned_for_final_write = false;
$this->stream_write_final = true;
if (false === $this->stream_write(file_get_contents($updraftplus_webdav_filepath))) {
$this->stream_write_final = false;
$updraftplus->log('WebDAV: All-in-one write failed');
// The return result is ignored; so, we throw an exception instead
throw new Exception('WebDAV: All-in-one write failed');
return false;
} else {
$updraftplus->log('WebDAV: All-in-one write succeeded');
}
$this->stream_write_final = false;
} else {
$updraftplus->log("File not readable: $updraftplus_webdav_filepath");
throw new Exception("File not readable: $updraftplus_webdav_filepath");
return false;
}
}
// unlock?
if ($this->locktoken) {
$this->stream_lock(LOCK_UN);
}
// closing is simple as HTTP is stateless
$this->url = false;
}
/**
* Stream wrapper interface stat() method
*
* @access public
* @return array stat entries
*/
public function stream_stat()
{
// we already have collected the needed information
// in stream_open() :)
return $this->stat;
}
/**
* Stream wrapper interface read() method
*
* @access public
* @param int requested byte count
* @return string read data
*/
public function stream_read($count)
{
// do some math
$start = $this->position;
$end = $start + $count - 1;
try {
// create a GET request with a range
$req = $this->_startRequest(HTTP_Request2::METHOD_GET);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
$req->setHeader("Range", "bytes=$start-$end");
// go! go! go!
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
$data = $result->getBody();
$len = strlen($data);
// lets see what happened
switch ($result->getStatus()) {
case 200:
// server doesn't support range requests
// TODO we should add some sort of cacheing here
$data = substr($data, $start, $count);
break;
case 206:
// server supports range requests
break;
case 416:
// reading beyond end of file is not an error
$data = "";
$len = 0;
break;
default:
return false;
}
// no data indicates end of file
if (!$len) {
$this->eof = true;
}
// update position
$this->position += $len;
// thats it!
return $data;
}
/**
* Stream wrapper interface write() method
*
* @access public
* @param string data to write
* @return int number of bytes actually written
*/
public function stream_write($buffer)
{
// do some math
$start = $this->position;
$end = $this->position + strlen($buffer) - 1;
if (((defined('UPDRAFTPLUS_WEBDAV_NEVER_CHUNK') && UPDRAFTPLUS_WEBDAV_NEVER_CHUNK) || $this->stream_write_returned_recoverable_error) && !$this->stream_write_final) {
$this->position += strlen($buffer);
$this->stream_write_returned_for_final_write = true;
return 1 + $end - $start;
}
$method = ($start > 0 && defined('UPDRAFTPLUS_WEBDAV_USE_SABRE_APPEND') && UPDRAFTPLUS_WEBDAV_USE_SABRE_APPEND) ? 'PATCH' : HTTP_Request2::METHOD_PUT;
try {
// create a partial PUT request
$req = $this->_startRequest($method);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
if (defined('UPDRAFTPLUS_WEBDAV_USE_SABRE_APPEND') && UPDRAFTPLUS_WEBDAV_USE_SABRE_APPEND) {
if ($start>0) {
$req->setHeader('Content-Type', 'application/x-sabredav-partialupdate');
$req->setHeader("X-Update-Range", "append");
}
} else {
# Special hack to drop Content-Range header for the test file
if (($start>0 || $end>8) && !$this->stream_write_final) $req->setHeader("Content-Range", "bytes $start-$end/*");
}
if ($this->locktoken) {
$req->setHeader("If", "(<{$this->locktoken}>)");
}
$req->setBody($buffer);
// go! go! go!
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
// check result
switch ($result->getStatus()) {
case 200:
case 201:
case 204:
$this->position += strlen($buffer);
return 1 + $end - $start;
// New in UD 1.11.13 for ownCloud 8.1.? (strictly, the version of SabreDav in it)
/*
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
<s:exception>Sabre\DAV\Exception\BadRequest</s:exception>
<s:message>Content-Range on PUT requests are forbidden.</s:message>
</d:error>
*/
case 400:
global $updraftplus, $updraftplus_webdav_filepath;
if (false !== strpos($result->getBody(), 'Content-Range') && !empty($updraftplus_webdav_filepath)) {
$updraftplus->log('WebDAV server returned 400 due to Content-Range issue; will try all-at-once method');
$this->stream_write_returned_recoverable_error = true;
# You lie!
return 1 + $end - $start;
} else {
trigger_error("Unexpected HTTP response code: ".$result->getStatus());
return false;
}
case 501:
global $updraftplus, $updraftplus_webdav_filepath;
if (!empty($updraftplus_webdav_filepath)) {
$updraftplus->log('WebDAV server returned 501; probably does not support Content-Range; will try all-at-once method');
$this->stream_write_returned_recoverable_error = true;
# You lie!
return 1 + $end - $start;
} else {
return false;
}
default:
trigger_error("Unexpected HTTP response code: ".$result->getStatus());
return false;
}
/*
We do not cope with servers that do not support partial PUTs!
And we do assume that a server does conform to the following
rule from RFC 2616 Section 9.6:
"The recipient of the entity MUST NOT ignore any Content-*
(e.g. Content-Range) headers that it does not understand or
implement and MUST return a 501 (Not Implemented) response
in such cases."
So the worst case scenario with a compliant server not
implementing partial PUTs should be a failed request. A
server simply ignoring "Content-Range" would replace
file contents with the request body instead of putting
the data at the requested place but we can blame it
for not being compliant in this case ;)
(TODO: maybe we should do a HTTP version check first?)
we *could* emulate partial PUT support by adding local
cacheing but for now we don't want to as it adds a lot
of complexity and storage overhead to the client ...
*/
}
/**
* Stream wrapper interface eof() method
*
* @access public
* @return bool true if end of file was reached
*/
public function stream_eof()
{
// another simple one
return $this->eof;
}
/**
* Stream wrapper interface tell() method
*
* @access public
* @return int current file position
*/
public function stream_tell()
{
// just return the current position
return $this->position;
}
/**
* Stream wrapper interface seek() method
*
* @access public
* @param int position to seek to
* @param int seek mode
* @return bool true on success
*/
public function stream_seek($pos, $whence)
{
switch ($whence) {
case SEEK_SET:
// absolute position
$this->position = $pos;
break;
case SEEK_CUR:
// relative position
$this->position += $pos;
break;
case SEEK_END:
// relative position form end
$this->position = $this->stat['size'] + $pos;
break;
default:
return false;
}
// TODO: this is rather naive (check how libc handles this)
$this->eof = false;
return true;
}
/**
* Stream wrapper interface URL stat() method
*
* @access public
* @param string URL to get stat information for
* @return array stat information
*/
public function url_stat($url)
{
// we map this one to open()/stat()/close()
// there won't be much gain in inlining this
if (!$this->stream_open($url, "r", array(), $dummy)) {
return false;
}
$stat = $this->stream_stat();
$this->stream_close();
return $stat;
}
/**
* Stream wrapper interface opendir() method
*
* @access public
* @param string directory resource URL
* @param array not used here
* @return bool true on success
*/
public function dir_opendir($path, $options)
{
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
// query server for WebDAV options
if (!$this->_check_options()) return false;
if (!isset($this->dav_allow[HTTP_REQUEST_METHOD_PROPFIND])) {
return false;
}
try {
// now read the directory
$req = $this->_startRequest(HTTP_REQUEST_METHOD_PROPFIND);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
$req->setHeader("Depth", "1");
$req->setHeader("Content-Type", "text/xml");
$req->setBody('<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<resourcetype/>
<getcontentlength/>
<creationdate/>
<getlastmodified/>
</prop>
</propfind>
');
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
switch ($result->getStatus()) {
case 207: // multistatus content
$this->dirfiles = array();
$this->dirpos = 0;
// for all returned resource entries
foreach (explode("\n", $result->getBody()) as $line) {
// Preg_match_all if the whole response is one line!
if (preg_match_all("/href>([^<]*)/", $line, $matches)) {
// skip the directory itself
foreach ($matches[1] as $match){
// Compare to $this->url too
if ($match == "" || $match == $this->path || $match == $this->url) {
continue;
}
// just remember the basenames to return them later with readdir()
$this->dirfiles[] = basename($match);
}
}
}
return true;
default:
// any other response state indicates an error
trigger_error("file not found");
return false;
}
}
/**
* Stream wrapper interface readdir() method
*
* @access public
* @return string filename
*/
public function dir_readdir()
{
// bailout if directory is empty
if (!is_array($this->dirfiles)) {
return false;
}
// bailout if we already reached end of dir
if ($this->dirpos >= count($this->dirfiles)) {
return false;
}
// return an entry and move on
return $this->dirfiles[$this->dirpos++];
}
/**
* Stream wrapper interface rewinddir() method
*
* @access public
*/
public function dir_rewinddir()
{
// bailout if directory content info has already
// been freed
if (!is_array($this->dirfiles)) {
return false;
}
// rewind to first entry
$this->dirpos = 0;
}
/**
* Stream wrapper interface closedir() method
*
* @access public
*/
public function dir_closedir()
{
// free stored directory content
if (is_array($this->dirfiles)) {
$this->dirfiles = false;
$this->dirpos = 0;
}
}
/**
* Stream wrapper interface mkdir() method
*
* @access public
* @param string collection URL to be created
* @return bool true on access
*/
public function mkdir($path)
{
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
// query server for WebDAV options
if (!$this->_check_options()) return false;
try {
$req = $this->_startRequest(HTTP_REQUEST_METHOD_MKCOL);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
if ($this->locktoken) {
$req->setHeader("If", "(<{$this->locktoken}>)");
}
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
// check the response code, anything but 201 indicates a problem
$stat = $result->getStatus();
switch ($stat) {
case 201:
return true;
default:
trigger_error("mkdir failed - ". $stat);
return false;
}
}
/**
* Stream wrapper interface rmdir() method
*
* @access public
* @param string collection URL to be created
* @return bool true on access
*/
public function rmdir($path)
{
// TODO: this should behave like "rmdir", currently it is more like "rm -rf"
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
// query server for WebDAV options
if (!$this->_check_options()) return false;
try {
$req = $this->_startRequest(HTTP_Request2::METHOD_DELETE);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
if ($this->locktoken) {
$req->setHeader("If", "(<{$this->locktoken}>)");
}
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
// check the response code, anything but 204 indicates a problem
$stat = $result->getStatus();
switch ($stat) {
case 204:
return true;
default:
trigger_error("rmdir failed - ". $stat);
return false;
}
}
/**
* Stream wrapper interface rename() method
*
* @access public
* @param string resource URL to be moved
* @param string resource URL to move to
* @return bool true on access
*/
public function rename($path, $new_path)
{
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
// query server for WebDAV options
if (!$this->_check_options()) return false;
try {
$req = $this->_startRequest(HTTP_REQUEST_METHOD_MOVE);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
if ($this->locktoken) {
$req->setHeader("If", "(<{$this->locktoken}>)");
}
if (!$this->_parse_url($new_path)) return false;
$req->setHeader("Destination", $this->url);
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
// check the response code, anything but 207 indicates a problem
$stat = $result->getStatus();
switch ($stat) {
case 201:
case 204:
return true;
default:
trigger_error("rename failed - ". $stat);
return false;
}
}
/**
* Stream wrapper interface unlink() method
*
* @access public
* @param string resource URL to be removed
* @return bool true on success
*/
public function unlink($path)
{
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
// query server for WebDAV options
if (!$this->_check_options()) return false;
// is DELETE supported?
if (!isset($this->dav_allow[HTTP_Request2::METHOD_DELETE])) {
return false;
}
try {
$req = $this->_startRequest(HTTP_Request2::METHOD_DELETE);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
if ($this->locktoken) {
$req->setHeader("If", "(<{$this->locktoken}>)");
}
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
switch ($result->getStatus()) {
case 204: // ok
return true;
default:
return false;
}
}
/**
* Static helper that registers the wrappers
*
* @access public, static
* @return bool true on success (even if SSL doesn't work)
*/
public static function register()
{
// check that we have the required feature
if (!function_exists("stream_register_wrapper")) {
return false;
}
// try to register the non-encrypted WebDAV wrapper
if (!stream_register_wrapper("webdav", "HTTP_WebDAV_Client_Stream")) {
return false;
}
// now try to register the SSL protocol variant
// it is not critical if this fails
// TODO check whether SSL is possible with HTTP_Request
stream_register_wrapper("webdavs", "HTTP_WebDAV_Client_Stream");
return true;
}
/**
* Helper function for URL analysis
*
* @access private
* @param string original request URL
* @return bool true on success else false
*/
private function _parse_url($path)
{
// rewrite the WebDAV url as a plain HTTP url
$url = parse_url($path);
// detect whether plain or SSL-encrypted transfer is requested
$scheme = $url['scheme'];
switch ($scheme) {
case "webdav":
$url['scheme'] = "http";
break;
case "webdavs":
$url['scheme'] = "https";
break;
default:
trigger_error("only 'webdav:' and 'webdavs:' are supported, not '$url[scheme]:'");
return false;
}
if (isset($this->context)) {
// extract settings from stream context
$context = stream_context_get_options($this->context);
// User-Agent
if (isset($context[$scheme]['user_agent'])) {
$this->userAgent = $context[$scheme]['user_agent'];
}
// Content-Type
if (isset($context[$scheme]['content_type'])) {
$this->contentType = $context[$scheme]['content_type'];
}
// TODO check whether to implement other HTTP specific
// context settings from http://php.net/manual/en/context.http.php
}
// if a TCP port is specified we have to add it after the host
if (isset($url['port'])) {
$url['host'] .= ":$url[port]";
}
// store the plain path for possible later use
$this->path = $url["path"];
// now we can put together the new URL
$this->url = "$url[scheme]://$url[host]$url[path]";
// extract authentication information
if (isset($url['user'])) {
$this->user = urldecode($url['user']);
}
if (isset($url['pass'])) {
$this->pass = urldecode($url['pass']);
}
return true;
}
/**
* Helper function for WebDAV OPTIONS detection
*
* @access private
* @return bool true on success else false
*/
private function _check_options()
{
try {
// now check OPTIONS reply for WebDAV response headers
$req = $this->_startRequest(HTTP_Request2::METHOD_OPTIONS);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
if ($result->getStatus() != 200) {
// If the status is 301 we want to return false so the calling code can deal with it but not trigger any errors on the front end
if ($result->getStatus() != 301) trigger_error($result->getStatus() . ' returned when checking WebDAV server options using URL: ' . $this->url . ' response: ' . json_encode($result->getBody()));
return false;
}
// get the supported DAV levels and extensions
$dav = $result->getHeader("DAV");
$this->dav_level = array();
foreach (explode(",", $dav) as $level) {
$this->dav_level[trim($level)] = true;
}
if (!isset($this->dav_level["1"])) {
// we need at least DAV Level 1 conformance
trigger_error('WebDAV server must be at least DAV level 1 conformance');
return false;
}
// get the supported HTTP methods
// TODO these are not checked for WebDAV compliance yet
$allow = $result->getHeader("Allow");
$this->dav_allow = array();
foreach (explode(",", $allow) as $method) {
$this->dav_allow[trim($method)] = true;
}
// TODO check for required WebDAV methods
return true;
}
/**
* Stream handler interface lock() method (experimental ...)
*
* @access private
* @return bool true on success else false
*/
private function stream_lock($mode)
{
/* TODO:
- think over how to refresh locks
*/
$ret = false;
// LOCK is only supported by DAV Level 2
if (!isset($this->dav_level["2"])) {
return false;
}
switch ($mode & ~LOCK_NB) {
case LOCK_UN:
if ($this->locktoken) {
try {
$req = $this->_startRequest(HTTP_REQUEST_METHOD_UNLOCK);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
$req->setHeader("Lock-Token", "<{$this->locktoken}>");
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
$ret = $result->getStatus() == 204;
}
break;
case LOCK_SH:
case LOCK_EX:
$body = sprintf('<?xml version="1.0" encoding="utf-8" ?>
<D:lockinfo xmlns:D="DAV:">
<D:lockscope><D:%s/></D:lockscope>
<D:locktype><D:write/></D:locktype>
<D:owner>%s</D:owner>
</D:lockinfo>',
($mode & LOCK_SH) ? "shared" : "exclusive",
get_class($this)); // TODO better owner string
try {
$req = $this->_startRequest(HTTP_REQUEST_METHOD_LOCK);
if (is_string($this->user)) {
$req->setAuth($this->user, @$this->pass);
}
if ($this->locktoken) { // needed for refreshing a lock
$req->setHeader("Lock-Token", "<{$this->locktoken}>");
}
$req->setHeader("Timeout", "Infinite, Second-4100000000");
$req->setHeader("Content-Type", 'text/xml; charset="utf-8"');
$req->setBody($body);
$result = $req->send();
} catch (Exception $e) {
if (preg_match("/Malformed response: /i", $e->getMessage(), $matches)) {
return $this->_check_options();
}
throw $e;
}
$ret = $result->getStatus() == 200;
if ($ret) {
#$propinfo = &new HTTP_WebDAV_Client_parse_lock_response($result->getBody());
$propinfo = new HTTP_WebDAV_Client_parse_lock_response($result->getBody());
$this->locktoken = $propinfo->locktoken;
// TODO deal with timeout
}
break;
default:
break;
}
return $ret;
}
private function _startRequest($method)
{
#$req = &new HTTP_Request($this->url);
$req = new HTTP_Request2($this->url);
// We need to set this to fix a bug in an old nginx as it sends a response body for HEAD requests which is a violation of RFC 2616 and fixed in newer nginx versions (https://pear.php.net/bugs/bug.php?id=20227)
$req->setHeader('Accept-Encoding', 'identity');
$req->setHeader('User-agent', $this->userAgent);
$req->setHeader('Content-type', $this->contentType);
$req->setMethod($method);
return $req;
}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode:nil
* End:
*/