Completed
Push — 16.1 ( 7ccc73...046888 )
by Nathan
64:46 queued 51:15
created
api/src/Storage.php 1 patch
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -210,12 +210,12 @@
 block discarded – undo
210 210
 	}
211 211
 
212 212
 	/**
213
-	* saves custom field data
214
-	*
215
-	* @param array $data data to save (cf's have to be prefixed with self::CF_PREFIX = #)
216
-	* @param array $extra_cols =array() extra-data to be saved
217
-	* @return bool false on success, errornumber on failure
218
-	*/
213
+	 * saves custom field data
214
+	 *
215
+	 * @param array $data data to save (cf's have to be prefixed with self::CF_PREFIX = #)
216
+	 * @param array $extra_cols =array() extra-data to be saved
217
+	 * @return bool false on success, errornumber on failure
218
+	 */
219 219
 	function save_customfields($data, array $extra_cols=array())
220 220
 	{
221 221
 		$id = isset($data[$this->autoinc_id]) ? $data[$this->autoinc_id] : $data[$this->db_key_cols[$this->autoinc_id]];
Please login to merge, or discard this patch.
api/src/Vfs/StreamWrapper.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -405,10 +405,10 @@  discard block
 block discarded – undo
405 405
  	 * Important: PHP 5.0 introduced a bug that wasn't fixed until 5.1: the return value has to be the oposite!
406 406
  	 *
407 407
  	 * if(version_compare(PHP_VERSION,'5.0','>=') && version_compare(PHP_VERSION,'5.1','<'))
408
-  	 * {
408
+ 	 * {
409 409
  	 * 		$eof = !$eof;
410 410
  	 * }
411
-  	 *
411
+ 	 *
412 412
  	 * @return boolean true if the read/write position is at the end of the stream and no more data availible, false otherwise
413 413
  	 */
414 414
 	function stream_eof ( )
@@ -906,7 +906,7 @@  discard block
 block discarded – undo
906 906
 		if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path',$flags,'$url'): ".function_backtrace(1));
907 907
 
908 908
 		while (($rel_path = Vfs::basename($url).($rel_path ? '/'.$rel_path : '')) &&
909
-		       ($url = Vfs::dirname($url)))
909
+			   ($url = Vfs::dirname($url)))
910 910
 		{
911 911
 			if (($stat = $this->url_stat($url,0,true,false)))
912 912
 			{
Please login to merge, or discard this patch.
api/src/WebDAV/Server.php 1 patch
Indentation   +2524 added lines, -2524 removed lines patch added patch discarded remove patch
@@ -48,632 +48,632 @@  discard block
 block discarded – undo
48 48
  */
49 49
 class HTTP_WebDAV_Server
50 50
 {
51
-    // {{{ Member Variables
51
+	// {{{ Member Variables
52 52
 
53
-    /**
54
-     * complete URI for this request
55
-     *
56
-     * @var string
57
-     */
58
-    var $uri;
53
+	/**
54
+	 * complete URI for this request
55
+	 *
56
+	 * @var string
57
+	 */
58
+	var $uri;
59 59
 
60
-    /**
61
-     * base URI for this request
62
-     *
63
-     * @var string
64
-     */
65
-    var $base_uri;
60
+	/**
61
+	 * base URI for this request
62
+	 *
63
+	 * @var string
64
+	 */
65
+	var $base_uri;
66 66
 
67
-    /**
68
-     * Set if client requires <D:href> to be a url (true) or a path (false).
69
-     * RFC 4918 allows both: http://www.webdav.org/specs/rfc4918.html#ELEMENT_href
70
-     * But some clients can NOT deal with one or the other!
71
-     *
72
-     * @var boolean
73
-     */
74
-    var $client_require_href_as_url;
75
-
76
-     /**
77
-     * Set if client requires or does not allow namespace redundacy.
78
-     * The XML Namespace specification does allow both
79
-     * But some clients can NOT deal with one or the other!
80
-     *
81
-     * $this->crrnd === false:
82
-     * <D:multistatus xmlns:D="DAV:">
83
-     *  <D:response xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
84
-     *   <D:href>/egroupware/webdav.php/home/ralf/</D:href>
85
-     *    <D:propstat>
86
-     *     <D:prop>
87
-     *      <D:resourcetype><D:collection /></D:resourcetype>
88
-     *     </D:prop>
89
-     *    <D:status>HTTP/1.1 200 OK</D:status>
90
-     *   </D:propstat>
91
-     *  </D:response>
92
-     * </D:multistatus>
93
-     *
94
-     * $this->crrnd === true:
95
-     * <multistatus xmlns="DAV:">
96
-     *  <response xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
97
-     *   <href>/egroupware/webdav.php/home/ralf/</href>
98
-     *    <propstat>
99
-     *     <prop>
100
-     *      <resourcetype><collection /></resourcetype>
101
-     *     </prop>
102
-     *    <status>HTTP/1.1 200 OK</status>
103
-     *   </propstat>
104
-     *  </response>
105
-     * </multistatus>
106
-     *
107
-     * @var boolean (client_refuses_redundand_namespace_declarations)
108
-     */
109
-    var $crrnd = false;
67
+	/**
68
+	 * Set if client requires <D:href> to be a url (true) or a path (false).
69
+	 * RFC 4918 allows both: http://www.webdav.org/specs/rfc4918.html#ELEMENT_href
70
+	 * But some clients can NOT deal with one or the other!
71
+	 *
72
+	 * @var boolean
73
+	 */
74
+	var $client_require_href_as_url;
75
+
76
+	 /**
77
+	  * Set if client requires or does not allow namespace redundacy.
78
+	  * The XML Namespace specification does allow both
79
+	  * But some clients can NOT deal with one or the other!
80
+	  *
81
+	  * $this->crrnd === false:
82
+	  * <D:multistatus xmlns:D="DAV:">
83
+	  *  <D:response xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
84
+	  *   <D:href>/egroupware/webdav.php/home/ralf/</D:href>
85
+	  *    <D:propstat>
86
+	  *     <D:prop>
87
+	  *      <D:resourcetype><D:collection /></D:resourcetype>
88
+	  *     </D:prop>
89
+	  *    <D:status>HTTP/1.1 200 OK</D:status>
90
+	  *   </D:propstat>
91
+	  *  </D:response>
92
+	  * </D:multistatus>
93
+	  *
94
+	  * $this->crrnd === true:
95
+	  * <multistatus xmlns="DAV:">
96
+	  *  <response xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
97
+	  *   <href>/egroupware/webdav.php/home/ralf/</href>
98
+	  *    <propstat>
99
+	  *     <prop>
100
+	  *      <resourcetype><collection /></resourcetype>
101
+	  *     </prop>
102
+	  *    <status>HTTP/1.1 200 OK</status>
103
+	  *   </propstat>
104
+	  *  </response>
105
+	  * </multistatus>
106
+	  *
107
+	  * @var boolean (client_refuses_redundand_namespace_declarations)
108
+	  */
109
+	var $crrnd = false;
110 110
 
111
-    /**
111
+	/**
112 112
 
113 113
 
114 114
     /**
115
-     * URI path for this request
116
-     *
117
-     * @var string
118
-     */
119
-    var $path;
115
+	 * URI path for this request
116
+	 *
117
+	 * @var string
118
+	 */
119
+	var $path;
120 120
 
121
-    /**
122
-     * Realm string to be used in authentification popups
123
-     *
124
-     * @var string
125
-     */
126
-    var $http_auth_realm = "PHP WebDAV";
121
+	/**
122
+	 * Realm string to be used in authentification popups
123
+	 *
124
+	 * @var string
125
+	 */
126
+	var $http_auth_realm = "PHP WebDAV";
127 127
 
128
-    /**
129
-     * String to be used in "X-Dav-Powered-By" header
130
-     *
131
-     * @var string
132
-     */
133
-    var $dav_powered_by = "";
128
+	/**
129
+	 * String to be used in "X-Dav-Powered-By" header
130
+	 *
131
+	 * @var string
132
+	 */
133
+	var $dav_powered_by = "";
134 134
 
135
-    /**
136
-     * Remember parsed If: (RFC2518/9.4) header conditions
137
-     *
138
-     * @var array
139
-     */
140
-    var $_if_header_uris = array();
135
+	/**
136
+	 * Remember parsed If: (RFC2518/9.4) header conditions
137
+	 *
138
+	 * @var array
139
+	 */
140
+	var $_if_header_uris = array();
141 141
 
142
-    /**
143
-     * HTTP response status/message
144
-     *
145
-     * @var string
146
-     */
147
-    var $_http_status = "200 OK";
142
+	/**
143
+	 * HTTP response status/message
144
+	 *
145
+	 * @var string
146
+	 */
147
+	var $_http_status = "200 OK";
148 148
 
149
-    /**
150
-     * encoding of property values passed in
151
-     *
152
-     * @var string
153
-     */
154
-    var $_prop_encoding = "utf-8";
149
+	/**
150
+	 * encoding of property values passed in
151
+	 *
152
+	 * @var string
153
+	 */
154
+	var $_prop_encoding = "utf-8";
155 155
 
156
-    /**
157
-     * Copy of $_SERVER superglobal array
158
-     *
159
-     * Derived classes may extend the constructor to
160
-     * modify its contents
161
-     *
162
-     * @var array
163
-     */
164
-    var $_SERVER;
156
+	/**
157
+	 * Copy of $_SERVER superglobal array
158
+	 *
159
+	 * Derived classes may extend the constructor to
160
+	 * modify its contents
161
+	 *
162
+	 * @var array
163
+	 */
164
+	var $_SERVER;
165 165
 
166
-    // }}}
166
+	// }}}
167 167
 
168
-    // {{{ Constructor
168
+	// {{{ Constructor
169 169
 
170
-    /**
171
-     * Constructor
172
-     *
173
-     * @param void
174
-     */
175
-    function __construct()
176
-    {
177
-        // PHP messages destroy XML output -> switch them off
178
-        ini_set("display_errors", 0);
179
-
180
-        // copy $_SERVER variables to local _SERVER array
181
-        // so that derived classes can simply modify these
182
-        $this->_SERVER = $_SERVER;
183
-    }
184
-
185
-    // }}}
186
-
187
-    // {{{ ServeRequest()
188
-    /**
189
-     * Serve WebDAV HTTP request
190
-     *
191
-     * dispatch WebDAV HTTP request to the apropriate method handler
192
-     *
193
-     * @param  $prefix =null prefix filesystem path with given path, eg. "/webdav" for owncloud 4.5 remote.php
194
-     * @return void
195
-     */
196
-    function ServeRequest($prefix=null)
197
-    {
198
-        // prevent warning in litmus check 'delete_fragment'
199
-        if (strstr($this->_SERVER["REQUEST_URI"], '#')) {
200
-            $this->http_status("400 Bad Request");
201
-            return;
202
-        }
203
-
204
-        // default is currently to use just the path, extending class can set $this->client_require_href_as_url depending on user-agent
205
-        if ($this->client_require_href_as_url)
206
-        {
207
-	        // default uri is the complete request uri
208
-	        $uri = (@$this->_SERVER["HTTPS"] === "on" ? "https:" : "http:") . '//'.$this->_SERVER['HTTP_HOST'];
209
-        }
210
-        $uri .= $this->_SERVER["SCRIPT_NAME"];
211
-
212
-        // WebDAV has no concept of a query string and clients (including cadaver)
213
-        // seem to pass '?' unencoded, so we need to extract the path info out
214
-        // of the request URI ourselves
215
-        // if request URI contains a full url, remove schema and domain
170
+	/**
171
+	 * Constructor
172
+	 *
173
+	 * @param void
174
+	 */
175
+	function __construct()
176
+	{
177
+		// PHP messages destroy XML output -> switch them off
178
+		ini_set("display_errors", 0);
179
+
180
+		// copy $_SERVER variables to local _SERVER array
181
+		// so that derived classes can simply modify these
182
+		$this->_SERVER = $_SERVER;
183
+	}
184
+
185
+	// }}}
186
+
187
+	// {{{ ServeRequest()
188
+	/**
189
+	 * Serve WebDAV HTTP request
190
+	 *
191
+	 * dispatch WebDAV HTTP request to the apropriate method handler
192
+	 *
193
+	 * @param  $prefix =null prefix filesystem path with given path, eg. "/webdav" for owncloud 4.5 remote.php
194
+	 * @return void
195
+	 */
196
+	function ServeRequest($prefix=null)
197
+	{
198
+		// prevent warning in litmus check 'delete_fragment'
199
+		if (strstr($this->_SERVER["REQUEST_URI"], '#')) {
200
+			$this->http_status("400 Bad Request");
201
+			return;
202
+		}
203
+
204
+		// default is currently to use just the path, extending class can set $this->client_require_href_as_url depending on user-agent
205
+		if ($this->client_require_href_as_url)
206
+		{
207
+			// default uri is the complete request uri
208
+			$uri = (@$this->_SERVER["HTTPS"] === "on" ? "https:" : "http:") . '//'.$this->_SERVER['HTTP_HOST'];
209
+		}
210
+		$uri .= $this->_SERVER["SCRIPT_NAME"];
211
+
212
+		// WebDAV has no concept of a query string and clients (including cadaver)
213
+		// seem to pass '?' unencoded, so we need to extract the path info out
214
+		// of the request URI ourselves
215
+		// if request URI contains a full url, remove schema and domain
216 216
 		$matches = null;
217
-        if (preg_match('|^https?://[^/]+(/.*)$|', $path_info=$this->_SERVER["REQUEST_URI"], $matches))
218
-        {
219
-        	$path_info = $matches[1];
220
-        }
221
-        $path_info_raw = substr($path_info, strlen($this->_SERVER["SCRIPT_NAME"]));
222
-
223
-        // just in case the path came in empty ...
224
-        if (empty($path_info_raw)) {
225
-            $path_info_raw = "/";
226
-        }
227
-
228
-        $path_info = self::_urldecode($path_info_raw);
229
-
230
-        if ($prefix && strpos($path_info, $prefix) === 0)
231
-        {
232
-        	$uri .= $prefix;
233
-        	list(,$path_info) = explode($prefix, $path_info, 2);
234
-        }
235
-
236
-        $this->base_uri = $uri;
237
-        $this->uri      = $uri . $path_info;
238
-
239
-        // set path
240
-        // $_SERVER['PATH_INFO'] is already urldecoded
241
-        //$this->path = self::_urldecode($path_info);
242
-        // quote '#' (e.g. OpenOffice uses this for lock-files)
243
-        $this->path = strtr($path_info,array(
244
-        	'%' => '%25',
245
-        	'#' => '%23',
246
-        	'?' => '%3F',
247
-        ));
248
-        if (!strlen($this->path)) {
249
-            if ($this->_SERVER["REQUEST_METHOD"] == "GET") {
250
-                // redirect clients that try to GET a collection
251
-                // WebDAV clients should never try this while
252
-                // regular HTTP clients might ...
253
-                header("Location: ".$this->base_uri."/");
254
-                return;
255
-            } else {
256
-                // if a WebDAV client didn't give a path we just assume '/'
257
-                $this->path = "/";
258
-            }
259
-        }
260
-
261
-        if (ini_get("magic_quotes_gpc")) {
262
-            $this->path = stripslashes($this->path);
263
-        }
264
-
265
-
266
-        // identify ourselves
267
-        if (empty($this->dav_powered_by)) {
268
-            header("X-Dav-Powered-By: PHP class: ".get_class($this));
269
-        } else {
270
-            header("X-Dav-Powered-By: ".$this->dav_powered_by);
271
-        }
272
-
273
-        // check authentication
274
-        // for the motivation for not checking OPTIONS requests on / see
275
-        // http://pear.php.net/bugs/bug.php?id=5363
276
-        if ( (   !(($this->_SERVER['REQUEST_METHOD'] == 'OPTIONS') && ($this->path == "/")))
277
-             && (!$this->_check_auth())) {
278
-            // RFC2518 says we must use Digest instead of Basic
279
-            // but Microsoft Clients do not support Digest
280
-            // and we don't support NTLM and Kerberos
281
-            // so we are stuck with Basic here
282
-            header('WWW-Authenticate: Basic realm="'.($this->http_auth_realm).'"');
283
-
284
-            // Windows seems to require this being the last header sent
285
-            // (changed according to PECL bug #3138)
286
-            $this->http_status('401 Unauthorized');
287
-
288
-            return;
289
-        }
290
-
291
-        // check
292
-        if (! $this->_check_if_header_conditions()) {
293
-            return;
294
-        }
295
-
296
-        // detect requested method names
297
-        $method  = strtolower($this->_SERVER["REQUEST_METHOD"]);
298
-        $wrapper = "http_".$method;
299
-
300
-        // activate HEAD emulation by GET if no HEAD method found
301
-        if ($method == "head" && !method_exists($this, "head")) {
302
-            $method = "get";
303
-        }
304
-
305
-        if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) {
306
-            $this->$wrapper();  // call method by name
307
-        } else { // method not found/implemented
308
-            if ($this->_SERVER["REQUEST_METHOD"] == "LOCK") {
309
-            	$error = '412 Precondition failed';
310
-            } else {
311
-                $error = '405 Method not allowed';
312
-                header("Allow: ".join(", ", $this->_allow()));  // tell client what's allowed
313
-            }
314
-            $this->http_status($error);
315
-            echo "<html><head><title>Error $error</title></head>\n";
316
-            echo "<body><h1>$error</h1>\n";
317
-            echo "The requested could not by handled by this server.\n";
318
-            echo '(URI ' . $this->_SERVER['REQUEST_URI'] . ")<br>\n<br>\n";
319
-            echo "</body></html>\n";
320
-        }
321
-    }
322
-
323
-    // }}}
324
-
325
-    // {{{ abstract WebDAV methods
326
-
327
-    // {{{ GET()
328
-    /**
329
-     * GET implementation
330
-     *
331
-     * overload this method to retrieve resources from your server
332
-     * <br>
333
-     *
334
-     *
335
-     * @abstract
336
-     * @param array &$params Array of input and output parameters
337
-     * <br><b>input</b><ul>
338
-     * <li> path -
339
-     * </ul>
340
-     * <br><b>output</b><ul>
341
-     * <li> size -
342
-     * </ul>
343
-     * @returns int HTTP-Statuscode
344
-     */
345
-
346
-    /* abstract
217
+		if (preg_match('|^https?://[^/]+(/.*)$|', $path_info=$this->_SERVER["REQUEST_URI"], $matches))
218
+		{
219
+			$path_info = $matches[1];
220
+		}
221
+		$path_info_raw = substr($path_info, strlen($this->_SERVER["SCRIPT_NAME"]));
222
+
223
+		// just in case the path came in empty ...
224
+		if (empty($path_info_raw)) {
225
+			$path_info_raw = "/";
226
+		}
227
+
228
+		$path_info = self::_urldecode($path_info_raw);
229
+
230
+		if ($prefix && strpos($path_info, $prefix) === 0)
231
+		{
232
+			$uri .= $prefix;
233
+			list(,$path_info) = explode($prefix, $path_info, 2);
234
+		}
235
+
236
+		$this->base_uri = $uri;
237
+		$this->uri      = $uri . $path_info;
238
+
239
+		// set path
240
+		// $_SERVER['PATH_INFO'] is already urldecoded
241
+		//$this->path = self::_urldecode($path_info);
242
+		// quote '#' (e.g. OpenOffice uses this for lock-files)
243
+		$this->path = strtr($path_info,array(
244
+			'%' => '%25',
245
+			'#' => '%23',
246
+			'?' => '%3F',
247
+		));
248
+		if (!strlen($this->path)) {
249
+			if ($this->_SERVER["REQUEST_METHOD"] == "GET") {
250
+				// redirect clients that try to GET a collection
251
+				// WebDAV clients should never try this while
252
+				// regular HTTP clients might ...
253
+				header("Location: ".$this->base_uri."/");
254
+				return;
255
+			} else {
256
+				// if a WebDAV client didn't give a path we just assume '/'
257
+				$this->path = "/";
258
+			}
259
+		}
260
+
261
+		if (ini_get("magic_quotes_gpc")) {
262
+			$this->path = stripslashes($this->path);
263
+		}
264
+
265
+
266
+		// identify ourselves
267
+		if (empty($this->dav_powered_by)) {
268
+			header("X-Dav-Powered-By: PHP class: ".get_class($this));
269
+		} else {
270
+			header("X-Dav-Powered-By: ".$this->dav_powered_by);
271
+		}
272
+
273
+		// check authentication
274
+		// for the motivation for not checking OPTIONS requests on / see
275
+		// http://pear.php.net/bugs/bug.php?id=5363
276
+		if ( (   !(($this->_SERVER['REQUEST_METHOD'] == 'OPTIONS') && ($this->path == "/")))
277
+			 && (!$this->_check_auth())) {
278
+			// RFC2518 says we must use Digest instead of Basic
279
+			// but Microsoft Clients do not support Digest
280
+			// and we don't support NTLM and Kerberos
281
+			// so we are stuck with Basic here
282
+			header('WWW-Authenticate: Basic realm="'.($this->http_auth_realm).'"');
283
+
284
+			// Windows seems to require this being the last header sent
285
+			// (changed according to PECL bug #3138)
286
+			$this->http_status('401 Unauthorized');
287
+
288
+			return;
289
+		}
290
+
291
+		// check
292
+		if (! $this->_check_if_header_conditions()) {
293
+			return;
294
+		}
295
+
296
+		// detect requested method names
297
+		$method  = strtolower($this->_SERVER["REQUEST_METHOD"]);
298
+		$wrapper = "http_".$method;
299
+
300
+		// activate HEAD emulation by GET if no HEAD method found
301
+		if ($method == "head" && !method_exists($this, "head")) {
302
+			$method = "get";
303
+		}
304
+
305
+		if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) {
306
+			$this->$wrapper();  // call method by name
307
+		} else { // method not found/implemented
308
+			if ($this->_SERVER["REQUEST_METHOD"] == "LOCK") {
309
+				$error = '412 Precondition failed';
310
+			} else {
311
+				$error = '405 Method not allowed';
312
+				header("Allow: ".join(", ", $this->_allow()));  // tell client what's allowed
313
+			}
314
+			$this->http_status($error);
315
+			echo "<html><head><title>Error $error</title></head>\n";
316
+			echo "<body><h1>$error</h1>\n";
317
+			echo "The requested could not by handled by this server.\n";
318
+			echo '(URI ' . $this->_SERVER['REQUEST_URI'] . ")<br>\n<br>\n";
319
+			echo "</body></html>\n";
320
+		}
321
+	}
322
+
323
+	// }}}
324
+
325
+	// {{{ abstract WebDAV methods
326
+
327
+	// {{{ GET()
328
+	/**
329
+	 * GET implementation
330
+	 *
331
+	 * overload this method to retrieve resources from your server
332
+	 * <br>
333
+	 *
334
+	 *
335
+	 * @abstract
336
+	 * @param array &$params Array of input and output parameters
337
+	 * <br><b>input</b><ul>
338
+	 * <li> path -
339
+	 * </ul>
340
+	 * <br><b>output</b><ul>
341
+	 * <li> size -
342
+	 * </ul>
343
+	 * @returns int HTTP-Statuscode
344
+	 */
345
+
346
+	/* abstract
347 347
      function GET(&$params)
348 348
      {
349 349
      // dummy entry for PHPDoc
350 350
      }
351 351
     */
352 352
 
353
-    // }}}
353
+	// }}}
354 354
 
355
-    // {{{ PUT()
356
-    /**
357
-     * PUT implementation
358
-     *
359
-     * PUT implementation
360
-     *
361
-     * @abstract
362
-     * @param array &$params
363
-     * @returns int HTTP-Statuscode
364
-     */
365
-
366
-    /* abstract
355
+	// {{{ PUT()
356
+	/**
357
+	 * PUT implementation
358
+	 *
359
+	 * PUT implementation
360
+	 *
361
+	 * @abstract
362
+	 * @param array &$params
363
+	 * @returns int HTTP-Statuscode
364
+	 */
365
+
366
+	/* abstract
367 367
      function PUT()
368 368
      {
369 369
      // dummy entry for PHPDoc
370 370
      }
371 371
     */
372 372
 
373
-    // }}}
373
+	// }}}
374 374
 
375
-    // {{{ COPY()
375
+	// {{{ COPY()
376 376
 
377
-    /**
378
-     * COPY implementation
379
-     *
380
-     * COPY implementation
381
-     *
382
-     * @abstract
383
-     * @param array &$params
384
-     * @returns int HTTP-Statuscode
385
-     */
386
-
387
-    /* abstract
377
+	/**
378
+	 * COPY implementation
379
+	 *
380
+	 * COPY implementation
381
+	 *
382
+	 * @abstract
383
+	 * @param array &$params
384
+	 * @returns int HTTP-Statuscode
385
+	 */
386
+
387
+	/* abstract
388 388
      function COPY()
389 389
      {
390 390
      // dummy entry for PHPDoc
391 391
      }
392 392
     */
393 393
 
394
-    // }}}
394
+	// }}}
395 395
 
396
-    // {{{ MOVE()
396
+	// {{{ MOVE()
397 397
 
398
-    /**
399
-     * MOVE implementation
400
-     *
401
-     * MOVE implementation
402
-     *
403
-     * @abstract
404
-     * @param array &$params
405
-     * @returns int HTTP-Statuscode
406
-     */
407
-
408
-    /* abstract
398
+	/**
399
+	 * MOVE implementation
400
+	 *
401
+	 * MOVE implementation
402
+	 *
403
+	 * @abstract
404
+	 * @param array &$params
405
+	 * @returns int HTTP-Statuscode
406
+	 */
407
+
408
+	/* abstract
409 409
      function MOVE()
410 410
      {
411 411
      // dummy entry for PHPDoc
412 412
      }
413 413
     */
414 414
 
415
-    // }}}
415
+	// }}}
416 416
 
417
-    // {{{ DELETE()
417
+	// {{{ DELETE()
418 418
 
419
-    /**
420
-     * DELETE implementation
421
-     *
422
-     * DELETE implementation
423
-     *
424
-     * @abstract
425
-     * @param array &$params
426
-     * @returns int HTTP-Statuscode
427
-     */
428
-
429
-    /* abstract
419
+	/**
420
+	 * DELETE implementation
421
+	 *
422
+	 * DELETE implementation
423
+	 *
424
+	 * @abstract
425
+	 * @param array &$params
426
+	 * @returns int HTTP-Statuscode
427
+	 */
428
+
429
+	/* abstract
430 430
      function DELETE()
431 431
      {
432 432
      // dummy entry for PHPDoc
433 433
      }
434 434
     */
435
-    // }}}
435
+	// }}}
436 436
 
437
-    // {{{ PROPFIND()
437
+	// {{{ PROPFIND()
438 438
 
439
-    /**
440
-     * PROPFIND implementation
441
-     *
442
-     * PROPFIND implementation
443
-     *
444
-     * @abstract
445
-     * @param array &$params
446
-     * @returns int HTTP-Statuscode
447
-     */
448
-
449
-    /* abstract
439
+	/**
440
+	 * PROPFIND implementation
441
+	 *
442
+	 * PROPFIND implementation
443
+	 *
444
+	 * @abstract
445
+	 * @param array &$params
446
+	 * @returns int HTTP-Statuscode
447
+	 */
448
+
449
+	/* abstract
450 450
      function PROPFIND()
451 451
      {
452 452
      // dummy entry for PHPDoc
453 453
      }
454 454
     */
455 455
 
456
-    // }}}
456
+	// }}}
457 457
 
458
-    // {{{ PROPPATCH()
458
+	// {{{ PROPPATCH()
459 459
 
460
-    /**
461
-     * PROPPATCH implementation
462
-     *
463
-     * PROPPATCH implementation
464
-     *
465
-     * @abstract
466
-     * @param array &$params
467
-     * @returns int HTTP-Statuscode
468
-     */
469
-
470
-    /* abstract
460
+	/**
461
+	 * PROPPATCH implementation
462
+	 *
463
+	 * PROPPATCH implementation
464
+	 *
465
+	 * @abstract
466
+	 * @param array &$params
467
+	 * @returns int HTTP-Statuscode
468
+	 */
469
+
470
+	/* abstract
471 471
      function PROPPATCH()
472 472
      {
473 473
      // dummy entry for PHPDoc
474 474
      }
475 475
     */
476
-    // }}}
476
+	// }}}
477 477
 
478
-    // {{{ LOCK()
478
+	// {{{ LOCK()
479 479
 
480
-    /**
481
-     * LOCK implementation
482
-     *
483
-     * LOCK implementation
484
-     *
485
-     * @abstract
486
-     * @param array &$params
487
-     * @returns int HTTP-Statuscode
488
-     */
489
-
490
-    /* abstract
480
+	/**
481
+	 * LOCK implementation
482
+	 *
483
+	 * LOCK implementation
484
+	 *
485
+	 * @abstract
486
+	 * @param array &$params
487
+	 * @returns int HTTP-Statuscode
488
+	 */
489
+
490
+	/* abstract
491 491
      function LOCK()
492 492
      {
493 493
      // dummy entry for PHPDoc
494 494
      }
495 495
     */
496
-    // }}}
496
+	// }}}
497 497
 
498
-    // {{{ UNLOCK()
498
+	// {{{ UNLOCK()
499 499
 
500
-    /**
501
-     * UNLOCK implementation
502
-     *
503
-     * UNLOCK implementation
504
-     *
505
-     * @abstract
506
-     * @param array &$params
507
-     * @returns int HTTP-Statuscode
508
-     */
509
-
510
-    /* abstract
500
+	/**
501
+	 * UNLOCK implementation
502
+	 *
503
+	 * UNLOCK implementation
504
+	 *
505
+	 * @abstract
506
+	 * @param array &$params
507
+	 * @returns int HTTP-Statuscode
508
+	 */
509
+
510
+	/* abstract
511 511
      function UNLOCK()
512 512
      {
513 513
      // dummy entry for PHPDoc
514 514
      }
515 515
     */
516
-    // }}}
516
+	// }}}
517 517
 
518
-    // {{{ ACL()
518
+	// {{{ ACL()
519 519
 
520
-    /**
521
-     * ACL implementation
522
-     *
523
-     * ACL implementation
524
-     *
525
-     * @abstract
526
-     * @param array &$params
527
-     * @returns int HTTP-Statuscode
528
-     */
529
-
530
-    /* abstract
520
+	/**
521
+	 * ACL implementation
522
+	 *
523
+	 * ACL implementation
524
+	 *
525
+	 * @abstract
526
+	 * @param array &$params
527
+	 * @returns int HTTP-Statuscode
528
+	 */
529
+
530
+	/* abstract
531 531
      function ACL()
532 532
      {
533 533
      // dummy entry for PHPDoc
534 534
      }
535 535
     */
536
-    // }}}
536
+	// }}}
537 537
 
538
-    // }}}
538
+	// }}}
539 539
 
540
-    // {{{ other abstract methods
540
+	// {{{ other abstract methods
541 541
 
542
-    // {{{ check_auth()
542
+	// {{{ check_auth()
543 543
 
544
-    /**
545
-     * check authentication
546
-     *
547
-     * overload this method to retrieve and confirm authentication information
548
-     *
549
-     * @abstract
550
-     * @param string type Authentication type, e.g. "basic" or "digest"
551
-     * @param string username Transmitted username
552
-     * @param string passwort Transmitted password
553
-     * @returns bool Authentication status
554
-     */
555
-
556
-    /* abstract
544
+	/**
545
+	 * check authentication
546
+	 *
547
+	 * overload this method to retrieve and confirm authentication information
548
+	 *
549
+	 * @abstract
550
+	 * @param string type Authentication type, e.g. "basic" or "digest"
551
+	 * @param string username Transmitted username
552
+	 * @param string passwort Transmitted password
553
+	 * @returns bool Authentication status
554
+	 */
555
+
556
+	/* abstract
557 557
      function checkAuth($type, $username, $password)
558 558
      {
559 559
      // dummy entry for PHPDoc
560 560
      }
561 561
     */
562 562
 
563
-    // }}}
563
+	// }}}
564 564
 
565
-    // {{{ checklock()
565
+	// {{{ checklock()
566 566
 
567
-    /**
568
-     * check lock status for a resource
569
-     *
570
-     * overload this method to return shared and exclusive locks
571
-     * active for this resource
572
-     *
573
-     * @abstract
574
-     * @param string resource Resource path to check
575
-     * @returns array An array of lock entries each consisting
576
-     *                of 'type' ('shared'/'exclusive'), 'token' and 'timeout'
577
-     */
578
-
579
-    /* abstract
567
+	/**
568
+	 * check lock status for a resource
569
+	 *
570
+	 * overload this method to return shared and exclusive locks
571
+	 * active for this resource
572
+	 *
573
+	 * @abstract
574
+	 * @param string resource Resource path to check
575
+	 * @returns array An array of lock entries each consisting
576
+	 *                of 'type' ('shared'/'exclusive'), 'token' and 'timeout'
577
+	 */
578
+
579
+	/* abstract
580 580
      function checklock($resource)
581 581
      {
582 582
      // dummy entry for PHPDoc
583 583
      }
584 584
     */
585 585
 
586
-    // }}}
586
+	// }}}
587 587
 
588
-    // }}}
588
+	// }}}
589 589
 
590
-    // {{{ WebDAV HTTP method wrappers
590
+	// {{{ WebDAV HTTP method wrappers
591 591
 
592
-    // {{{ http_OPTIONS()
592
+	// {{{ http_OPTIONS()
593 593
 
594
-    /**
595
-     * OPTIONS method handler
596
-     *
597
-     * The OPTIONS method handler creates a valid OPTIONS reply
598
-     * including Dav: and Allowed: headers
599
-     * based on the implemented methods found in the actual instance
600
-     *
601
-     * @param  void
602
-     * @return void
603
-     */
604
-    function http_OPTIONS()
605
-    {
606
-        // Microsoft clients default to the Frontpage protocol
607
-        // unless we tell them to use WebDAV
608
-        header("MS-Author-Via: DAV");
609
-
610
-        // get allowed methods
611
-        $allow = $this->_allow();
612
-
613
-        // dav header
614
-        $dav = array(1);        // assume we are always dav class 1 compliant
615
-        if (isset($allow['LOCK'])) {
616
-            $dav[] = 2;         // dav class 2 requires that locking is supported
617
-        }
618
-
619
-        // allow extending class to modify DAV and Allow headers
594
+	/**
595
+	 * OPTIONS method handler
596
+	 *
597
+	 * The OPTIONS method handler creates a valid OPTIONS reply
598
+	 * including Dav: and Allowed: headers
599
+	 * based on the implemented methods found in the actual instance
600
+	 *
601
+	 * @param  void
602
+	 * @return void
603
+	 */
604
+	function http_OPTIONS()
605
+	{
606
+		// Microsoft clients default to the Frontpage protocol
607
+		// unless we tell them to use WebDAV
608
+		header("MS-Author-Via: DAV");
609
+
610
+		// get allowed methods
611
+		$allow = $this->_allow();
612
+
613
+		// dav header
614
+		$dav = array(1);        // assume we are always dav class 1 compliant
615
+		if (isset($allow['LOCK'])) {
616
+			$dav[] = 2;         // dav class 2 requires that locking is supported
617
+		}
618
+
619
+		// allow extending class to modify DAV and Allow headers
620 620
 		if (method_exists($this,'OPTIONS')) {
621 621
 			$this->OPTIONS($this->path,$dav,$allow);
622 622
 		}
623 623
 
624
-        // tell clients what we found
625
-        $this->http_status("200 OK");
626
-        header("DAV: "  .join(", ", $dav));
627
-        header("Allow: ".join(", ", $allow));
624
+		// tell clients what we found
625
+		$this->http_status("200 OK");
626
+		header("DAV: "  .join(", ", $dav));
627
+		header("Allow: ".join(", ", $allow));
628 628
 
629
-        header("Content-length: 0");
630
-    }
629
+		header("Content-length: 0");
630
+	}
631 631
 
632
-    // }}}
632
+	// }}}
633 633
 
634 634
 
635
-    // {{{ http_PROPFIND()
635
+	// {{{ http_PROPFIND()
636 636
 
637
-    /**
638
-     * Should the whole PROPFIND request (xml) be stored
639
-     *
640
-     * @var boolean
641
-     */
642
-    var $store_request = false;
643
-    /**
644
-     * Content of (last) PROPFIND request
645
-     *
646
-     * @var string
647
-     */
648
-    var $request;
637
+	/**
638
+	 * Should the whole PROPFIND request (xml) be stored
639
+	 *
640
+	 * @var boolean
641
+	 */
642
+	var $store_request = false;
643
+	/**
644
+	 * Content of (last) PROPFIND request
645
+	 *
646
+	 * @var string
647
+	 */
648
+	var $request;
649 649
 
650
-    /**
651
-     * PROPFIND method handler
652
-     *
653
-     * @param  string $handler ='PROPFIND' allows to use method eg. for CalDAV REPORT
654
-     * @return void
655
-     */
656
-    function http_PROPFIND($handler='PROPFIND')
657
-    {
658
-        $options = Array();
659
-        $files   = Array();
660
-
661
-        $options["path"] = $this->path;
662
-
663
-        // search depth from header (default is "infinity)
664
-        if (isset($this->_SERVER['HTTP_DEPTH'])) {
665
-            $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
666
-        } else {
667
-            $options["depth"] = "infinity";
668
-        }
669
-
670
-        // analyze request payload
671
-        $propinfo = new _parse_propfind("php://input", $this->store_request);
672
-        if ($this->store_request) $this->request = $propinfo->request;
673
-        if (!$propinfo->success) {
674
-            $this->http_status("400 Error");
675
-            return;
676
-        }
650
+	/**
651
+	 * PROPFIND method handler
652
+	 *
653
+	 * @param  string $handler ='PROPFIND' allows to use method eg. for CalDAV REPORT
654
+	 * @return void
655
+	 */
656
+	function http_PROPFIND($handler='PROPFIND')
657
+	{
658
+		$options = Array();
659
+		$files   = Array();
660
+
661
+		$options["path"] = $this->path;
662
+
663
+		// search depth from header (default is "infinity)
664
+		if (isset($this->_SERVER['HTTP_DEPTH'])) {
665
+			$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
666
+		} else {
667
+			$options["depth"] = "infinity";
668
+		}
669
+
670
+		// analyze request payload
671
+		$propinfo = new _parse_propfind("php://input", $this->store_request);
672
+		if ($this->store_request) $this->request = $propinfo->request;
673
+		if (!$propinfo->success) {
674
+			$this->http_status("400 Error");
675
+			return;
676
+		}
677 677
 		$options['root'] = $propinfo->root;
678 678
 		$options['props'] = $propinfo->props;
679 679
 		if ($propinfo->filters)
@@ -681,34 +681,34 @@  discard block
 block discarded – undo
681 681
 		if ($propinfo->other)
682 682
 			$options['other'] = $propinfo->other;
683 683
 
684
-        // call user handler
685
-        if (!($retval =$this->$handler($options, $files))) {
686
-            $files = array("files" => array());
687
-            if (method_exists($this, "checkLock")) {
688
-                // is locked?
689
-                $lock = $this->checkLock($this->path);
690
-
691
-                if (is_array($lock) && count($lock)) {
692
-                    $created          = isset($lock['created'])  ? $lock['created']  : time();
693
-                    $modified         = isset($lock['modified']) ? $lock['modified'] : time();
694
-                    $files['files'][] = array("path"  => self::_slashify($this->path),
695
-                                              "props" => array($this->mkprop("displayname",      $this->path),
696
-                                                               $this->mkprop("creationdate",     $created),
697
-                                                               $this->mkprop("getlastmodified",  $modified),
698
-                                                               $this->mkprop("resourcetype",     ""),
699
-                                                               $this->mkprop("getcontenttype",   ""),
700
-                                                               $this->mkprop("getcontentlength", 0))
701
-                                              );
702
-                }
703
-            }
704
-
705
-            if (empty($files['files'])) {
706
-                $this->http_status("404 Not Found");
707
-                return;
708
-            }
709
-        }
710
-
711
-        // now we generate the reply header ...
684
+		// call user handler
685
+		if (!($retval =$this->$handler($options, $files))) {
686
+			$files = array("files" => array());
687
+			if (method_exists($this, "checkLock")) {
688
+				// is locked?
689
+				$lock = $this->checkLock($this->path);
690
+
691
+				if (is_array($lock) && count($lock)) {
692
+					$created          = isset($lock['created'])  ? $lock['created']  : time();
693
+					$modified         = isset($lock['modified']) ? $lock['modified'] : time();
694
+					$files['files'][] = array("path"  => self::_slashify($this->path),
695
+											  "props" => array($this->mkprop("displayname",      $this->path),
696
+															   $this->mkprop("creationdate",     $created),
697
+															   $this->mkprop("getlastmodified",  $modified),
698
+															   $this->mkprop("resourcetype",     ""),
699
+															   $this->mkprop("getcontenttype",   ""),
700
+															   $this->mkprop("getcontentlength", 0))
701
+											  );
702
+				}
703
+			}
704
+
705
+			if (empty($files['files'])) {
706
+				$this->http_status("404 Not Found");
707
+				return;
708
+			}
709
+		}
710
+
711
+		// now we generate the reply header ...
712 712
 		if ($retval === true)
713 713
 		{
714 714
 			$this->http_status('207 Multi-Status');
@@ -732,506 +732,506 @@  discard block
 block discarded – undo
732 732
 			return;
733 733
 		}
734 734
 		// dav header
735
-        $dav = array(1);        // assume we are always dav class 1 compliant
736
-        $allow = false;
735
+		$dav = array(1);        // assume we are always dav class 1 compliant
736
+		$allow = false;
737 737
 
738
-        // allow extending class to modify DAV
738
+		// allow extending class to modify DAV
739 739
 		if (method_exists($this,'OPTIONS')) {
740 740
 			$this->OPTIONS($this->path,$dav,$allow);
741 741
 		}
742
-        header("DAV: "  .join(", ", $dav));
743
-        header('Content-Type: text/xml; charset="utf-8"');
742
+		header("DAV: "  .join(", ", $dav));
743
+		header('Content-Type: text/xml; charset="utf-8"');
744 744
 
745
-        // add Vary and Preference-Applied header for Prefer: return=minimal
746
-        if (isset($this->_SERVER['HTTP_PREFER']) && in_array('return=minimal', preg_split('/, ?/', $this->_SERVER['HTTP_PREFER'])))
747
-        {
748
-        	header("Preference-Applied: return=minimal");
749
-        	header("Vary: Prefer");
750
-        }
751
-
752
-        // ... and payload
753
-        echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
754
-        echo $this->crrnd ? "<multistatus xmlns=\"DAV:\">\n" : "<D:multistatus xmlns:D=\"DAV:\">\n";
755
-
756
-        $this->multistatus_responses($files['files'], $options['props']);
757
-
758
-        // WebDAV sync report sync-token, can be either the sync-token or a callback (called with params in $files['sync-token-params'])
759
-        if (isset($files['sync-token']))
760
-        {
761
-            echo ($this->crrnd ? " <" : " <D:")."sync-token>".
762
-            	htmlspecialchars(!is_callable($files['sync-token']) ? $files['sync-token'] :
763
-            		call_user_func_array($files['sync-token'], (array)$files['sync-token-params'])).
764
-            	($this->crrnd ? "</" : "</D:")."sync-token>\n";
765
-        }
766
-
767
-        echo '</'.($this->crrnd?'':'D:')."multistatus>\n";
768
-    }
769
-
770
-    /**
771
-     * Render (echo) XML for given multistatus responses
772
-     *
773
-     * @param array|Iterator $files
774
-     * @param array|string $props
775
-     */
776
-    function multistatus_responses(&$files, $props, $initial_ns_hash=null, $initial_ns_defs=null)
777
-    {
778
-    	if (!isset($initial_ns_hash)) $initial_ns_hash = array('DAV:' => 'D');
779
-    	if (!isset($initial_ns_defs)) $initial_ns_defs = 'xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"';
780
-
781
-    	// using an ArrayIterator to prevent foreach from copying the array,
782
-        // as we cant loop by reference, when an iterator is given in $files
783
-        if (is_array($files))
784
-        {
785
-        	$files = new ArrayIterator($files);
786
-        }
787
-        // support for "Prefer: depth-noroot" header on PROPFIND
788
-        $skip_root = $this->_SERVER['REQUEST_METHOD'] == 'PROPFIND' &&
789
-        	!isset($initial_ns_hash) &&	// multistatus_response calls itself, do NOT apply skip in that case
790
-        	isset($this->_SERVER['HTTP_PREFER']) && in_array('depth-noroot', preg_split('/, ?/', $this->_SERVER['HTTP_PREFER']));
791
-
792
-        // now we loop over all returned file entries
793
-        foreach ($files as $file) {
794
-
795
-        	// skip first element (root), if requested by Prefer: depth-noroot
796
-        	if ($skip_root) {
797
-        		$skip_root = false;
798
-        		continue;
799
-        	}
800
-
801
-	        // collect namespaces here
802
-	        $ns_hash = $initial_ns_hash;
803
-
804
-	        // Microsoft Clients need this special namespace for date and time values
805
-	        $ns_defs = $initial_ns_defs;
806
-
807
-            // nothing to do if no properties were returend for a file
808
-			if (isset($file["props"]) && is_array($file["props"])) {
809
-
810
-	            // now loop over all returned properties
811
-	            foreach ($file["props"] as &$prop) {
812
-	                // as a convenience feature we do not require that user handlers
813
-	                // restrict returned properties to the requested ones
814
-	                // here we strip all unrequested entries out of the response
815
-
816
-	            	// this can happen if we have allprop and prop in one propfind:
817
-	            	// <allprop /><prop><blah /></prop>, eg. blah is not automatic returned by allprop
818
-	                switch(is_array($props) ? $props[0] : $props) {
819
-	                case "all":
820
-	                    // nothing to remove
821
-	                    break;
822
-
823
-	                case "names":
824
-	                    // only the names of all existing properties were requested
825
-	                    // so we remove all values
826
-	                    unset($prop["val"]);
827
-	                    break;
828
-
829
-	                default:
830
-	                    $found = false;
831
-
832
-	                    // search property name in requested properties
833
-	                    foreach ((array)$props as $reqprop) {
834
-	                        if (   $reqprop["name"]  == $prop["name"]
835
-	                               && @$reqprop["xmlns"] == $prop["ns"]) {
836
-	                            $found = true;
837
-	                            break;
838
-	                        }
839
-	                    }
840
-
841
-	                    // unset property and continue with next one if not found/requested
842
-	                    if (!$found) {
843
-	                        $prop="";
844
-	                        continue(2);
845
-	                    }
846
-	                    break;
847
-	                }
848
-
849
-	                // namespace handling
850
-	                if (empty($prop["ns"])) continue; // no namespace
851
-	                $ns = $prop["ns"];
852
-	                //if ($ns == "DAV:") continue; // default namespace
853
-	                if (isset($ns_hash[$ns])) continue; // already known
854
-
855
-	                // register namespace
856
-	                $ns_name = "ns".(count($ns_hash) + 1);
857
-	                $ns_hash[$ns] = $ns_name;
858
-	                $ns_defs .= " xmlns:$ns_name=\"$ns\"";
859
-	            }
860
-
861
-	            // we also need to add empty entries for properties that were requested
862
-	            // but for which no values where returned by the user handler
863
-	            if (is_array($props)) {
864
-	                foreach ($props as $reqprop) {
865
-	                    if (!is_array($reqprop) || $reqprop['name']=="") continue; // skip empty entries, or 'all' if <allprop /> used together with <prop>
866
-
867
-	                    $found = false;
868
-
869
-	                    // check if property exists in result
870
-	                    foreach ($file["props"] as &$prop) {
871
-	                        if (is_array($prop) && $reqprop["name"] == $prop["name"]
872
-	                               && @$reqprop["xmlns"] == $prop["ns"]) {
873
-	                            $found = true;
874
-	                            break;
875
-	                        }
876
-	                    }
877
-
878
-	                    if (!$found) {
879
-	                        if ($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
880
-	                            // lockdiscovery is handled by the base class
881
-	                            $file["props"][]
882
-	                                = $this->mkprop("DAV:",
883
-	                                                "lockdiscovery",
884
-	                                                $this->lockdiscovery($file['path']));
885
-	                        // only collect $file['noprops'] if we have NO Brief: t and NO Prefer: return=minimal HTTP Header
886
-	                        } elseif ((!isset($this->_SERVER['HTTP_BRIEF']) || $this->_SERVER['HTTP_BRIEF'] != 't') &&
887
-	                        	(!isset($this->_SERVER['HTTP_PREFER']) || !in_array('return=minimal', preg_split('/, ?/', $this->_SERVER['HTTP_PREFER'])))) {
888
-	                            // add empty value for this property
889
-	                            $file["noprops"][] =
890
-	                                $this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
891
-
892
-	                            // register property namespace if not known yet
893
-	                            if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) {
894
-	                                $ns_name = "ns".(count($ns_hash) + 1);
895
-	                                $ns_hash[$reqprop["xmlns"]] = $ns_name;
896
-	                                $ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\"";
897
-	                            }
898
-	                        }
899
-	                    }
900
-	                }
901
-	            }
902
-	        }
903
-            // ignore empty or incomplete entries
904
-            if (!is_array($file) || empty($file) || !isset($file["path"])) continue;
905
-            $path = $file['path'];
906
-            if (!is_string($path) || $path==="") continue;
907
-
908
-            if ($this->crrnd)
909
-            {
910
-            	echo " <response $ns_defs>\n";
911
-            }
912
-            else
913
-            {
914
-            	echo " <D:response $ns_defs>\n";
915
-            }
916
-
917
-            /* TODO right now the user implementation has to make sure
918
-             collections end in a slash, this should be done in here
919
-             by checking the resource attribute */
920
-            $href_raw = $this->_mergePaths($this->base_uri, $path);
921
-
922
-            /* minimal urlencoding is needed for the resource path */
923
-            $href = $this->_urlencode($href_raw);
924
-
925
-            if ($this->crrnd)
926
-            {
927
-            	echo "  <href>$href</href>\n";
928
-            }
929
-            else
930
-            {
931
-            	echo "  <D:href>$href</D:href>\n";
932
-            }
933
-
934
-            // report all found properties and their values (if any)
935
-            if (isset($file["props"]) && is_array($file["props"])) {
936
-                echo '   <'.($this->crrnd?'':'D:')."propstat>\n";
937
-                echo '    <'.($this->crrnd?'':'D:')."prop>\n";
938
-
939
-                foreach ($file["props"] as &$prop) {
940
-
941
-                    if (!is_array($prop)) continue;
942
-                    if (!isset($prop["name"])) continue;
943
-
944
-                    if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
945
-                        // empty properties (cannot use empty() for check as "0" is a legal value here)
946
-                        if ($prop["ns"]=="DAV:") {
947
-                            echo '     <'.($this->crrnd?'':'D:')."$prop[name]/>\n";
948
-                        } else if (!empty($prop["ns"])) {
949
-                            echo "     <".$ns_hash[$prop["ns"]].":$prop[name]/>\n";
950
-                        } else {
951
-                            echo "     <$prop[name] xmlns=\"\"/>";
952
-                        }
953
-                    }
954
-                    // multiple level of responses required for expand-property reports
955
-                    elseif(isset($prop['props']) && is_array($prop['val']))
956
-                    {
957
-                        if ($prop['ns'] && !isset($ns_hash[$prop['ns']])) {
958
-                            $ns_name = "ns".(count($ns_hash) + 1);
959
-                            $ns_hash[$prop['ns']] = $ns_name;
960
-                        }
961
-                    	echo '     <'.$ns_hash[$prop['ns']].":$prop[name]>\n";
962
-                        $this->multistatus_responses($prop['val'], $prop['props'], $ns_hash, '');
963
-                    	echo '     </'.$ns_hash[$prop['ns']].":$prop[name]>\n";
964
-                    } else if ($prop["ns"] == "DAV:") {
965
-                        // some WebDAV properties need special treatment
966
-                        switch ($prop["name"]) {
967
-                        case "creationdate":
968
-                            echo '     <'.($this->crrnd?'':'D:')."creationdate ns0:dt=\"dateTime.tz\">"
969
-                                . gmdate("Y-m-d\\TH:i:s\\Z", $prop['val'])
970
-                                . '</'.($this->crrnd?'':'D:')."creationdate>\n";
971
-                            break;
972
-                        case "getlastmodified":
973
-                            echo '     <'.($this->crrnd?'':'D:')."getlastmodified ns0:dt=\"dateTime.rfc1123\">"
974
-                                . gmdate("D, d M Y H:i:s ", $prop['val'])
975
-                                . "GMT</".($this->crrnd?'':'D:')."getlastmodified>\n";
976
-                            break;
977
-                        case "supportedlock":
978
-                            echo '     <'.($this->crrnd?'':'D:')."supportedlock>$prop[val]</".($this->crrnd?'':'D:')."supportedlock>\n";
979
-                            break;
980
-                        case "lockdiscovery":
981
-                            echo '     <'.($this->crrnd?'':'D:')."lockdiscovery>\n";
982
-                            echo $prop["val"];
983
-                            echo '     </'.($this->crrnd?'':'D:')."lockdiscovery>\n";
984
-                            break;
985
-                        // the following are non-standard Microsoft extensions to the DAV namespace
986
-                        case "lastaccessed":
987
-                            echo '     <'.($this->crrnd?'':'D:')."lastaccessed ns0:dt=\"dateTime.rfc1123\">"
988
-                                . gmdate("D, d M Y H:i:s ", $prop['val'])
989
-                                . 'GMT</'.($this->crrnd?'':'D:')."lastaccessed>\n";
990
-                            break;
991
-                        case "ishidden":
992
-                            echo '     <'.($this->crrnd?'':'D:')."ishidden>"
993
-                                . is_string($prop['val']) ? $prop['val'] : ($prop['val'] ? 'true' : 'false')
994
-                                . '</'.($this->crrnd?'':'D:')."</D:ishidden>\n";
995
-                            break;
996
-                        default:
997
-                        	$ns_defs = '';
998
-                            if (is_array($prop['val']))
999
-                            {
1000
-                            	$hns_hash = $ns_hash;
1001
-                            	$val = $this->_hierarchical_prop_encode($prop['val'], 'DAV:', $ns_defs, $hns_hash);
1002
-                            } elseif (isset($prop['raw'])) {
1003
-                            	$val = $this->_prop_encode('<![CDATA['.$prop['val'].']]>');
1004
-                            } else {
1005
-	                    		$val = $this->_prop_encode(htmlspecialchars($prop['val'], ENT_NOQUOTES, 'utf-8'));
1006
-                            }
1007
-	                        echo '     <'.($this->crrnd?'':'D:')."$prop[name]$ns_defs>$val".
1008
-	                        	'</'.($this->crrnd?'':'D:')."$prop[name]>\n";
1009
-                            break;
1010
-                        }
1011
-                    } else {
1012
-                        // allow multiple values and attributes, required eg. for caldav:supported-calendar-component-set
1013
-                        if ($prop['ns'] && is_array($prop['val'])) {
1014
-                    		if (!isset($ns_hash[$prop['ns']])) {
1015
-                                $ns_name = "ns".(count($ns_hash) + 1);
1016
-                                $ns_hash[$prop['ns']] = $ns_name;
1017
-                    		}
1018
-                  			$vals = $extra_ns = '';
1019
-                    		foreach($prop['val'] as $subprop)
1020
-                    		{
1021
-                    			if ($subprop['ns'] && $subprop['ns'] != 'DAV:') {
1022
-		                    		// register property namespace if not known yet
1023
-		                    		if (!isset($ns_hash[$subprop['ns']])) {
1024
-			                    		$ns_name = "ns".(count($ns_hash) + 1);
1025
-			                    		$ns_hash[$subprop['ns']] = $ns_name;
1026
-		                    		} else {
1027
-			                    		$ns_name = $ns_hash[$subprop['ns']];
1028
-		                    		}
1029
-		                    		if (strchr($extra_ns,$extra=' xmlns:'.$ns_name.'="'.$subprop['ns'].'"') === false) {
1030
-			                    		$extra_ns .= $extra;
1031
-		                    		}
1032
-		                    		$ns_name .= ':';
1033
-	                    		} elseif ($subprop['ns'] == 'DAV:') {
1034
-		                    		$ns_name = 'D:';
1035
-	                    		} else {
1036
-		                    		$ns_name = '';
1037
-	                    		}
1038
-	                    		$vals .= "<$ns_name$subprop[name]";
1039
-	                    		if (is_array($subprop['val']))
1040
-	                    		{
1041
-	                    			if (isset($subprop['val'][0]))
1042
-	                    			{
1043
-		                    			$vals .= '>';
1044
-		                    			$vals .= $this->_hierarchical_prop_encode($subprop['val'], $subprop['ns'], $ns_defs, $ns_hash);
1045
-			                    		$vals .= "</$ns_name$subprop[name]>";
1046
-	                    			}
1047
-	                    			else	// val contains only attributes, no value
1048
-	                    			{
1049
-			                    		foreach($subprop['val'] as $attr => $val)
1050
-										{
1051
-				                    		$vals .= ' '.$attr.'="'.htmlspecialchars($val, ENT_NOQUOTES, 'utf-8').'"';
1052
-										}
1053
-			                    		$vals .= '/>';
1054
-	                    			}
1055
-	                    		}
1056
-	                    		else
1057
-	                    		{
1058
-	                    			$vals .= '>';
1059
-	                    			if (isset($subprop['raw'])) {
1060
-	                    				$vals .= '<![CDATA['.$subprop['val'].']]>';
1061
-	                    			} else {
1062
-	                    				if($subprop['name'] == 'href') $subprop['val'] = $this->_urlencode($subprop['val']);
1063
-		                    			$vals .= htmlspecialchars($subprop['val'], ENT_NOQUOTES, 'utf-8');
1064
-	                    			}
1065
-	                    			$vals .= "</$ns_name$subprop[name]>";
1066
-	                    		}
1067
-                    		}
1068
-                    		echo '     <'.$ns_hash[$prop['ns']].":$prop[name]$extra_ns>$vals</".$ns_hash[$prop['ns']].":$prop[name]>\n";
1069
-                        } else {
1070
-                        	if ($prop['raw'])
1071
-                        	{
1072
-                        		$val = '<![CDATA['.$prop['val'].']]>';
1073
-                        	} else {
1074
-                        		$val = htmlspecialchars($prop['val'], ENT_NOQUOTES, 'utf-8');
1075
-                        	}
1076
-                        	$val = $this->_prop_encode($val);
1077
-	                        // properties from namespaces != "DAV:" or without any namespace
1078
-	                        if ($prop['ns']) {
1079
-		                        if ($this->crrnd) {
1080
-			                        echo "     <$prop[name] xmlns=".'"'.$prop["ns"].'">'
1081
-									. $val . "</$prop[name]>\n";
1082
-		                        } else {
1083
-			                        echo "     <" . $ns_hash[$prop["ns"]] . ":$prop[name]>"
1084
-									. $val . '</'.$ns_hash[$prop['ns']].":$prop[name]>\n";
1085
-		                        }
1086
-	                        } else {
1087
-		                        echo "     <$prop[name] xmlns=\"\">$val</$prop[name]>\n";
1088
-	                        }
1089
-                        }
1090
-                    }
1091
-                }
1092
-
1093
-                if ($this->crrnd)
1094
-                {
1095
-	                echo "    </prop>\n";
1096
-	                echo "   <status>HTTP/1.1 200 OK</status>\n";
1097
-	                echo "  </propstat>\n";
1098
-                }
1099
-                else
1100
-                {
1101
-	                echo "    </D:prop>\n";
1102
-	                echo "   <D:status>HTTP/1.1 200 OK</D:status>\n";
1103
-	                echo "  </D:propstat>\n";
1104
-                }
1105
-            }
1106
-
1107
-            // now report all properties requested but not found
1108
-            if (isset($file["noprops"])) {
1109
-                echo '   <'.($this->crrnd?'':'D:')."propstat>\n";
1110
-                echo '    <'.($this->crrnd?'':'D:')."prop>\n";
1111
-
1112
-                foreach ($file["noprops"] as &$prop) {
1113
-                    if ($prop["ns"] == "DAV:") {
1114
-                        echo '     <'.($this->crrnd?'':'D:')."$prop[name]/>\n";
1115
-                    } else if ($prop["ns"] == "") {
1116
-                        echo "     <$prop[name] xmlns=\"\"/>\n";
1117
-                    } else {
1118
-                        echo "     <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
1119
-                    }
1120
-                }
1121
-
1122
-                if ($this->crrnd)
1123
-                {
1124
-	                echo "   </prop>\n";
1125
-	                echo "   <status>HTTP/1.1 404 Not Found</status>\n";
1126
-	                echo "  </propstat>\n";
1127
-                }
1128
-                else
1129
-                {
1130
-	                echo "   </D:prop>\n";
1131
-	                echo "   <D:status>HTTP/1.1 404 Not Found</D:status>\n";
1132
-	                echo "  </D:propstat>\n";
1133
-                }
1134
-            }
1135
-
1136
-            // 404 Not Found status element for WebDAV sync report
1137
-            if (!isset($file['props']) && !isset($file['noprops']))
1138
-            {
1139
-                if ($this->crrnd)
1140
-                {
1141
-	                echo "  <status>HTTP/1.1 404 Not Found</status>\n";
1142
-                }
1143
-                else
1144
-                {
1145
-	                echo "  <D:status>HTTP/1.1 404 Not Found</D:status>\n";
1146
-                }
1147
-            }
1148
-
1149
-            echo ' </'.($this->crrnd?'':'D:')."response>\n";
1150
-        }
1151
-    }
1152
-
1153
-
1154
-    // }}}
1155
-
1156
-    // {{{ http_PROPPATCH()
745
+		// add Vary and Preference-Applied header for Prefer: return=minimal
746
+		if (isset($this->_SERVER['HTTP_PREFER']) && in_array('return=minimal', preg_split('/, ?/', $this->_SERVER['HTTP_PREFER'])))
747
+		{
748
+			header("Preference-Applied: return=minimal");
749
+			header("Vary: Prefer");
750
+		}
1157 751
 
1158
-    /**
1159
-     * PROPPATCH method handler
1160
-     *
1161
-     * @param  void
1162
-     * @return void
1163
-     */
1164
-    function http_PROPPATCH()
1165
-    {
1166
-        if ($this->_check_lock_status($this->path)) {
1167
-            $options = Array();
752
+		// ... and payload
753
+		echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
754
+		echo $this->crrnd ? "<multistatus xmlns=\"DAV:\">\n" : "<D:multistatus xmlns:D=\"DAV:\">\n";
1168 755
 
1169
-            $options["path"] = $this->path;
756
+		$this->multistatus_responses($files['files'], $options['props']);
1170 757
 
1171
-            $propinfo = new _parse_proppatch("php://input", $this->store_request);
1172
-            if ($this->store_request) $this->request = $propinfo->request;
758
+		// WebDAV sync report sync-token, can be either the sync-token or a callback (called with params in $files['sync-token-params'])
759
+		if (isset($files['sync-token']))
760
+		{
761
+			echo ($this->crrnd ? " <" : " <D:")."sync-token>".
762
+				htmlspecialchars(!is_callable($files['sync-token']) ? $files['sync-token'] :
763
+					call_user_func_array($files['sync-token'], (array)$files['sync-token-params'])).
764
+				($this->crrnd ? "</" : "</D:")."sync-token>\n";
765
+		}
1173 766
 
1174
-            if (!$propinfo->success) {
1175
-                $this->http_status("400 Error");
1176
-                return;
1177
-            }
767
+		echo '</'.($this->crrnd?'':'D:')."multistatus>\n";
768
+	}
1178 769
 
1179
-            $options['props'] = $propinfo->props;
770
+	/**
771
+	 * Render (echo) XML for given multistatus responses
772
+	 *
773
+	 * @param array|Iterator $files
774
+	 * @param array|string $props
775
+	 */
776
+	function multistatus_responses(&$files, $props, $initial_ns_hash=null, $initial_ns_defs=null)
777
+	{
778
+		if (!isset($initial_ns_hash)) $initial_ns_hash = array('DAV:' => 'D');
779
+		if (!isset($initial_ns_defs)) $initial_ns_defs = 'xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"';
1180 780
 
1181
-            $responsedescr = $this->PROPPATCH($options);
781
+		// using an ArrayIterator to prevent foreach from copying the array,
782
+		// as we cant loop by reference, when an iterator is given in $files
783
+		if (is_array($files))
784
+		{
785
+			$files = new ArrayIterator($files);
786
+		}
787
+		// support for "Prefer: depth-noroot" header on PROPFIND
788
+		$skip_root = $this->_SERVER['REQUEST_METHOD'] == 'PROPFIND' &&
789
+			!isset($initial_ns_hash) &&	// multistatus_response calls itself, do NOT apply skip in that case
790
+			isset($this->_SERVER['HTTP_PREFER']) && in_array('depth-noroot', preg_split('/, ?/', $this->_SERVER['HTTP_PREFER']));
791
+
792
+		// now we loop over all returned file entries
793
+		foreach ($files as $file) {
794
+
795
+			// skip first element (root), if requested by Prefer: depth-noroot
796
+			if ($skip_root) {
797
+				$skip_root = false;
798
+				continue;
799
+			}
1182 800
 
1183
-            $this->http_status("207 Multi-Status");
1184
-            header('Content-Type: text/xml; charset="utf-8"');
801
+			// collect namespaces here
802
+			$ns_hash = $initial_ns_hash;
1185 803
 
1186
-            echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
804
+			// Microsoft Clients need this special namespace for date and time values
805
+			$ns_defs = $initial_ns_defs;
1187 806
 
1188
-            echo "<D:multistatus xmlns:D=\"DAV:\">\n";
1189
-            echo ' <'.($this->crrnd?'':'D:')."response>\n";
1190
-            echo '  <'.($this->crrnd?'':'D:')."href>".$this->_urlencode($this->_mergePaths($this->_SERVER["SCRIPT_NAME"], $this->path)).'</'.($this->crrnd?'':'D:')."href>\n";
807
+			// nothing to do if no properties were returend for a file
808
+			if (isset($file["props"]) && is_array($file["props"])) {
1191 809
 
1192
-            foreach ($options["props"] as $prop) {
1193
-                echo '   <'.($this->crrnd?'':'D:')."propstat>\n";
1194
-                echo '    <'.($this->crrnd?'':'D:')."prop><$prop[name] xmlns=\"$prop[ns]\"/></".($this->crrnd?'':'D:')."prop>\n";
1195
-                echo '    <'.($this->crrnd?'':'D:')."status>HTTP/1.1 $prop[status]</".($this->crrnd?'':'D:')."status>\n";
1196
-                echo '   </'.($this->crrnd?'':'D:')."propstat>\n";
1197
-            }
810
+				// now loop over all returned properties
811
+				foreach ($file["props"] as &$prop) {
812
+					// as a convenience feature we do not require that user handlers
813
+					// restrict returned properties to the requested ones
814
+					// here we strip all unrequested entries out of the response
815
+
816
+					// this can happen if we have allprop and prop in one propfind:
817
+					// <allprop /><prop><blah /></prop>, eg. blah is not automatic returned by allprop
818
+					switch(is_array($props) ? $props[0] : $props) {
819
+					case "all":
820
+						// nothing to remove
821
+						break;
822
+
823
+					case "names":
824
+						// only the names of all existing properties were requested
825
+						// so we remove all values
826
+						unset($prop["val"]);
827
+						break;
828
+
829
+					default:
830
+						$found = false;
831
+
832
+						// search property name in requested properties
833
+						foreach ((array)$props as $reqprop) {
834
+							if (   $reqprop["name"]  == $prop["name"]
835
+								   && @$reqprop["xmlns"] == $prop["ns"]) {
836
+								$found = true;
837
+								break;
838
+							}
839
+						}
1198 840
 
1199
-            if ($responsedescr) {
1200
-                echo '  <'.($this->crrnd?'':'D:')."responsedescription>".
1201
-                    $this->_prop_encode(htmlspecialchars($responsedescr, ENT_NOQUOTES, 'utf-8')).
1202
-                    '</'.($this->crrnd?'':'D:')."responsedescription>\n";
1203
-            }
841
+						// unset property and continue with next one if not found/requested
842
+						if (!$found) {
843
+							$prop="";
844
+							continue(2);
845
+						}
846
+						break;
847
+					}
1204 848
 
1205
-            echo ' </'.($this->crrnd?'':'D:')."response>\n";
1206
-            echo '</'.($this->crrnd?'':'D:')."multistatus>\n";
1207
-        } else {
1208
-            $this->http_status("423 Locked");
1209
-        }
1210
-    }
849
+					// namespace handling
850
+					if (empty($prop["ns"])) continue; // no namespace
851
+					$ns = $prop["ns"];
852
+					//if ($ns == "DAV:") continue; // default namespace
853
+					if (isset($ns_hash[$ns])) continue; // already known
1211 854
 
1212
-    // }}}
855
+					// register namespace
856
+					$ns_name = "ns".(count($ns_hash) + 1);
857
+					$ns_hash[$ns] = $ns_name;
858
+					$ns_defs .= " xmlns:$ns_name=\"$ns\"";
859
+				}
1213 860
 
861
+				// we also need to add empty entries for properties that were requested
862
+				// but for which no values where returned by the user handler
863
+				if (is_array($props)) {
864
+					foreach ($props as $reqprop) {
865
+						if (!is_array($reqprop) || $reqprop['name']=="") continue; // skip empty entries, or 'all' if <allprop /> used together with <prop>
866
+
867
+						$found = false;
868
+
869
+						// check if property exists in result
870
+						foreach ($file["props"] as &$prop) {
871
+							if (is_array($prop) && $reqprop["name"] == $prop["name"]
872
+								   && @$reqprop["xmlns"] == $prop["ns"]) {
873
+								$found = true;
874
+								break;
875
+							}
876
+						}
1214 877
 
1215
-    // {{{ http_MKCOL()
878
+						if (!$found) {
879
+							if ($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
880
+								// lockdiscovery is handled by the base class
881
+								$file["props"][]
882
+									= $this->mkprop("DAV:",
883
+													"lockdiscovery",
884
+													$this->lockdiscovery($file['path']));
885
+							// only collect $file['noprops'] if we have NO Brief: t and NO Prefer: return=minimal HTTP Header
886
+							} elseif ((!isset($this->_SERVER['HTTP_BRIEF']) || $this->_SERVER['HTTP_BRIEF'] != 't') &&
887
+								(!isset($this->_SERVER['HTTP_PREFER']) || !in_array('return=minimal', preg_split('/, ?/', $this->_SERVER['HTTP_PREFER'])))) {
888
+								// add empty value for this property
889
+								$file["noprops"][] =
890
+									$this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
891
+
892
+								// register property namespace if not known yet
893
+								if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) {
894
+									$ns_name = "ns".(count($ns_hash) + 1);
895
+									$ns_hash[$reqprop["xmlns"]] = $ns_name;
896
+									$ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\"";
897
+								}
898
+							}
899
+						}
900
+					}
901
+				}
902
+			}
903
+			// ignore empty or incomplete entries
904
+			if (!is_array($file) || empty($file) || !isset($file["path"])) continue;
905
+			$path = $file['path'];
906
+			if (!is_string($path) || $path==="") continue;
1216 907
 
1217
-    /**
1218
-     * MKCOL method handler
1219
-     *
1220
-     * @param  void
1221
-     * @return void
1222
-     */
1223
-    function http_MKCOL()
1224
-    {
1225
-        $options = Array();
908
+			if ($this->crrnd)
909
+			{
910
+				echo " <response $ns_defs>\n";
911
+			}
912
+			else
913
+			{
914
+				echo " <D:response $ns_defs>\n";
915
+			}
1226 916
 
1227
-        $options["path"] = $this->path;
917
+			/* TODO right now the user implementation has to make sure
918
+             collections end in a slash, this should be done in here
919
+             by checking the resource attribute */
920
+			$href_raw = $this->_mergePaths($this->base_uri, $path);
1228 921
 
1229
-        $stat = $this->MKCOL($options);
922
+			/* minimal urlencoding is needed for the resource path */
923
+			$href = $this->_urlencode($href_raw);
1230 924
 
1231
-        $this->http_status($stat);
1232
-    }
925
+			if ($this->crrnd)
926
+			{
927
+				echo "  <href>$href</href>\n";
928
+			}
929
+			else
930
+			{
931
+				echo "  <D:href>$href</D:href>\n";
932
+			}
1233 933
 
1234
-    // }}}
934
+			// report all found properties and their values (if any)
935
+			if (isset($file["props"]) && is_array($file["props"])) {
936
+				echo '   <'.($this->crrnd?'':'D:')."propstat>\n";
937
+				echo '    <'.($this->crrnd?'':'D:')."prop>\n";
938
+
939
+				foreach ($file["props"] as &$prop) {
940
+
941
+					if (!is_array($prop)) continue;
942
+					if (!isset($prop["name"])) continue;
943
+
944
+					if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
945
+						// empty properties (cannot use empty() for check as "0" is a legal value here)
946
+						if ($prop["ns"]=="DAV:") {
947
+							echo '     <'.($this->crrnd?'':'D:')."$prop[name]/>\n";
948
+						} else if (!empty($prop["ns"])) {
949
+							echo "     <".$ns_hash[$prop["ns"]].":$prop[name]/>\n";
950
+						} else {
951
+							echo "     <$prop[name] xmlns=\"\"/>";
952
+						}
953
+					}
954
+					// multiple level of responses required for expand-property reports
955
+					elseif(isset($prop['props']) && is_array($prop['val']))
956
+					{
957
+						if ($prop['ns'] && !isset($ns_hash[$prop['ns']])) {
958
+							$ns_name = "ns".(count($ns_hash) + 1);
959
+							$ns_hash[$prop['ns']] = $ns_name;
960
+						}
961
+						echo '     <'.$ns_hash[$prop['ns']].":$prop[name]>\n";
962
+						$this->multistatus_responses($prop['val'], $prop['props'], $ns_hash, '');
963
+						echo '     </'.$ns_hash[$prop['ns']].":$prop[name]>\n";
964
+					} else if ($prop["ns"] == "DAV:") {
965
+						// some WebDAV properties need special treatment
966
+						switch ($prop["name"]) {
967
+						case "creationdate":
968
+							echo '     <'.($this->crrnd?'':'D:')."creationdate ns0:dt=\"dateTime.tz\">"
969
+								. gmdate("Y-m-d\\TH:i:s\\Z", $prop['val'])
970
+								. '</'.($this->crrnd?'':'D:')."creationdate>\n";
971
+							break;
972
+						case "getlastmodified":
973
+							echo '     <'.($this->crrnd?'':'D:')."getlastmodified ns0:dt=\"dateTime.rfc1123\">"
974
+								. gmdate("D, d M Y H:i:s ", $prop['val'])
975
+								. "GMT</".($this->crrnd?'':'D:')."getlastmodified>\n";
976
+							break;
977
+						case "supportedlock":
978
+							echo '     <'.($this->crrnd?'':'D:')."supportedlock>$prop[val]</".($this->crrnd?'':'D:')."supportedlock>\n";
979
+							break;
980
+						case "lockdiscovery":
981
+							echo '     <'.($this->crrnd?'':'D:')."lockdiscovery>\n";
982
+							echo $prop["val"];
983
+							echo '     </'.($this->crrnd?'':'D:')."lockdiscovery>\n";
984
+							break;
985
+						// the following are non-standard Microsoft extensions to the DAV namespace
986
+						case "lastaccessed":
987
+							echo '     <'.($this->crrnd?'':'D:')."lastaccessed ns0:dt=\"dateTime.rfc1123\">"
988
+								. gmdate("D, d M Y H:i:s ", $prop['val'])
989
+								. 'GMT</'.($this->crrnd?'':'D:')."lastaccessed>\n";
990
+							break;
991
+						case "ishidden":
992
+							echo '     <'.($this->crrnd?'':'D:')."ishidden>"
993
+								. is_string($prop['val']) ? $prop['val'] : ($prop['val'] ? 'true' : 'false')
994
+								. '</'.($this->crrnd?'':'D:')."</D:ishidden>\n";
995
+							break;
996
+						default:
997
+							$ns_defs = '';
998
+							if (is_array($prop['val']))
999
+							{
1000
+								$hns_hash = $ns_hash;
1001
+								$val = $this->_hierarchical_prop_encode($prop['val'], 'DAV:', $ns_defs, $hns_hash);
1002
+							} elseif (isset($prop['raw'])) {
1003
+								$val = $this->_prop_encode('<![CDATA['.$prop['val'].']]>');
1004
+							} else {
1005
+								$val = $this->_prop_encode(htmlspecialchars($prop['val'], ENT_NOQUOTES, 'utf-8'));
1006
+							}
1007
+							echo '     <'.($this->crrnd?'':'D:')."$prop[name]$ns_defs>$val".
1008
+								'</'.($this->crrnd?'':'D:')."$prop[name]>\n";
1009
+							break;
1010
+						}
1011
+					} else {
1012
+						// allow multiple values and attributes, required eg. for caldav:supported-calendar-component-set
1013
+						if ($prop['ns'] && is_array($prop['val'])) {
1014
+							if (!isset($ns_hash[$prop['ns']])) {
1015
+								$ns_name = "ns".(count($ns_hash) + 1);
1016
+								$ns_hash[$prop['ns']] = $ns_name;
1017
+							}
1018
+				  			$vals = $extra_ns = '';
1019
+							foreach($prop['val'] as $subprop)
1020
+							{
1021
+								if ($subprop['ns'] && $subprop['ns'] != 'DAV:') {
1022
+									// register property namespace if not known yet
1023
+									if (!isset($ns_hash[$subprop['ns']])) {
1024
+										$ns_name = "ns".(count($ns_hash) + 1);
1025
+										$ns_hash[$subprop['ns']] = $ns_name;
1026
+									} else {
1027
+										$ns_name = $ns_hash[$subprop['ns']];
1028
+									}
1029
+									if (strchr($extra_ns,$extra=' xmlns:'.$ns_name.'="'.$subprop['ns'].'"') === false) {
1030
+										$extra_ns .= $extra;
1031
+									}
1032
+									$ns_name .= ':';
1033
+								} elseif ($subprop['ns'] == 'DAV:') {
1034
+									$ns_name = 'D:';
1035
+								} else {
1036
+									$ns_name = '';
1037
+								}
1038
+								$vals .= "<$ns_name$subprop[name]";
1039
+								if (is_array($subprop['val']))
1040
+								{
1041
+									if (isset($subprop['val'][0]))
1042
+									{
1043
+										$vals .= '>';
1044
+										$vals .= $this->_hierarchical_prop_encode($subprop['val'], $subprop['ns'], $ns_defs, $ns_hash);
1045
+										$vals .= "</$ns_name$subprop[name]>";
1046
+									}
1047
+									else	// val contains only attributes, no value
1048
+									{
1049
+										foreach($subprop['val'] as $attr => $val)
1050
+										{
1051
+											$vals .= ' '.$attr.'="'.htmlspecialchars($val, ENT_NOQUOTES, 'utf-8').'"';
1052
+										}
1053
+										$vals .= '/>';
1054
+									}
1055
+								}
1056
+								else
1057
+								{
1058
+									$vals .= '>';
1059
+									if (isset($subprop['raw'])) {
1060
+										$vals .= '<![CDATA['.$subprop['val'].']]>';
1061
+									} else {
1062
+										if($subprop['name'] == 'href') $subprop['val'] = $this->_urlencode($subprop['val']);
1063
+										$vals .= htmlspecialchars($subprop['val'], ENT_NOQUOTES, 'utf-8');
1064
+									}
1065
+									$vals .= "</$ns_name$subprop[name]>";
1066
+								}
1067
+							}
1068
+							echo '     <'.$ns_hash[$prop['ns']].":$prop[name]$extra_ns>$vals</".$ns_hash[$prop['ns']].":$prop[name]>\n";
1069
+						} else {
1070
+							if ($prop['raw'])
1071
+							{
1072
+								$val = '<![CDATA['.$prop['val'].']]>';
1073
+							} else {
1074
+								$val = htmlspecialchars($prop['val'], ENT_NOQUOTES, 'utf-8');
1075
+							}
1076
+							$val = $this->_prop_encode($val);
1077
+							// properties from namespaces != "DAV:" or without any namespace
1078
+							if ($prop['ns']) {
1079
+								if ($this->crrnd) {
1080
+									echo "     <$prop[name] xmlns=".'"'.$prop["ns"].'">'
1081
+									. $val . "</$prop[name]>\n";
1082
+								} else {
1083
+									echo "     <" . $ns_hash[$prop["ns"]] . ":$prop[name]>"
1084
+									. $val . '</'.$ns_hash[$prop['ns']].":$prop[name]>\n";
1085
+								}
1086
+							} else {
1087
+								echo "     <$prop[name] xmlns=\"\">$val</$prop[name]>\n";
1088
+							}
1089
+						}
1090
+					}
1091
+				}
1092
+
1093
+				if ($this->crrnd)
1094
+				{
1095
+					echo "    </prop>\n";
1096
+					echo "   <status>HTTP/1.1 200 OK</status>\n";
1097
+					echo "  </propstat>\n";
1098
+				}
1099
+				else
1100
+				{
1101
+					echo "    </D:prop>\n";
1102
+					echo "   <D:status>HTTP/1.1 200 OK</D:status>\n";
1103
+					echo "  </D:propstat>\n";
1104
+				}
1105
+			}
1106
+
1107
+			// now report all properties requested but not found
1108
+			if (isset($file["noprops"])) {
1109
+				echo '   <'.($this->crrnd?'':'D:')."propstat>\n";
1110
+				echo '    <'.($this->crrnd?'':'D:')."prop>\n";
1111
+
1112
+				foreach ($file["noprops"] as &$prop) {
1113
+					if ($prop["ns"] == "DAV:") {
1114
+						echo '     <'.($this->crrnd?'':'D:')."$prop[name]/>\n";
1115
+					} else if ($prop["ns"] == "") {
1116
+						echo "     <$prop[name] xmlns=\"\"/>\n";
1117
+					} else {
1118
+						echo "     <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
1119
+					}
1120
+				}
1121
+
1122
+				if ($this->crrnd)
1123
+				{
1124
+					echo "   </prop>\n";
1125
+					echo "   <status>HTTP/1.1 404 Not Found</status>\n";
1126
+					echo "  </propstat>\n";
1127
+				}
1128
+				else
1129
+				{
1130
+					echo "   </D:prop>\n";
1131
+					echo "   <D:status>HTTP/1.1 404 Not Found</D:status>\n";
1132
+					echo "  </D:propstat>\n";
1133
+				}
1134
+			}
1135
+
1136
+			// 404 Not Found status element for WebDAV sync report
1137
+			if (!isset($file['props']) && !isset($file['noprops']))
1138
+			{
1139
+				if ($this->crrnd)
1140
+				{
1141
+					echo "  <status>HTTP/1.1 404 Not Found</status>\n";
1142
+				}
1143
+				else
1144
+				{
1145
+					echo "  <D:status>HTTP/1.1 404 Not Found</D:status>\n";
1146
+				}
1147
+			}
1148
+
1149
+			echo ' </'.($this->crrnd?'':'D:')."response>\n";
1150
+		}
1151
+	}
1152
+
1153
+
1154
+	// }}}
1155
+
1156
+	// {{{ http_PROPPATCH()
1157
+
1158
+	/**
1159
+	 * PROPPATCH method handler
1160
+	 *
1161
+	 * @param  void
1162
+	 * @return void
1163
+	 */
1164
+	function http_PROPPATCH()
1165
+	{
1166
+		if ($this->_check_lock_status($this->path)) {
1167
+			$options = Array();
1168
+
1169
+			$options["path"] = $this->path;
1170
+
1171
+			$propinfo = new _parse_proppatch("php://input", $this->store_request);
1172
+			if ($this->store_request) $this->request = $propinfo->request;
1173
+
1174
+			if (!$propinfo->success) {
1175
+				$this->http_status("400 Error");
1176
+				return;
1177
+			}
1178
+
1179
+			$options['props'] = $propinfo->props;
1180
+
1181
+			$responsedescr = $this->PROPPATCH($options);
1182
+
1183
+			$this->http_status("207 Multi-Status");
1184
+			header('Content-Type: text/xml; charset="utf-8"');
1185
+
1186
+			echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1187
+
1188
+			echo "<D:multistatus xmlns:D=\"DAV:\">\n";
1189
+			echo ' <'.($this->crrnd?'':'D:')."response>\n";
1190
+			echo '  <'.($this->crrnd?'':'D:')."href>".$this->_urlencode($this->_mergePaths($this->_SERVER["SCRIPT_NAME"], $this->path)).'</'.($this->crrnd?'':'D:')."href>\n";
1191
+
1192
+			foreach ($options["props"] as $prop) {
1193
+				echo '   <'.($this->crrnd?'':'D:')."propstat>\n";
1194
+				echo '    <'.($this->crrnd?'':'D:')."prop><$prop[name] xmlns=\"$prop[ns]\"/></".($this->crrnd?'':'D:')."prop>\n";
1195
+				echo '    <'.($this->crrnd?'':'D:')."status>HTTP/1.1 $prop[status]</".($this->crrnd?'':'D:')."status>\n";
1196
+				echo '   </'.($this->crrnd?'':'D:')."propstat>\n";
1197
+			}
1198
+
1199
+			if ($responsedescr) {
1200
+				echo '  <'.($this->crrnd?'':'D:')."responsedescription>".
1201
+					$this->_prop_encode(htmlspecialchars($responsedescr, ENT_NOQUOTES, 'utf-8')).
1202
+					'</'.($this->crrnd?'':'D:')."responsedescription>\n";
1203
+			}
1204
+
1205
+			echo ' </'.($this->crrnd?'':'D:')."response>\n";
1206
+			echo '</'.($this->crrnd?'':'D:')."multistatus>\n";
1207
+		} else {
1208
+			$this->http_status("423 Locked");
1209
+		}
1210
+	}
1211
+
1212
+	// }}}
1213
+
1214
+
1215
+	// {{{ http_MKCOL()
1216
+
1217
+	/**
1218
+	 * MKCOL method handler
1219
+	 *
1220
+	 * @param  void
1221
+	 * @return void
1222
+	 */
1223
+	function http_MKCOL()
1224
+	{
1225
+		$options = Array();
1226
+
1227
+		$options["path"] = $this->path;
1228
+
1229
+		$stat = $this->MKCOL($options);
1230
+
1231
+		$this->http_status($stat);
1232
+	}
1233
+
1234
+	// }}}
1235 1235
 
1236 1236
 	/**
1237 1237
 	 * Check or set if we want ot use compression as transfer encoding
@@ -1258,1387 +1258,1387 @@  discard block
 block discarded – undo
1258 1258
 		return $compression;
1259 1259
 	}
1260 1260
 
1261
-    // {{{ http_GET()
1261
+	// {{{ http_GET()
1262 1262
 
1263
-    /**
1264
-     * GET method handler
1265
-     *
1266
-     * @param void
1267
-     * @return void
1268
-     */
1269
-    function http_GET()
1270
-    {
1271
-        // TODO check for invalid stream
1272
-        $options         = Array();
1273
-        $options["path"] = $this->path;
1274
-
1275
-        $this->_get_ranges($options);
1276
-
1277
-        if (true === ($status = $this->GET($options))) {
1278
-            if (!headers_sent()) {
1279
-                $status = "200 OK";
1280
-
1281
-                if (!isset($options['mimetype'])) {
1282
-                    $options['mimetype'] = "application/octet-stream";
1283
-                }
1284
-                // switching off zlib.output_compression for everything but text files,
1285
-                // as the double compression of zip files makes problems eg. with lighttpd
1286
-                // and anyway little sense with with other content like pictures
1287
-                if (substr($options['mimetype'],0,5) != 'text/')
1288
-                {
1263
+	/**
1264
+	 * GET method handler
1265
+	 *
1266
+	 * @param void
1267
+	 * @return void
1268
+	 */
1269
+	function http_GET()
1270
+	{
1271
+		// TODO check for invalid stream
1272
+		$options         = Array();
1273
+		$options["path"] = $this->path;
1274
+
1275
+		$this->_get_ranges($options);
1276
+
1277
+		if (true === ($status = $this->GET($options))) {
1278
+			if (!headers_sent()) {
1279
+				$status = "200 OK";
1280
+
1281
+				if (!isset($options['mimetype'])) {
1282
+					$options['mimetype'] = "application/octet-stream";
1283
+				}
1284
+				// switching off zlib.output_compression for everything but text files,
1285
+				// as the double compression of zip files makes problems eg. with lighttpd
1286
+				// and anyway little sense with with other content like pictures
1287
+				if (substr($options['mimetype'],0,5) != 'text/')
1288
+				{
1289 1289
 					self::use_compression(false);
1290
-                }
1291
-                header("Content-type: $options[mimetype]");
1292
-
1293
-                if (isset($options['mtime'])) {
1294
-                    header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
1295
-                }
1296
-                // fix for IE and https, thanks to [email protected]
1297
-                // see http://us3.php.net/manual/en/function.header.php#83219
1298
-                // and http://support.microsoft.com/kb/812935
1290
+				}
1291
+				header("Content-type: $options[mimetype]");
1292
+
1293
+				if (isset($options['mtime'])) {
1294
+					header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
1295
+				}
1296
+				// fix for IE and https, thanks to [email protected]
1297
+				// see http://us3.php.net/manual/en/function.header.php#83219
1298
+				// and http://support.microsoft.com/kb/812935
1299 1299
 				header("Cache-Control: maxage=1"); //In seconds
1300 1300
 				header("Pragma: public");
1301 1301
 
1302
-                if (isset($options['stream'])) {
1303
-                    // GET handler returned a stream
1304
-                    if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
1305
-                        // partial request and stream is seekable
1306
-
1307
-                        if (count($options['ranges']) === 1) {
1308
-                            $range = $options['ranges'][0];
1309
-
1310
-                            if (isset($range['start'])) {
1311
-                                fseek($options['stream'], $range['start'], SEEK_SET);
1312
-                                if (feof($options['stream'])) {
1313
-                                    $this->http_status($status = "416 Requested range not satisfiable");
1314
-                                    return;
1315
-                                }
1316
-
1317
-                                if (!empty($range['end'])) {
1318
-                                    $size = $range['end']-$range['start']+1;
1319
-                                    $this->http_status($status = "206 Partial content");
1320
-                                    if (!self::use_compression()) header("Content-Length: $size");
1321
-                                    header("Content-Range: bytes $range[start]-$range[end]/"
1322
-                                           . (isset($options['size']) ? $options['size'] : "*"));
1323
-                                    while ($size > 0 && !feof($options['stream'])) {
1324
-                                        $buffer = fread($options['stream'], $size < 8192 ? $size : 8192);
1325
-                                        $size  -= self::bytes($buffer);
1326
-                                        echo $buffer;
1327
-                                    }
1328
-                                } else {
1329
-                                    $this->http_status($status = "206 Partial content");
1330
-                                    if (isset($options['size'])) {
1331
-                                        if (!self::use_compression()) header("Content-Length: ".($options['size'] - $range['start']));
1332
-                                        header("Content-Range: bytes ".$range['start']."-".
1302
+				if (isset($options['stream'])) {
1303
+					// GET handler returned a stream
1304
+					if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
1305
+						// partial request and stream is seekable
1306
+
1307
+						if (count($options['ranges']) === 1) {
1308
+							$range = $options['ranges'][0];
1309
+
1310
+							if (isset($range['start'])) {
1311
+								fseek($options['stream'], $range['start'], SEEK_SET);
1312
+								if (feof($options['stream'])) {
1313
+									$this->http_status($status = "416 Requested range not satisfiable");
1314
+									return;
1315
+								}
1316
+
1317
+								if (!empty($range['end'])) {
1318
+									$size = $range['end']-$range['start']+1;
1319
+									$this->http_status($status = "206 Partial content");
1320
+									if (!self::use_compression()) header("Content-Length: $size");
1321
+									header("Content-Range: bytes $range[start]-$range[end]/"
1322
+										   . (isset($options['size']) ? $options['size'] : "*"));
1323
+									while ($size > 0 && !feof($options['stream'])) {
1324
+										$buffer = fread($options['stream'], $size < 8192 ? $size : 8192);
1325
+										$size  -= self::bytes($buffer);
1326
+										echo $buffer;
1327
+									}
1328
+								} else {
1329
+									$this->http_status($status = "206 Partial content");
1330
+									if (isset($options['size'])) {
1331
+										if (!self::use_compression()) header("Content-Length: ".($options['size'] - $range['start']));
1332
+										header("Content-Range: bytes ".$range['start']."-".
1333 1333
 											(isset($options['size']) ? $options['size']-1 : "")."/"
1334 1334
 										   . (isset($options['size']) ? $options['size'] : "*"));
1335
-                                    }
1336
-                                    fpassthru($options['stream']);
1337
-                                }
1338
-                            } else {
1339
-                                if (!self::use_compression()) header("Content-length: ".$range['last']);
1340
-                                fseek($options['stream'], -$range['last'], SEEK_END);
1341
-                                fpassthru($options['stream']);
1342
-                            }
1343
-                        } else {
1344
-                            $this->_multipart_byterange_header(); // init multipart
1345
-                            foreach ($options['ranges'] as $range) {
1346
-                                // TODO what if size unknown? 500?
1347
-                                if (isset($range['start'])) {
1348
-                                    $from = $range['start'];
1349
-                                    $to   = !empty($range['end']) ? $range['end'] : $options['size']-1;
1350
-                                } else {
1351
-                                    $from = $options['size'] - $range['last']-1;
1352
-                                    $to   = $options['size'] -1;
1353
-                                }
1354
-                                $total = isset($options['size']) ? $options['size'] : "*";
1355
-                                $size  = $to - $from + 1;
1356
-                                $this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
1357
-
1358
-
1359
-                                fseek($options['stream'], $from, SEEK_SET);
1360
-                                while ($size && !feof($options['stream'])) {
1361
-                                    $buffer = fread($options['stream'], 4096);
1362
-                                    $size  -= self::bytes($buffer);
1363
-                                    echo $buffer;
1364
-                                }
1365
-                            }
1366
-                            $this->_multipart_byterange_header(); // end multipart
1367
-                        }
1368
-                    } else {
1369
-                        // normal request or stream isn't seekable, return full content
1370
-                        if (isset($options['size']) && !self::use_compression()) {
1371
-                            header("Content-Length: ".$options['size']);
1372
-                        }
1373
-                        fpassthru($options['stream']);
1374
-                        return; // no more headers
1375
-                    }
1376
-                } elseif (isset($options['data'])) {
1377
-                    if (is_array($options['data'])) {
1378
-                        // reply to partial request
1379
-                    } else {
1380
-                        if (!self::use_compression()) header("Content-Length: ".self::bytes($options['data']));
1381
-                        echo $options['data'];
1382
-                    }
1383
-                }
1384
-            }
1385
-        }
1386
-
1387
-        if (!headers_sent()) {
1388
-            if (false === $status) {
1389
-                $this->http_status("404 not found");
1390
-            } else {
1391
-                // TODO: check setting of headers in various code paths above
1392
-                $this->http_status("$status");
1393
-            }
1394
-        }
1395
-    }
1335
+									}
1336
+									fpassthru($options['stream']);
1337
+								}
1338
+							} else {
1339
+								if (!self::use_compression()) header("Content-length: ".$range['last']);
1340
+								fseek($options['stream'], -$range['last'], SEEK_END);
1341
+								fpassthru($options['stream']);
1342
+							}
1343
+						} else {
1344
+							$this->_multipart_byterange_header(); // init multipart
1345
+							foreach ($options['ranges'] as $range) {
1346
+								// TODO what if size unknown? 500?
1347
+								if (isset($range['start'])) {
1348
+									$from = $range['start'];
1349
+									$to   = !empty($range['end']) ? $range['end'] : $options['size']-1;
1350
+								} else {
1351
+									$from = $options['size'] - $range['last']-1;
1352
+									$to   = $options['size'] -1;
1353
+								}
1354
+								$total = isset($options['size']) ? $options['size'] : "*";
1355
+								$size  = $to - $from + 1;
1356
+								$this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
1357
+
1358
+
1359
+								fseek($options['stream'], $from, SEEK_SET);
1360
+								while ($size && !feof($options['stream'])) {
1361
+									$buffer = fread($options['stream'], 4096);
1362
+									$size  -= self::bytes($buffer);
1363
+									echo $buffer;
1364
+								}
1365
+							}
1366
+							$this->_multipart_byterange_header(); // end multipart
1367
+						}
1368
+					} else {
1369
+						// normal request or stream isn't seekable, return full content
1370
+						if (isset($options['size']) && !self::use_compression()) {
1371
+							header("Content-Length: ".$options['size']);
1372
+						}
1373
+						fpassthru($options['stream']);
1374
+						return; // no more headers
1375
+					}
1376
+				} elseif (isset($options['data'])) {
1377
+					if (is_array($options['data'])) {
1378
+						// reply to partial request
1379
+					} else {
1380
+						if (!self::use_compression()) header("Content-Length: ".self::bytes($options['data']));
1381
+						echo $options['data'];
1382
+					}
1383
+				}
1384
+			}
1385
+		}
1386
+
1387
+		if (!headers_sent()) {
1388
+			if (false === $status) {
1389
+				$this->http_status("404 not found");
1390
+			} else {
1391
+				// TODO: check setting of headers in various code paths above
1392
+				$this->http_status("$status");
1393
+			}
1394
+		}
1395
+	}
1396
+
1397
+
1398
+	/**
1399
+	 * parse HTTP Range: header
1400
+	 *
1401
+	 * @param  array options array to store result in
1402
+	 * @return void
1403
+	 */
1404
+	function _get_ranges(&$options)
1405
+	{
1406
+		// process Range: header if present
1407
+		if (isset($this->_SERVER['HTTP_RANGE'])) {
1408
+
1409
+			// we only support standard "bytes" range specifications for now
1410
+			$matches = null;
1411
+			if (preg_match('/bytes\s*=\s*(.+)/', $this->_SERVER['HTTP_RANGE'], $matches)) {
1412
+				$options["ranges"] = array();
1413
+
1414
+				// ranges are comma separated
1415
+				foreach (explode(",", $matches[1]) as $range) {
1416
+					// ranges are either from-to pairs or just end positions
1417
+					list($start, $end) = explode("-", $range);
1418
+					$options["ranges"][] = ($start==="")
1419
+						? array("last"=>$end)
1420
+						: array("start"=>$start, "end"=>$end);
1421
+				}
1422
+			}
1423
+		}
1424
+	}
1425
+
1426
+	/**
1427
+	 * generate separator headers for multipart response
1428
+	 *
1429
+	 * first and last call happen without parameters to generate
1430
+	 * the initial header and closing sequence, all calls inbetween
1431
+	 * require content mimetype, start and end byte position and
1432
+	 * optionaly the total byte length of the requested resource
1433
+	 *
1434
+	 * @param  string  mimetype
1435
+	 * @param  int     start byte position
1436
+	 * @param  int     end   byte position
1437
+	 * @param  int     total resource byte size
1438
+	 */
1439
+	function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false)
1440
+	{
1441
+		if ($mimetype === false) {
1442
+			if (!isset($this->multipart_separator)) {
1443
+				// initial
1444
+
1445
+				// a little naive, this sequence *might* be part of the content
1446
+				// but it's really not likely and rather expensive to check
1447
+				$this->multipart_separator = "SEPARATOR_".md5(microtime());
1448
+
1449
+				// generate HTTP header
1450
+				header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator);
1451
+			} else {
1452
+				// final
1453
+
1454
+				// generate closing multipart sequence
1455
+				echo "\n--{$this->multipart_separator}--";
1456
+			}
1457
+		} else {
1458
+			// generate separator and header for next part
1459
+			echo "\n--{$this->multipart_separator}\n";
1460
+			echo "Content-type: $mimetype\n";
1461
+			echo "Content-range: $from-$to/". ($total === false ? "*" : $total);
1462
+			echo "\n\n";
1463
+		}
1464
+	}
1465
+
1466
+
1467
+
1468
+	// }}}
1469
+
1470
+	// {{{ http_HEAD()
1471
+
1472
+	/**
1473
+	 * HEAD method handler
1474
+	 *
1475
+	 * @param  void
1476
+	 * @return void
1477
+	 */
1478
+	function http_HEAD()
1479
+	{
1480
+		$status          = false;
1481
+		$options         = Array();
1482
+		$options["path"] = $this->path;
1483
+
1484
+		if (method_exists($this, "HEAD")) {
1485
+			$status = $this->head($options);
1486
+		} else if (method_exists($this, "GET")) {
1487
+			ob_start();
1488
+			$status = $this->GET($options);
1489
+			if (!isset($options['size'])) {
1490
+				$options['size'] = ob_get_length();
1491
+			}
1492
+			ob_end_clean();
1493
+		}
1494
+
1495
+		if (!isset($options['mimetype'])) {
1496
+			$options['mimetype'] = "application/octet-stream";
1497
+		}
1498
+		header("Content-type: $options[mimetype]");
1499
+
1500
+		if (isset($options['mtime'])) {
1501
+			header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
1502
+		}
1503
+
1504
+		if (isset($options['size'])) {
1505
+			header("Content-Length: ".$options['size']);
1506
+		}
1507
+
1508
+		if ($status === true)  $status = "200 OK";
1509
+		if ($status === false) $status = "404 Not found";
1510
+
1511
+		$this->http_status($status);
1512
+	}
1513
+
1514
+	// }}}
1515
+
1516
+	// {{{ http_POST()
1517
+
1518
+	/**
1519
+	 * POST method handler
1520
+	 *
1521
+	 * @param  void
1522
+	 * @return void
1523
+	 */
1524
+	function http_POST()
1525
+	{
1526
+		$status          = '405 Method not allowed';
1527
+		$options         = Array();
1528
+		$options['path'] = $this->path;
1529
+
1530
+		if (isset($this->_SERVER['CONTENT_LENGTH']))
1531
+		{
1532
+			$options['content_length'] = $this->_SERVER['CONTENT_LENGTH'];
1533
+		}
1534
+		elseif (isset($this->_SERVER['X-Expected-Entity-Length']))
1535
+		{
1536
+			// MacOS gives us that hint
1537
+			$options['content_length'] = $this->_SERVER['X-Expected-Entity-Length'];
1538
+		}
1539
+
1540
+		// get the Content-type
1541
+		if (isset($this->_SERVER["CONTENT_TYPE"])) {
1542
+			// for now we do not support any sort of multipart requests
1543
+			if (!strncmp($this->_SERVER["CONTENT_TYPE"], 'multipart/', 10)) {
1544
+				$this->http_status('501 not implemented');
1545
+				echo 'The service does not support mulipart POST requests';
1546
+				return;
1547
+			}
1548
+			$options['content_type'] = $this->_SERVER['CONTENT_TYPE'];
1549
+		} else {
1550
+			// default content type if none given
1551
+			$options['content_type'] = 'application/octet-stream';
1552
+		}
1553
+
1554
+		$options['stream'] = fopen('php://input', 'r');
1555
+		switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1556
+		{
1557
+			case 'gzip':
1558
+			case 'deflate':	//zlib
1559
+				if (extension_loaded('zlib'))
1560
+	 			{
1561
+	  				stream_filter_append($options['stream'], 'zlib.inflate', STREAM_FILTER_READ);
1562
+	   			}
1563
+		}
1564
+		// store request in $this->request, if requested via $this->store_request
1565
+		if ($this->store_request)
1566
+		{
1567
+			$options['content'] = '';
1568
+			while(!feof($options['stream']))
1569
+			{
1570
+				$options['content'] .= fread($options['stream'],8192);
1571
+			}
1572
+			$this->request =& $options['content'];
1573
+			unset($options['stream']);
1574
+		}
1575
+
1576
+		/* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1577
+         ignore any Content-* (e.g. Content-Range) headers that it
1578
+         does not understand or implement and MUST return a 501
1579
+         (Not Implemented) response in such cases."
1580
+         */
1581
+		foreach ($this->_SERVER as $key => $val) {
1582
+			if (strncmp($key, 'HTTP_CONTENT', 11)) continue;
1583
+			switch ($key) {
1584
+				case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1585
+					switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1586
+					{
1587
+						case 'gzip':
1588
+						case 'deflate':	//zlib
1589
+							if (extension_loaded('zlib')) break;
1590
+							// fall through for no zlib support
1591
+						default:
1592
+							$this->http_status('415 Unsupported Media Type');
1593
+							echo "The service does not support '$val' content encoding";
1594
+							return;
1595
+					}
1596
+					break;
1597
+
1598
+				case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1599
+					// we assume it is not critical if this one is ignored
1600
+					// in the actual POST implementation ...
1601
+					$options['content_language'] = $val;
1602
+					break;
1603
+
1604
+				case 'HTTP_CONTENT_LENGTH':
1605
+					// defined on IIS and has the same value as CONTENT_LENGTH
1606
+					break;
1607
+
1608
+				case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1609
+					/* The meaning of the Content-Location header in PUT
1610
+			         or POST requests is undefined; servers are free
1611
+			         to ignore it in those cases. */
1612
+					break;
1613
+
1614
+				case 'HTTP_CONTENT_RANGE':    // RFC 2616 14.16
1615
+					// single byte range requests are supported
1616
+					// the header format is also specified in RFC 2616 14.16
1617
+					// TODO we have to ensure that implementations support this or send 501 instead
1618
+					$matches = null;
1619
+					if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
1620
+						$this->http_status('400 bad request');
1621
+						echo 'The service does only support single byte ranges';
1622
+						return;
1623
+					}
1624
+
1625
+					$range = array('start'=>$matches[1], 'end'=>$matches[2]);
1626
+					if (is_numeric($matches[3])) {
1627
+						$range['total_length'] = $matches[3];
1628
+					}
1629
+					$options['ranges'][] = $range;
1630
+
1631
+					// TODO make sure the implementation supports partial POST
1632
+					// this has to be done in advance to avoid data being overwritten
1633
+					// on implementations that do not support this ...
1634
+					break;
1635
+
1636
+				case 'HTTP_CONTENT_TYPE':
1637
+					// defined on IIS and has the same value as CONTENT_TYPE
1638
+					break;
1639
+
1640
+				case 'HTTP_CONTENT_MD5':      // RFC 2616 14.15
1641
+					// TODO: maybe we can just pretend here?
1642
+					$this->http_status('501 not implemented');
1643
+					echo 'The service does not support content MD5 checksum verification';
1644
+					return;
1645
+
1646
+				case 'HTTP_CONTENT_DISPOSITION':
1647
+					// do NOT care about Content-Disposition in POST requests required by CalDAV managed attachments
1648
+					break;
1649
+
1650
+				default:
1651
+					// any other unknown Content-* headers
1652
+					$this->http_status('501 not implemented');
1653
+				echo "The service does not support '$key'";
1654
+				return;
1655
+			}
1656
+		}
1657
+
1658
+		if (method_exists($this, 'POST')) {
1659
+			$status = $this->POST($options);
1660
+
1661
+			if ($status === false) {
1662
+				$status = '400 Something went wrong';
1663
+			} else if ($status === true) {
1664
+				$status = '200 OK';
1665
+			} else if (is_resource($status) && get_resource_type($status) == 'stream') {
1666
+				$stream = $status;
1667
+
1668
+				$status = empty($options['new']) ? '200 OK' : '201 Created';
1669
+
1670
+				if (!empty($options['ranges'])) {
1671
+					// TODO multipart support is missing (see also above)
1672
+					if (0 == fseek($stream, $range[0]['start'], SEEK_SET)) {
1673
+						$length = $range[0]['end']-$range[0]['start']+1;
1674
+						if (!fwrite($stream, fread($options['stream'], $length))) {
1675
+							$status = '403 Forbidden';
1676
+						}
1677
+					} else {
1678
+						$status = '403 Forbidden';
1679
+					}
1680
+				} else {
1681
+					while (!feof($options['stream'])) {
1682
+						if (false === fwrite($stream, fread($options['stream'], 4096))) {
1683
+							$status = '403 Forbidden';
1684
+							break;
1685
+						}
1686
+					}
1687
+				}
1688
+				fclose($stream);
1689
+			}
1690
+		}
1691
+		$this->http_status($status);
1692
+	}
1693
+
1694
+	// }}}
1695
+
1696
+	// {{{ http_PUT()
1697
+
1698
+	/**
1699
+	 * PUT method handler
1700
+	 *
1701
+	 * @param  void
1702
+	 * @return void
1703
+	 */
1704
+	function http_PUT()
1705
+	{
1706
+		if ($this->_check_lock_status($this->path)) {
1707
+			$options                   = Array();
1708
+			$options["path"]           = $this->path;
1709
+
1710
+			if (isset($this->_SERVER['CONTENT_LENGTH']))
1711
+			{
1712
+				$options['content_length'] = $this->_SERVER['CONTENT_LENGTH'];
1713
+			}
1714
+			elseif (isset($this->_SERVER['X-Expected-Entity-Length']))
1715
+			{
1716
+				// MacOS gives us that hint
1717
+				$options['content_length'] = $this->_SERVER['X-Expected-Entity-Length'];
1718
+			}
1719
+
1720
+			// get the Content-type
1721
+			if (isset($this->_SERVER["CONTENT_TYPE"])) {
1722
+				// for now we do not support any sort of multipart requests
1723
+				if (!strncmp($this->_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
1724
+					$this->http_status("501 not implemented");
1725
+					echo "The service does not support multipart PUT requests";
1726
+					return;
1727
+				}
1728
+				$options["content_type"] = $this->_SERVER["CONTENT_TYPE"];
1729
+			} else {
1730
+				// default content type if none given
1731
+				$options["content_type"] = "application/octet-stream";
1732
+			}
1733
+
1734
+			$options["stream"] = fopen("php://input", "r");
1735
+			switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1736
+			{
1737
+				case 'gzip':
1738
+				case 'deflate':	//zlib
1739
+					if (extension_loaded('zlib'))
1740
+		 			{
1741
+		  				stream_filter_append($options['stream'], 'zlib.inflate', STREAM_FILTER_READ);
1742
+		   			}
1743
+			}
1744
+			// store request in $this->request, if requested via $this->store_request
1745
+			if ($this->store_request)
1746
+			{
1747
+				$options['content'] = '';
1748
+				while(!feof($options['stream']))
1749
+				{
1750
+					$options['content'] .= fread($options['stream'],8192);
1751
+				}
1752
+				$this->request =& $options['content'];
1753
+				unset($options['stream']);
1754
+			}
1755
+
1756
+			/* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1757
+             ignore any Content-* (e.g. Content-Range) headers that it
1758
+             does not understand or implement and MUST return a 501
1759
+             (Not Implemented) response in such cases."
1760
+            */
1761
+			foreach ($this->_SERVER as $key => $val) {
1762
+				if (strncmp($key, "HTTP_CONTENT", 11)) continue;
1763
+				switch ($key) {
1764
+				case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1765
+					switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1766
+					{
1767
+						case 'gzip':
1768
+						case 'deflate':	//zlib
1769
+							if (extension_loaded('zlib')) break;
1770
+							// fall through for no zlib support
1771
+						default:
1772
+							$this->http_status('415 Unsupported Media Type');
1773
+							echo "The service does not support '$val' content encoding";
1774
+							return;
1775
+					}
1776
+					break;
1777
+
1778
+				case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1779
+					// we assume it is not critical if this one is ignored
1780
+					// in the actual PUT implementation ...
1781
+					$options["content_language"] = $val;
1782
+					break;
1783
+
1784
+				case 'HTTP_CONTENT_LENGTH':
1785
+					// defined on IIS and has the same value as CONTENT_LENGTH
1786
+					break;
1787
+
1788
+				case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1789
+					/* The meaning of the Content-Location header in PUT
1790
+                     or POST requests is undefined; servers are free
1791
+                     to ignore it in those cases. */
1792
+					break;
1793
+
1794
+				case 'HTTP_CONTENT_RANGE':    // RFC 2616 14.16
1795
+					// single byte range requests are supported
1796
+					// the header format is also specified in RFC 2616 14.16
1797
+					// TODO we have to ensure that implementations support this or send 501 instead
1798
+					$matches = null;
1799
+					if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
1800
+						$this->http_status("400 bad request");
1801
+						echo "The service does only support single byte ranges";
1802
+						return;
1803
+					}
1804
+
1805
+					$range = array("start" => $matches[1], "end" => $matches[2]);
1806
+					if (is_numeric($matches[3])) {
1807
+						$range["total_length"] = $matches[3];
1808
+					}
1809
+
1810
+					if (!isset($options['ranges'])) {
1811
+						$options['ranges'] = array();
1812
+					}
1813
+
1814
+					$options["ranges"][] = $range;
1815
+
1816
+					// TODO make sure the implementation supports partial PUT
1817
+					// this has to be done in advance to avoid data being overwritten
1818
+					// on implementations that do not support this ...
1819
+					break;
1820
+
1821
+				case 'HTTP_CONTENT_TYPE':
1822
+					// defined on IIS and has the same value as CONTENT_TYPE
1823
+					break;
1824
+
1825
+				case 'HTTP_CONTENT_MD5':      // RFC 2616 14.15
1826
+					// TODO: maybe we can just pretend here?
1827
+					$this->http_status("501 not implemented");
1828
+					echo "The service does not support content MD5 checksum verification";
1829
+					return;
1830
+
1831
+				default:
1832
+					// any other unknown Content-* headers
1833
+					$this->http_status("501 not implemented");
1834
+					echo "The service does not support '$key'";
1835
+					return;
1836
+				}
1837
+			}
1838
+
1839
+			$stat = $this->PUT($options);
1840
+
1841
+			if ($stat === false) {
1842
+				$stat = "403 Forbidden";
1843
+			} else if (is_resource($stat) && get_resource_type($stat) == "stream") {
1844
+				$stream = $stat;
1845
+
1846
+				$stat = $options["new"] ? "201 Created" : "204 No Content";
1847
+
1848
+				if (!empty($options["ranges"])) {
1849
+					// TODO multipart support is missing (see also above)
1850
+					if (0 == fseek($stream, $options['ranges'][0]["start"], SEEK_SET)) {
1851
+						$length = $options['ranges'][0]["end"] - $options['ranges'][0]["start"]+1;
1852
+
1853
+						while (!feof($options['stream'])) {
1854
+							if ($length <= 0) {
1855
+							   break;
1856
+							}
1857
+
1858
+							if ($length <= 8192) {
1859
+								$data = fread($options['stream'], $length);
1860
+							} else {
1861
+								$data = fread($options['stream'], 8192);
1862
+							}
1863
+
1864
+							if ($data === false) {
1865
+								$stat = "400 Bad request";
1866
+							} elseif (strlen($data)) {
1867
+								if (false === fwrite($stream, $data)) {
1868
+									$stat = "403 Forbidden";
1869
+									break;
1870
+								}
1871
+								$length -= strlen($data);
1872
+							}
1873
+						}
1874
+					} else {
1875
+						$stat = "403 Forbidden";
1876
+					}
1877
+				} else {
1878
+					while (!feof($options["stream"])) {
1879
+						if (false === fwrite($stream, fread($options["stream"], 8192))) {
1880
+							$stat = "403 Forbidden";
1881
+							break;
1882
+						}
1883
+					}
1884
+				}
1885
+
1886
+				fclose($stream);
1887
+			}
1888
+
1889
+			$this->http_status($stat);
1890
+		} else {
1891
+			$this->http_status("423 Locked");
1892
+		}
1893
+	}
1894
+
1895
+	// }}}
1896
+
1897
+
1898
+	// {{{ http_DELETE()
1899
+
1900
+	/**
1901
+	 * DELETE method handler
1902
+	 *
1903
+	 * @param  void
1904
+	 * @return void
1905
+	 */
1906
+	function http_DELETE()
1907
+	{
1908
+		// check RFC 2518 Section 9.2, last paragraph
1909
+		if (isset($this->_SERVER["HTTP_DEPTH"])) {
1910
+			if ($this->_SERVER["HTTP_DEPTH"] != "infinity") {
1911
+				if (stripos($_SERVER['HTTP_USER_AGENT'],'webdrive') !== false)
1912
+				{
1913
+					// pretend we didnt see it, as webdrive does not handle the depth parameter correctly while deleting collections
1914
+				}
1915
+				else
1916
+				{
1917
+					$this->http_status("400 Bad Request");
1918
+					return;
1919
+				}
1920
+			}
1921
+		}
1922
+
1923
+		// check lock status
1924
+		if ($this->_check_lock_status($this->path)) {
1925
+			// ok, proceed
1926
+			$options         = Array();
1927
+			$options["path"] = $this->path;
1928
+
1929
+			$stat = $this->DELETE($options);
1930
+
1931
+			$this->http_status($stat);
1932
+		} else {
1933
+			// sorry, its locked
1934
+			$this->http_status("423 Locked");
1935
+		}
1936
+	}
1937
+
1938
+	// }}}
1939
+
1940
+	// {{{ http_COPY()
1941
+
1942
+	/**
1943
+	 * COPY method handler
1944
+	 *
1945
+	 * @param  void
1946
+	 * @return void
1947
+	 */
1948
+	function http_COPY()
1949
+	{
1950
+		// no need to check source lock status here
1951
+		// destination lock status is always checked by the helper method
1952
+		$this->_copymove("copy");
1953
+	}
1954
+
1955
+	// }}}
1956
+
1957
+	// {{{ http_MOVE()
1958
+
1959
+	/**
1960
+	 * MOVE method handler
1961
+	 *
1962
+	 * @param  void
1963
+	 * @return void
1964
+	 */
1965
+	function http_MOVE()
1966
+	{
1967
+		if ($this->_check_lock_status($this->path)) {
1968
+			// destination lock status is always checked by the helper method
1969
+			$this->_copymove("move");
1970
+		} else {
1971
+			$this->http_status("423 Locked");
1972
+		}
1973
+	}
1974
+
1975
+	// }}}
1976
+
1977
+
1978
+	// {{{ http_LOCK()
1979
+
1980
+	/**
1981
+	 * LOCK method handler
1982
+	 *
1983
+	 * @param  void
1984
+	 * @return void
1985
+	 */
1986
+	function http_LOCK()
1987
+	{
1988
+		$options         = Array();
1989
+		$options["path"] = $this->path;
1990
+
1991
+		if (isset($this->_SERVER['HTTP_DEPTH'])) {
1992
+			$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
1993
+		} else {
1994
+			$options["depth"] = "infinity";
1995
+		}
1996
+
1997
+		if (isset($this->_SERVER["HTTP_TIMEOUT"])) {
1998
+			$options["timeout"] = explode(",", $this->_SERVER["HTTP_TIMEOUT"]);
1999
+		}
2000
+
2001
+		if (empty($this->_SERVER['CONTENT_LENGTH']) && !empty($this->_SERVER['HTTP_IF'])) {
2002
+			// check if locking is possible
2003
+			if (!$this->_check_lock_status($this->path)) {
2004
+				$this->http_status("423 Locked");
2005
+				return;
2006
+			}
2007
+
2008
+			// refresh lock
2009
+			$options["locktoken"] = substr($this->_SERVER['HTTP_IF'], 2, -2);
2010
+			$options["update"]    = $options["locktoken"];
2011
+
2012
+			// setting defaults for required fields, LOCK() SHOULD overwrite these
2013
+			$options['owner']     = "unknown";
2014
+			$options['scope']     = "exclusive";
2015
+			$options['type']      = "write";
2016
+
2017
+
2018
+			$stat = $this->LOCK($options);
2019
+		} else {
2020
+			// extract lock request information from request XML payload
2021
+			$lockinfo = new _parse_lockinfo("php://input");
2022
+			if (!$lockinfo->success) {
2023
+				$this->http_status("400 bad request");
2024
+			}
2025
+
2026
+			// check if locking is possible
2027
+			if (!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) {
2028
+				$this->http_status("423 Locked");
2029
+				return;
2030
+			}
2031
+
2032
+			// new lock
2033
+			$options["scope"]     = $lockinfo->lockscope;
2034
+			$options["type"]      = $lockinfo->locktype;
2035
+			// Todo: lockinfo::owner still contains D:href opening and closing tags, maybe they should be removed here with strip_tags
2036
+			$options["owner"]     = $lockinfo->owner;
2037
+			$options["locktoken"] = $this->_new_locktoken();
2038
+
2039
+			$stat = $this->LOCK($options);
2040
+		}
2041
+
2042
+		if (is_bool($stat)) {
2043
+			$http_stat = $stat ? "200 OK" : "423 Locked";
2044
+		} else {
2045
+			$http_stat = (string)$stat;
2046
+		}
2047
+		$this->http_status($http_stat);
2048
+
2049
+		if ($http_stat{0} == 2) { // 2xx states are ok
2050
+			if ($options["timeout"]) {
2051
+				// if multiple timeout values were given we take the first only
2052
+				if (is_array($options["timeout"])) {
2053
+					reset($options["timeout"]);
2054
+					$options["timeout"] = current($options["timeout"]);
2055
+				}
2056
+				// if the timeout is numeric only we need to reformat it
2057
+				if (is_numeric($options["timeout"])) {
2058
+					// more than a million is considered an absolute timestamp
2059
+					// less is more likely a relative value
2060
+					if ($options["timeout"]>1000000) {
2061
+						$timeout = "Second-".($options['timeout']-time());
2062
+					} else {
2063
+						$timeout = "Second-$options[timeout]";
2064
+					}
2065
+				} else {
2066
+					// non-numeric values are passed on verbatim,
2067
+					// no error checking is performed here in this case
2068
+					// TODO: send "Infinite" on invalid timeout strings?
2069
+					$timeout = $options["timeout"];
2070
+				}
2071
+			} else {
2072
+				$timeout = "Infinite";
2073
+			}
2074
+
2075
+			header('Content-Type: text/xml; charset="utf-8"');
2076
+			header("Lock-Token: <$options[locktoken]>");
2077
+			echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2078
+			echo "<D:prop xmlns:D=\"DAV:\">\n";
2079
+			echo ' <'.($this->crrnd?'':'D:')."lockdiscovery>\n";
2080
+			echo '  <'.($this->crrnd?'':'D:')."activelock>\n";
2081
+			echo '   <'.($this->crrnd?'':'D:')."lockscope><D:$options[scope]/></".($this->crrnd?'':'D:')."lockscope>\n";
2082
+			echo '   <'.($this->crrnd?'':'D:')."locktype><D:$options[type]/></".($this->crrnd?'':'D:')."locktype>\n";
2083
+			echo '   <'.($this->crrnd?'':'D:')."depth>$options[depth]</".($this->crrnd?'':'D:')."depth>\n";
2084
+			echo '   <'.($this->crrnd?'':'D:')."owner>$options[owner]</".($this->crrnd?'':'D:')."owner>\n";
2085
+			echo '   <'.($this->crrnd?'':'D:')."timeout>$timeout</".($this->crrnd?'':'D:')."timeout>\n";
2086
+			echo '   <'.($this->crrnd?'':'D:')."locktoken><D:href>$options[locktoken]</D:href></".($this->crrnd?'':'D:')."locktoken>\n";
2087
+			echo '  </'.($this->crrnd?'':'D:')."activelock>\n";
2088
+			echo ' </'.($this->crrnd?'':'D:')."lockdiscovery>\n";
2089
+			echo '</'.($this->crrnd?'':'D:')."prop>\n\n";
2090
+		}
2091
+	}
2092
+
2093
+
2094
+	// }}}
2095
+
2096
+	// {{{ http_UNLOCK()
2097
+
2098
+	/**
2099
+	 * UNLOCK method handler
2100
+	 *
2101
+	 * @param  void
2102
+	 * @return void
2103
+	 */
2104
+	function http_UNLOCK()
2105
+	{
2106
+		$options         = Array();
2107
+		$options["path"] = $this->path;
2108
+
2109
+		if (isset($this->_SERVER['HTTP_DEPTH'])) {
2110
+			$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2111
+		} else {
2112
+			$options["depth"] = "infinity";
2113
+		}
2114
+
2115
+		// strip surrounding <>
2116
+		$options["token"] = substr(trim($this->_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
2117
+
2118
+		// call user method
2119
+		$stat = $this->UNLOCK($options);
2120
+
2121
+		$this->http_status($stat);
2122
+	}
2123
+
2124
+	// }}}
2125
+
2126
+	// {{{ http_ACL()
2127
+
2128
+	/**
2129
+	 * ACL method handler
2130
+	 *
2131
+	 * @param  void
2132
+	 * @return void
2133
+	 */
2134
+	function http_ACL()
2135
+	{
2136
+		$options         = Array();
2137
+		$options['path'] = $this->path;
2138
+		$options['errors'] = array();
2139
+
2140
+		if (isset($this->_SERVER['HTTP_DEPTH'])) {
2141
+			$options['depth'] = $this->_SERVER['HTTP_DEPTH'];
2142
+		} else {
2143
+			$options['depth'] = 'infinity';
2144
+		}
2145
+
2146
+		// call user method
2147
+		$status = $this->ACL($options);
2148
+
2149
+		// now we generate the reply header ...
2150
+		$this->http_status($status);
2151
+		$content = '';
2152
+
2153
+		if (is_array($options['errors']) && count($options['errors'])) {
2154
+			header('Content-Type: text/xml; charset="utf-8"');
2155
+			// ... and payload
2156
+			$content .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2157
+			$content .= "<D:error xmlns:D=\"DAV:\"> \n";
2158
+			foreach ($options['errors'] as $violation) {
2159
+				$content .= '<'.($this->crrnd?'':'D:')."$violation/>\n";
2160
+			}
2161
+			$content .=  '</'.($this->crrnd?'':'D:')."error>\n";
2162
+		}
2163
+		if (!self::use_compression()) header("Content-Length: ".self::bytes($content));
2164
+		if ($content) echo $options['content'];
2165
+	}
2166
+
2167
+	// }}}
2168
+
2169
+	// }}}
2170
+
2171
+	// {{{ _copymove()
2172
+
2173
+	function _copymove($what)
2174
+	{
2175
+		$options         = Array();
2176
+		$options["path"] = $this->path;
2177
+
2178
+		if (isset($this->_SERVER["HTTP_DEPTH"])) {
2179
+			$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2180
+		} else {
2181
+			$options["depth"] = "infinity";
2182
+		}
2183
+
2184
+		$http_header_host = preg_replace("/:80$/", "", $this->_SERVER["HTTP_HOST"]);
2185
+
2186
+		$url  = parse_url($this->_SERVER["HTTP_DESTINATION"]);
2187
+		$path = urldecode($url["path"]);
2188
+
2189
+		if (isset($url["host"])) {
2190
+			// TODO check url scheme, too
2191
+			$http_host = $url["host"];
2192
+			if (isset($url["port"]) && $url["port"] != 80)
2193
+				$http_host.= ":".$url["port"];
2194
+		} else {
2195
+			// only path given, set host to self
2196
+			$http_host = $http_header_host;
2197
+		}
2198
+
2199
+		if ($http_host == $http_header_host &&
2200
+			!strncmp($this->_SERVER["SCRIPT_NAME"], $path,
2201
+					 strlen($this->_SERVER["SCRIPT_NAME"]))) {
2202
+			$options["dest"] = substr($path, strlen($this->_SERVER["SCRIPT_NAME"]));
2203
+			if (!$this->_check_lock_status($options["dest"])) {
2204
+				$this->http_status("423 Locked");
2205
+				return;
2206
+			}
2207
+
2208
+		} else {
2209
+			$options["dest_url"] = $this->_SERVER["HTTP_DESTINATION"];
2210
+		}
2211
+
2212
+		// see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3
2213
+		if (isset($this->_SERVER["HTTP_OVERWRITE"])) {
2214
+			$options["overwrite"] = $this->_SERVER["HTTP_OVERWRITE"] == "T";
2215
+		} else {
2216
+			$options["overwrite"] = true;
2217
+		}
2218
+
2219
+		$stat = $this->$what($options);
2220
+		$this->http_status($stat);
2221
+	}
2222
+
2223
+	// }}}
2224
+
2225
+	// {{{ _allow()
2226
+
2227
+	/**
2228
+	 * check for implemented HTTP methods
2229
+	 *
2230
+	 * @param  void
2231
+	 * @return array something
2232
+	 */
2233
+	function _allow()
2234
+	{
2235
+		// OPTIONS is always there
2236
+		$allow = array("OPTIONS" =>"OPTIONS");
2237
+
2238
+		// all other METHODS need both a http_method() wrapper
2239
+		// and a method() implementation
2240
+		// the base class supplies wrappers only
2241
+		foreach (get_class_methods($this) as $method) {
2242
+			if (!strncmp("http_", $method, 5)) {
2243
+				$method = strtoupper(substr($method, 5));
2244
+				if (method_exists($this, $method)) {
2245
+					$allow[$method] = $method;
2246
+				}
2247
+			}
2248
+		}
1396 2249
 
2250
+		// we can emulate a missing HEAD implemetation using GET
2251
+		if (isset($allow["GET"]))
2252
+			$allow["HEAD"] = "HEAD";
1397 2253
 
1398
-    /**
1399
-     * parse HTTP Range: header
1400
-     *
1401
-     * @param  array options array to store result in
1402
-     * @return void
1403
-     */
1404
-    function _get_ranges(&$options)
1405
-    {
1406
-        // process Range: header if present
1407
-        if (isset($this->_SERVER['HTTP_RANGE'])) {
1408
-
1409
-            // we only support standard "bytes" range specifications for now
1410
-			$matches = null;
1411
-            if (preg_match('/bytes\s*=\s*(.+)/', $this->_SERVER['HTTP_RANGE'], $matches)) {
1412
-                $options["ranges"] = array();
1413
-
1414
-                // ranges are comma separated
1415
-                foreach (explode(",", $matches[1]) as $range) {
1416
-                    // ranges are either from-to pairs or just end positions
1417
-                    list($start, $end) = explode("-", $range);
1418
-                    $options["ranges"][] = ($start==="")
1419
-                        ? array("last"=>$end)
1420
-                        : array("start"=>$start, "end"=>$end);
1421
-                }
1422
-            }
1423
-        }
1424
-    }
2254
+		// no LOCK without checklok()
2255
+		if (!method_exists($this, "checklock")) {
2256
+			unset($allow["LOCK"]);
2257
+			unset($allow["UNLOCK"]);
2258
+		}
1425 2259
 
1426
-    /**
1427
-     * generate separator headers for multipart response
1428
-     *
1429
-     * first and last call happen without parameters to generate
1430
-     * the initial header and closing sequence, all calls inbetween
1431
-     * require content mimetype, start and end byte position and
1432
-     * optionaly the total byte length of the requested resource
1433
-     *
1434
-     * @param  string  mimetype
1435
-     * @param  int     start byte position
1436
-     * @param  int     end   byte position
1437
-     * @param  int     total resource byte size
1438
-     */
1439
-    function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false)
1440
-    {
1441
-        if ($mimetype === false) {
1442
-            if (!isset($this->multipart_separator)) {
1443
-                // initial
1444
-
1445
-                // a little naive, this sequence *might* be part of the content
1446
-                // but it's really not likely and rather expensive to check
1447
-                $this->multipart_separator = "SEPARATOR_".md5(microtime());
1448
-
1449
-                // generate HTTP header
1450
-                header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator);
1451
-            } else {
1452
-                // final
1453
-
1454
-                // generate closing multipart sequence
1455
-                echo "\n--{$this->multipart_separator}--";
1456
-            }
1457
-        } else {
1458
-            // generate separator and header for next part
1459
-            echo "\n--{$this->multipart_separator}\n";
1460
-            echo "Content-type: $mimetype\n";
1461
-            echo "Content-range: $from-$to/". ($total === false ? "*" : $total);
1462
-            echo "\n\n";
1463
-        }
1464
-    }
1465
-
1466
-
1467
-
1468
-    // }}}
1469
-
1470
-    // {{{ http_HEAD()
2260
+		return $allow;
2261
+	}
1471 2262
 
