Вход Регистрация
Файл: library/Sabre/Sabre/DAV/Locks/Plugin.php
Строк: 720
<?php

/**
 * Locking plugin
 *
 * This plugin provides locking support to a WebDAV server.
 * The easiest way to get started, is by hooking it up as such:
 *
 * $lockBackend = new Sabre_DAV_Locks_Backend_FS('./my_lock_directory');
 * $lockPlugin = new Sabre_DAV_Locks_Plugin($lockBackend);
 * $server->addPlugin($lockPlugin);
 * 
 * @package Sabre
 * @subpackage DAV
 * @version $Id$
 * @copyright Copyright (C) 2007-2010 Rooftop Solutions. All rights reserved.
 * @author Evert Pot (http://www.rooftopsolutions.nl/) 
 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
 */
class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin {

    
/**
     * locksBackend 
     * 
     * @var Sabre_DAV_Locks_Backend_Abstract 
     */
    
private $locksBackend;

    
/**
     * server
     * 
     * @var Sabre_DAV_Server 
     */
    
private $server;

    
/**
     * __construct 
     * 
     * @param Sabre_DAV_Locks_Backend_Abstract $locksBackend 
     * @return void
     */
    
public function __construct(Sabre_DAV_Locks_Backend_Abstract $locksBackend null) {

        
$this->locksBackend $locksBackend;        

    }

    
/**
     * Initializes the plugin
     *
     * This method is automatically called by the Server class after addPlugin.
     * 
     * @param Sabre_DAV_Server $server 
     * @return void
     */
    
public function initialize(Sabre_DAV_Server $server) {

        
$this->server $server;
        
$server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
        
$server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),50);
        
$server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'));

    }

    
/**
     * This method is called by the Server if the user used an HTTP method 
     * the server didn't recognize.
     *
     * This plugin intercepts the LOCK and UNLOCK methods.
     * 
     * @param string $method 
     * @return bool 
     */
    
public function unknownMethod($method) {

        switch(
$method) { 

            case 
'LOCK'   $this->httpLock(); return false; break;
            case 
'UNLOCK' $this->httpUnlock(); return false; break;
            default       : return 
true;
        }

    }

    
/**
     * This method is called after most properties have been found
     * it allows us to add in any Lock-related properties
     * 
     * @param string $path 
     * @param array $properties 
     * @return bool 
     */
    
