Source for file Stream.php
Documentation is available at Stream.php
require_once "HTTP/Request.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', true );
define('HTTP_REQUEST_METHOD_MOVE', 'MOVE', true );
define('HTTP_REQUEST_METHOD_MKCOL', 'MKCOL', true );
define('HTTP_REQUEST_METHOD_PROPFIND', 'PROPFIND', true );
define('HTTP_REQUEST_METHOD_PROPPATCH', 'PROPPATCH', true );
define('HTTP_REQUEST_METHOD_LOCK', 'LOCK', true );
define('HTTP_REQUEST_METHOD_UNLOCK', 'UNLOCK', true );
* A stream wrapper class for WebDAV access
class HTTP_WebDAV_Client_Stream
* User-Agent: header string
var $userAgent = "PEAR::HTTP_WebDAV_Client";
* Content-type: header string
var $contentType = "application/octet-stream";
* The http or https resource URL
* File position indicator
* @var int offset in bytes
* File status information cache
* @var array stat information
* User name for authentication
* Password for authentication
* WebDAV protocol levels supported by the server
* @var array level entries
var $dav_level = array ();
* HTTP methods supported by the server
* @var array method entries
var $dav_allow = array ();
* Directory content cache
* @var array filename entries
* Current readdir() position
* Remember if end of file was reached
* Stream wrapper interface open() method
* @var string resource URL
* @var array not used here
* @var string return real path here if suitable
* @return bool true on success
function stream_open ($path, $mode, $options, &$opened_path)
// rewrite the request URL
if (!$this->_parse_url ($path)) return false;
// query server for WebDAV options
if (!$this->_check_options ()) return false;
// now get the file metadata
// we only need type, size, creation and modification date
$req->setBasicAuth ($this->user, @$this->pass);
$req->addHeader ("Depth", "0");
$req->addHeader ("Content-Type", "text/xml");
$req->addRawPostData ('<?xml version="1.0" encoding="utf-8"?>
// check the response code, anything but 207 indicates a problem
switch ($req->getResponseCode ()) {
// now we have to parse the result to get the status info items
$propinfo = &new HTTP_WebDAV_Client_parse_propfind_response ($req->getResponseBody ());
$this->stat = $propinfo->stat ();
case 404: // not found is ok in write modes
error_log("file not found: ". $req->getResponseCode ());
// 'w' -> open for writing, truncate existing files
if (strpos($mode, "w") !== false ) {
$req = &$this->_startRequest (HTTP_REQUEST_METHOD_PUT );
$req->addHeader ('Content-length', 0 );
$req->setBasicAuth ($this->user, @$this->pass);
// 'a' -> open for appending
if (strpos($mode, "a") !== false ) {
$this->position = $this->stat['size'];
* Streap wrapper interface close() method
$this->stream_lock (LOCK_UN );
// closing is simple as HTTP is stateless
* Stream wrapper interface stat() method
* @return array stat entries
// we already have collected the needed information
* Stream wrapper interface read() method
* @param int requested byte count
* @return string read data
function stream_read ($count)
$start = $this->position;
$end = $start + $count - 1;
// create a GET request with a range
$req = &$this->_startRequest (HTTP_REQUEST_METHOD_GET );
$req->setBasicAuth ($this->user, @$this->pass);
$req->addHeader ("Range", " bytes=$start-$end" );
$data = $req->getResponseBody ();
// lets see what happened
switch ($req->getResponseCode ()) {
// server doesn't support range requests
// TODO we should add some sort of cacheing here
$data = substr($data, $start, $count);
// server supports range requests
// reading beyond end of file is not an error
// no data indicates end of file
* Stream wrapper interface write() method
* @param string data to write
* @return int number of bytes actually written
function stream_write ($buffer)
$start = $this->position;
$end = $this->position + strlen($buffer) - 1;
// create a partial PUT request
$req = &$this->_startRequest (HTTP_REQUEST_METHOD_PUT );
$req->setBasicAuth ($this->user, @$this->pass);
$req->addHeader ("Content-Range", " bytes $start-$end/*" );
$req->addHeader ("If", " (<{$this->locktoken}>) ");
$req->addRawPostData ($buffer);
switch ($req->getResponseCode ()) {
$this->position += strlen ($buffer);
return 1 + $end - $start;
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
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
* @return bool true if end of file was reached
* Stream wrapper interface tell() method
* @return int current file position
// just return the current position
* Stream wrapper interface seek() method
* @param int position to seek to
* @return bool true on success
function stream_seek($pos, $whence)
// relative position form end
$this->position = $this->stat['size'] + $pos;
// TODO: this is rather naive (check how libc handles this)
* Stream wrapper interface URL stat() method
* @param string URL to get stat information for
* @return array stat information
// 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)) {
$stat = $this->stream_stat();
* Stream wrapper interface opendir() method
* @param string directory resource URL
* @param array not used here
* @return bool true on success
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[<a href="../HTTP_WebDAV_Client/_HTTP_WebDAV_Client-1.0.2---HTTP---WebDAV---Client---Stream.php.html#defineHTTP_REQUEST_METHOD_PROPFIND">HTTP_REQUEST_METHOD_PROPFIND</a>])) {
// now read the directory
$req = &$this->_startRequest(<a href="../HTTP_WebDAV_Client/_HTTP_WebDAV_Client-1.0.2---HTTP---WebDAV---Client---Stream.php.html#defineHTTP_REQUEST_METHOD_PROPFIND">HTTP_REQUEST_METHOD_PROPFIND</a>);
$req->setBasicAuth($this->user, @$this->pass);
$req->addHeader("Depth", "1");
$req->addHeader("Content-Type", "text/xml");
$req->addRawPostData('<?xml version="1.0" encoding="utf-8"?>
switch ($req->getResponseCode()) {
case 207: // multistatus content
$this->dirfiles = array();
// for all returned resource entries
foreach (explode("\n", $req->getResponseBody()) as $line) {
// Preg_match_all if the whole response is one line!
// skip the directory itself
foreach ($matches[1] as $match){
// Compare to $this->url too
if ($match == "" || $match == $this->path || $match == $this->url) {
// just remember the basenames to return them later with readdir()
// any other response state indicates an error
* Stream wrapper interface readdir() method
* @return string filename
// bailout if directory is empty
// bailout if we already reached end of dir
if ($this->dirpos >= count($this->dirfiles)) {
// return an entry and move on
return $this->dirfiles[$this->dirpos++];
* Stream wrapper interface rewinddir() method
// bailout if directory content info has already
* Stream wrapper interface closedir() method
// free stored directory content
* Stream wrapper interface mkdir() method
* @param string collection URL to be created
* @return bool true on access
// rewrite the request URL
if (!$this->_parse_url($path)) return false;
// query server for WebDAV options
if (!$this->_check_options()) return false;
$req = &$this->_startRequest(<a href="../HTTP_WebDAV_Client/_HTTP_WebDAV_Client-1.0.2---HTTP---WebDAV---Client---Stream.php.html#defineHTTP_REQUEST_METHOD_MKCOL">HTTP_REQUEST_METHOD_MKCOL</a>);
$req->setBasicAuth($this->user, @$this->pass);
$req->addHeader("If", "(<{ $this->locktoken }>)" );
// check the response code, anything but 201 indicates a problem
$stat = $req->getResponseCode();
error_log("mkdir failed - ". $stat);
* Stream wrapper interface rmdir() method
* @param string collection URL to be created
* @return bool true on access
// 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;
$req = &$this->_startRequest(HTTP_REQUEST_METHOD_DELETE);
$req->setBasicAuth($this->user, @$this->pass);
$req->addHeader("If", "(<{ $this->locktoken }>)" );
// check the response code, anything but 204 indicates a problem
$stat = $req->getResponseCode();
error_log("rmdir failed - ". $stat);
* Stream wrapper interface rename() method
* @param string resource URL to be moved
* @param string resource URL to move to
* @return bool true on access
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;
$req = &$this->_startRequest(<a href="../HTTP_WebDAV_Client/_HTTP_WebDAV_Client-1.0.2---HTTP---WebDAV---Client---Stream.php.html#defineHTTP_REQUEST_METHOD_MOVE">HTTP_REQUEST_METHOD_MOVE</a>);
$req->setBasicAuth($this->user, @$this->pass);
$req->addHeader("If", "(<{ $this->locktoken }>)" );
if (!$this->_parse_url($new_path)) return false;
$req->addHeader("Destination", $this->url);
// check the response code, anything but 207 indicates a problem
$stat = $req->getResponseCode();
* Stream wrapper interface unlink() method
* @param string resource URL to be removed
* @return bool true on success
// 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_DELETE])) {
$req = &$this->_startRequest(HTTP_REQUEST_METHOD_DELETE);
$req->setBasicAuth($this->user, @$this->pass);
$req->addHeader("If", "(<{ $this->locktoken }>)" );
switch ($req->getResponseCode()) {
* Static helper that registers the wrappers
* @return bool true on success (even if SSL doesn't work)
// check that we have the required feature
if (!function_exists("stream_register_wrapper")) {
// try to register the non-encrypted WebDAV wrapper
// now try to register the SSL protocol variant
// it is not critical if this fails
// TODO check whether SSL is possible with HTTP_Request
* Helper function for URL analysis
* @param string original request URL
* @return bool true on success else false
function _parse_url($path)
// rewrite the WebDAV url as a plain HTTP url
// detect whether plain or SSL-encrypted transfer is requested
$scheme = $url['scheme'];
$url['scheme'] = "https";
error_log("only 'webdav:' and 'webdavs:' are supported, not ' $url[scheme ]:'" );
if (isset($this->context)) {
// extract settings from stream context
$context = stream_context_get_options($this->context);
if (isset($context[$scheme]['user_agent'])) {
$this->userAgent = $context[$scheme]['user_agent'];
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'])) {
if (isset($url['pass'])) {
* Helper function for WebDAV OPTIONS detection
* @return bool true on success else false
function _check_options()
// now check OPTIONS reply for WebDAV response headers
$req = &$this->_startRequest(HTTP_REQUEST_METHOD_OPTIONS);
$req->setBasicAuth($this->user, @$this->pass);
if ($req->getResponseCode() != 200) {
// get the supported DAV levels and extensions
$dav = $req->getResponseHeader("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
// get the supported HTTP methods
// TODO these are not checked for WebDAV compliance yet
$allow = $req->getResponseHeader("Allow");
$this->dav_allow = array();
foreach (explode(",", $allow) as $method) {
$this->dav_allow[trim($method)] = true;
// TODO check for required WebDAV methods
* Stream handler interface lock() method (experimental ...)
* @return bool true on success else false
function stream_lock($mode)
- think over how to refresh locks
// LOCK is only supported by DAV Level 2
if (!isset($this->dav_level["2"])) {
switch ($mode & ~LOCK_NB) {
$req = &$this->_startRequest(<a href="../HTTP_WebDAV_Client/_HTTP_WebDAV_Client-1.0.2---HTTP---WebDAV---Client---Stream.php.html#defineHTTP_REQUEST_METHOD_UNLOCK">HTTP_REQUEST_METHOD_UNLOCK</a>);
$req->setBasicAuth($this->user, @$this->pass);
$req->addHeader("Lock-Token", "<{ $this->locktoken }>" );
$ret = $req->getResponseCode() == 204;
$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>
($mode & LOCK_SH) ? "shared" : "exclusive",
get_class($this)); // TODO better owner string
$req = &$this->_startRequest(<a href="../HTTP_WebDAV_Client/_HTTP_WebDAV_Client-1.0.2---HTTP---WebDAV---Client---Stream.php.html#defineHTTP_REQUEST_METHOD_LOCK">HTTP_REQUEST_METHOD_LOCK</a>);
$req->setBasicAuth($this->user, @$this->pass);
if ($this->locktoken) { // needed for refreshing a lock
$req->addHeader("Lock-Token", "<{ $this->locktoken }>" );
$req->addHeader("Timeout", "Infinite, Second-4100000000");
$req->addHeader("Content-Type", 'text/xml; charset="utf-8"');
$req->addRawPostData($body);
$ret = $req->getResponseCode() == 200;
$propinfo = &new HTTP_WebDAV_Client_parse_lock_response($req->getResponseBody());
$this->locktoken = $propinfo->locktoken;
// TODO deal with timeout
function &_startRequest($method)
$req = &new HTTP_Request($this->url);
$req->addHeader('User-agent', $this->userAgent);
$req->addHeader('Content-type', $this->contentType);
$req->setMethod($method);
Documentation generated on Mon, 11 Mar 2019 15:46:45 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|