1472
-    /**
1473
-     * HEAD method handler
1474
-     *
1475
-     * @param  void
1476
-     * @return void
1477
-     */
1478
-    function http_HEAD()
1479
-    {
1480
-        $status          = false;
1481
-        $options         = Array();
1482
-        $options["path"] = $this->path;
1483
-
1484
-        if (method_exists($this, "HEAD")) {
1485
-            $status = $this->head($options);
1486
-        } else if (method_exists($this, "GET")) {
1487
-            ob_start();
1488
-            $status = $this->GET($options);
1489
-            if (!isset($options['size'])) {
1490
-                $options['size'] = ob_get_length();
1491
-            }
1492
-            ob_end_clean();
1493
-        }
1494
-
1495
-        if (!isset($options['mimetype'])) {
1496
-            $options['mimetype'] = "application/octet-stream";
1497
-        }
1498
-        header("Content-type: $options[mimetype]");
1499
-
1500
-        if (isset($options['mtime'])) {
1501
-            header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
1502
-        }
1503
-
1504
-        if (isset($options['size'])) {
1505
-            header("Content-Length: ".$options['size']);
1506
-        }
1507
-
1508
-        if ($status === true)  $status = "200 OK";
1509
-        if ($status === false) $status = "404 Not found";
1510
-
1511
-        $this->http_status($status);
1512
-    }
1513
-
1514
-    // }}}
1515
-
1516
-    // {{{ http_POST()
2263
+	// }}}
1517 2264
 