public function afterGetProperties($path,&$newProperties) {

        foreach(
$newProperties[404] as $propName=>$discard) {

            
$node null;

            switch(
$propName) {

                case 
'{DAV:}supportedlock' :
                    
$val false;
                    if (
$this->locksBackend$val true;
                    else {
                        if (!
$node$node $this->server->tree->getNodeForPath($path);
                        if (
$node instanceof Sabre_DAV_ILockable$val true;
                    }
                    
$newProperties[200][$propName] = new Sabre_DAV_Property_SupportedLock($val);
                    unset(
$newProperties[404][$propName]);
                    break;

                case 
'{DAV:}lockdiscovery' :
                    
$newProperties[200][$propName] = new Sabre_DAV_Property_LockDiscovery($this->getLocks($path));
                    unset(
$newProperties[404][$propName]);
                    break;

            }


        }
        return 
true;

    }


    
/**
     * This method is called before the logic for any HTTP method is
     * handled.
     *
     * This plugin uses that feature to intercept access to locked resources.
     * 
     * @param string $method 
     * @return bool 
     */
    
public function beforeMethod($method) {

        switch(
$method) {

            case 
'DELETE' :
            case 
'MKCOL' :
            case 
'PROPPATCH' :
            case 
'PUT' :
                
$lastLock null;
                if (!
$this->validateLock(null,$lastLock))
                    throw new 
Sabre_DAV_Exception_Locked($lastLock);
                break;
            case 
'MOVE' :
                
$lastLock null;
                if (!
$this->validateLock(array(
                      
$this->server->getRequestUri(),
                      
$this->server->calculateUri($this->server->httpRequest->getHeader('Destination')),
                    ),
$lastLock))
                        throw new 
Sabre_DAV_Exception_Locked($lastLock);
                break;
            case 
'COPY' :
                
$lastLock null;
                if (!
$this->validateLock(
                      
$this->server->calculateUri($this->server->httpRequest->getHeader('Destination')),
                      
$lastLock))
                        throw new 
Sabre_DAV_Exception_Locked($lastLock);
                break;
        }

        return 
true;

    }

    
/**
     * New HTTP methods defined by this plugin
     *
     * This list is only used for the Allow: header in the HTTP OPTIONS
     * request.
     * 
     * @return array 
     */
    
public function getHTTPMethods() {

        return array(
'lock','unlock');

    }

    
/**
     * Returns a list of features for the HTTP OPTIONS Dav: header.
     *
     * In this case this is only the number 2. The 2 in the Dav: header
     * indicates the server supports locks.
     * 
     * @return array 
     */
    
public function getFeatures() {

        return array(
2);

    }

    
/**
     * Returns all lock information on a particular uri 
     * 
     * This function should return an array with Sabre_DAV_Locks_LockInfo objects. If there are no locks on a file, return an empty array.
     *
     * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree 
     *
     * @param string $uri 
     * @return array 
     */
    
public function getLocks($uri) {

        
$lockList = array();
        
$currentPath '';
        foreach(
explode('/',$uri) as $uriPart) {

            
$uriLocks = array();
            if (
$currentPath$currentPath.='/'
            
$currentPath.=$uriPart;

            try {

                
$node $this->server->tree->getNodeForPath($currentPath);
                if (
$node instanceof Sabre_DAV_ILockable$uriLocks $node->getLocks();

            } catch (
Sabre_DAV_Exception_FileNotFound $e){
                
// In case the node didn't exist, this could be a lock-null request
            
}

            foreach(
$uriLocks as $uriLock) {

                
// Unless we're on the leaf of the uri-tree we should ingore locks with depth 0
                
if($uri==$currentPath || $uriLock->depth!=0) {
                    
$uriLock->uri $currentPath;
                    
$lockList[] = $uriLock;
                }

            }

        }
        if (
$this->locksBackend$lockList array_merge($lockList,$this->locksBackend->getLocks($uri));
        return 
$lockList;

    }

    
/**
     * Locks an uri
     *
     * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
     * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type 
     * of lock (shared or exclusive) and the owner of the lock
     *
     * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
     *
     * Additionally, a lock can be requested for a non-existant file. In these case we're obligated to create an empty file as per RFC4918:S7.3
     * 
     * @return void
     */
    
protected function httpLock() {

        
$uri $this->server->getRequestUri();

        
$lastLock null;
        if (!
$this->validateLock($uri,$lastLock)) {

            
// If the existing lock was an exclusive lock, we need to fail
            
if (!$lastLock || $lastLock->scope == Sabre_DAV_Locks_LockInfo::EXCLUSIVE) {
                
//var_dump($lastLock);
                
throw new Sabre_DAV_Exception_ConflictingLock($lastLock);
            }

        }

        if (
$body $this->server->httpRequest->getBody(true)) {
            
// This is a new lock request
            
$lockInfo $this->parseLockRequest($body);
            
$lockInfo->depth $this->server->getHTTPDepth(0); 
            
$lockInfo->uri $uri;
            if(
$lastLock && $lockInfo->scope != Sabre_DAV_Locks_LockInfo::SHARED) throw new Sabre_DAV_Exception_ConflictingLock($lastLock);

        } elseif (
$lastLock) {

            
// This must have been a lock refresh
            
$lockInfo $lastLock;

        } else {
            
            
// There was neither a lock refresh nor a new lock request
            
throw new Sabre_DAV_Exception_BadRequest('An xml body is required for lock requests');

        }

        if (
$timeout $this->getTimeoutHeader()) $lockInfo->timeout $timeout;

        
$newFile false;

        
// If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
        
try {
            
$node $this->server->tree->getNodeForPath($uri);
            
            
// We need to call the beforeWriteContent event for RFC3744
            
$this->server->broadcastEvent('beforeWriteContent',array($uri));

        } catch (
Sabre_DAV_Exception_FileNotFound $e) {
            
            
// It didn't, lets create it
            
$this->server->createFile($uri,fopen('php://memory','r')); 
            
$newFile true

        }

        
$this->lockNode($uri,$lockInfo);

        
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
        
$this->server->httpResponse->setHeader('Lock-Token','<opaquelocktoken:' $lockInfo->token '>');
        
$this->server->httpResponse->sendStatus($newFile?201:200);
        
$this->server->httpResponse->sendBody($this->generateLockResponse($lockInfo));

    }

    
/**
     * Unlocks a uri
     *
     * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
     * The server should return 204 (No content) on success
     *
     * @return void
     */
    
protected function httpUnlock() {

        
$uri $this->server->getRequestUri();
        
        
$lockToken $this->server->httpRequest->getHeader('Lock-Token');

        
// If the locktoken header is not supplied, we need to throw a bad request exception
        
if (!$lockToken) throw new Sabre_DAV_Exception_BadRequest('No lock token was supplied');

        
$locks $this->getLocks($uri);

        
// We're grabbing the node information, just to rely on the fact it will throw a 404 when the node doesn't exist 
        //$this->server->tree->getNodeForPath($uri);

        
foreach($locks as $lock) {

            if (
'<opaquelocktoken:' $lock->token '>' == $lockToken) {

                
$this->unlockNode($uri,$lock);
                
$this->server->httpResponse->setHeader('Content-Length','0');
                
$this->server->httpResponse->sendStatus(204);
                return;

            }

        }

        
// If we got here, it means the locktoken was invalid
        
throw new Sabre_DAV_Exception_LockTokenMatchesRequestUri();

    }

    
/**
     * Locks a uri
     *
     * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
     * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
     * 
     * @param string $uri 
     * @param Sabre_DAV_Locks_LockInfo $lockInfo 
     * @return void
     */
    
public function lockNode($uri,Sabre_DAV_Locks_LockInfo $lockInfo) {

        if (!
$this->server->broadcastEvent('beforeLock',array($uri,$lockInfo))) return;

        try {
            
$node $this->server->tree->getNodeForPath($uri);
            if (
$node instanceof Sabre_DAV_ILockable) return $node->lock($lockInfo);
        } catch (
Sabre_DAV_Exception_FileNotFound $e) {
            
// In case the node didn't exist, this could be a lock-null request
        
}
        if (
$this->locksBackend) return $this->locksBackend->lock($uri,$lockInfo);
        throw new 
Sabre_DAV_Exception_NotImplemented('Locking support not implemented for this resource. No Locking backend was found so if you didn't expect this errorplease check your configuration.');

    }

    /**
     * Unlocks a uri
     *
     * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
     * 
     * @param string $uri 
     * @param Sabre_DAV_Locks_LockInfo $lockInfo 
     * @return void
     */
    public function unlockNode($uri,Sabre_DAV_Locks_LockInfo $lockInfo) {

        if (!$this->server->broadcastEvent('
beforeUnlock',array($uri,$lockInfo))) return;
        try {
            $node = $this->server->tree->getNodeForPath($uri);
            if ($node instanceof Sabre_DAV_ILockable) return $node->unlock($lockInfo);
        } catch (Sabre_DAV_Exception_FileNotFound $e) {
            // In case the node didn'
t existthis could be a lock-null request
        
}

        if (
$this->locksBackend) return $this->locksBackend->unlock($uri,$lockInfo);

    }


    
/**
     * Returns the contents of the HTTP Timeout header. 
     * 
     * The method formats the header into an integer.
     *
     * @return int
     */
    
public function getTimeoutHeader() {

        
$header $this->server->httpRequest->getHeader('Timeout');
        
        if (
$header) {

            if (
stripos($header,'second-')===0$header = (int)(substr($header,7));
            else if (
strtolower($header)=='infinite'$header=Sabre_DAV_Locks_LockInfo::TIMEOUT_INFINITE;
            else throw new 
Sabre_DAV_Exception_BadRequest('Invalid HTTP timeout header');

        } else {

            
$header 0;

        }

        return 
$header;

    }

    
/**
     * Generates the response for successfull LOCK requests 
     * 
     * @param Sabre_DAV_Locks_LockInfo $lockInfo 
     * @return string 
     */
    
protected function generateLockResponse(Sabre_DAV_Locks_LockInfo $lockInfo) {

        
$dom = new DOMDocument('1.0','utf-8');
        
$dom->formatOutput true;
        
        
$prop $dom->createElementNS('DAV:','d:prop');
        
$dom->appendChild($prop);

        
$lockDiscovery $dom->createElementNS('DAV:','d:lockdiscovery');
        
$prop->appendChild($lockDiscovery);

        
$lockObj = new Sabre_DAV_Property_LockDiscovery(array($lockInfo),true);
        
$lockObj->serialize($this->server,$lockDiscovery);

        return 
$dom->saveXML();

    }
    
    
/**
     * validateLock should be called when a write operation is about to happen
     * It will check if the requested url is locked, and see if the correct lock tokens are passed 
     *
     * @param mixed $urls List of relevant urls. Can be an array, a string or nothing at all for the current request uri
     * @param mixed $lastLock This variable will be populated with the last checked lock object (Sabre_DAV_Locks_LockInfo)
     * @return bool
     */
    
protected function validateLock($urls null,&$lastLock null) {

        if (
is_null($urls)) {
            
$urls = array($this->server->getRequestUri());
        } elseif (
is_string($urls)) {
            
$urls = array($urls);
        } elseif (!
is_array($urls)) {
            throw new 
Sabre_DAV_Exception('The urls parameter should either be null, a string or an array');
        }

        
$conditions $this->getIfConditions();

        
// We're going to loop through the urls and make sure all lock conditions are satisfied
        
foreach($urls as $url) {

            
$locks $this->getLocks($url);

            
// If there were no conditions, but there were locks, we fail 
            
if (!$conditions && $locks) {
                
reset($locks);
                
$lastLock current($locks);
                return 
false;
            }
          
            
// If there were no locks or conditions, we go to the next url
            
if (!$locks && !$conditions) continue;

            foreach(
$conditions as $condition) {

                
$conditionUri $condition['uri']?$this->server->calculateUri($condition['uri']):'';

                
// If the condition has a url, and it isn't part of the affected url at all, check the next condition
                
if ($conditionUri && strpos($url,$conditionUri)!==0) continue;

                
// The tokens array contians arrays with 2 elements. 0=true/false for normal/not condition, 1=locktoken
                // At least 1 condition has to be satisfied
                
foreach($condition['tokens'] as $conditionToken) {

                    
$etagValid true;
                    
$lockValid  true;

                    
// key 2 can contain an etag
                    
if ($conditionToken[2]) {

                        
$uri $conditionUri?$conditionUri:$this->server->getRequestUri(); 
                        
$node $this->server->tree->getNodeForPath($uri);
                        
$etagValid $node->getETag()==$conditionToken[2]; 

                    }

                    
// key 1 can contain a lock token
                    
if ($conditionToken[1]) {

                        
$lockValid false;
                        
// Match all the locks
                        
foreach($locks as $lockIndex=>$lock) {

                            
$lockToken 'opaquelocktoken:' $lock->token;

                            
// Checking NOT
                            
if (!$conditionToken[0] && $lockToken != $conditionToken[1]) {

                                
// Condition valid, onto the next
                                
$lockValid true;
                                break;
                            }
                            if (
$conditionToken[0] && $lockToken == $conditionToken[1]) {

                                
$lastLock $lock;
                                
// Condition valid and lock matched
                                
unset($locks[$lockIndex]);
                                
$lockValid true;
                                break;

                            }

                        }

                        if (
$etagValid && $lockValid) continue 2;

                    }
               }
               
// No conditions matched, so we fail
               
throw new Sabre_DAV_Exception_PreconditionFailed('The tokens provided in the if header did not match');
            }

            
// Conditions were met, we'll also need to check if all the locks are gone
            
if (count($locks)) {

                
// There's still locks, we fail
                
$lastLock current($locks);
                return 
false;

            }


        }

        
// We got here, this means every condition was satisfied
        
return true;

    }

    
/**
     * This method is created to extract information from the WebDAV HTTP 'If:' header
     *
     * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information
     * The function will return an array, containg structs with the following keys
     *
     *   * uri   - the uri the condition applies to. This can be an empty string for 'every relevant url'
     *   * tokens - The lock token. another 2 dimensional array containg 2 elements (0 = true/false.. If this is a negative condition its set to false, 1 = the actual token)
     *   * etag - an etag, if supplied
     * 
     * @return void
     */
    
public function getIfConditions() {

        
$header $this->server->httpRequest->getHeader('If'); 
        if (!
$header) return array();

        
$matches = array();

        
$regex '/(?:<(?P<uri>.*?)>s)?((?P<not>Nots)?(?:<(?P<token>[^>]*)>)?(?:s?)(?:[(?P<etag>[^]]*)])?)/im'// (?:s?)(?:[(?P<etag>[^]]*)])';
        
preg_match_all($regex,$header,$matches,PREG_SET_ORDER);

        
$conditions = array();

        foreach(
$matches as $match) {

            
$condition = array(
                
'uri'   => $match['uri'],
                
'tokens' => array(
                    array(
$match['not']?0:1,$match['token'],isset($match['etag'])?$match['etag']:'')
                ),    
            );

            if (!
$condition['uri'] && count($conditions)) $conditions[count($conditions)-1]['tokens'][] = array(
                
$match['not']?0:1,
                
$match['token'],
                isset(
$match['etag'])?$match['etag']:''
            
);
            else {
                
$conditions[] = $condition;
            }

        }

        return 
$conditions;

    }

    
/**
     * Parses a webdav lock xml body, and returns a new Sabre_DAV_Locks_LockInfo object 
     * 
     * @param string $body 
     * @return Sabre_DAV_Locks_LockInfo
     */
    
protected function parseLockRequest($body) {

        
$xml simplexml_load_string($body,null,LIBXML_NOWARNING);
        
$xml->registerXPathNamespace('d','DAV:');
        
$lockInfo = new Sabre_DAV_Locks_LockInfo();
     
        
$lockInfo->owner = (string)$xml->owner;

        
$lockToken '44445502';
        
$id md5(microtime() . 'somethingrandom');
        
$lockToken.='-' substr($id,0,4) . '-' substr($id,4,4) . '-' substr($id,8,4) . '-' substr($id,12,12);

        
$lockInfo->token $lockToken;
        
$lockInfo->scope count($xml->xpath('d:lockscope/d:exclusive'))>0?Sabre_DAV_Locks_LockInfo::EXCLUSIVE:Sabre_DAV_Locks_LockInfo::SHARED;

        return 
$lockInfo;

    }


}
Онлайн: 1
Реклама