blob: e2fbe312406364a4f3ab6d581df5e0b0c5239251 [file] [log] [blame]
<?php
/**
* File containing the ezcWebdavTransport class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Webdav
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Transport layer mainclass that implements RFC compliant client communication.
*
* This basis transport class is able to interact with RFC 2518 compliant
* WebDAV clients. It can parse all request types defined in the RFC into the
* abstraction layer of the Webdav component, defined by the base classes
* mentioned below. An exception are LOCK related requests, which will be
* handled by a plugin.
*
* To adjust this base transport layer main class to the needs of
* RFC-2518-inconform client implementations, there is the powerful
* possibility of extending this class and overwriting certain necessary
* protected methods. The easier way to adjust smaller issues is to replace one
* of the helper components during construction of via property access.
*
* The {@link ezcWebdavServer->xmlTool} property will be used which is
* accessed for different XML related operations. Exchanging this one will
* allow you to manipulate the XML handling for the transport layer in
* general.
*
* The {@link ezcWebdavServer->propertyHandler} property, of type {@link
* ezcWebdavPropertyHandler} will be used in the accordingly named property and
* is responsible for extracting WebDAV properties from a {@link DOMElement}
* and to serialize them back to one.
*
* The {@link ezcWebdavServer->pathFactory} property must be an instance of
* {@link ezcWebdavPathFactory} and is used to convert between internal WebDAV
* paths (resource locations understood by the {@link ezcWebdavBackend}) and
* URIs that reference a resource on the web.
*
* An instance of this class is by default capable of parsing the follwoing
* HTTP request methods:
* <ul>
* <li>COPY</li>
* <li>DELETE</li>
* <li>GET</li>
* <li>HEAD</li>
* <li>MKCOL</li>
* <li>MOVE</li>
* <li>OPTIONS</li>
* <li>PROPFIND</li>
* <li>PROPPATCH'</li>
* <li>PUT</li>
* </ul>
*
* The transport implementation is capable of handling the following response
* classes and output the to the client:
* <ul>
* <li>{@link ezcWebdavCopyResponse}</li>
* <li>{@link ezcWebdavDeleteResponse}</li>
* <li>{@link ezcWebdavErrorResponse}</li>
* <li>{@link ezcWebdavGetCollectionResponse}</li>
* <li>{@link ezcWebdavGetResourceResponse}</li>
* <li>{@link ezcWebdavHeadResponse}</li>
* <li>{@link ezcWebdavMakeCollectionResponse}</li>
* <li>{@link ezcWebdavMoveResponse}</li>
* <li>{@link ezcWebdavMultiStatusResponse}</li>
* <li>{@link ezcWebdavOptionsResponse}</li>
* <li>{@link ezcWebdavPropFindResponse}</li>
* <li>{@link ezcWebdavPropPatchResponse}</li>
* <li>{@link ezcWebdavPutResponse}</li>
* </ul>
*
* @see ezcWebdavRequest
* @see ezcWebdavResponse
* @see ezcWebdavProperty
* @link http://tools.ietf.org/html/rfc2518 RFC 2518
*
* @version //autogentag//
* @package Webdav
*/
class ezcWebdavTransport
{
/**
* Used for server software string in Server header.
*/
const VERSION = '//autogentag//';
/**
* Map of HTTP methods to object method names for parsing.
*
* Need public access here to retrieve this in {@link
* ezcWebdavPluginRegistry}.
*
* @var array(string=>string)
* @access private
*/
static public $parsingMap = array(
'COPY' => 'parseCopyRequest',
'DELETE' => 'parseDeleteRequest',
'GET' => 'parseGetRequest',
'HEAD' => 'parseHeadRequest',
'MKCOL' => 'parseMakeCollectionRequest',
'MOVE' => 'parseMoveRequest',
'OPTIONS' => 'parseOptionsRequest',
'PROPFIND' => 'parsePropFindRequest',
'PROPPATCH' => 'parsePropPatchRequest',
'PUT' => 'parsePutRequest',
);
/**
* Map of response objects to handling methods.
*
* Need public access here to retrieve this in {@link
* ezcWebdavPluginRegistry}.
*
* @var array(string=>string)
* @access private
*/
static public $handlingMap = array(
'ezcWebdavCopyResponse' => 'processCopyResponse',
'ezcWebdavDeleteResponse' => 'processDeleteResponse',
'ezcWebdavErrorResponse' => 'processErrorResponse',
'ezcWebdavGetCollectionResponse' => 'processGetCollectionResponse',
'ezcWebdavGetResourceResponse' => 'processGetResourceResponse',
'ezcWebdavHeadResponse' => 'processHeadResponse',
'ezcWebdavMakeCollectionResponse' => 'processMakeCollectionResponse',
'ezcWebdavMoveResponse' => 'processMoveResponse',
'ezcWebdavMultistatusResponse' => 'processMultiStatusResponse',
'ezcWebdavOptionsResponse' => 'processOptionsResponse',
'ezcWebdavPropFindResponse' => 'processPropFindResponse',
'ezcWebdavPropPatchResponse' => 'processPropPatchResponse',
'ezcWebdavPutResponse' => 'processPutResponse',
);
/**
* Properties.
*
* @var array(string=>mixed)
*/
protected $properties = array();
/**
* Parses the incoming request into a fitting request abstraction object.
*
* This method is the main entry point of {@link ezcWebdavServer} and is
* utilized by it to parse the incoming request into an instance of {@link
* ezcWebdavRequest}.
*
* The submitted $uri must be formatted in a way, that the {@link
* ezcWebdavPathFactory} (by default this is {@link
* ezcWebdavAutomaticPathFactory}) can convert it into a path absolute to
* the base of the WebDAV repository.
*
* The retrieval of the request body is performed by the {@link
* retrieveBody()} method, the request method from {@link
* $_SERVER['REQUEST_METHOD']}. The latter one is mapped through the
* {@link self::$parsingMap} attribute to a local object method.
*
* This method is marked final and may not be overwritten, because it
* belongs to the essential communication API with {@link ezcWebdavServer}
* and is responsible to dispatch the {@link ezcWebdavPluginRegistry} hooks
* of the transport layer. NOTE: The plugin API is not public, yet, and
* will be part of a next release.
*
* If an error occurs during request parsing, an instance of {@link
* ezcWebdavResponse} may be returned instead of an instance of {@link
* ezcWebdavRequest}. {@link ezcWebdavServer} will handle this correctly.
*
* @param string $uri
* @return ezcWebdavRequest|ezcWebdavResponse
*/
public final function parseRequest( $uri )
{
$body = $this->retrieveBody();
$path = $this->retrievePath( $uri );
if ( isset( self::$parsingMap[$_SERVER['REQUEST_METHOD']] ) )
{
try
{
// Plugin hook beforeParseRequest
ezcWebdavServer::getInstance()->pluginRegistry->announceHook(
__CLASS__,
'beforeParseRequest',
new ezcWebdavPluginParameters(
array(
'path' => &$path,
'body' => &$body,
)
)
);
$request = call_user_func( array( $this, self::$parsingMap[$_SERVER['REQUEST_METHOD']] ), $path, $body );
}
catch ( Exception $e )
{
return $this->handleException( $e, $uri );
}
}
else
{
// Plugin hook parseUnknownRequest
$request = ezcWebdavServer::getInstance()->pluginRegistry->announceHook(
__CLASS__,
'parseUnknownRequest',
new ezcWebdavPluginParameters(
array(
'path' => &$path,
'body' => &$body,
'requestMethod' => &$_SERVER['REQUEST_METHOD'],
)
)
);
// If hooks did not return a valid request object, generate error
if ( !( $request instanceof ezcWebdavRequest ) && !( $request instanceof ezcWebdavResponse ) )
{
// Error code 501: Not implemented
return new ezcWebdavErrorResponse(
ezcWebdavResponse::STATUS_501,
$uri
);
}
}
$request->validateHeaders();
return $request;
}
/**
* Handle a response and send it to the WebDAV client.
*
* This method is part of the integral communication API between the WebDAV
* client and the {@link ezcWebdavServer}. It is declared final to ensure a
* minimal compatibile API between the extended classes and it is
* responsible to dispatch the {@link ezcWebdavPluginRegistry} hooks. NOTE:
* The plugin API is not public, yet, and will be part of a next release.
*
* It currently just maps internally to {@link processResponse()} and
* passes the result to {@ $this->sendResponse()}. It is not recommended
* that the {@link $this->processResponse()} method is overwritten, because
* this one takes care about the dispatching. The {@link
* $this->sendResponse()} may be overwritten, mainly for debugging, testing
* and logging purposes.
*
* @param ezcWebdavResponse $response
* @return void
*/
public final function handleResponse( ezcWebdavResponse $response )
{
// Set the Server header with information about eZ Components version
// and transport implementation.
$headers = ezcWebdavServer::getInstance()->headerHandler->parseHeaders( array( 'Server' ) );
$response->setHeader(
'Server',
( isset( $headers['Server'] ) && strlen( $headers['Server'] ) > 0 ? $headers['Server'] . '/' : '' )
. 'eZComponents/'
. ( self::VERSION === '//auto'. 'gentag//' ? 'dev' : self::VERSION )
. '/'
. get_class( $this )
);
try
{
$response->validateHeaders();
$this->sendResponse( $this->flattenResponse( $this->processResponse( $response ) ) );
}
catch ( Exception $e )
{
if ( $response instanceof ezcWebdavErrorResponse )
{
// Attention: Recursion detected!
throw $e;
}
$this->handleResponse( $this->handleException( $e ) );
throw $e;
}
}
/**
* Handle a thrown exception and generate an error response from it.
*
* Takes the given exception $e and generates a response object from it.
* The $uri parameter will be given to {@link
* ezcWebdavErrorResponse::__construct()}.
*
* For special exceptions, special responses will be generated:
* <ul>
* <li>ezcWebdavBadRequestException: 400 Bad Request</li>
* <li>ezcWebdavInvalidRequestMethodException: 501 Not Implemented</li>
* </ul>
*
* Per default, a 500 Internal Server Error response will be generated.
*
* Depending on where this is called, the generatedResponse hook will be
* issued (if during request parsing), but the processErrorResponse hooks
* will allways be called. NOTE: The plugin API is not public, yet, and
* will be part of a next release.
*
* @param Exception $e
* @param string $uri
* @return ezcWebdavErrorResponse
*/
protected function handleException( Exception $e, $uri = null )
{
$message = ( php_sapi_name() !== 'cli' ? htmlspecialchars_decode( $e->getMessage() ) : $e->getMessage() );
switch ( true )
{
case ( $e instanceof ezcWebdavBadRequestException ):
case ( $e instanceof ezcWebdavInvalidRequestBodyException ):
$code = ezcWebdavResponse::STATUS_400;
break;
case ( $e instanceof ezcWebdavInvalidRequestMethodException ):
$code = ezcWebdavResponse::STATUS_501;
break;
default:
$code = ezcWebdavResponse::STATUS_500;
break;
}
return new ezcWebdavErrorResponse( $code, $uri, $message );
}
/**
* Returns the body content of the request.
*
* This method is only kept for BC reasons. Please refer to {@link
* retrieveBody()}.
*
* @return string The request body.
*
* @apichange This method will be removed in the next major version. Please
* use {@link retrieveBody()} instead.
*/
protected function retreiveBody()
{
return $this->retrieveBody();
}
/**
* Returns the body content of the request.
*
* This method mainly exists for unit testing purpose. It reads the request
* body and returns the contents as a string. This method can also be
* usefull to be overriden during inheritence to filter the body of
* missbehaving WebDAV clients.
*
* @return string The request body.
*/
protected function retrieveBody()
{
$body = '';
$in = fopen( 'php://input', 'r' );
while ( $data = fread( $in, 1024 ) )
{
// This line is untestable, since it reads from STDIN and during
// testing there is no input to read.
// @codeCoverageIgnoreStart
$body .= $data;
}
// @codeCoverageIgnoreEnd
return $body;
}
/**
* Returns the translated request path.
*
* This method calls the configured path factory to translate the
* submitted $uri into a local path. It can be overwritten to perform client
* specific path adjustments.
*
* @param string $uri
* @return string
*/
protected function retrievePath( $uri )
{
return ezcWebdavServer::getInstance()->pathFactory->parseUriToPath( $uri );
}
/**
* Serializes a response object to XML.
*
* This method performs the internal dispatching of a given $response
* object. It determines the method to handle the response by {@link
* self::$handlingMap} and throws an Exception if the given class could not
* be dispatched.
*
* The method internally calls one of the handle*Response() methods to get
* the repsonse object processed and returns an instance of {@link
* ezcWebdavDisplayInformation} to be displayed.
*
* @param ezcWebdavResponse $response
* @return ezcWebdavDisplayInformation
*
* @throws ezcWebdavMissingHeaderException
* if the generated result is an {@link
* ezcWebdavStringDisplayInformation} struct and the contained
* {@link ezcWebdavResponse} object has no Content-Type header set.
* @throws ezcWebdavInvalidHeaderException
* if the generated result is an {@link
* ezcWebdavEmptyDisplayInformation} and the contained {@link
* ezcWebdavResponse} object has a Content-Type or a Content-Length
* header set.
*/
private function processResponse( ezcWebdavResponse $response )
{
// Check if response can be processed by default
if ( !isset( self::$handlingMap[( $responseClass = get_class( $response ) )] ) )
{
// Plugin hook processUnknownResponse
$result = ezcWebdavServer::getInstance()->pluginRegistry->announceHook(
__CLASS__,
'processUnknownResponse',
new ezcWebdavPluginParameters(
array(
'response' => $response,
)
)
);
if ( $result === null )
{
// No plugin could process the response: 500 Internal Server Error
return $this->processResponse( new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_500 ) );
}
else
{
return $result;
}
}
$result = call_user_func( array( $this, self::$handlingMap[( $responseClass = get_class( $response ) )] ), $response );
// Plugin hook afterProcessResponse
ezcWebdavServer::getInstance()->pluginRegistry->announceHook(
__CLASS__,
'afterProcessResponse',
new ezcWebdavPluginParameters(
array(
'result' => $result,
)
)
);
return $result;
}
/**
* Flattens a processed response object to headers and body.
*
* Takes a given {@link ezcWebdavDisplayInformation} object and returns an
* array containg the headers and body it represents.
*
* The returned information can be processed (send out to the client) by
* {@link ezcWebdavTransport::sendResponse()}.
*
* @param ezcWebdavDisplayInformation $info
* @return ezcWebdavOutputResult
*
* @throws ezcWebdavMissingHeaderException
* if the generated result is an {@link
* ezcWebdavStringDisplayInformation} struct and the contained
* {@link ezcWebdavResponse} object has no Content-Type header set.
* @throws ezcWebdavInvalidHeaderException
* if the generated result is an {@link
* ezcWebdavEmptyDisplayInformation} and the contained {@link
* ezcWebdavResponse} object has a Content-Type or a Content-Length
* header set.
*/
protected function flattenResponse( ezcWebdavDisplayInformation $info )
{
$output = new ezcWebdavOutputResult();
$output->status = (string) $info->response;
$output->headers = $info->response->getHeaders();
$output->body = '';
switch ( true )
{
case ( $info instanceof ezcWebdavXmlDisplayInformation ):
$output->headers['Content-Type'] = ( isset( $output->headers['Content-Type'] ) ? $output->headers['Content-Type'] : 'text/xml; charset="utf-8"' );
$info->body->formatOutput = true;
$output->body = $info->body->saveXML( $info->body );
break;
case ( $info instanceof ezcWebdavStringDisplayInformation ):
if ( $info->response->getHeader( 'Content-Type' ) === null )
{
throw new ezcWebdavMissingHeaderException( 'Content-Type' );
}
$output->body = $info->body;
break;
case ( $info instanceof ezcWebdavEmptyDisplayInformation ):
default:
// Ensure a content length header is set
if ( ( $header = $info->response->getHeader( 'Content-Length' ) ) === null )
{
$output->headers['Content-Length'] = 0;
}
break;
}
return $output;
}
/**
* Finally sends out the response.
*
* This method is called to finally send the response to the client. It
* can be overwritten in test cases to change the behaviour of printing out
* the result and sending the headers.
*
* @param ezcWebdavOutputResult $output
* @return void
*/
protected function sendResponse( ezcWebdavOutputResult $output )
{
// Sends HTTP headers
foreach ( $output->headers as $name => $content )
{
$content = is_array( $content ) ? $content : array( $content );
$overwrite = true;
foreach ( $content as $contentLine )
{
header( "{$name}: {$contentLine}", $overwrite );
// Append additional values
$overwrite = false;
}
}
// Send HTTP status code
header( $output->status );
// Content-Length header automatically send
echo $output->body;
}
/*
***************************
* Request handling follows.
***************************
*/
// GET
/**
* Parses the GET request and returns a request object.
*
* This method is responsible for parsing the GET request. It retrieves the
* current request URI in $path and the request body as $body. The return
* value, if no exception is thrown, is a valid {@link
* ezcWebdavGetResourceResponse} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavGetRequest
*/
protected function parseGetRequest( $path, $body )
{
$req = new ezcWebdavGetRequest( $path );
$req->setHeaders(
// Parse default headers
ezcWebdavServer::getInstance()->headerHandler->parseHeaders()
);
return $req;
}
// PUT
/**
* Parses the PUT request and returns a request object.
*
* This method is responsible for parsing the PUT request. It retrieves the
* current request URI in $path and the request body as $body. The return
* value, if no exception is thrown, is a valid {@link ezcWebdavPutRequest}
* object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavPutRequest
*/
protected function parsePutRequest( $path, $body )
{
$req = new ezcWebdavPutRequest( $path, $body );
$req->setHeaders(
ezcWebdavServer::getInstance()->headerHandler->parseHeaders(
array(
'Content-Length', 'Content-Type'
)
)
);
return $req;
}
// HEAD
/**
* Parses the HEAD request and returns a request object.
*
* This method is responsible for parsing the HEAD request. It retrieves
* the current request URI in $path and the request body as $body. The
* return value, if no exception is thrown, is a valid {@link
* ezcWebdavHeadRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavHeadRequest
*/
protected function parseHeadRequest( $path, $body )
{
$req = new ezcWebdavHeadRequest( $path );
$req->setHeaders(
// Parse default headers
ezcWebdavServer::getInstance()->headerHandler->parseHeaders()
);
return $req;
}
// COPY
/**
* Parses the COPY request and returns a request object.
*
* This method is responsible for parsing the COPY request. It retrieves
* the current request URI in $path and the request body as $body. The
* return value, if no exception is thrown, is a valid {@link
* ezcWebdavCopyRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavCopyRequest
*
* @throws ezcWebdavInvalidRequestBodyException
* if the body of the copy request is invalid (XML wise or RFC
* wise).
*/
protected function parseCopyRequest( $path, $body )
{
$headers = ezcWebdavServer::getInstance()->headerHandler->parseHeaders(
array( 'Destination', 'Depth', 'Overwrite' )
);
if ( !isset( $headers['Destination'] ) )
{
throw new ezcWebdavMissingHeaderException( 'Destination' );
}
$request = new ezcWebdavCopyRequest( $path, $headers['Destination'] );
$request->setHeaders( $headers );
if ( trim( $body ) === '' )
{
// No body present
return $request;
}
try
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom( $body );
}
catch ( ezcWebdavInvalidXmlException $e )
{
throw new ezcWebdavInvalidRequestBodyException(
'COPY',
$e->getMessage()
);
}
if ( $dom->documentElement->localName !== 'propertybehavior' )
{
throw new ezcWebdavInvalidRequestBodyException(
'COPY',
"Expected XML element <propertybehavior />, received <{$dom->documentElement->localName} />."
);
}
return $this->parsePropertyBehaviourContent( $dom, $request );
}
// MOVE
/**
* Parses the MOVE request and returns a request object.
*
* This method is responsible for parsing the MOVE request. It retrieves
* the current request URI in $path and the request body as $body. The
* return value, if no exception is thrown, is a valid {@link
* ezcWebdavMoveRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavMoveRequest
*/
protected function parseMoveRequest( $path, $body )
{
$headers = ezcWebdavServer::getInstance()->headerHandler->parseHeaders(
array( 'Destination', 'Depth', 'Overwrite' )
);
if ( !isset( $headers['Destination'] ) )
{
throw new ezcWebdavMissingHeaderException( 'Destination' );
}
$request = new ezcWebdavMoveRequest( $path, $headers['Destination'] );
$request->setHeaders( $headers );
if ( trim( $body ) === '' )
{
// No body present
return $request;
}
try
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom( $body );
}
catch ( ezcWebdavInvalidXmlException $e )
{
throw new ezcWebdavInvalidRequestBodyException(
'MOVE',
$e->getMessage()
);
}
if ( $dom->documentElement->localName !== 'propertybehavior' )
{
throw new ezcWebdavInvalidRequestBodyException(
'MOVE',
"Expected XML element <propertybehavior />, received <{$dom->documentElement->localName} />."
);
}
return $this->parsePropertyBehaviourContent( $dom, $request );
}
/**
* Parses the <propertybehavior /> XML element.
*
* This element is part of the COPY and MOVE requests, which are handled by
* {@link $this->parseCopyRequest()} respectivly {@link
* $this->parseMoveRequest()}.
*
* The $dom parameter is the DOMDocument where the <propertybehavior />
* content should be parsed from. The $request object submitted will get
* the resulting {@link ezcWebdavRequestPropertyBehaviourContent} set into
* its $propertyBehavior property.
*
* This method may be overwritten to adjust it to special client behaviour.
* If you overwrite the {@link $this->processCopyResponse()} or {@link
* $this->parseMoveRequest()} methods, you might disable this method
* accedentally. You should explicitly use it there and overwrite it, if
* necessary. This makes extending your extended transport easier.
*
* @param DOMDocument $dom
* @param ezcWebdavRequest $request ezcWebdavCopyRequest or ezcWebdavMoveRequest
* @return ezcWebdavCopyRequest|ezcWebdavMoveRequest As submitted.
*/
protected function parsePropertyBehaviourContent( DOMDocument $dom, ezcWebdavRequest $request )
{
$propertyBehaviourNode = $dom->documentElement;
$request->propertyBehaviour = new ezcWebdavRequestPropertyBehaviourContent();
switch ( $propertyBehaviourNode->firstChild->localName )
{
case 'omit':
$request->propertyBehaviour->omit = true;
break;
case 'keepalive':
if ( $propertyBehaviourNode->firstChild->nodeValue === '*' )
{
$request->propertyBehaviour->keepAlive = ezcWebdavRequestPropertyBehaviourContent::ALL;
}
else
{
$keepAliveContent = array();
$hrefNodes = $propertyBehaviourNode->firstChild->getElementsByTagName( 'href' );
for ( $i = 0; $i < $hrefNodes->length; ++$i )
{
$keepAliveContent[] = $hrefNodes->item( $i )->nodeValue;
}
$request->propertyBehaviour->keepAlive = $keepAliveContent;
}
}
return $request;
}
// DELETE
/**
* Parses the DELETE request and returns a request object.
*
* This method is responsible for parsing the DELETE request. It retrieves
* the current request URI in $path and the request body as $body. The
* return value, if no exception is thrown, is a valid {@link
* ezcWebdavDeleteRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavDeleteRequest
*/
protected function parseDeleteRequest( $path, $body )
{
$req = new ezcWebdavDeleteRequest( $path );
$req->setHeaders(
// Parse default headers
ezcWebdavServer::getInstance()->headerHandler->parseHeaders()
);
return $req;
}
// MKCOL
/**
* Parses the MKCOL request and returns a request object.
*
* This method is responsible for parsing the MKCOL request. It retrieves
* the current request URI in $path and the request body as $body. The
* return value, if no exception is thrown, is a valid {@link
* ezcWebdavMakeCollectionRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavMakeCollectionRequest
*/
protected function parseMakeCollectionRequest( $path, $body )
{
$req = new ezcWebdavMakeCollectionRequest( $path, ( trim( $body ) === '' ? null : $body ) );
$req->setHeaders(
// Parse default headers
ezcWebdavServer::getInstance()->headerHandler->parseHeaders()
);
return $req;
}
// OPTIONS
/**
* Parses the OPTIONS request and returns a request object.
*
* This method is responsible for parsing the OPTIONS request. It retrieves
* the current request URI in $path and the request body as $body. The
* return value, if no exception is thrown, is a valid {@link
* ezcWebdavOptionsRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavOptionsRequest
*/
protected function parseOptionsRequest( $path, $body )
{
$req = new ezcWebdavOptionsRequest( $path, ( trim( $body ) === '' ? null : $body ) );
$req->setHeaders(
// Parse default headers
ezcWebdavServer::getInstance()->headerHandler->parseHeaders()
);
return $req;
}
// PROPFIND
/**
* Parses the PROPFIND request and returns a request object.
*
* This method is responsible for parsing the PROPFIND request. It
* retrieves the current request URI in $path and the request body as
* $body. The return value, if no exception is thrown, is a valid {@link
* ezcWebdavPropFindRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavPropFindRequest
*/
protected function parsePropFindRequest( $path, $body )
{
$request = new ezcWebdavPropFindRequest( $path );
$request->setHeaders(
ezcWebdavServer::getInstance()->headerHandler->parseHeaders(
array( 'Depth' )
)
);
if ( empty( $body ) )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPFIND',
"Could not open XML as DOMDocument: '{$body}'."
);
}
try
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom( $body );
}
catch ( ezcWebdavInvalidXmlException $e )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPFIND',
$e->getMessage()
);
}
if ( $dom->documentElement->localName !== 'propfind' )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPFIND',
"Expected XML element <propfind />, received <{$dom->documentElement->localName} />."
);
}
if ( $dom->documentElement->firstChild === null )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPFIND',
"Element <propfind /> does not have a child element."
);
}
switch ( $dom->documentElement->firstChild->localName )
{
case 'allprop':
$request->allProp = true;
break;
case 'propname':
$request->propName = true;
break;
case 'prop':
$request->prop = new ezcWebdavBasicPropertyStorage();
try
{
ezcWebdavServer::getInstance()->propertyHandler->extractProperties(
$dom->documentElement->firstChild->childNodes,
$request->prop
);
}
catch ( ezcBaseValueException $e )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPFIND',
"Property extraction produced value exception: '{$e->getMessage()}'."
);
}
break;
default:
throw new ezcWebdavInvalidRequestBodyException(
'PROPFIND',
"XML element <{$dom->documentElement->firstChild->nodeName} /> is not a valid child element for <propfind />."
);
}
return $request;
}
// PROPPATCH
/**
* Parses the PROPPATCH request and returns a request object.
*
* This method is responsible for parsing the PROPPATCH request. It
* retrieves the current request URI in $path and the request body as
* $body. The return value, if no exception is thrown, is a valid {@link
* ezcWebdavPropPatchRequest} object.
*
* This method may be overwritten to adjust it to special client behaviour.
*
* @param string $path
* @param string $body
* @return ezcWebdavPropPatchRequest
*/
protected function parsePropPatchRequest( $path, $body )
{
$request = new ezcWebdavPropPatchRequest( $path );
try
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom( $body );
}
catch ( ezcWebdavInvalidXmlException $e )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPPATCH',
$e->getMessage()
);
}
if ( $dom->documentElement->localName !== 'propertyupdate' )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPPATCH',
"Expected XML element <propertyupdate />, received <{$dom->documentElement->localName} />."
);
}
$propElements = $dom->documentElement->getElementsByTagNameNS( ezcWebdavXmlTool::XML_DEFAULT_NAMESPACE, 'prop' );
try
{
foreach ( $propElements as $propElement )
{
if ( $propElement->hasChildNodes() )
{
ezcWebdavServer::getInstance()->propertyHandler->extractProperties(
$propElement->childNodes,
$request->updates,
$propElement->parentNode->localName === 'remove'
? ezcWebdavPropPatchRequest::REMOVE
: ezcWebdavPropPatchRequest::SET
);
}
}
}
catch ( ezcBaseValueException $e )
{
throw new ezcWebdavInvalidRequestBodyException(
'PROPPATCH',
"Property extraction produced value exception: '{$e->getMessage()}'."
);
}
$request->setHeaders(
// Parse default headers
ezcWebdavServer::getInstance()->headerHandler->parseHeaders()
);
return $request;
}
/*
****************************
* Response handling follows.
****************************
*/
/**
* Returns display information for a multistatus response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavMultiStatusResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information generated by this response contains the post
* processed $response and a {@link DOMDocument} representing the XML
* response body.
*
* This method utilizes {@link ezcWebdavServer->xmlTool} to
* perform basic XML operations, so this is the place to perform such
* changeds. You should overwrite this method, if your client has problems
* specifically with the {@link ezcWebdavMultiStatusResponse} response.
*
* @param ezcWebdavMultistatusResponse $response
* @return ezcWebdavXmlDisplayInformation
*/
protected function processMultiStatusResponse( ezcWebdavMultistatusResponse $response )
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom();
$multistatusElement = $dom->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'multistatus' )
);
foreach ( $response->responses as $subResponse )
{
$multistatusElement->appendChild(
( $subResponse instanceof ezcWebdavErrorResponse
? $dom->importNode( $this->processErrorResponse( $subResponse, true )->body->documentElement, true )
: $dom->importNode( $this->processResponse( $subResponse )->body->documentElement, true )
)
);
}
return new ezcWebdavXmlDisplayInformation( $response, $dom );
}
/**
* Returns display information for a prop find response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavPropFindResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information generated by this response contains the post
* processed $response and a {@link DOMDocument} representing the XML
* response body.
*
* This method utilizes {@link ezcWebdavServer->xmlTool} to
* perform basic XML operations, so this is the place to perform such
* changeds. You should overwrite this method, if your client has problems
* specifically with the {@link ezcWebdavPropFindResponse} response.
*
* @param ezcWebdavPropFindResponse $response
* @return ezcWebdavXmlDisplayInformation
*/
protected function processPropFindResponse( ezcWebdavPropFindResponse $response )
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom();
$responseElement = $dom->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'response' )
);
$responseElement->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'href' )
)->nodeValue = ezcWebdavServer::getInstance()->pathFactory->generateUriFromPath( $response->node->path );
foreach ( $response->responses as $propStat )
{
if ( count( $propStat->storage ) > 0 )
{
$responseElement->appendChild(
$dom->importNode( $this->processPropStatResponse( $propStat )->body->documentElement, true )
);
}
}
return new ezcWebdavXmlDisplayInformation( $response, $dom );
}
/**
* Returns display information for a prop patch response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavPropPatchResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavPropPatchResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processPropPatchResponse( ezcWebdavPropPatchResponse $response )
{
if ( count( $response->responses ) === 0 )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
return $this->processPropFindResponse( $response );
}
/**
* Returns display information for a copy response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavCopyResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavCopyResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processCopyResponse( ezcWebdavCopyResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a move response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavMoveResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavMoveResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processMoveResponse( ezcWebdavMoveResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a delete response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavDeleteResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavDeleteResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processDeleteResponse( ezcWebdavDeleteResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a error response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavErrorResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The $xml parameter defines, if an XML representation should be
* generated, too (for use in {@link $this->processMultiStatusResponse()}),
* or if only the headers should be manipulated and an empty response body
* should be used.
*
* The display information generated by this response contains the post
* processed $response and a {@link DOMDocument} representing the XML
* response body. If the $xml parameter is set to false, an empty display
* information is generated, to indicate that only headers should be send.
*
* This method utilizes {@link ezcWebdavServer->xmlTool} to
* perform basic XML operations, so this is the place to perform such
* changeds. You should overwrite this method, if your client has problems
* specifically with the {@link ezcWebdavErrorResponse} response.
*
* @param ezcWebdavErrorResponse $response
* @param bool $xml DOMDocument in result only generated of true.
* @return ezcWebdavXmlDisplayInformation|ezcWebdavEmptyDisplayInformation
*/
protected function processErrorResponse( ezcWebdavErrorResponse $response, $xml = false )
{
$res = new ezcWebdavEmptyDisplayInformation( $response );
if ( $xml === true )
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom();
$responseElement = $dom->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'response' )
);
$responseElement->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'href' )
)->nodeValue = ezcWebdavServer::getInstance()->pathFactory->generateUriFromPath( $response->requestUri );
$responseElement->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'status' )
)->nodeValue = (string) $response;
if ( !empty( $response->responseDescription ) )
{
$responseElement->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'responsedescription' )
)->nodeValue = $response->responseDescription;
}
$res = new ezcWebdavXmlDisplayInformation( $response, $dom );
}
elseif ( $response->responseDescription !== null )
{
// User $responseDescription as body
$response->setHeader( 'Content-Type', 'text/plain; charset="utf-8"' );
$response->setHeader( 'Content-Length', (string) strlen( $response->responseDescription ) );
$res = new ezcWebdavStringDisplayInformation( $response, $response->responseDescription );
}
return $res;
}
/**
* Returns display information for a get response for a collection.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavGetCollectionResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavGetCollectionResponse $response
* @return ezcWebdavEmptyDisplayInformation
*
* @todo We should possibly offer an ezcWebdavTemplateTiein, which brings
* an extension that adds a directory listing body here (possibly in
* selectable formats like XHTML, HTML, Apache style, ...).
*/
protected function processGetCollectionResponse( ezcWebdavGetCollectionResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a get response on a non-collection.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavGetResourceResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* This response returns a very seldom (for this component) string
* response, since it returns the raw content of the requested resource.
*
* @param ezcWebdavGetResourceResponse $response
* @return ezcWebdavStringDisplayInformation
*/
protected function processGetResourceResponse( ezcWebdavGetResourceResponse $response )
{
// Generate Content-Type header if necessary
if ( $response->getHeader( 'Content-Type' ) === null )
{
$contentTypeProperty = $response->resource->liveProperties->get( 'getcontenttype' );
$contentTypeHeader = ( $contentTypeProperty->mime !== null ? $contentTypeProperty->mime : 'application/octet-stream' ) .
'; charset="' . ( $contentTypeProperty->charset !== null ? $contentTypeProperty->charset : 'utf-8' ) . '"';
$response->setHeader( 'Content-Type', $contentTypeHeader );
}
// Content-Length automatically send by web server
return new ezcWebdavStringDisplayInformation( $response, $response->resource->content );
}
/**
* Returns display information for a put response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavPutResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavPutResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processPutResponse( ezcWebdavPutResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a head response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavHeadResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* This method always must be structured quite similar to {@link
* $this->processGetCollectionResponse} or {@link
* $this->processGetResourceResponse()}, since HEAD is more or less GET
* without a body.
*
* @param ezcWebdavHeadResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processHeadResponse( ezcWebdavHeadResponse $response )
{
if ( $response->resource instanceof ezcWebdavResource )
{
return $this->processHeadResourceResponse( $response );
}
return $this->processHeadCollectionResponse( $response );
}
/**
* Creates display information for a HEAD response of a non-collection resource.
*
* Generates default Content-Type and Content-Length header, if not set in
* the $response (generated from the backend). Content-Type is determined
* by the 'getcontenttype' property, if set, otherwise
* 'application/octet-stream' is used. Same applies to the charset
* parameter, where 'utf-8' is the default. The Content-Length is determined
* by strlen() on the resource content.
*
* @param ezcWebdavHeadResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
private function processHeadResourceResponse( ezcWebdavHeadResponse $response )
{
// Generate default Content-Type header if necessary
if ( $response->getHeader( 'Content-Type' ) === null )
{
$contentTypeProperty = $response->resource->liveProperties->get( 'getcontenttype' );
$contentTypeHeader = ( $contentTypeProperty->mime !== null ? $contentTypeProperty->mime : 'application/octet-stream' )
. '; charset="' . ( $contentTypeProperty->charset !== null ? $contentTypeProperty->charset : 'utf-8' ) . '"';
$response->setHeader( 'Content-Type', $contentTypeHeader );
}
// Generate default Content-Length header if necessary
if ( $response->getHeader( 'Content-Length' ) === null )
{
$response->setHeader( 'Content-Length', strlen( $response->resource->content ) );
}
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Creates display information for a HEAD response of a collection resource.
*
* Generates default Content-Type and Content-Length header, if not set in
* the $response (generated from the backend). Content-Type is set to .
*
* @param ezcWebdavHeadResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
private function processHeadCollectionResponse( ezcWebdavHeadResponse $response )
{
// Generate default Content-Type header if necessary
if ( $response->getHeader( 'Content-Type' ) === null )
{
$response->setHeader( 'Content-Type', 'httpd/unix-directory' );
}
// Generate default Content-Length header if necessary
if ( $response->getHeader( 'Content-Length' ) === null )
{
$response->setHeader( 'Content-Length', ezcWebdavGetContentLengthProperty::COLLECTION );
}
return new ezcWebdavEmptyDisplayInformation( $response );
}
// ezcWebdavMakeCollectionResponse
/**
* Returns display information for a make collection response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavMakeCollectionResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavMakeCollectionResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processMakeCollectionResponse( ezcWebdavMakeCollectionResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a options response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavOptionsResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information returned by this method indicates, that only
* headers, but no response body, should be send.
*
* @param ezcWebdavOptionsResponse $response
* @return ezcWebdavEmptyDisplayInformation
*/
protected function processOptionsResponse( ezcWebdavOptionsResponse $response )
{
return new ezcWebdavEmptyDisplayInformation( $response );
}
/**
* Returns display information for a prop stat response object.
*
* This method returns the display information generated for a $response
* object of type {@link ezcWebdavPropStatResponse}. It returns an
* instance of {@link ezcWebdavDisplayInformation} containing the
* post-processed response object and the appropriate body.
*
* The display information generated by this response contains the post
* processed $response and a {@link DOMDocument} representing the XML
* response body.
*
* This method utilizes {@link ezcWebdavServer->xmlTool} to
* perform basic XML operations, so this is the place to perform such
* changeds. You should overwrite this method, if your client has problems
* specifically with the {@link ezcWebdavPropStatResponse} response.
*
* @param ezcWebdavPropStatResponse $response
* @return ezcWebdavXmlDisplayInformation
*/
protected function processPropStatResponse( ezcWebdavPropStatResponse $response )
{
$dom = ezcWebdavServer::getInstance()->xmlTool->createDom();
$propstatElement = $dom->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'propstat' )
);
ezcWebdavServer::getInstance()->propertyHandler->serializeProperties(
$response->storage,
$propstatElement->appendChild( ezcWebdavServer::getInstance()->xmlTool->createDomElement( $dom, 'prop' ) )
);
$propstatElement->appendChild(
ezcWebdavServer::getInstance()->xmlTool->createDomElement(
$dom,
'status'
)
)->nodeValue = (string) $response;
return new ezcWebdavXmlDisplayInformation( $response, $dom );
}
}
?>