1518
-    /**
1519
-     * POST method handler
1520
-     *
1521
-     * @param  void
1522
-     * @return void
1523
-     */
1524
-    function http_POST()
1525
-    {
1526
-        $status          = '405 Method not allowed';
1527
-        $options         = Array();
1528
-        $options['path'] = $this->path;
1529
-
1530
-        if (isset($this->_SERVER['CONTENT_LENGTH']))
1531
-        {
1532
-	        $options['content_length'] = $this->_SERVER['CONTENT_LENGTH'];
1533
-        }
1534
-        elseif (isset($this->_SERVER['X-Expected-Entity-Length']))
1535
-		{
1536
-	        // MacOS gives us that hint
1537
-	        $options['content_length'] = $this->_SERVER['X-Expected-Entity-Length'];
1538
-		}
1539
-
1540
-        // get the Content-type
1541
-        if (isset($this->_SERVER["CONTENT_TYPE"])) {
1542
-	        // for now we do not support any sort of multipart requests
1543
-	        if (!strncmp($this->_SERVER["CONTENT_TYPE"], 'multipart/', 10)) {
1544
-		        $this->http_status('501 not implemented');
1545
-		        echo 'The service does not support mulipart POST requests';
1546
-		        return;
1547
-	        }
1548
-	        $options['content_type'] = $this->_SERVER['CONTENT_TYPE'];
1549
-        } else {
1550
-	        // default content type if none given
1551
-	        $options['content_type'] = 'application/octet-stream';
1552
-        }
1553
-
1554
-        $options['stream'] = fopen('php://input', 'r');
1555
-    	switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1556
-    	{
1557
-    		case 'gzip':
1558
-    		case 'deflate':	//zlib
1559
-    			if (extension_loaded('zlib'))
1560
-     			{
1561
-      				stream_filter_append($options['stream'], 'zlib.inflate', STREAM_FILTER_READ);
1562
-       			}
1563
-    	}
1564
-		// store request in $this->request, if requested via $this->store_request
1565
-		if ($this->store_request)
1566
-		{
1567
-			$options['content'] = '';
1568
-			while(!feof($options['stream']))
1569
-			{
1570
-				$options['content'] .= fread($options['stream'],8192);
1571
-			}
1572
-			$this->request =& $options['content'];
1573
-			unset($options['stream']);
2265
+	/**
2266
+	 * helper for property element creation
2267
+	 *
2268
+	 * @param  string  XML namespace (optional)
2269
+	 * @param  string  property name
2270
+	 * @param  string  property value
2271
+	 * @praram boolen  property raw-flag
2272
+	 * @return array   property array
2273
+	 */
2274
+	public static function mkprop()
2275
+	{
2276
+		$args = func_get_args();
2277
+		switch (count($args)) {
2278
+			case 4:
2279
+				return array('ns'   => $args[0],
2280
+					'name' => $args[1],
2281
+					'val'  => $args[2],
2282
+					'raw'	=> true);
2283
+			case 3:
2284
+				return array('ns'   => $args[0],
2285
+					'name' => $args[1],
2286
+					'val'  => $args[2]);
2287
+			default:
2288
+				return array('ns'   => 'DAV:',
2289
+					'name' => $args[0],
2290
+					'val'  => $args[1]);
1574 2291
 		}
2292
+	}
1575 2293
 
1576
-        /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1577
-         ignore any Content-* (e.g. Content-Range) headers that it
1578
-         does not understand or implement and MUST return a 501
1579
-         (Not Implemented) response in such cases."
1580
-         */
1581
-        foreach ($this->_SERVER as $key => $val) {
1582
-	        if (strncmp($key, 'HTTP_CONTENT', 11)) continue;
1583
-	        switch ($key) {
1584
-		        case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1585
-		        	switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1586
-		        	{
1587
-		        		case 'gzip':
1588
-		        		case 'deflate':	//zlib
1589
-		        			if (extension_loaded('zlib')) break;
1590
-		        			// fall through for no zlib support
1591
-		        		default:
1592
-					        $this->http_status('415 Unsupported Media Type');
1593
-					        echo "The service does not support '$val' content encoding";
1594
-					        return;
1595
-		        	}
1596
-		        	break;
1597
-
1598
-		        case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1599
-			        // we assume it is not critical if this one is ignored
1600
-			        // in the actual POST implementation ...
1601
-			        $options['content_language'] = $val;
1602
-			        break;
1603
-
1604
-		        case 'HTTP_CONTENT_LENGTH':
1605
-			        // defined on IIS and has the same value as CONTENT_LENGTH
1606
-			        break;
1607
-
1608
-		        case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1609
-			        /* The meaning of the Content-Location header in PUT
1610
-			         or POST requests is undefined; servers are free
1611
-			         to ignore it in those cases. */
1612
-			        break;
2294
+	// {{{ _check_auth
1613 2295
 
1614
-		        case 'HTTP_CONTENT_RANGE':    // RFC 2616 14.16
1615
-			        // single byte range requests are supported
1616
-			        // the header format is also specified in RFC 2616 14.16
1617
-			        // TODO we have to ensure that implementations support this or send 501 instead
1618
-					$matches = null;
1619
-			        if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
1620
-				        $this->http_status('400 bad request');
1621
-				        echo 'The service does only support single byte ranges';
1622
-				        return;
1623
-			        }
1624
-
1625
-			        $range = array('start'=>$matches[1], 'end'=>$matches[2]);
1626
-			        if (is_numeric($matches[3])) {
1627
-				        $range['total_length'] = $matches[3];
1628
-			        }
1629
-			        $options['ranges'][] = $range;
1630
-
1631
-			        // TODO make sure the implementation supports partial POST
1632
-			        // this has to be done in advance to avoid data being overwritten
1633
-			        // on implementations that do not support this ...
1634
-			        break;
1635
-
1636
-		        case 'HTTP_CONTENT_TYPE':
1637
-			        // defined on IIS and has the same value as CONTENT_TYPE
1638
-			        break;
1639
-
1640
-		        case 'HTTP_CONTENT_MD5':      // RFC 2616 14.15
1641
-			        // TODO: maybe we can just pretend here?
1642
-			        $this->http_status('501 not implemented');
1643
-			        echo 'The service does not support content MD5 checksum verification';
1644
-			        return;
1645
-
1646
-		        case 'HTTP_CONTENT_DISPOSITION':
1647
-		        	// do NOT care about Content-Disposition in POST requests required by CalDAV managed attachments
1648
-		        	break;
1649
-
1650
-		        default:
1651
-			        // any other unknown Content-* headers
1652
-			        $this->http_status('501 not implemented');
1653
-		        echo "The service does not support '$key'";
1654
-		        return;
1655
-	        }
1656
-        }
1657
-
1658
-        if (method_exists($this, 'POST')) {
1659
-	        $status = $this->POST($options);
1660
-
1661
-	        if ($status === false) {
1662
-		        $status = '400 Something went wrong';
1663
-	        } else if ($status === true) {
1664
-	        	$status = '200 OK';
1665
-	        } else if (is_resource($status) && get_resource_type($status) == 'stream') {
1666
-		        $stream = $status;
1667
-
1668
-		        $status = empty($options['new']) ? '200 OK' : '201 Created';
1669
-
1670
-		        if (!empty($options['ranges'])) {
1671
-			        // TODO multipart support is missing (see also above)
1672
-			        if (0 == fseek($stream, $range[0]['start'], SEEK_SET)) {
1673
-				        $length = $range[0]['end']-$range[0]['start']+1;
1674
-				        if (!fwrite($stream, fread($options['stream'], $length))) {
1675
-					        $status = '403 Forbidden';
1676
-				        }
1677
-			        } else {
1678
-				        $status = '403 Forbidden';
1679
-			        }
1680
-		        } else {
1681
-			        while (!feof($options['stream'])) {
1682
-				        if (false === fwrite($stream, fread($options['stream'], 4096))) {
1683
-					        $status = '403 Forbidden';
1684
-					        break;
1685
-				        }
1686
-			        }
1687
-		        }
1688
-		        fclose($stream);
1689
-	        }
1690
-        }
1691
-        $this->http_status($status);
1692
-    }
1693
-
1694
-    // }}}
1695
-
1696
-    // {{{ http_PUT()
2296
+	/**
2297
+	 * check authentication if check is implemented
2298
+	 *
2299
+	 * @param  void
2300
+	 * @return bool  true if authentication succeded or not necessary
2301
+	 */
2302
+	function _check_auth()
2303
+	{
2304
+		if (method_exists($this, "checkAuth")) {
2305
+			// PEAR style method name
2306
+			return $this->checkAuth(@$this->_SERVER["AUTH_TYPE"],
2307
+									@$this->_SERVER["PHP_AUTH_USER"],
2308
+									@$this->_SERVER["PHP_AUTH_PW"]);
2309
+		} else if (method_exists($this, "check_auth")) {
2310
+			// old (pre 1.0) method name
2311
+			return $this->check_auth(@$this->_SERVER["AUTH_TYPE"],
2312
+									 @$this->_SERVER["PHP_AUTH_USER"],
2313
+									 @$this->_SERVER["PHP_AUTH_PW"]);
2314
+		} else {
2315
+			// no method found -> no authentication required
2316
+			return true;
2317
+		}
2318
+	}
1697 2319
 
