Merge branch '2016.11-develop' into 2017.02
[tine20] / tine20 / Tinebase / Server / WebDAV.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Server
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2011-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * webdav Server class with handle() function
14  * 
15  * @package     Tinebase
16  * @subpackage  Server
17  */
18 class Tinebase_Server_WebDAV extends Tinebase_Server_Abstract implements Tinebase_Server_Interface
19 {
20     const REQUEST_TYPE = 'WebDAV';
21     
22    /**
23     * @var \Sabre\DAV\Server
24     */
25     protected static $_server;
26     
27     /**
28      * (non-PHPdoc)
29      * @see Tinebase_Server_Interface::handle()
30      */
31     public function handle(\Zend\Http\Request $request = null, $body = null)
32     {
33         $this->_request = $request instanceof \Zend\Http\Request ? $request : Tinebase_Core::get(Tinebase_Core::REQUEST);
34         if ($body !== null) {
35             $this->_body = $body;
36         } else if ($this->_request instanceof \Zend\Http\Request) {
37             $this->_body = fopen('php://temp', 'r+');
38             fwrite($this->_body, $request->getContent());
39             rewind($this->_body);
40             /*
41              * JN: dirty hack for native Windows 7 & 10 webdav client (after early 2017):
42              * client sends empty request instead empty xml-sceleton -> inject it here
43              */
44             $broken_user_agent_preg = '/^Microsoft-WebDAV-MiniRedir\/[6,10]/';
45             if (isset($_SERVER['HTTP_USER_AGENT']) && (preg_match($broken_user_agent_preg, $_SERVER['HTTP_USER_AGENT']) === 1) ) {
46                 if ($request->getContent() == '') {
47                     $broken_user_agent_body = '<?xml version="1.0" encoding="utf-8" ?><D:propfind xmlns:D="DAV:"><D:prop>';
48                     $broken_user_agent_body.= '<D:creationdate/><D:displayname/><D:getcontentlength/><D:getcontenttype/><D:getetag/><D:getlastmodified/><D:resourcetype/>';
49                     $broken_user_agent_body.= '</D:prop></D:propfind>';
50                     fwrite($this->_body, $broken_user_agent_body);
51                     rewind($this->_body);
52                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
53                         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " broken userAgent detected: " .
54                         $_SERVER['HTTP_USER_AGENT'] . " --> inserted xml body");
55                 }
56             }
57         }
58         
59         try {
60             list($loginName, $password) = $this->_getAuthData($this->_request);
61             
62         } catch (Tinebase_Exception_NotFound $tenf) {
63             header('WWW-Authenticate: Basic realm="WebDAV for Tine 2.0"');
64             header('HTTP/1.1 401 Unauthorized');
65             
66             return;
67         }
68         
69         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
70             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .' is CalDav, CardDAV or WebDAV request.');
71         
72         Tinebase_Core::initFramework();
73
74         if (null !== ($denyList = Tinebase_Config::getInstance()->get(Tinebase_Config::DENY_WEBDAV_CLIENT_LIST)) &&
75                 is_array($denyList)) {
76             foreach ($denyList as $deny) {
77                 if (preg_match($deny, $_SERVER['HTTP_USER_AGENT'])) {
78                     header('HTTP/1.1 420 Policy Not Fulfilled User Agent Not Accepted');
79                     return;
80                 }
81             }
82         }
83         
84         if (Tinebase_Controller::getInstance()->login(
85             $loginName,
86             $password,
87             $this->_request,
88             self::REQUEST_TYPE
89         ) !== true) {
90             header('WWW-Authenticate: Basic realm="WebDAV for Tine 2.0"');
91             header('HTTP/1.1 401 Unauthorized');
92             
93             return;
94         }
95         
96         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
97             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .' requestUri:' . $this->_request->getRequestUri());
98         
99         self::$_server = new \Sabre\DAV\Server(new Tinebase_WebDav_Root());
100         \Sabre\DAV\Server::$exposeVersion = false;
101         
102         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
103             self::$_server->debugExceptions = true;
104             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " headers: " . print_r(self::$_server->httpRequest->getHeaders(), true));
105             $contentType = self::$_server->httpRequest->getHeader('Content-Type');
106             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " requestContentType: " . $contentType);
107             
108             if (stripos($contentType, 'text') === 0 || stripos($contentType, '/xml') !== false) {
109                 // NOTE inputstream can not be rewinded
110                 $debugStream = fopen('php://temp','r+');
111                 stream_copy_to_stream($this->_body, $debugStream);
112                 rewind($debugStream);
113                 $this->_body = $debugStream;
114                 
115                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " <<< *DAV request\n" . stream_get_contents($this->_body));
116                 rewind($this->_body);
117             } else {
118                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " <<< *DAV request\n -- BINARY DATA --");
119             }
120         }
121         
122         self::$_server->httpRequest->setBody($this->_body);
123         
124         // compute base uri
125         self::$_server->setBaseUri($this->_request->getBaseUrl() . '/');
126         
127         $tempDir = Tinebase_Core::getTempDir();
128         if (!empty($tempDir)) {
129             self::$_server->addPlugin(
130                 new \Sabre\DAV\Locks\Plugin(new \Sabre\DAV\Locks\Backend\File($tempDir . '/webdav.lock'))
131             );
132         }
133         
134         self::$_server->addPlugin(
135             new \Sabre\DAV\Auth\Plugin(new Tinebase_WebDav_Auth(), null)
136         );
137         
138         $aclPlugin = new \Sabre\DAVACL\Plugin();
139         $aclPlugin->defaultUsernamePath    = Tinebase_WebDav_PrincipalBackend::PREFIX_USERS;
140         $aclPlugin->principalCollectionSet = array (Tinebase_WebDav_PrincipalBackend::PREFIX_USERS, Tinebase_WebDav_PrincipalBackend::PREFIX_GROUPS, Tinebase_WebDav_PrincipalBackend::PREFIX_INTELLIGROUPS);
141         
142         $aclPlugin->principalSearchPropertySet = array(
143             '{DAV:}displayname'                                                   => 'Display name',
144             '{' . \Sabre\DAV\Server::NS_SABREDAV . '}email-address'               => 'Email address',
145             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}email-address-set'  => 'Email addresses',
146             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'         => 'First name',
147             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'          => 'Last name',
148             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV         . '}calendar-user-address-set' => 'Calendar user address set',
149             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV         . '}calendar-user-type' => 'Calendar user type'
150         );
151         
152         self::$_server->addPlugin($aclPlugin);
153         
154         self::$_server->addPlugin(new \Sabre\CardDAV\Plugin());
155         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_SpeedUpPlugin); // this plugin must be loaded before CalDAV plugin
156         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_FixMultiGet404Plugin()); // replacement for new \Sabre\CalDAV\Plugin());
157         self::$_server->addPlugin(new \Sabre\CalDAV\SharingPlugin());
158         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_PluginAutoSchedule());
159         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_PluginDefaultAlarms());
160         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_PluginManagedAttachments());
161         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_PluginPrivateEvents());
162         self::$_server->addPlugin(new Tinebase_WebDav_Plugin_Inverse());
163         self::$_server->addPlugin(new Tinebase_WebDav_Plugin_OwnCloud());
164         self::$_server->addPlugin(new Tinebase_WebDav_Plugin_PrincipalSearch());
165         self::$_server->addPlugin(new Tinebase_WebDav_Plugin_ExpandedPropertiesReport());
166         self::$_server->addPlugin(new \Sabre\DAV\Browser\Plugin());
167         if (Tinebase_Config::getInstance()->get(Tinebase_Config::WEBDAV_SYNCTOKEN_ENABLED)) {
168             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
169                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport enabled');
170             self::$_server->addPlugin(new Tinebase_WebDav_Plugin_SyncToken());
171         } else {
172             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
173                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport disabled');
174         }
175         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_SpeedUpPropfindPlugin());
176
177         $contentType = self::$_server->httpRequest->getHeader('Content-Type');
178         $logOutput = Tinebase_Core::isLogLevel(Zend_Log::DEBUG) && (stripos($contentType, 'text') === 0 || stripos($contentType, '/xml') !== false);
179
180         if ($logOutput) {
181             ob_start();
182         }
183         
184         self::$_server->exec();
185         
186         if ($logOutput) {
187             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " >>> *DAV response:\n" . ob_get_contents());
188             ob_end_flush();
189         } else {
190
191             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " <<< *DAV response\n -- BINARY DATA --");
192         }
193
194         Tinebase_Controller::getInstance()->logout($this->_request->getServer('REMOTE_ADDR'));
195     }
196     
197    /**
198     * helper to return request
199     *
200     * @return Sabre\HTTP\Request
201     */
202     public static function getRequest()
203     {
204         return self::$_server ? self::$_server->httpRequest : new Sabre\HTTP\Request();
205     }
206
207     /**
208      * helper to return response
209      *
210      * @return Sabre\HTTP\Response
211      */
212     public static function getResponse()
213     {
214         return self::$_server ? self::$_server->httpResponse : new Sabre\HTTP\Response();
215     }
216
217     /**
218     * returns request method
219     *
220     * @return string
221     */
222     public function getRequestMethod()
223     {
224         return self::getRequest()->getMethod();
225     }
226 }