1698
-    /**
1699
-     * PUT method handler
1700
-     *
1701
-     * @param  void
1702
-     * @return void
1703
-     */
1704
-    function http_PUT()
1705
-    {
1706
-        if ($this->_check_lock_status($this->path)) {
1707
-            $options                   = Array();
1708
-            $options["path"]           = $this->path;
1709
-
1710
-            if (isset($this->_SERVER['CONTENT_LENGTH']))
1711
-            {
1712
-            	$options['content_length'] = $this->_SERVER['CONTENT_LENGTH'];
1713
-            }
1714
-            elseif (isset($this->_SERVER['X-Expected-Entity-Length']))
1715
-            {
1716
-            	// MacOS gives us that hint
1717
-            	$options['content_length'] = $this->_SERVER['X-Expected-Entity-Length'];
1718
-            }
1719
-
1720
-            // get the Content-type
1721
-            if (isset($this->_SERVER["CONTENT_TYPE"])) {
1722
-                // for now we do not support any sort of multipart requests
1723
-                if (!strncmp($this->_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
1724
-                    $this->http_status("501 not implemented");
1725
-                    echo "The service does not support multipart PUT requests";
1726
-                    return;
1727
-                }
1728
-                $options["content_type"] = $this->_SERVER["CONTENT_TYPE"];
1729
-            } else {
1730
-                // default content type if none given
1731
-                $options["content_type"] = "application/octet-stream";
1732
-            }
1733
-
1734
-            $options["stream"] = fopen("php://input", "r");
1735
-	    	switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1736
-	    	{
1737
-	    		case 'gzip':
1738
-	    		case 'deflate':	//zlib
1739
-	    			if (extension_loaded('zlib'))
1740
-	     			{
1741
-	      				stream_filter_append($options['stream'], 'zlib.inflate', STREAM_FILTER_READ);
1742
-	       			}
1743
-	    	}
1744
-			// store request in $this->request, if requested via $this->store_request
1745
-			if ($this->store_request)
1746
-			{
1747
-				$options['content'] = '';
1748
-				while(!feof($options['stream']))
1749
-				{
1750
-					$options['content'] .= fread($options['stream'],8192);
1751
-				}
1752
-				$this->request =& $options['content'];
1753
-				unset($options['stream']);
1754
-			}
2320
+	// }}}
1755 2321
 
1756
-            /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1757
-             ignore any Content-* (e.g. Content-Range) headers that it
1758
-             does not understand or implement and MUST return a 501
1759
-             (Not Implemented) response in such cases."
1760
-            */
1761
-            foreach ($this->_SERVER as $key => $val) {
1762
-                if (strncmp($key, "HTTP_CONTENT", 11)) continue;
1763
-                switch ($key) {
1764
-                case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1765
-		        	switch($this->_SERVER['HTTP_CONTENT_ENCODING'])
1766
-		        	{
1767
-		        		case 'gzip':
1768
-		        		case 'deflate':	//zlib
1769
-		        			if (extension_loaded('zlib')) break;
1770
-		        			// fall through for no zlib support
1771
-		        		default:
1772
-					        $this->http_status('415 Unsupported Media Type');
1773
-					        echo "The service does not support '$val' content encoding";
1774
-					        return;
1775
-		        	}
1776
-		        	break;
1777
-
1778
-                case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1779
-                    // we assume it is not critical if this one is ignored
1780
-                    // in the actual PUT implementation ...
1781
-                    $options["content_language"] = $val;
1782
-                    break;
1783
-
1784
-                case 'HTTP_CONTENT_LENGTH':
1785
-                    // defined on IIS and has the same value as CONTENT_LENGTH
1786
-                    break;
1787
-
1788
-                case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1789
-                    /* The meaning of the Content-Location header in PUT
1790
-                     or POST requests is undefined; servers are free
1791
-                     to ignore it in those cases. */
1792
-                    break;
2322
+	// {{{ UUID stuff
1793 2323
 
1794
-                case 'HTTP_CONTENT_RANGE':    // RFC 2616 14.16
1795
-                    // single byte range requests are supported
1796
-                    // the header format is also specified in RFC 2616 14.16
1797
-                    // TODO we have to ensure that implementations support this or send 501 instead
1798
-					$matches = null;
1799
-                    if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
1800
-                        $this->http_status("400 bad request");
1801
-                        echo "The service does only support single byte ranges";
1802
-                        return;
1803
-                    }
1804
-
1805
-                    $range = array("start" => $matches[1], "end" => $matches[2]);
1806
-                    if (is_numeric($matches[3])) {
1807
-                        $range["total_length"] = $matches[3];
1808
-                    }
1809
-
1810
-                    if (!isset($options['ranges'])) {
1811
-                        $options['ranges'] = array();
1812
-                    }
1813
-
1814
-                    $options["ranges"][] = $range;
1815
-
1816
-                    // TODO make sure the implementation supports partial PUT
1817
-                    // this has to be done in advance to avoid data being overwritten
1818
-                    // on implementations that do not support this ...
1819
-                    break;
1820
-
1821
-                case 'HTTP_CONTENT_TYPE':
1822
-                    // defined on IIS and has the same value as CONTENT_TYPE
1823
-                    break;
1824
-
1825
-                case 'HTTP_CONTENT_MD5':      // RFC 2616 14.15
1826
-                    // TODO: maybe we can just pretend here?
1827
-                    $this->http_status("501 not implemented");
1828
-                    echo "The service does not support content MD5 checksum verification";
1829
-                    return;
1830
-
1831
-                default:
1832
-                    // any other unknown Content-* headers
1833
-                    $this->http_status("501 not implemented");
1834
-                    echo "The service does not support '$key'";
1835
-                    return;
1836
-                }
1837
-            }
1838
-
1839
-            $stat = $this->PUT($options);
1840
-
1841
-            if ($stat === false) {
1842
-                $stat = "403 Forbidden";
1843
-            } else if (is_resource($stat) && get_resource_type($stat) == "stream") {
1844
-                $stream = $stat;
1845
-
1846
-                $stat = $options["new"] ? "201 Created" : "204 No Content";
1847
-
1848
-                if (!empty($options["ranges"])) {
1849
-                    // TODO multipart support is missing (see also above)
1850
-                    if (0 == fseek($stream, $options['ranges'][0]["start"], SEEK_SET)) {
1851
-                        $length = $options['ranges'][0]["end"] - $options['ranges'][0]["start"]+1;
1852
-
1853
-                        while (!feof($options['stream'])) {
1854
-                            if ($length <= 0) {
1855
-                               break;
1856
-                            }
1857
-
1858
-                            if ($length <= 8192) {
1859
-                                $data = fread($options['stream'], $length);
1860
-                            } else {
1861
-                                $data = fread($options['stream'], 8192);
1862
-                            }
1863
-
1864
-                            if ($data === false) {
1865
-                                $stat = "400 Bad request";
1866
-                            } elseif (strlen($data)) {
1867
-                                if (false === fwrite($stream, $data)) {
1868
-                                    $stat = "403 Forbidden";
1869
-                                    break;
1870
-                                }
1871
-                                $length -= strlen($data);
1872
-                            }
1873
-                        }
1874
-                    } else {
1875
-                        $stat = "403 Forbidden";
1876
-                    }
1877
-                } else {
1878
-                    while (!feof($options["stream"])) {
1879
-                        if (false === fwrite($stream, fread($options["stream"], 8192))) {
1880
-                            $stat = "403 Forbidden";
1881
-                            break;
1882
-                        }
1883
-                    }
1884
-                }
1885
-
1886
-                fclose($stream);
1887
-            }
1888
-
1889
-            $this->http_status($stat);
1890
-        } else {
1891
-            $this->http_status("423 Locked");
1892
-        }
1893
-    }
1894
-
1895
-    // }}}
1896
-
1897
-
1898
-    // {{{ http_DELETE()
2324
+	/**
2325
+	 * generate Unique Universal IDentifier for lock token
2326
+	 *
2327
+	 * @param  void
2328
+	 * @return string  a new UUID
2329
+	 */
2330
+	public static function _new_uuid()
2331
+	{
2332
+		// use uuid extension from PECL if available
2333
+		if (function_exists("uuid_create")) {
2334
+			return uuid_create();
2335
+		}
1899 2336
 
1900
-    /**
1901
-     * DELETE method handler
1902
-     *
1903
-     * @param  void
1904
-     * @return void
1905
-     */
1906
-    function http_DELETE()
1907
-    {
1908
-        // check RFC 2518 Section 9.2, last paragraph
1909
-        if (isset($this->_SERVER["HTTP_DEPTH"])) {
1910
-            if ($this->_SERVER["HTTP_DEPTH"] != "infinity") {
1911
-				if (stripos($_SERVER['HTTP_USER_AGENT'],'webdrive') !== false)
1912
-				{
1913
-					// pretend we didnt see it, as webdrive does not handle the depth parameter correctly while deleting collections
1914
-				}
1915
-				else
1916
-				{
1917
-                	$this->http_status("400 Bad Request");
1918
-                	return;
1919
-				}
1920
-            }
1921
-        }
2337
+		// fallback
2338
+		$uuid = md5(microtime().getmypid());    // this should be random enough for now
2339
+
2340
+		// set variant and version fields for 'true' random uuid
2341
+		$uuid{12} = "4";
2342
+		$n = 8 + (ord($uuid{16}) & 3);
2343
+		$hex = "0123456789abcdef";
2344
+		$uuid{16} = $hex{$n};
2345
+
2346
+		// return formated uuid
2347
+		return substr($uuid,  0, 8)."-"
2348
+			.  substr($uuid,  8, 4)."-"
2349
+			.  substr($uuid, 12, 4)."-"
2350
+			.  substr($uuid, 16, 4)."-"
2351
+			.  substr($uuid, 20);
2352
+	}
1922 2353
 
1923
-        // check lock status
1924
-        if ($this->_check_lock_status($this->path)) {
1925
-            // ok, proceed
1926
-            $options         = Array();
1927
-            $options["path"] = $this->path;
2354
+	/**
2355
+	 * create a new opaque lock token as defined in RFC2518
2356
+	 *
2357
+	 * @param  void
2358
+	 * @return string  new RFC2518 opaque lock token
2359
+	 */
2360
+	public static function _new_locktoken()
2361
+	{
2362
+		return "opaquelocktoken:".self::_new_uuid();
2363
+	}
1928 2364
 
1929
-            $stat = $this->DELETE($options);
2365
+	// }}}
1930 2366
 
1931
-            $this->http_status($stat);
1932
-        } else {
1933
-            // sorry, its locked
1934
-            $this->http_status("423 Locked");
1935
-        }
1936
-    }
2367
+	// {{{ WebDAV If: header parsing
1937 2368
 
1938
-    // }}}
2369
+	/**
2370
+	 *
2371
+	 *
2372
+	 * @param  string  header string to parse
2373
+	 * @param  int     current parsing position
2374
+	 * @return array   next token (type and value)
2375
+	 */
2376
+	function _if_header_lexer($string, &$pos)
2377
+	{
2378
+		// skip whitespace
2379
+		while (ctype_space($string{$pos})) {
2380
+			++$pos;
2381
+		}
1939 2382
 
1940
-    // {{{ http_COPY()
2383
+		// already at end of string?
2384
+		if (strlen($string) <= $pos) {
2385
+			return false;
2386
+		}
1941 2387
 
1942
-    /**
1943
-     * COPY method handler
1944
-     *
1945
-     * @param  void
1946
-     * @return void
1947
-     */
1948
-    function http_COPY()
1949
-    {
1950
-        // no need to check source lock status here
1951
-        // destination lock status is always checked by the helper method
1952
-        $this->_copymove("copy");
1953
-    }
1954
-
1955
-    // }}}
1956
-
1957
-    // {{{ http_MOVE()
2388
+		// get next character
2389
+		$c = $string{$pos++};
2390
+
2391
+		// now it depends on what we found
2392
+		switch ($c) {
2393
+		case "<":
2394
+			// URIs are enclosed in <...>
2395
+			$pos2 = strpos($string, ">", $pos);
2396
+			$uri  = substr($string, $pos, $pos2 - $pos);
2397
+			$pos  = $pos2 + 1;
2398
+			return array("URI", $uri);
2399
+
2400
+		case "[":
2401
+			//Etags are enclosed in [...]
2402
+			if ($string{$pos} == "W") {
2403
+				$type = "ETAG_WEAK";
2404
+				$pos += 2;
2405
+			} else {
2406
+				$type = "ETAG_STRONG";
2407
+			}
2408
+			$pos2 = strpos($string, "]", $pos);
2409
+			$etag = substr($string, $pos + 1, $pos2 - $pos - 2);
2410
+			$pos  = $pos2 + 1;
2411
+			return array($type, $etag);
2412
+
2413
+		case "N":
2414
+			// "N" indicates negation
2415
+			$pos += 2;
2416
+			return array("NOT", "Not");
2417
+
2418
+		default:
2419
+			// anything else is passed verbatim char by char
2420
+			return array("CHAR", $c);
2421
+		}
2422
+	}
1958 2423
 
1959
-    /**
1960
-     * MOVE method handler
1961
-     *
1962
-     * @param  void
1963
-     * @return void
1964
-     */
1965
-    function http_MOVE()
1966
-    {
1967
-        if ($this->_check_lock_status($this->path)) {
1968
-            // destination lock status is always checked by the helper method
1969
-            $this->_copymove("move");
1970
-        } else {
1971
-            $this->http_status("423 Locked");
1972
-        }
1973
-    }
1974
-
1975
-    // }}}
1976
-
1977
-
1978
-    // {{{ http_LOCK()
2424
+	/**
2425
+	 * parse If: header
2426
+	 *
2427
+	 * @param  string  header string
2428
+	 * @return array   URIs and their conditions
2429
+	 */
2430
+	function _if_header_parser($str)
2431
+	{
2432
+		$pos  = 0;
2433
+		$len  = strlen($str);
2434
+		$uris = array();
2435
+
2436
+		// parser loop
2437
+		while ($pos < $len) {
2438
+			// get next token
2439
+			$token = $this->_if_header_lexer($str, $pos);
2440
+
2441
+			// check for URI
2442
+			if ($token[0] == "URI") {
2443
+				$uri   = $token[1]; // remember URI
2444
+				$token = $this->_if_header_lexer($str, $pos); // get next token
2445
+			} else {
2446
+				$uri = "";
2447
+			}
1979 2448
 
1980
-    /**
1981
-     * LOCK method handler
1982
-     *
1983
-     * @param  void
1984
-     * @return void
1985
-     */
1986
-    function http_LOCK()
1987
-    {
1988
-        $options         = Array();
1989
-        $options["path"] = $this->path;
1990
-
1991
-        if (isset($this->_SERVER['HTTP_DEPTH'])) {
1992
-            $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
1993
-        } else {
1994
-            $options["depth"] = "infinity";
1995
-        }
1996
-
1997
-        if (isset($this->_SERVER["HTTP_TIMEOUT"])) {
1998
-            $options["timeout"] = explode(",", $this->_SERVER["HTTP_TIMEOUT"]);
1999
-        }
2000
-
2001
-        if (empty($this->_SERVER['CONTENT_LENGTH']) && !empty($this->_SERVER['HTTP_IF'])) {
2002
-            // check if locking is possible
2003
-            if (!$this->_check_lock_status($this->path)) {
2004
-                $this->http_status("423 Locked");
2005
-                return;
2006
-            }
2007
-
2008
-            // refresh lock
2009
-            $options["locktoken"] = substr($this->_SERVER['HTTP_IF'], 2, -2);
2010
-            $options["update"]    = $options["locktoken"];
2011
-
2012
-            // setting defaults for required fields, LOCK() SHOULD overwrite these
2013
-            $options['owner']     = "unknown";
2014
-            $options['scope']     = "exclusive";
2015
-            $options['type']      = "write";
2016
-
2017
-
2018
-            $stat = $this->LOCK($options);
2019
-        } else {
2020
-            // extract lock request information from request XML payload
2021
-            $lockinfo = new _parse_lockinfo("php://input");
2022
-            if (!$lockinfo->success) {
2023
-                $this->http_status("400 bad request");
2024
-            }
2025
-
2026
-            // check if locking is possible
2027
-            if (!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) {
2028
-                $this->http_status("423 Locked");
2029
-                return;
2030
-            }
2031
-
2032
-            // new lock
2033
-            $options["scope"]     = $lockinfo->lockscope;
2034
-            $options["type"]      = $lockinfo->locktype;
2035
-            // Todo: lockinfo::owner still contains D:href opening and closing tags, maybe they should be removed here with strip_tags
2036
-            $options["owner"]     = $lockinfo->owner;
2037
-            $options["locktoken"] = $this->_new_locktoken();
2038
-
2039
-            $stat = $this->LOCK($options);
2040
-        }
2041
-
2042
-        if (is_bool($stat)) {
2043
-            $http_stat = $stat ? "200 OK" : "423 Locked";
2044
-        } else {
2045
-            $http_stat = (string)$stat;
2046
-        }
2047
-        $this->http_status($http_stat);
2048
-
2049
-        if ($http_stat{0} == 2) { // 2xx states are ok
2050
-            if ($options["timeout"]) {
2051
-                // if multiple timeout values were given we take the first only
2052
-                if (is_array($options["timeout"])) {
2053
-                    reset($options["timeout"]);
2054
-                    $options["timeout"] = current($options["timeout"]);
2055
-                }
2056
-                // if the timeout is numeric only we need to reformat it
2057
-                if (is_numeric($options["timeout"])) {
2058
-                    // more than a million is considered an absolute timestamp
2059
-                    // less is more likely a relative value
2060
-                    if ($options["timeout"]>1000000) {
2061
-                        $timeout = "Second-".($options['timeout']-time());
2062
-                    } else {
2063
-                        $timeout = "Second-$options[timeout]";
2064
-                    }
2065
-                } else {
2066
-                    // non-numeric values are passed on verbatim,
2067
-                    // no error checking is performed here in this case
2068
-                    // TODO: send "Infinite" on invalid timeout strings?
2069
-                    $timeout = $options["timeout"];
2070
-                }
2071
-            } else {
2072
-                $timeout = "Infinite";
2073
-            }
2074
-
2075
-            header('Content-Type: text/xml; charset="utf-8"');
2076
-            header("Lock-Token: <$options[locktoken]>");
2077
-            echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2078
-            echo "<D:prop xmlns:D=\"DAV:\">\n";
2079
-            echo ' <'.($this->crrnd?'':'D:')."lockdiscovery>\n";
2080
-            echo '  <'.($this->crrnd?'':'D:')."activelock>\n";
2081
-            echo '   <'.($this->crrnd?'':'D:')."lockscope><D:$options[scope]/></".($this->crrnd?'':'D:')."lockscope>\n";
2082
-            echo '   <'.($this->crrnd?'':'D:')."locktype><D:$options[type]/></".($this->crrnd?'':'D:')."locktype>\n";
2083
-            echo '   <'.($this->crrnd?'':'D:')."depth>$options[depth]</".($this->crrnd?'':'D:')."depth>\n";
2084
-            echo '   <'.($this->crrnd?'':'D:')."owner>$options[owner]</".($this->crrnd?'':'D:')."owner>\n";
2085
-            echo '   <'.($this->crrnd?'':'D:')."timeout>$timeout</".($this->crrnd?'':'D:')."timeout>\n";
2086
-            echo '   <'.($this->crrnd?'':'D:')."locktoken><D:href>$options[locktoken]</D:href></".($this->crrnd?'':'D:')."locktoken>\n";
2087
-            echo '  </'.($this->crrnd?'':'D:')."activelock>\n";
2088
-            echo ' </'.($this->crrnd?'':'D:')."lockdiscovery>\n";
2089
-            echo '</'.($this->crrnd?'':'D:')."prop>\n\n";
2090
-        }
2091
-    }
2092
-
2093
-
2094
-    // }}}
2095
-
2096
-    // {{{ http_UNLOCK()
2449
+			// sanity check
2450
+			if ($token[0] != "CHAR" || $token[1] != "(") {
2451
+				return false;
2452
+			}
2097 2453
 
2098
-    /**
2099
-     * UNLOCK method handler
2100
-     *
2101
-     * @param  void
2102
-     * @return void
2103
-     */
2104
-    function http_UNLOCK()
2105
-    {
2106
-        $options         = Array();
2107
-        $options["path"] = $this->path;
2108
-
2109
-        if (isset($this->_SERVER['HTTP_DEPTH'])) {
2110
-            $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2111
-        } else {
2112
-            $options["depth"] = "infinity";
2113
-        }
2114
-
2115
-        // strip surrounding <>
2116
-        $options["token"] = substr(trim($this->_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
2117
-
2118
-        // call user method
2119
-        $stat = $this->UNLOCK($options);
2120
-
2121
-        $this->http_status($stat);
2122
-    }
2123
-
2124
-    // }}}
2125
-
2126
-    // {{{ http_ACL()
2127
-
2128
-	/**
2129
-     * ACL method handler
2130
-     *
2131
-     * @param  void
2132
-     * @return void
2133
-     */
2134
-    function http_ACL()
2135
-    {
2136
-        $options         = Array();
2137
-        $options['path'] = $this->path;
2138
-        $options['errors'] = array();
2139
-
2140
-        if (isset($this->_SERVER['HTTP_DEPTH'])) {
2141
-            $options['depth'] = $this->_SERVER['HTTP_DEPTH'];
2142
-        } else {
2143
-            $options['depth'] = 'infinity';
2144
-        }
2145
-
2146
-        // call user method
2147
-        $status = $this->ACL($options);
2454
+			$list  = array();
2455
+			$level = 1;
2456
+			$not   = "";
2457
+			while ($level) {
2458
+				$token = $this->_if_header_lexer($str, $pos);
2459
+				if ($token[0] == "NOT") {
2460
+					$not = "!";
2461
+					continue;
2462
+				}
2463
+				switch ($token[0]) {
2464
+				case "CHAR":
2465
+					switch ($token[1]) {
2466
+					case "(":
2467
+						$level++;
2468
+						break;
2469
+					case ")":
2470
+						$level--;
2471
+						break;
2472
+					default:
2473
+						return false;
2474
+					}
2475
+					break;
2148 2476
 
2149
-		// now we generate the reply header ...
2150
-		$this->http_status($status);
2151
-		$content = '';
2477
+				case "URI":
2478
+					$list[] = $not."<$token[1]>";
2479
+					break;
2152 2480
 
2153
-        if (is_array($options['errors']) && count($options['errors'])) {
2154
-	        header('Content-Type: text/xml; charset="utf-8"');
2155
-	        // ... and payload
2156
-	        $content .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2157
-	        $content .= "<D:error xmlns:D=\"DAV:\"> \n";
2158
-	        foreach ($options['errors'] as $violation) {
2159
-	        	$content .= '<'.($this->crrnd?'':'D:')."$violation/>\n";
2160
-	        }
2161
-	        $content .=  '</'.($this->crrnd?'':'D:')."error>\n";
2162
-        }
2163
-        if (!self::use_compression()) header("Content-Length: ".self::bytes($content));
2164
-        if ($content) echo $options['content'];
2165
-    }
2166
-
2167
-    // }}}
2168
-
2169
-    // }}}
2170
-
2171
-    // {{{ _copymove()
2172
-
2173
-    function _copymove($what)
2174
-    {
2175
-        $options         = Array();
2176
-        $options["path"] = $this->path;
2177
-
2178
-        if (isset($this->_SERVER["HTTP_DEPTH"])) {
2179
-            $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2180
-        } else {
2181
-            $options["depth"] = "infinity";
2182
-        }
2183
-
2184
-        $http_header_host = preg_replace("/:80$/", "", $this->_SERVER["HTTP_HOST"]);
2185
-
2186
-        $url  = parse_url($this->_SERVER["HTTP_DESTINATION"]);
2187
-        $path = urldecode($url["path"]);
2188
-
2189
-        if (isset($url["host"])) {
2190
-            // TODO check url scheme, too
2191
-            $http_host = $url["host"];
2192
-            if (isset($url["port"]) && $url["port"] != 80)
2193
-                $http_host.= ":".$url["port"];
2194
-        } else {
2195
-            // only path given, set host to self
2196
-            $http_host = $http_header_host;
2197
-        }
2198
-
2199
-        if ($http_host == $http_header_host &&
2200
-            !strncmp($this->_SERVER["SCRIPT_NAME"], $path,
2201
-                     strlen($this->_SERVER["SCRIPT_NAME"]))) {
2202
-            $options["dest"] = substr($path, strlen($this->_SERVER["SCRIPT_NAME"]));
2203
-            if (!$this->_check_lock_status($options["dest"])) {
2204
-                $this->http_status("423 Locked");
2205
-                return;
2206
-            }
2207
-
2208
-        } else {
2209
-            $options["dest_url"] = $this->_SERVER["HTTP_DESTINATION"];
2210
-        }
2211
-
2212
-        // see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3
2213
-        if (isset($this->_SERVER["HTTP_OVERWRITE"])) {
2214
-            $options["overwrite"] = $this->_SERVER["HTTP_OVERWRITE"] == "T";
2215
-        } else {
2216
-            $options["overwrite"] = true;
2217
-        }
2218
-
2219
-        $stat = $this->$what($options);
2220
-        $this->http_status($stat);
2221
-    }
2222
-
2223
-    // }}}
2224
-
2225
-    // {{{ _allow()
2481
+				case "ETAG_WEAK":
2482
+					$list[] = $not."[W/'$token[1]']>";
2483
+					break;
2226 2484
 
2227
-    /**
2228
-     * check for implemented HTTP methods
2229
-     *
2230
-     * @param  void
2231
-     * @return array something
2232
-     */
2233
-    function _allow()
2234
-    {
2235
-        // OPTIONS is always there
2236
-        $allow = array("OPTIONS" =>"OPTIONS");
2237
-
2238
-        // all other METHODS need both a http_method() wrapper
2239
-        // and a method() implementation
2240
-        // the base class supplies wrappers only
2241
-        foreach (get_class_methods($this) as $method) {
2242
-            if (!strncmp("http_", $method, 5)) {
2243
-                $method = strtoupper(substr($method, 5));
2244
-                if (method_exists($this, $method)) {
2245
-                    $allow[$method] = $method;
2246
-                }
2247
-            }
2248
-        }
2249
-
2250
-        // we can emulate a missing HEAD implemetation using GET
2251
-        if (isset($allow["GET"]))
2252
-            $allow["HEAD"] = "HEAD";
2253
-
2254
-        // no LOCK without checklok()
2255
-        if (!method_exists($this, "checklock")) {
2256
-            unset($allow["LOCK"]);
2257
-            unset($allow["UNLOCK"]);
2258
-        }
2259
-
2260
-        return $allow;
2261
-    }
2262
-
2263
-    // }}}
2485
+				case "ETAG_STRONG":
2486
+					$list[] = $not."['$token[1]']>";
2487
+					break;
2264 2488
 
2265
-    /**
2266
-     * helper for property element creation
2267
-     *
2268
-     * @param  string  XML namespace (optional)
2269
-     * @param  string  property name
2270
-     * @param  string  property value
2271
-     * @praram boolen  property raw-flag
2272
-     * @return array   property array
2273
-     */
2274
-    public static function mkprop()
2275
-    {
2276
-	    $args = func_get_args();
2277
-	    switch (count($args)) {
2278
-		    case 4:
2279
-			    return array('ns'   => $args[0],
2280
-				    'name' => $args[1],
2281
-					'val'  => $args[2],
2282
-					'raw'	=> true);
2283
-		    case 3:
2284
-			    return array('ns'   => $args[0],
2285
-				    'name' => $args[1],
2286
-					'val'  => $args[2]);
2287
-		    default:
2288
-			    return array('ns'   => 'DAV:',
2289
-				    'name' => $args[0],
2290
-					'val'  => $args[1]);
2291
-	    }
2292
-    }
2489
+				default:
2490
+					return false;
2491
+				}
2492
+				$not = "";
2493
+			}
2293 2494
 
2294
-    // {{{ _check_auth
2495
+			if (@is_array($uris[$uri])) {
2496
+				$uris[$uri] = array_merge($uris[$uri], $list);
2497
+			} else {
2498
+				$uris[$uri] = $list;
2499
+			}
2500
+		}
2295 2501
 
2296
-    /**
2297
-     * check authentication if check is implemented
2298
-     *
2299
-     * @param  void
2300
-     * @return bool  true if authentication succeded or not necessary
2301
-     */
2302
-    function _check_auth()
2303
-    {
2304
-        if (method_exists($this, "checkAuth")) {
2305
-            // PEAR style method name
2306
-            return $this->checkAuth(@$this->_SERVER["AUTH_TYPE"],
2307
-                                    @$this->_SERVER["PHP_AUTH_USER"],
2308
-                                    @$this->_SERVER["PHP_AUTH_PW"]);
2309
-        } else if (method_exists($this, "check_auth")) {
2310
-            // old (pre 1.0) method name
2311
-            return $this->check_auth(@$this->_SERVER["AUTH_TYPE"],
2312
-                                     @$this->_SERVER["PHP_AUTH_USER"],
2313
-                                     @$this->_SERVER["PHP_AUTH_PW"]);
2314
-        } else {
2315
-            // no method found -> no authentication required
2316
-            return true;
2317
-        }
2318
-    }
2319
-
2320
-    // }}}
2321
-
2322
-    // {{{ UUID stuff
2502
+		return $uris;
2503
+	}
2323 2504
 
2324
-    /**
2325
-     * generate Unique Universal IDentifier for lock token
2326
-     *
2327
-     * @param  void
2328
-     * @return string  a new UUID
2329
-     */
2330
-    public static function _new_uuid()
2331
-    {
2332
-        // use uuid extension from PECL if available
2333
-        if (function_exists("uuid_create")) {
2334
-            return uuid_create();
2335
-        }
2336
-
2337
-        // fallback
2338
-        $uuid = md5(microtime().getmypid());    // this should be random enough for now
2339
-
2340
-        // set variant and version fields for 'true' random uuid
2341
-        $uuid{12} = "4";
2342
-        $n = 8 + (ord($uuid{16}) & 3);
2343
-        $hex = "0123456789abcdef";
2344
-        $uuid{16} = $hex{$n};
2345
-
2346
-        // return formated uuid
2347
-        return substr($uuid,  0, 8)."-"
2348
-            .  substr($uuid,  8, 4)."-"
2349
-            .  substr($uuid, 12, 4)."-"
2350
-            .  substr($uuid, 16, 4)."-"
2351
-            .  substr($uuid, 20);
2352
-    }
2505
+	/**
2506
+	 * check if conditions from "If:" headers are meat
2507
+	 *
2508
+	 * the "If:" header is an extension to HTTP/1.1
2509
+	 * defined in RFC 2518 section 9.4
2510
+	 *
2511
+	 * @param  void
2512
+	 * @return void
2513
+	 */
2514
+	function _check_if_header_conditions()
2515
+	{
2516
+		if (isset($this->_SERVER["HTTP_IF"])) {
2517
+			$this->_if_header_uris =
2518
+				$this->_if_header_parser($this->_SERVER["HTTP_IF"]);
2353 2519
 
2354
-    /**
2355
-     * create a new opaque lock token as defined in RFC2518
2356
-     *
2357
-     * @param  void
2358
-     * @return string  new RFC2518 opaque lock token
2359
-     */
2360
-    public static function _new_locktoken()
2361
-    {
2362
-        return "opaquelocktoken:".self::_new_uuid();
2363
-    }
2520
+			foreach ($this->_if_header_uris as $uri => $conditions) {
2521
+				if ($uri == "") {
2522
+					$uri = $this->uri;
2523
+				}
2524
+				// all must match
2525
+				$state = true;
2526
+				foreach ($conditions as $condition) {
2527
+					// lock tokens may be free form (RFC2518 6.3)
2528
+					// but if opaquelocktokens are used (RFC2518 6.4)
2529
+					// we have to check the format (litmus tests this)
2530
+					if (!strncmp($condition, "<opaquelocktoken:", strlen("<opaquelocktoken"))) {
2531
+						if (!preg_match('/^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$/', $condition)) {
2532
+							$this->http_status("423 Locked");
2533
+							return false;
2534
+						}
2535
+					}
2536
+					if (!$this->_check_uri_condition($uri, $condition)) {
2537
+						$this->http_status("412 Precondition failed");
2538
+						$state = false;
2539
+						break;
2540
+					}
2541
+				}
2364 2542
 
2365
-    // }}}
2543
+				// any match is ok
2544
+				if ($state == true) {
2545
+					return true;
2546
+				}
2547
+			}
2548
+			return false;
2549
+		}
2550
+		return true;
2551
+	}
2366 2552
 
2367
-    // {{{ WebDAV If: header parsing
2553
+	/**
2554
+	 * Check a single URI condition parsed from an if-header
2555
+	 *
2556
+	 * Check a single URI condition parsed from an if-header
2557
+	 *
2558
+	 * @abstract
2559
+	 * @param string $uri URI to check
2560
+	 * @param string $condition Condition to check for this URI
2561
+	 * @returns bool Condition check result
2562
+	 */
2563
+	function _check_uri_condition($uri, $condition)
2564
+	{
2565
+		unset($uri);	// not used, but required by function signature
2566
+		// not really implemented here,
2567
+		// implementations must override
2368 2568
 
2369
-    /**
2370
-     *
2371
-     *
2372
-     * @param  string  header string to parse
2373
-     * @param  int     current parsing position
2374
-     * @return array   next token (type and value)
2375
-     */
2376
-    function _if_header_lexer($string, &$pos)
2377
-    {
2378
-        // skip whitespace
2379
-        while (ctype_space($string{$pos})) {
2380
-            ++$pos;
2381
-        }
2382
-
2383
-        // already at end of string?
2384
-        if (strlen($string) <= $pos) {
2385
-            return false;
2386
-        }
2387
-
2388
-        // get next character
2389
-        $c = $string{$pos++};
2390
-
2391
-        // now it depends on what we found
2392
-        switch ($c) {
2393
-        case "<":
2394
-            // URIs are enclosed in <...>
2395
-            $pos2 = strpos($string, ">", $pos);
2396
-            $uri  = substr($string, $pos, $pos2 - $pos);
2397
-            $pos  = $pos2 + 1;
2398
-            return array("URI", $uri);
2399
-
2400
-        case "[":
2401
-            //Etags are enclosed in [...]
2402
-            if ($string{$pos} == "W") {
2403
-                $type = "ETAG_WEAK";
2404
-                $pos += 2;
2405
-            } else {
2406
-                $type = "ETAG_STRONG";
2407
-            }
2408
-            $pos2 = strpos($string, "]", $pos);
2409
-            $etag = substr($string, $pos + 1, $pos2 - $pos - 2);
2410
-            $pos  = $pos2 + 1;
2411
-            return array($type, $etag);
2412
-
2413
-        case "N":
2414
-            // "N" indicates negation
2415
-            $pos += 2;
2416
-            return array("NOT", "Not");
2417
-
2418
-        default:
2419
-            // anything else is passed verbatim char by char
2420
-            return array("CHAR", $c);
2421
-        }
2422
-    }
2569
+		// a lock token can never be from the DAV: scheme
2570
+		// litmus uses DAV:no-lock in some tests
2571
+		if (!strncmp("<DAV:", $condition, 5)) {
2572
+			return false;
2573
+		}
2423 2574
 
2424
-    /**
2425
-     * parse If: header
2426
-     *
2427
-     * @param  string  header string
2428
-     * @return array   URIs and their conditions
2429
-     */
2430
-    function _if_header_parser($str)
2431
-    {
2432
-        $pos  = 0;
2433
-        $len  = strlen($str);
2434
-        $uris = array();
2435
-
2436
-        // parser loop
2437
-        while ($pos < $len) {
2438
-            // get next token
2439
-            $token = $this->_if_header_lexer($str, $pos);
2440
-
2441
-            // check for URI
2442
-            if ($token[0] == "URI") {
2443
-                $uri   = $token[1]; // remember URI
2444
-                $token = $this->_if_header_lexer($str, $pos); // get next token
2445
-            } else {
2446
-                $uri = "";
2447
-            }
2448
-
2449
-            // sanity check
2450
-            if ($token[0] != "CHAR" || $token[1] != "(") {
2451
-                return false;
2452
-            }
2453
-
2454
-            $list  = array();
2455
-            $level = 1;
2456
-            $not   = "";
2457
-            while ($level) {
2458
-                $token = $this->_if_header_lexer($str, $pos);
2459
-                if ($token[0] == "NOT") {
2460
-                    $not = "!";
2461
-                    continue;
2462
-                }
2463
-                switch ($token[0]) {
2464
-                case "CHAR":
2465
-                    switch ($token[1]) {
2466
-                    case "(":
2467
-                        $level++;
2468
-                        break;
2469
-                    case ")":
2470
-                        $level--;
2471
-                        break;
2472
-                    default:
2473
-                        return false;
2474
-                    }
2475
-                    break;
2476
-
2477
-                case "URI":
2478
-                    $list[] = $not."<$token[1]>";
2479
-                    break;
2480
-
2481
-                case "ETAG_WEAK":
2482
-                    $list[] = $not."[W/'$token[1]']>";
2483
-                    break;
2484
-
2485
-                case "ETAG_STRONG":
2486
-                    $list[] = $not."['$token[1]']>";
2487
-                    break;
2488
-
2489
-                default:
2490
-                    return false;
2491
-                }
2492
-                $not = "";
2493
-            }
2494
-
2495
-            if (@is_array($uris[$uri])) {
2496
-                $uris[$uri] = array_merge($uris[$uri], $list);
2497
-            } else {
2498
-                $uris[$uri] = $list;
2499
-            }
2500
-        }
2501
-
2502
-        return $uris;
2503
-    }
2575
+		return true;
2576
+	}
2504 2577
 
2505
-    /**
2506
-     * check if conditions from "If:" headers are meat
2507
-     *
2508
-     * the "If:" header is an extension to HTTP/1.1
2509
-     * defined in RFC 2518 section 9.4
2510
-     *
2511
-     * @param  void
2512
-     * @return void
2513
-     */
2514
-    function _check_if_header_conditions()
2515
-    {
2516
-        if (isset($this->_SERVER["HTTP_IF"])) {
2517
-            $this->_if_header_uris =
2518
-                $this->_if_header_parser($this->_SERVER["HTTP_IF"]);
2519
-
2520
-            foreach ($this->_if_header_uris as $uri => $conditions) {
2521
-                if ($uri == "") {
2522
-                    $uri = $this->uri;
2523
-                }
2524
-                // all must match
2525
-                $state = true;
2526
-                foreach ($conditions as $condition) {
2527
-                    // lock tokens may be free form (RFC2518 6.3)
2528
-                    // but if opaquelocktokens are used (RFC2518 6.4)
2529
-                    // we have to check the format (litmus tests this)
2530
-                    if (!strncmp($condition, "<opaquelocktoken:", strlen("<opaquelocktoken"))) {
2531
-                        if (!preg_match('/^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$/', $condition)) {
2532
-                            $this->http_status("423 Locked");
2533
-                            return false;
2534
-                        }
2535
-                    }
2536
-                    if (!$this->_check_uri_condition($uri, $condition)) {
2537
-                        $this->http_status("412 Precondition failed");
2538
-                        $state = false;
2539
-                        break;
2540
-                    }
2541
-                }
2542
-
2543
-                // any match is ok
2544
-                if ($state == true) {
2545
-                    return true;
2546
-                }
2547
-            }
2548
-            return false;
2549
-        }
2550
-        return true;
2551
-    }
2552 2578
 
2553
-    /**
2554
-     * Check a single URI condition parsed from an if-header
2555
-     *
2556
-     * Check a single URI condition parsed from an if-header
2557
-     *
2558
-     * @abstract
2559
-     * @param string $uri URI to check
2560
-     * @param string $condition Condition to check for this URI
2561
-     * @returns bool Condition check result
2562
-     */
2563
-    function _check_uri_condition($uri, $condition)
2564
-    {
2565
-		unset($uri);	// not used, but required by function signature
2566
-        // not really implemented here,
2567
-        // implementations must override
2579
+	/**
2580
+	 *
2581
+	 *
2582
+	 * @param  string  path of resource to check
2583
+	 * @param  bool    exclusive lock?
2584
+	 */
2585
+	function _check_lock_status($path, $exclusive_only = false)
2586
+	{
2587
+		// FIXME depth -> ignored for now
2588
+		if (method_exists($this, "checkLock")) {
2589
+			// is locked?
2590
+			$lock = $this->checkLock($path);
2591
+
2592
+			// ... and lock is not owned?
2593
+			if (is_array($lock) && count($lock)) {
2594
+				// FIXME doesn't check uri restrictions yet
2595
+				if (!isset($this->_SERVER["HTTP_IF"]) || !strstr($this->_SERVER["HTTP_IF"], $lock["token"])) {
2596
+					if (!$exclusive_only || ($lock["scope"] !== "shared"))
2597
+						return false;
2598
+				}
2599
+			}
2600
+		}
2601
+		return true;
2602
+	}
2568 2603
 
2569
-        // a lock token can never be from the DAV: scheme
2570
-        // litmus uses DAV:no-lock in some tests
2571
-        if (!strncmp("<DAV:", $condition, 5)) {
2572
-            return false;
2573
-        }
2574 2604
 
2575
-        return true;
2576
-    }
2605
+	// }}}
2577 2606
 
2578 2607
 
2579
-    /**
2580
-     *
2581
-     *
2582
-     * @param  string  path of resource to check
2583
-     * @param  bool    exclusive lock?
2584
-     */
2585
-    function _check_lock_status($path, $exclusive_only = false)
2586
-    {
2587
-        // FIXME depth -> ignored for now
2588
-        if (method_exists($this, "checkLock")) {
2589
-            // is locked?
2590
-            $lock = $this->checkLock($path);
2591
-
2592
-            // ... and lock is not owned?
2593
-            if (is_array($lock) && count($lock)) {
2594
-                // FIXME doesn't check uri restrictions yet
2595
-                if (!isset($this->_SERVER["HTTP_IF"]) || !strstr($this->_SERVER["HTTP_IF"], $lock["token"])) {
2596
-                    if (!$exclusive_only || ($lock["scope"] !== "shared"))
2597
-                        return false;
2598
-                }
2599
-            }
2600
-        }
2601
-        return true;
2602
-    }
2603
-
2604
-
2605
-    // }}}
2608
+	/**
2609
+	 * Generate lockdiscovery reply from checklock() result
2610
+	 *
2611
+	 * @param   string  resource path to check
2612
+	 * @return  string  lockdiscovery response
2613
+	 */
2614
+	function lockdiscovery($path)
2615
+	{
2616
+		// no lock support without checklock() method
2617
+		if (!method_exists($this, "checklock")) {
2618
+			return "";
2619
+		}
2606 2620
 
2621
+		// collect response here
2622
+		$activelocks = "";
2623
+
2624
+		// get checklock() reply
2625
+		$lock = $this->checklock($path);
2626
+
2627
+		// generate <activelock> block for returned data
2628
+		if (is_array($lock) && count($lock)) {
2629
+			// check for 'timeout' or 'expires'
2630
+			if (!empty($lock["expires"])) {
2631
+				$timeout = "Second-".($lock["expires"] - time());
2632
+			} else if (!empty($lock["timeout"])) {
2633
+				$timeout = "Second-$lock[timeout]";
2634
+			} else {
2635
+				$timeout = "Infinite";
2636
+			}
2607 2637
 
2608
-    /**
2609
-     * Generate lockdiscovery reply from checklock() result
2610
-     *
2611
-     * @param   string  resource path to check
2612
-     * @return  string  lockdiscovery response
2613
-     */
2614
-    function lockdiscovery($path)
2615
-    {
2616
-        // no lock support without checklock() method
2617
-        if (!method_exists($this, "checklock")) {
2618
-            return "";
2619
-        }
2620
-
2621
-        // collect response here
2622
-        $activelocks = "";
2623
-
2624
-        // get checklock() reply
2625
-        $lock = $this->checklock($path);
2626
-
2627
-        // generate <activelock> block for returned data
2628
-        if (is_array($lock) && count($lock)) {
2629
-            // check for 'timeout' or 'expires'
2630
-            if (!empty($lock["expires"])) {
2631
-                $timeout = "Second-".($lock["expires"] - time());
2632
-            } else if (!empty($lock["timeout"])) {
2633
-                $timeout = "Second-$lock[timeout]";
2634
-            } else {
2635
-                $timeout = "Infinite";
2636
-            }
2637
-
2638
-            // genreate response block
2639
-            if ($this->crrnd)
2640
-            {
2641
-	            $activelocks.= "
2638
+			// genreate response block
2639
+			if ($this->crrnd)
2640
+			{
2641
+				$activelocks.= "
2642 2642
 		            <activelock>
2643 2643
 		            <lockscope><$lock[scope]/></lockscope>
2644 2644
 		            <locktype><$lock[type]/></locktype>
@@ -2648,10 +2648,10 @@  discard block
 block discarded – undo
2648 2648
 		            <locktoken><href>$lock[token]</href></locktoken>
2649 2649
 		            </activelock>
2650 2650
 		            ";
2651
-            }
2652
-            else
2653
-            {
2654
-	            $activelocks.= "
2651
+			}
2652
+			else
2653
+			{
2654
+				$activelocks.= "
2655 2655
 		            <D:activelock>
2656 2656
 		            <D:lockscope><D:$lock[scope]/></D:lockscope>
2657 2657
 		            <D:locktype><D:$lock[type]/></D:locktype>
@@ -2661,47 +2661,47 @@  discard block
 block discarded – undo
2661 2661
 		            <D:locktoken><D:href>$lock[token]</D:href></D:locktoken>
2662 2662
 		            </D:activelock>
2663 2663
 		            ";
2664
-            }
2665
-        }
2664
+			}
2665
+		}
2666 2666
 
2667
-        // return generated response
2668
-        //error_log(__METHOD__."\n".print_r($activelocks,true));
2667
+		// return generated response
2668
+		//error_log(__METHOD__."\n".print_r($activelocks,true));
2669 2669
 		return $activelocks;
2670
-    }
2670
+	}
2671 2671
 
2672
-    /**
2673
-     * set HTTP return status and mirror it in a private header
2674
-     *
2675
-     * @param  string  status code and message
2676
-     * @return void
2677
-     */
2678
-    function http_status($status)
2679
-    {
2680
-        // simplified success case
2681
-        if ($status === true) {
2682
-            $status = "200 OK";
2683
-        }
2684
-
2685
-        // remember status
2686
-        $this->_http_status = $status;
2687
-
2688
-        // generate HTTP status response
2689
-        header("HTTP/1.1 $status");
2690
-        header("X-WebDAV-Status: $status", true);
2691
-    }
2672
+	/**
2673
+	 * set HTTP return status and mirror it in a private header
2674
+	 *
2675
+	 * @param  string  status code and message
2676
+	 * @return void
2677
+	 */
2678
+	function http_status($status)
2679
+	{
2680
+		// simplified success case
2681
+		if ($status === true) {
2682
+			$status = "200 OK";
2683
+		}
2692 2684
 
2693
-    /**
2694
-     * private minimalistic version of PHP urlencode()
2695
-     *
2696
-     * only blanks, percent and XML special chars must be encoded here
2697
-     * full urlencode() encoding confuses some clients ...
2698
-     *
2699
-     * @param  string  URL to encode
2700
-     * @return string  encoded URL
2701
-     * @todo check if other not explicitly named user-agents are happy with full url-encoding too and we can make it the default
2702
-     */
2703
-    public static function _urlencode($url)
2704
-    {
2685
+		// remember status
2686
+		$this->_http_status = $status;
2687
+
2688
+		// generate HTTP status response
2689
+		header("HTTP/1.1 $status");
2690
+		header("X-WebDAV-Status: $status", true);
2691
+	}
2692
+
2693
+	/**
2694
+	 * private minimalistic version of PHP urlencode()
2695
+	 *
2696
+	 * only blanks, percent and XML special chars must be encoded here
2697
+	 * full urlencode() encoding confuses some clients ...
2698
+	 *
2699
+	 * @param  string  URL to encode
2700
+	 * @return string  encoded URL
2701
+	 * @todo check if other not explicitly named user-agents are happy with full url-encoding too and we can make it the default
2702
+	 */
2703
+	public static function _urlencode($url)
2704
+	{
2705 2705
 		// cadaver (and probably all neon using agents) need a more complete url encoding
2706 2706
 		// otherwise special chars like "$,()'" in filenames do NOT work
2707 2707
 		if (strpos($_SERVER['HTTP_USER_AGENT'],'neon') !== false ||
@@ -2719,31 +2719,31 @@  discard block
 block discarded – undo
2719 2719
 		}
2720 2720
 		//error_log( __METHOD__."\n" .print_r($url,true));
2721 2721
 		return strtr($url, array(' '	=>	'%20',
2722
-                                 '%'	=>	'%25',
2723
-                                 '&'	=>	'%26',
2724
-                                 '<'	=>	'%3C',
2725
-                                 '>'	=>	'%3E',
2726
-                                 '+'	=>	'%2B',
2727
-                                 ));
2728
-    }
2722
+								 '%'	=>	'%25',
2723
+								 '&'	=>	'%26',
2724
+								 '<'	=>	'%3C',
2725
+								 '>'	=>	'%3E',
2726
+								 '+'	=>	'%2B',
2727
+								 ));
2728
+	}
2729 2729
 
2730
-    /**
2731
-     * private version of PHP urldecode
2732
-     *
2733
-     * not really needed but added for completenes
2734
-     *
2735
-     * @param  string  URL to decode
2736
-     * @return string  decoded URL
2737
-     */
2738
-    public static function _urldecode($path)
2739
-    {
2740
-        return rawurldecode($path);
2741
-    }
2730
+	/**
2731
+	 * private version of PHP urldecode
2732
+	 *
2733
+	 * not really needed but added for completenes
2734
+	 *
2735
+	 * @param  string  URL to decode
2736
+	 * @return string  decoded URL
2737
+	 */
2738
+	public static function _urldecode($path)
2739
+	{
2740
+		return rawurldecode($path);
2741
+	}
2742 2742
 
2743
-    /**
2744
-     * Encode a hierarchical properties like:
2745
-     *
2746
- 	 * <D:supported-report-set>
2743
+	/**
2744
+	 * Encode a hierarchical properties like:
2745
+	 *
2746
+	 * <D:supported-report-set>
2747 2747
 	 *    <supported-report>
2748 2748
 	 *       <report>
2749 2749
 	 *          <addressbook-query xmlns='urn:ietf:params:xml:ns:carddav'/>
@@ -2755,60 +2755,60 @@  discard block
 block discarded – undo
2755 2755
 	 *       </report>
2756 2756
 	 *    </supported-report>
2757 2757
 	 * </D:supported-report-set>
2758
-     *
2759
-     * @param array $props
2760
-     * @param string $ns
2761
-     * @param strin $ns_defs
2762
-     * @param array $ns_hash
2763
-     * @return string
2764
-     */
2758
+	 *
2759
+	 * @param array $props
2760
+	 * @param string $ns
2761
+	 * @param strin $ns_defs
2762
+	 * @param array $ns_hash
2763
+	 * @return string
2764
+	 */
2765 2765
 	function _hierarchical_prop_encode(array $props, $ns, &$ns_defs, array &$ns_hash)
2766
-    {
2767
-    	$ret = '';
2766
+	{
2767
+		$ret = '';
2768 2768
 
2769
-    	//error_log(__METHOD__.'('.array2string($props).')');
2770
-    	if (isset($props['name'])) $props = array($props);
2769
+		//error_log(__METHOD__.'('.array2string($props).')');
2770
+		if (isset($props['name'])) $props = array($props);
2771 2771
 
2772
-    	foreach($props as $prop)
2772
+		foreach($props as $prop)
2773 2773
 		{
2774
-	    	if (!isset($ns_hash[$prop['ns']])) // unknown namespace
2775
-	    	{
2776
-		    	// register namespace
2777
-		    	$ns_name = 'ns'.(count($ns_hash) + 1);
2778
-		    	$ns_hash[$prop['ns']] = $ns_name;
2779
-		    	$ns_defs .= ' xmlns:'.$ns_name.'="'.$prop['ns'].'"';
2780
-	    	}
2781
-	    	if (is_array($prop['val']))
2782
-	    	{
2783
-	    		$subprop = $prop['val'];
2784
-		    	if (isset($subprop['ns']) || isset($subprop[0]['ns']))
2785
-		    	{
2786
-			    	$ret .= '<'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : $ns_hash[$prop['ns']].':').$prop['name'].
2774
+			if (!isset($ns_hash[$prop['ns']])) // unknown namespace
2775
+			{
2776
+				// register namespace
2777
+				$ns_name = 'ns'.(count($ns_hash) + 1);
2778
+				$ns_hash[$prop['ns']] = $ns_name;
2779
+				$ns_defs .= ' xmlns:'.$ns_name.'="'.$prop['ns'].'"';
2780
+			}
2781
+			if (is_array($prop['val']))
2782
+			{
2783
+				$subprop = $prop['val'];
2784
+				if (isset($subprop['ns']) || isset($subprop[0]['ns']))
2785
+				{
2786
+					$ret .= '<'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : $ns_hash[$prop['ns']].':').$prop['name'].
2787 2787
 						(empty($prop['val']) ? '/>' : '>'.$this->_hierarchical_prop_encode($prop['val'], $prop['ns'], $ns_defs, $ns_hash).
2788 2788
 						'</'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : ($this->crrnd ? '' : $ns_hash[$prop['ns']].':')).$prop['name'].'>');
2789
-		    	}
2790
-		    	else // val contains only attributes, no value
2791
-		    	{
2792
-			    	$vals = '';
2789
+				}
2790
+				else // val contains only attributes, no value
2791
+				{
2792
+					$vals = '';
2793 2793
 
2794
-			    	foreach($subprop as $attr => $val)
2794
+					foreach($subprop as $attr => $val)
2795 2795
 					{
2796
-				    	$vals .= ' '.$attr.'="'.htmlspecialchars($val, ENT_NOQUOTES, 'utf-8').'"';
2796
+						$vals .= ' '.$attr.'="'.htmlspecialchars($val, ENT_NOQUOTES, 'utf-8').'"';
2797 2797
 					}
2798 2798
 
2799
-		             $ret .= '<'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : $ns_hash[$prop['ns']].':').$prop['name'].
2800
-				    	$vals .'/>';
2801
-		    	}
2802
-	    	}
2803
-	    	else
2804
-	    	{
2805
-		    	if (empty($prop['val']))
2806
-		    	{
2807
-			    	$val = '';
2808
-		    	}
2809
-		    	else
2810
-		    	{
2811
-			    	if(isset($prop['raw']))
2799
+					 $ret .= '<'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : $ns_hash[$prop['ns']].':').$prop['name'].
2800
+						$vals .'/>';
2801
+				}
2802
+			}
2803
+			else
2804
+			{
2805
+				if (empty($prop['val']))
2806
+				{
2807
+					$val = '';
2808
+				}
2809
+				else
2810
+				{
2811
+					if(isset($prop['raw']))
2812 2812
 					{
2813 2813
 						$val = $this->_prop_encode('<![CDATA['.$prop['val'].']]>');
2814 2814
 					} else {
@@ -2819,32 +2819,32 @@  discard block
 block discarded – undo
2819 2819
 							$val = $this->_urlencode($val);
2820 2820
 						}
2821 2821
 					}
2822
-		    	}
2822
+				}
2823 2823
 
2824
-		    	$ret .= '<'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : $ns_hash[$prop['ns']].':').$prop['name'].
2825
-			    	(empty($prop['val']) ? ' />' : '>'.$val.'</'.
2826
-			    	($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : ($this->crrnd ? '' : $ns_hash[$prop['ns']].':')).$prop['name'].'>');
2827
-	    	}
2824
+				$ret .= '<'.($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : $ns_hash[$prop['ns']].':').$prop['name'].
2825
+					(empty($prop['val']) ? ' />' : '>'.$val.'</'.
2826
+					($prop['ns'] == $ns ? ($this->crrnd ? '' : $ns_hash[$ns].':') : ($this->crrnd ? '' : $ns_hash[$prop['ns']].':')).$prop['name'].'>');
2827
+			}
2828 2828
 		}
2829 2829
 
2830
-    	//error_log(__METHOD__.'('.array2string($props).") crrnd=$this->crrnd returning ".array2string($ret));
2831
-    	return $ret;
2832
-    }
2830
+		//error_log(__METHOD__.'('.array2string($props).") crrnd=$this->crrnd returning ".array2string($ret));
2831
+		return $ret;
2832
+	}
2833 2833
 
2834
-    /**
2835
-     * UTF-8 encode property values if not already done so
2836
-     *
2837
-     * @param  string  text to encode
2838
-     * @return string  utf-8 encoded text
2839
-     */
2840
-    function _prop_encode($text)
2841
-    {
2834
+	/**
2835
+	 * UTF-8 encode property values if not already done so
2836
+	 *
2837
+	 * @param  string  text to encode
2838
+	 * @return string  utf-8 encoded text
2839
+	 */
2840
+	function _prop_encode($text)
2841
+	{
2842 2842
 		//error_log( __METHOD__."\n" .print_r($text,true));
2843 2843
 		//error_log("prop-encode:" . print_r($this->_prop_encoding,true));
2844 2844
 
2845 2845
 		switch (strtolower($this->_prop_encoding)) {
2846 2846
 			case "utf-8":
2847
-       			//error_log( __METHOD__."allready encoded\n" .print_r($text,true));
2847
+	   			//error_log( __METHOD__."allready encoded\n" .print_r($text,true));
2848 2848
 				return $text;
2849 2849
 			case "iso-8859-1":
2850 2850
 			case "iso-8859-15":
@@ -2852,75 +2852,75 @@  discard block
 block discarded – undo
2852 2852
 			default:
2853 2853
 				error_log( __METHOD__."utf8 encode\n" .print_r(utf8_encode($text),true));
2854 2854
 				return utf8_encode($text);
2855
-        }
2856
-    }
2855
+		}
2856
+	}
2857 2857
 
2858
-    /**
2859
-     * Slashify - make sure path ends in a slash
2860
-     *
2861
-     * @param   string directory path
2862
-     * @returns string directory path wiht trailing slash
2863
-     */
2864
-    public static function _slashify($path)
2865
-    {
2858
+	/**
2859
+	 * Slashify - make sure path ends in a slash
2860
+	 *
2861
+	 * @param   string directory path
2862
+	 * @returns string directory path wiht trailing slash
2863
+	 */
2864
+	public static function _slashify($path)
2865
+	{
2866 2866
 		//error_log(__METHOD__." called with $path");
2867 2867
 		if ($path[self::bytes($path)-1] != '/') {
2868 2868
 			//error_log(__METHOD__." added slash at the end of path");
2869 2869
 			$path = $path."/";
2870 2870
 		}
2871 2871
 		return $path;
2872
-    }
2872
+	}
2873 2873
 
2874
-    /**
2875
-     * Unslashify - make sure path doesn't in a slash
2876
-     *
2877
-     * @param   string directory path
2878
-     * @returns string directory path wihtout trailing slash
2879
-     */
2880
-    public static function _unslashify($path)
2881
-    {
2882
-        //error_log(__METHOD__." called with $path");
2883
-        if ($path[self::bytes($path)-1] == '/') {
2884
-            $path = substr($path, 0, -1);
2874
+	/**
2875
+	 * Unslashify - make sure path doesn't in a slash
2876
+	 *
2877
+	 * @param   string directory path
2878
+	 * @returns string directory path wihtout trailing slash
2879
+	 */
2880
+	public static function _unslashify($path)
2881
+	{
2882
+		//error_log(__METHOD__." called with $path");
2883
+		if ($path[self::bytes($path)-1] == '/') {
2884
+			$path = substr($path, 0, -1);
2885 2885
 			//error_log(__METHOD__." removed slash at the end of path");
2886
-        }
2887
-        return $path;
2888
-    }
2886
+		}
2887
+		return $path;
2888
+	}
2889 2889
 
2890
-    /**
2891
-     * Merge two paths, make sure there is exactly one slash between them
2892
-     *
2893
-     * @param  string  parent path
2894
-     * @param  string  child path
2895
-     * @return string  merged path
2896
-     */
2897
-    public static function _mergePaths($parent, $child)
2898
-    {
2899
-        //error_log("merge called :\n$parent \n$child\n" . function_backtrace());
2900
-        //error_log("merge :\n".print_r($this->_mergePaths($this->_SERVER["SCRIPT_NAME"], $this->path)true));
2901
-        if ($child{0} == '/') {
2902
-            return self::_unslashify($parent).$child;
2903
-        } else {
2904
-            return self::_slashify($parent).$child;
2905
-        }
2906
-    }
2890
+	/**
2891
+	 * Merge two paths, make sure there is exactly one slash between them
2892
+	 *
2893
+	 * @param  string  parent path
2894
+	 * @param  string  child path
2895
+	 * @return string  merged path
2896
+	 */
2897
+	public static function _mergePaths($parent, $child)
2898
+	{
2899
+		//error_log("merge called :\n$parent \n$child\n" . function_backtrace());
2900
+		//error_log("merge :\n".print_r($this->_mergePaths($this->_SERVER["SCRIPT_NAME"], $this->path)true));
2901
+		if ($child{0} == '/') {
2902
+			return self::_unslashify($parent).$child;
2903
+		} else {
2904
+			return self::_slashify($parent).$child;
2905
+		}
2906
+	}
2907 2907
 
2908
-    /**
2909
-     * mbstring.func_overload save strlen version: counting the bytes not the chars
2910
-     *
2911
-     * @param string $str
2912
-     * @return int
2913
-     */
2914
-    public static function bytes($str)
2915
-    {
2916
-    	static $func_overload=null;
2917
-
2918
-    	if (is_null($func_overload))
2919
-    	{
2920
-    		$func_overload = @extension_loaded('mbstring') ? ini_get('mbstring.func_overload') : 0;
2921
-    	}
2922
-    	return $func_overload & 2 ? mb_strlen($str,'ascii') : strlen($str);
2923
-    }
2908
+	/**
2909
+	 * mbstring.func_overload save strlen version: counting the bytes not the chars
2910
+	 *
2911
+	 * @param string $str
2912
+	 * @return int
2913
+	 */
2914
+	public static function bytes($str)
2915
+	{
2916
+		static $func_overload=null;
2917
+
2918
+		if (is_null($func_overload))
2919
+		{
2920
+			$func_overload = @extension_loaded('mbstring') ? ini_get('mbstring.func_overload') : 0;
2921
+		}
2922
+		return $func_overload & 2 ? mb_strlen($str,'ascii') : strlen($str);
2923
+	}
2924 2924
 }
2925 2925
 
2926 2926
 /*
Please login to merge, or discard this patch.