1 | <?php |
||||||
2 | /** |
||||||
3 | * EGroupware: CalDAV/CardDAV/GroupDAV access |
||||||
4 | * |
||||||
5 | * @link http://www.egroupware.org |
||||||
6 | * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
||||||
7 | * @package api |
||||||
8 | * @subpackage caldav |
||||||
9 | * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> |
||||||
10 | * @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de> |
||||||
11 | * @version $Id$ |
||||||
12 | */ |
||||||
13 | |||||||
14 | namespace EGroupware\Api; |
||||||
15 | |||||||
16 | use EGroupware\Api\CalDAV\Handler; |
||||||
17 | use EGroupware\Api\CalDAV\Principals; |
||||||
18 | |||||||
19 | // explicit import non-namespaced classes |
||||||
20 | require_once(__DIR__.'/WebDAV/Server.php'); |
||||||
21 | use HTTP_WebDAV_Server; |
||||||
22 | use calendar_hooks; |
||||||
23 | |||||||
24 | /** |
||||||
25 | * EGroupware: GroupDAV access |
||||||
26 | * |
||||||
27 | * Using a modified PEAR HTTP/WebDAV/Server class from API! |
||||||
28 | * |
||||||
29 | * One can use the following url's releative (!) to http://domain.com/egroupware/groupdav.php |
||||||
30 | * |
||||||
31 | * - / base of Cal|Card|GroupDAV tree, only certain clients (KDE, Apple) can autodetect folders from here |
||||||
32 | * - /principals/ principal-collection-set for WebDAV ACL |
||||||
33 | * - /principals/users/<username>/ |
||||||
34 | * - /principals/groups/<groupname>/ |
||||||
35 | * - /<username>/ users home-set with |
||||||
36 | * - /<username>/addressbook/ addressbook of user or group <username> given the user has rights to view it |
||||||
37 | * - /<current-username>/addressbook-<other-username>/ shared addressbooks from other user or group |
||||||
38 | * - /<current-username>/addressbook-accounts/ all accounts current user has rights to see |
||||||
39 | * - /<username>/calendar/ calendar of user <username> given the user has rights to view it |
||||||
40 | * - /<username>/calendar/?download download whole calendar as .ics file (GET request!) |
||||||
41 | * - /<current-username>/calendar-<other-username>/ shared calendar from other user or group (only current <username>!) |
||||||
42 | * - /<username>/inbox/ scheduling inbox of user <username> |
||||||
43 | * - /<username>/outbox/ scheduling outbox of user <username> |
||||||
44 | * - /<username>/infolog/ InfoLog's of user <username> given the user has rights to view it |
||||||
45 | * - /addressbook/ all addressbooks current user has rights to, announced as directory-gateway now |
||||||
46 | * - /addressbook-accounts/ all accounts current user has rights to see |
||||||
47 | * - /calendar/ calendar of current user |
||||||
48 | * - /infolog/ infologs of current user |
||||||
49 | * - /(resources|locations)/<resource-name>/calendar calendar of a resource/location, if user has rights to view |
||||||
50 | * - /<current-username>/(resource|location)-<resource-name> shared calendar from a resource/location |
||||||
51 | * |
||||||
52 | * Shared addressbooks or calendars are only shown in in users home-set, if he subscribed to it via his CalDAV preferences! |
||||||
53 | * |
||||||
54 | * Calling one of the above collections with a GET request / regular browser generates an automatic index |
||||||
55 | * from the data of a allprop PROPFIND, allow to browse CalDAV/CardDAV/GroupDAV tree with a regular browser. |
||||||
56 | * |
||||||
57 | * Permanent error_log() calls should use groupdav->log($str) instead, to be send to PHP error_log() |
||||||
58 | * and our request-log (prefixed with "### " after request and response, like exceptions). |
||||||
59 | * |
||||||
60 | * @link http://www.groupdav.org/ GroupDAV spec |
||||||
61 | * @link http://caldav.calconnect.org/ CalDAV resources |
||||||
62 | * @link http://carddav.calconnect.org/ CardDAV resources |
||||||
63 | * @link http://calendarserver.org/ Apple calendar and contacts server |
||||||
64 | */ |
||||||
65 | class CalDAV extends HTTP_WebDAV_Server |
||||||
66 | { |
||||||
67 | /** |
||||||
68 | * DAV namespace |
||||||
69 | */ |
||||||
70 | const DAV = 'DAV:'; |
||||||
71 | /** |
||||||
72 | * GroupDAV namespace |
||||||
73 | */ |
||||||
74 | const GROUPDAV = 'http://groupdav.org/'; |
||||||
75 | /** |
||||||
76 | * CalDAV namespace |
||||||
77 | */ |
||||||
78 | const CALDAV = 'urn:ietf:params:xml:ns:caldav'; |
||||||
79 | /** |
||||||
80 | * CardDAV namespace |
||||||
81 | */ |
||||||
82 | const CARDDAV = 'urn:ietf:params:xml:ns:carddav'; |
||||||
83 | /** |
||||||
84 | * Apple Calendarserver namespace (eg. for ctag) |
||||||
85 | */ |
||||||
86 | const CALENDARSERVER = 'http://calendarserver.org/ns/'; |
||||||
87 | /** |
||||||
88 | * Apple Addressbookserver namespace (eg. for ctag) |
||||||
89 | */ |
||||||
90 | const ADDRESSBOOKSERVER = 'http://addressbookserver.org/ns/'; |
||||||
91 | /** |
||||||
92 | * Apple iCal namespace (eg. for calendar color) |
||||||
93 | */ |
||||||
94 | const ICAL = 'http://apple.com/ns/ical/'; |
||||||
95 | /** |
||||||
96 | * Realm and powered by string |
||||||
97 | */ |
||||||
98 | const REALM = 'EGroupware CalDAV/CardDAV/GroupDAV server'; |
||||||
99 | |||||||
100 | var $dav_powered_by = self::REALM; |
||||||
101 | var $http_auth_realm = self::REALM; |
||||||
102 | |||||||
103 | /** |
||||||
104 | * Folders in root or user home |
||||||
105 | * |
||||||
106 | * @var array |
||||||
107 | */ |
||||||
108 | var $root = array( |
||||||
109 | 'addressbook' => array( |
||||||
110 | 'resourcetype' => array(self::GROUPDAV => 'vcard-collection', self::CARDDAV => 'addressbook'), |
||||||
111 | 'component-set' => array(self::GROUPDAV => 'VCARD'), |
||||||
112 | ), |
||||||
113 | 'calendar' => array( |
||||||
114 | 'resourcetype' => array(self::GROUPDAV => 'vevent-collection', self::CALDAV => 'calendar'), |
||||||
115 | 'component-set' => array(self::GROUPDAV => 'VEVENT'), |
||||||
116 | ), |
||||||
117 | 'inbox' => array( |
||||||
118 | 'resourcetype' => array(self::CALDAV => 'schedule-inbox'), |
||||||
119 | 'app' => 'calendar', |
||||||
120 | 'user-only' => true, // display just in user home |
||||||
121 | ), |
||||||
122 | 'outbox' => array( |
||||||
123 | 'resourcetype' => array(self::CALDAV => 'schedule-outbox'), |
||||||
124 | 'app' => 'calendar', |
||||||
125 | 'user-only' => true, // display just in user home |
||||||
126 | ), |
||||||
127 | 'infolog' => array( |
||||||
128 | 'resourcetype' => array(self::GROUPDAV => 'vtodo-collection', self::CALDAV => 'calendar'), |
||||||
129 | 'component-set' => array(self::GROUPDAV => 'VTODO'), |
||||||
130 | ), |
||||||
131 | ); |
||||||
132 | /** |
||||||
133 | * Debug level: 0 = nothing, 1 = function calls, 2 = more info, 3 = complete $_SERVER array |
||||||
134 | * |
||||||
135 | * Can now be enabled on a per user basis in GroupDAV prefs, if it is set here to 0! |
||||||
136 | * |
||||||
137 | * The debug messages are send to the apache error_log |
||||||
138 | * |
||||||
139 | * @var integer |
||||||
140 | */ |
||||||
141 | var $debug = 0; |
||||||
142 | |||||||
143 | /** |
||||||
144 | * eGW's charset |
||||||
145 | * |
||||||
146 | * @var string |
||||||
147 | */ |
||||||
148 | var $egw_charset; |
||||||
149 | /** |
||||||
150 | * Instance of our application specific handler |
||||||
151 | * |
||||||
152 | * @var Handler |
||||||
153 | */ |
||||||
154 | var $handler; |
||||||
155 | /** |
||||||
156 | * current-user-principal URL |
||||||
157 | * |
||||||
158 | * @var string |
||||||
159 | */ |
||||||
160 | var $current_user_principal; |
||||||
161 | /** |
||||||
162 | * Reference to the accounts class |
||||||
163 | * |
||||||
164 | * @var accounts |
||||||
165 | */ |
||||||
166 | var $accounts; |
||||||
167 | /** |
||||||
168 | * Supported privileges with name and description |
||||||
169 | * |
||||||
170 | * privileges are hierarchical |
||||||
171 | * |
||||||
172 | * @var array |
||||||
173 | */ |
||||||
174 | var $supported_privileges = array( |
||||||
175 | 'all' => array( |
||||||
176 | '*description*' => 'all privileges', |
||||||
177 | 'read' => array( |
||||||
178 | '*description*' => 'read resource', |
||||||
179 | 'read-free-busy' => array( |
||||||
180 | '*ns*' => self::CALDAV, |
||||||
181 | '*description*' => 'allow free busy report query', |
||||||
182 | '*only*' => '/calendar/', |
||||||
183 | ), |
||||||
184 | ), |
||||||
185 | 'write' => array( |
||||||
186 | '*description*' => 'write resource', |
||||||
187 | 'write-properties' => 'write resource properties', |
||||||
188 | 'write-content' => 'write resource content', |
||||||
189 | 'bind' => 'add child resource', |
||||||
190 | 'unbind' => 'remove child resource', |
||||||
191 | ), |
||||||
192 | 'unlock' => 'unlock resource without ownership of lock', |
||||||
193 | 'read-acl' => 'read resource access control list', |
||||||
194 | 'write-acl' => 'write resource access control list', |
||||||
195 | 'read-current-user-privilege-set' => 'read privileges for current principal', |
||||||
196 | 'schedule-deliver' => array( |
||||||
197 | '*ns*' => self::CALDAV, |
||||||
198 | '*description*' => 'schedule privileges for current principal', |
||||||
199 | '*only*' => '/inbox/', |
||||||
200 | ), |
||||||
201 | 'schedule-send' => array( |
||||||
202 | '*ns*' => self::CALDAV, |
||||||
203 | '*description*' => 'schedule privileges for current principal', |
||||||
204 | '*only*' => '/outbox/', |
||||||
205 | ), |
||||||
206 | ), |
||||||
207 | ); |
||||||
208 | /** |
||||||
209 | * $options parameter to PROPFIND request, eg. to check what props are requested |
||||||
210 | * |
||||||
211 | * @var array |
||||||
212 | */ |
||||||
213 | var $propfind_options; |
||||||
214 | |||||||
215 | /** |
||||||
216 | * Reference to active instance, used by exception handler |
||||||
217 | * |
||||||
218 | * @var groupdav |
||||||
0 ignored issues
–
show
|
|||||||
219 | */ |
||||||
220 | protected static $instance; |
||||||
221 | |||||||
222 | function __construct() |
||||||
223 | { |
||||||
224 | // log which CalDAVTester test is currently running, set as User-Agent header |
||||||
225 | if (substr($_SERVER['HTTP_USER_AGENT'], 0, 14) == 'scripts/tests/') error_log('****** '.$_SERVER['HTTP_USER_AGENT']); |
||||||
226 | |||||||
227 | if (!$this->debug) $this->debug = (int)$GLOBALS['egw_info']['user']['preferences']['groupdav']['debug_level']; |
||||||
228 | |||||||
229 | if ($this->debug > 2) error_log('groupdav: $_SERVER='.array2string($_SERVER)); |
||||||
230 | |||||||
231 | // setting our own exception handler, to be able to still log the requests |
||||||
232 | set_exception_handler(array(__CLASS__,'exception_handler')); |
||||||
233 | |||||||
234 | // crrnd: client refuses redundand namespace declarations |
||||||
235 | // setting redundand namespaces as the default for (Cal|Card|Group)DAV, as the majority of the clients either require or can live with it |
||||||
236 | $this->crrnd = false; |
||||||
237 | |||||||
238 | // identify clients, which do NOT support path AND full url in <D:href> of PROPFIND request |
||||||
239 | switch(($agent = Handler::get_agent())) |
||||||
240 | { |
||||||
241 | case 'kde': // KAddressbook (at least in 3.5 can NOT subscribe / does NOT find addressbook) |
||||||
242 | $this->client_require_href_as_url = true; |
||||||
243 | break; |
||||||
244 | case 'cfnetwork': // Apple addressbook app |
||||||
245 | case 'dataaccess': // iPhone addressbook |
||||||
246 | $this->client_require_href_as_url = false; |
||||||
247 | break; |
||||||
248 | case 'davkit': // iCal app in OS X 10.6 created wrong request, if full url given |
||||||
249 | case 'coredav': // iCal app in OS X 10.7 |
||||||
250 | case 'calendarstore': // Apple iCal 5.0.1 under OS X 10.7.2 |
||||||
251 | $this->client_require_href_as_url = false; |
||||||
252 | break; |
||||||
253 | case 'cfnetwork_old': |
||||||
254 | $this->crrnd = true; // Older Apple Addressbook.app does not cope with namespace redundancy |
||||||
255 | break; |
||||||
256 | } |
||||||
257 | if ($this->debug) error_log(__METHOD__."() HTTP_USER_AGENT='$_SERVER[HTTP_USER_AGENT]' --> '$agent' --> client_requires_href_as_url=$this->client_require_href_as_url, crrnd(client refuses redundand namespace declarations)=$this->crrnd"); |
||||||
258 | |||||||
259 | // adding EGroupware version to X-Dav-Powered-By header eg. "EGroupware 1.8.001 CalDAV/CardDAV/GroupDAV server" |
||||||
260 | $this->dav_powered_by = str_replace('EGroupware','EGroupware '.$GLOBALS['egw_info']['server']['versions']['phpgwapi'], |
||||||
261 | $this->dav_powered_by); |
||||||
262 | |||||||
263 | parent::__construct(); |
||||||
264 | // hack to allow to use query parameters in WebDAV, which HTTP_WebDAV_Server interprets as part of the path |
||||||
265 | list($this->_SERVER['REQUEST_URI']) = explode('?',$this->_SERVER['REQUEST_URI']); |
||||||
266 | // OSX Addressbook sends ?add-member url-encoded |
||||||
267 | if (substr($this->_SERVER['REQUEST_URI'], -14) == '/%3Fadd-member') |
||||||
268 | { |
||||||
269 | $_GET['add-member'] = ''; |
||||||
270 | $this->_SERVER['REQUEST_URI'] = substr($this->_SERVER['REQUEST_URI'], 0, -14); |
||||||
271 | } |
||||||
272 | //error_log($_SERVER['REQUEST_URI']." --> ".$this->_SERVER['REQUEST_URI']); |
||||||
273 | |||||||
274 | $this->egw_charset = Translation::charset(); |
||||||
275 | if (strpos($this->base_uri, 'http') === 0) |
||||||
276 | { |
||||||
277 | $this->current_user_principal = $this->_slashify($this->base_uri); |
||||||
278 | } |
||||||
279 | else |
||||||
280 | { |
||||||
281 | $this->current_user_principal = Framework::getUrl($_SERVER['SCRIPT_NAME']) . '/'; |
||||||
282 | } |
||||||
283 | $this->current_user_principal .= 'principals/users/'.$GLOBALS['egw_info']['user']['account_lid'].'/'; |
||||||
284 | |||||||
285 | // if client requires pathes instead of URLs |
||||||
286 | if (!$this->client_require_href_as_url) |
||||||
287 | { |
||||||
288 | $this->current_user_principal = parse_url($this->current_user_principal,PHP_URL_PATH); |
||||||
289 | } |
||||||
290 | $this->accounts = $GLOBALS['egw']->accounts; |
||||||
291 | |||||||
292 | self::$instance = $this; |
||||||
0 ignored issues
–
show
It seems like
$this of type EGroupware\Api\CalDAV is incompatible with the declared type EGroupware\Api\groupdav of property $instance .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||||||
293 | } |
||||||
294 | |||||||
295 | /** |
||||||
296 | * get the handler for $app |
||||||
297 | * |
||||||
298 | * @param string $app |
||||||
299 | * @return Handler |
||||||
300 | */ |
||||||
301 | function app_handler($app) |
||||||
302 | { |
||||||
303 | if (isset($this->root[$app]['app'])) $app = $this->root[$app]['app']; |
||||||
304 | |||||||
305 | return Handler::app_handler($app,$this); |
||||||
306 | } |
||||||
307 | |||||||
308 | /** |
||||||
309 | * OPTIONS request, allow to modify the standard responses from the pear-class |
||||||
310 | * |
||||||
311 | * @param string $path |
||||||
312 | * @param array &$dav |
||||||
313 | * @param array &$allow |
||||||
314 | */ |
||||||
315 | function OPTIONS($path, &$dav, &$allow) |
||||||
316 | { |
||||||
317 | unset($allow); // not used, but required by function signature |
||||||
318 | |||||||
319 | // locking support |
||||||
320 | if (!in_array('2', $dav)) $dav[] = '2'; |
||||||
321 | |||||||
322 | if (preg_match('#/(calendar(-[^/]+)?|inbox|outbox)/#', $path)) // eg. /<username>/calendar-<otheruser>/ |
||||||
323 | { |
||||||
324 | $app = 'calendar'; |
||||||
325 | } |
||||||
326 | elseif (preg_match('#/addressbook(-[^/]+)?/#', $path)) // eg. /<username>/addressbook-<otheruser>/ |
||||||
327 | { |
||||||
328 | $app = 'addressbook'; |
||||||
329 | } |
||||||
330 | // CalDAV and CardDAV |
||||||
331 | $dav[] = 'access-control'; |
||||||
332 | |||||||
333 | if ($app !== 'addressbook') // CalDAV |
||||||
334 | { |
||||||
335 | $dav[] = 'calendar-access'; |
||||||
336 | $dav[] = 'calendar-auto-schedule'; |
||||||
337 | $dav[] = 'calendar-proxy'; |
||||||
338 | // required by iOS iCal to use principal-property-search to autocomplete participants (and locations) |
||||||
339 | $dav[] = 'calendarserver-principal-property-search'; |
||||||
340 | // required by iOS & OS X iCal to show private checkbox (X-CALENDARSERVER-ACCESS: CONFIDENTIAL on VCALENDAR) |
||||||
341 | $dav[] = 'calendarserver-private-events'; |
||||||
342 | // managed attachments |
||||||
343 | $dav[] = 'calendar-managed-attachments'; |
||||||
344 | // other capabilities calendarserver announces |
||||||
345 | //$dav[] = 'calendar-schedule'; |
||||||
346 | //$dav[] = 'calendar-availability'; |
||||||
347 | //$dav[] = 'inbox-availability'; |
||||||
348 | //$dav[] = 'calendarserver-private-comments'; |
||||||
349 | //$dav[] = 'calendarserver-sharing'; |
||||||
350 | //$dav[] = 'calendarserver-sharing-no-scheduling'; |
||||||
351 | } |
||||||
352 | if ($app !== 'calendar') // CardDAV |
||||||
353 | { |
||||||
354 | $dav[] = 'addressbook'; // CardDAV uses "addressbook" NOT "addressbook-access" |
||||||
355 | } |
||||||
356 | //error_log(__METHOD__."('$path') --> app='$app' --> DAV: ".implode(', ', $dav)); |
||||||
357 | } |
||||||
358 | |||||||
359 | /** |
||||||
360 | * PROPFIND and REPORT method handler |
||||||
361 | * |
||||||
362 | * @param array general parameter passing array |
||||||
363 | * @param array return array for file properties |
||||||
0 ignored issues
–
show
The type
EGroupware\Api\return was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
364 | * @return bool true on success |
||||||
365 | */ |
||||||
366 | function PROPFIND(&$options, &$files, $method='PROPFIND') |
||||||
367 | { |
||||||
368 | if ($this->debug) error_log(__CLASS__."::$method(".array2string($options).')'); |
||||||
369 | |||||||
370 | // make options (readonly) available to all class methods, eg. prop_requested |
||||||
371 | $this->propfind_options = $options; |
||||||
372 | |||||||
373 | // parse path in form [/account_lid]/app[/more] |
||||||
374 | $id = $app = $user = $user_prefix = null; |
||||||
375 | if (!self::_parse_path($options['path'],$id,$app,$user,$user_prefix) && $app && !$user && $user !== 0) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::_parse_path() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
376 | { |
||||||
377 | if ($this->debug > 1) error_log(__CLASS__."::$method: user='$user', app='$app', id='$id': 404 not found!"); |
||||||
378 | return '404 Not Found'; |
||||||
379 | } |
||||||
380 | if ($this->debug > 1) error_log(__CLASS__."::$method(path='$options[path]'): user='$user', user_prefix='$user_prefix', app='$app', id='$id'"); |
||||||
381 | |||||||
382 | $files = array('files' => array()); |
||||||
383 | $path = $user_prefix = $this->_slashify($user_prefix); |
||||||
0 ignored issues
–
show
|
|||||||
384 | |||||||
385 | if (!$app) // user root folder containing apps |
||||||
386 | { |
||||||
387 | // add root with current users apps |
||||||
388 | $this->add_home($files, $path, $user, $options['depth']); |
||||||
389 | |||||||
390 | if ($path == '/') |
||||||
391 | { |
||||||
392 | Hooks::process(array( |
||||||
393 | 'location' => 'groupdav_root_props', |
||||||
394 | 'props' => &$files['files'][0]['props'], |
||||||
395 | 'options' => $options, |
||||||
396 | 'caldav' => $this, |
||||||
397 | )); |
||||||
398 | } |
||||||
399 | |||||||
400 | // add principals and user-homes |
||||||
401 | if ($path == '/' && $options['depth']) |
||||||
402 | { |
||||||
403 | // principals collection |
||||||
404 | $files['files'][] = $this->add_collection('/principals/', array( |
||||||
405 | 'displayname' => lang('Accounts'), |
||||||
406 | )); |
||||||
407 | foreach($this->accounts->search(array('type' => 'both','order'=>'account_lid')) as $account) |
||||||
408 | { |
||||||
409 | $this->add_home($files, $path.$account['account_lid'].'/', $account['account_id'], $options['depth'] == 'infinity' ? 'infinity' : $options['depth']-1); |
||||||
410 | } |
||||||
411 | } |
||||||
412 | return true; |
||||||
413 | } |
||||||
414 | if ($path == '/' && ($app == 'resources' || $app == 'locations')) |
||||||
415 | { |
||||||
416 | return $this->add_resources_collection($files, '/'.$app.'/', $options['depth']); |
||||||
417 | } |
||||||
418 | if ($app != 'principals' && !isset($GLOBALS['egw_info']['user']['apps'][$this->root[$app]['app'] ? $this->root[$app]['app'] : $app])) |
||||||
419 | { |
||||||
420 | if ($this->debug) error_log(__CLASS__."::$method(path=$options[path]) 403 Forbidden: no app rights for '$app'"); |
||||||
421 | return "403 Forbidden: no app rights for '$app'"; // no rights for the given app |
||||||
422 | } |
||||||
423 | if (($handler = self::app_handler($app))) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
424 | { |
||||||
425 | if ($method != 'REPORT' && !$id) // no self URL for REPORT requests (only PROPFIND) or propfinds on an id |
||||||
426 | { |
||||||
427 | // KAddressbook doubles the folder, if the self URL contains the GroupDAV/CalDAV resourcetypes |
||||||
428 | $files['files'][0] = $this->add_app($app,$app=='addressbook'&&$handler->get_agent()=='kde',$user, |
||||||
429 | $this->_slashify($options['path'])); |
||||||
430 | |||||||
431 | // Hack for iOS 5.0.1 addressbook to stop asking directory gateway permissions with depth != 0 |
||||||
432 | // values for depth are 0, 1, "infinit" or not set which has to be interpreted as "infinit" |
||||||
433 | if ($method == 'PROPFIND' && $options['path'] == '/addressbook/' && |
||||||
434 | (!isset($options['depth']) || $options['depth']) && $handler->get_agent() == 'dataaccess') |
||||||
435 | { |
||||||
436 | $this->log(__CLASS__."::$method(".array2string($options).') Enabling hack for iOS 5.0.1 addressbook: force Depth: 0 on PROPFIND for directory gateway!'); |
||||||
437 | return true; |
||||||
438 | } |
||||||
439 | if (!$options['depth']) return true; // depth 0 --> show only the self url |
||||||
440 | } |
||||||
441 | return $handler->propfind($this->_slashify($options['path']),$options,$files,$user,$id); |
||||||
0 ignored issues
–
show
The call to
EGroupware\Api\CalDAV\Handler::propfind() has too many arguments starting with $id .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
442 | } |
||||||
443 | return '501 Not Implemented'; |
||||||
444 | } |
||||||
445 | |||||||
446 | /** |
||||||
447 | * Add a collection to a PROPFIND request |
||||||
448 | * |
||||||
449 | * @param string $path |
||||||
450 | * @param array $props =array() extra properties 'resourcetype' is added anyway, name => value pairs or name => HTTP_WebDAV_Server([namespace,]name,value) |
||||||
451 | * @param array $privileges =array('read') values for current-user-privilege-set |
||||||
452 | * @param array $supported_privileges =null default $this->supported_privileges |
||||||
453 | * @return array with values for keys 'path' and 'props' |
||||||
454 | */ |
||||||
455 | public function add_collection($path, array $props = array(), array $privileges=array('read','read-acl','read-current-user-privilege-set'), array $supported_privileges=null) |
||||||
456 | { |
||||||
457 | // resourcetype: collection |
||||||
458 | $props['resourcetype'][] = self::mkprop('collection',''); |
||||||
459 | |||||||
460 | if (!isset($props['getcontenttype'])) $props['getcontenttype'] = 'httpd/unix-directory'; |
||||||
461 | |||||||
462 | return $this->add_resource($path, $props, $privileges, $supported_privileges); |
||||||
463 | } |
||||||
464 | |||||||
465 | /** |
||||||
466 | * Add a resource to a PROPFIND request |
||||||
467 | * |
||||||
468 | * @param string $path |
||||||
469 | * @param array $props =array() extra properties 'resourcetype' is added anyway, name => value pairs or name => HTTP_WebDAV_Server([namespace,]name,value) |
||||||
470 | * @param array $privileges =array('read') values for current-user-privilege-set |
||||||
471 | * @param array $supported_privileges =null default $this->supported_privileges |
||||||
472 | * @return array with values for keys 'path' and 'props' |
||||||
473 | */ |
||||||
474 | public function add_resource($path, array $props = array(), array $privileges=array('read','read-current-user-privilege-set'), array $supported_privileges=null) |
||||||
475 | { |
||||||
476 | // props for all collections: current-user-principal and principal-collection-set |
||||||
477 | $props['current-user-principal'] = array( |
||||||
478 | self::mkprop('href',$this->current_user_principal)); |
||||||
479 | $props['principal-collection-set'] = array( |
||||||
480 | self::mkprop('href',$this->base_uri.'/principals/')); |
||||||
481 | |||||||
482 | // required props per WebDAV standard |
||||||
483 | foreach(array( |
||||||
484 | 'displayname' => basename($path), |
||||||
485 | 'getetag' => 'none', |
||||||
486 | 'getcontentlength' => '', |
||||||
487 | 'getlastmodified' => '', |
||||||
488 | 'getcontenttype' => '', |
||||||
489 | 'resourcetype' => '', |
||||||
490 | ) as $name => $default) |
||||||
491 | { |
||||||
492 | if (!isset($props[$name])) $props[$name] = $default; |
||||||
493 | } |
||||||
494 | |||||||
495 | // if requested add privileges |
||||||
496 | if (is_null($supported_privileges)) $supported_privileges = $this->supported_privileges; |
||||||
497 | if ($this->prop_requested('current-user-privilege-set') === true) |
||||||
498 | { |
||||||
499 | foreach($privileges as $name) |
||||||
500 | { |
||||||
501 | $props['current-user-privilege-set'][] = self::mkprop('privilege', array( |
||||||
502 | is_array($name) ? self::mkprop($name['ns'], $name['name'], '') : self::mkprop($name, ''))); |
||||||
503 | } |
||||||
504 | } |
||||||
505 | if ($this->prop_requested('supported-privilege-set') === true) |
||||||
506 | { |
||||||
507 | foreach($supported_privileges as $name => $data) |
||||||
508 | { |
||||||
509 | $props['supported-privilege-set'][] = $this->supported_privilege($name, $data, $path); |
||||||
510 | } |
||||||
511 | } |
||||||
512 | if (!isset($props['owner']) && $this->prop_requested('owner') === true) |
||||||
513 | { |
||||||
514 | $props['owner'] = ''; |
||||||
515 | } |
||||||
516 | |||||||
517 | if ($this->debug > 1) error_log(__METHOD__."(path='$path', props=".array2string($props).')'); |
||||||
518 | |||||||
519 | // convert simple associative properties to HTTP_WebDAV_Server ones |
||||||
520 | foreach($props as $name => &$prop) |
||||||
521 | { |
||||||
522 | if (!is_array($prop) || !isset($prop['name'])) |
||||||
523 | { |
||||||
524 | $prop = self::mkprop($name, $prop); |
||||||
525 | } |
||||||
526 | // add quotes around etag, if they are not already there |
||||||
527 | if ($prop['name'] == 'getetag' && $prop['val'][0] != '"') |
||||||
528 | { |
||||||
529 | $prop['val'] = '"'.$prop['val'].'"'; |
||||||
530 | } |
||||||
531 | } |
||||||
532 | |||||||
533 | return array( |
||||||
534 | 'path' => $path, |
||||||
535 | 'props' => $props, |
||||||
536 | ); |
||||||
537 | } |
||||||
538 | |||||||
539 | /** |
||||||
540 | * Generate (hierachical) supported-privilege property |
||||||
541 | * |
||||||
542 | * @param string $name name of privilege |
||||||
543 | * @param string|array $data string with describtion or array with agregated privileges plus value for key '*description*', '*ns*', '*only*' |
||||||
544 | * @param string $path =null path to match with $data['*only*'] |
||||||
545 | * @return array of self::mkprop() arrays |
||||||
546 | */ |
||||||
547 | protected function supported_privilege($name, $data, $path=null) |
||||||
548 | { |
||||||
549 | $props = array(); |
||||||
550 | $props[] = self::mkprop('privilege', array(is_array($data) && $data['*ns*'] ? |
||||||
551 | self::mkprop($data['*ns*'], $name, '') : self::mkprop($name, ''))); |
||||||
552 | $props[] = self::mkprop('description', is_array($data) ? $data['*description*'] : $data); |
||||||
553 | if (is_array($data)) |
||||||
554 | { |
||||||
555 | foreach($data as $name => $data) |
||||||
0 ignored issues
–
show
|
|||||||
556 | { |
||||||
557 | if ($name[0] == '*') continue; |
||||||
558 | if (is_array($data) && $data['*only*'] && strpos($path, $data['*only*']) === false) |
||||||
559 | { |
||||||
560 | continue; // wrong path |
||||||
561 | } |
||||||
562 | $props[] = $this->supported_privilege($name, $data, $path); |
||||||
563 | } |
||||||
564 | } |
||||||
565 | return self::mkprop('supported-privilege', $props); |
||||||
566 | } |
||||||
567 | |||||||
568 | /** |
||||||
569 | * Checks if a given property was requested in propfind request |
||||||
570 | * |
||||||
571 | * @param string $name property name |
||||||
572 | * @param string $ns =null namespace, if that is to be checked too |
||||||
573 | * @param boolean $return_prop =false if true return the property array with values for 'name', 'xmlns', 'attrs', 'children' |
||||||
574 | * @return boolean|string|array true: $name explicitly requested (or autoindex), "all": allprop or "names": propname requested, false: $name was not requested |
||||||
575 | */ |
||||||
576 | function prop_requested($name, $ns=null, $return_prop=false) |
||||||
577 | { |
||||||
578 | if (!is_array($this->propfind_options) || !isset($this->propfind_options['props'])) |
||||||
0 ignored issues
–
show
|
|||||||
579 | { |
||||||
580 | $ret = true; // no props set, should happen only in autoindex, we return true to show all available props |
||||||
581 | } |
||||||
582 | elseif (!is_array($this->propfind_options['props'])) |
||||||
583 | { |
||||||
584 | $ret = $this->propfind_options['props']; // "all": allprop or "names": propname |
||||||
585 | } |
||||||
586 | else |
||||||
587 | { |
||||||
588 | $ret = false; |
||||||
589 | foreach($this->propfind_options['props'] as $prop) |
||||||
590 | { |
||||||
591 | if ($prop['name'] == $name && (is_null($ns) || $prop['xmlns'] == $ns)) |
||||||
592 | { |
||||||
593 | $ret = $return_prop ? $prop : true; |
||||||
594 | break; |
||||||
595 | } |
||||||
596 | } |
||||||
597 | } |
||||||
598 | //error_log(__METHOD__."('$name', '$ns', $return_prop) propfind_options=".array2string($this->propfind_options)); |
||||||
599 | return $ret; |
||||||
600 | } |
||||||
601 | |||||||
602 | /** |
||||||
603 | * Add user home with addressbook, calendar, infolog |
||||||
604 | * |
||||||
605 | * @param array $files |
||||||
606 | * @param string $path / or /<username>/ |
||||||
607 | * @param int $user |
||||||
608 | * @param int $depth |
||||||
609 | * @return string|boolean http status or true|false |
||||||
610 | */ |
||||||
611 | protected function add_home(array &$files, $path, $user, $depth) |
||||||
612 | { |
||||||
613 | if ($user) |
||||||
614 | { |
||||||
615 | $account_lid = $this->accounts->id2name($user); |
||||||
616 | } |
||||||
617 | else |
||||||
618 | { |
||||||
619 | $account_lid = $GLOBALS['egw_info']['user']['account_lid']; |
||||||
620 | } |
||||||
621 | $account = $this->accounts->read($account_lid); |
||||||
622 | |||||||
623 | $calendar_user_address_set = array( |
||||||
624 | self::mkprop('href','urn:uuid:'.$account['account_lid']), |
||||||
625 | ); |
||||||
626 | if ($user < 0) |
||||||
627 | { |
||||||
628 | $principalType = 'groups'; |
||||||
629 | $displayname = lang('Group').' '.$account['account_lid']; |
||||||
630 | } |
||||||
631 | else |
||||||
632 | { |
||||||
633 | $principalType = 'users'; |
||||||
634 | $displayname = $account['account_fullname']; |
||||||
635 | $calendar_user_address_set[] = self::mkprop('href','mailto:'.$account['account_email']); |
||||||
636 | } |
||||||
637 | $calendar_user_address_set[] = self::mkprop('href',$this->base_uri.'/principals/'.$principalType.'/'.$account['account_lid'].'/'); |
||||||
638 | |||||||
639 | if ($depth && $path == '/') |
||||||
640 | { |
||||||
641 | $displayname = 'EGroupware (Cal|Card|Group)DAV server'; |
||||||
642 | } |
||||||
643 | |||||||
644 | $displayname = Translation::convert($displayname, Translation::charset(),'utf-8'); |
||||||
645 | // self url |
||||||
646 | $props = array( |
||||||
647 | 'displayname' => $displayname, |
||||||
648 | 'owner' => $path == '/' ? '' : array(self::mkprop('href',$this->base_uri.'/principals/'.$principalType.'/'.$account_lid.'/')), |
||||||
649 | ); |
||||||
650 | |||||||
651 | if ($path != '/') |
||||||
652 | { |
||||||
653 | // add props modifyable via proppatch from client, eg. jqcalendar stores it's preferences there |
||||||
654 | foreach((array)$GLOBALS['egw_info']['user']['preferences']['groupdav'] as $name => $value) |
||||||
655 | { |
||||||
656 | list($prop,$prop4path,$ns) = explode(':', $name, 3); |
||||||
657 | if ($prop4path == $path && (!in_array($ns,self::$ns_needs_explicit_named_props) || |
||||||
658 | isset(self::$proppatch_props[$prop]) && self::$proppatch_props[$prop] === $ns)) |
||||||
659 | { |
||||||
660 | $props[] = self::mkprop($ns, $prop, $value); |
||||||
661 | //error_log(__METHOD__."() arbitrary $ns:$prop=".array2string($value)); |
||||||
662 | } |
||||||
663 | } |
||||||
664 | } |
||||||
665 | $files['files'][] = $this->add_collection($path, $props); |
||||||
666 | |||||||
667 | if ($depth) |
||||||
668 | { |
||||||
669 | foreach($this->root as $app => $data) |
||||||
670 | { |
||||||
671 | if (!$GLOBALS['egw_info']['user']['apps'][$data['app'] ? $data['app'] : $app]) continue; // no rights for the given app |
||||||
672 | if (!empty($data['user-only']) && ($path == '/' || $user < 0)) continue; |
||||||
673 | |||||||
674 | $files['files'][] = $this->add_app($app,false,$user,$path.$app.'/'); |
||||||
675 | |||||||
676 | // only add global /addressbook-accounts/ as the one in home-set is added (and controled) by add_shared |
||||||
677 | if ($path == '/' && $app == 'addressbook' && |
||||||
678 | $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1') |
||||||
679 | { |
||||||
680 | $file = $this->add_app($app,false,0,$path.$app.'-accounts/'); |
||||||
681 | $file['props']['resourcetype']['val'][] = self::mkprop(self::CALENDARSERVER,'shared',''); |
||||||
682 | $files['files'][] = $file; |
||||||
683 | } |
||||||
684 | // added shared calendars or addressbooks |
||||||
685 | $this->add_shared($files['files'], $path, $app, $user); |
||||||
686 | } |
||||||
687 | if ($path == '/' && $GLOBALS['egw_info']['user']['apps']['resources']) |
||||||
688 | { |
||||||
689 | $this->add_resources_collection($files, $path.'resources/'); |
||||||
690 | $this->add_resources_collection($files, $path.'locations/'); |
||||||
691 | } |
||||||
692 | } |
||||||
693 | return true; |
||||||
694 | } |
||||||
695 | |||||||
696 | /** |
||||||
697 | * Add collection with available resources or locations calendar-home-sets |
||||||
698 | * |
||||||
699 | * @param array &$files |
||||||
700 | * @param string $path / or /<username>/ |
||||||
701 | * @param int $depth =0 |
||||||
702 | * @return string|boolean http status or true|false |
||||||
703 | */ |
||||||
704 | protected function add_resources_collection(array &$files, $path, $depth=0) |
||||||
705 | { |
||||||
706 | if (!isset($GLOBALS['egw_info']['user']['apps']['resources'])) |
||||||
707 | { |
||||||
708 | if ($this->debug) error_log(__METHOD__."(path=$path) 403 Forbidden: no app rights for 'resources'"); |
||||||
709 | return "403 Forbidden: no app rights for 'resources'"; // no rights for the given app |
||||||
710 | } |
||||||
711 | list(,$what) = explode('/', $path); |
||||||
712 | if (($is_location = ($what == 'locations'))) |
||||||
713 | { |
||||||
714 | $files['files'][] = $this->add_collection('/locations/', array('displayname' => lang('Location calendars'))); |
||||||
715 | } |
||||||
716 | else |
||||||
717 | { |
||||||
718 | $files['files'][] = $this->add_collection('/resources/', array('displayname' => lang('Resource calendars'))); |
||||||
719 | } |
||||||
720 | if ($depth) |
||||||
721 | { |
||||||
722 | foreach(Principals::get_resources() as $resource) |
||||||
723 | { |
||||||
724 | if ($is_location == Principals::resource_is_location($resource)) |
||||||
725 | { |
||||||
726 | $files['files'][] = $this->add_app('calendar', false, 'r'.$resource['res_id'], |
||||||
727 | '/'.Principals::resource2name($resource, $is_location).'/'); |
||||||
728 | } |
||||||
729 | } |
||||||
730 | } |
||||||
731 | return true; |
||||||
732 | } |
||||||
733 | |||||||
734 | /** |
||||||
735 | * Add shared addressbook, calendar, infolog to user home |
||||||
736 | * |
||||||
737 | * @param array &$files |
||||||
738 | * @param string $path /<username>/ |
||||||
739 | * @param int $app |
||||||
740 | * @param int $user |
||||||
741 | * @return string|boolean http status or true|false |
||||||
742 | */ |
||||||
743 | protected function add_shared(array &$files, $path, $app, $user) |
||||||
744 | { |
||||||
745 | // currently only show shared calendars/addressbooks for current user and not in the root |
||||||
746 | if ($path == '/' || $user != $GLOBALS['egw_info']['user']['account_id'] || |
||||||
747 | !isset($GLOBALS['egw_info']['user']['apps'][$app])) // also avoids principals, inbox and outbox |
||||||
748 | { |
||||||
749 | return true; |
||||||
750 | } |
||||||
751 | $handler = $this->app_handler($app); |
||||||
752 | if (($shared = $handler->get_shared())) |
||||||
753 | { |
||||||
754 | foreach($shared as $id => $owner) |
||||||
755 | { |
||||||
756 | $file = $this->add_app($app,false,$id,$path.$owner.'/'); |
||||||
757 | // mark other users calendar as shared (iOS 5.0.1 AB does NOT display AB marked as shared!) |
||||||
758 | if ($app == 'calendar') $file['props']['resourcetype']['val'][] = self::mkprop(self::CALENDARSERVER,'shared',''); |
||||||
759 | $files[] = $file; |
||||||
760 | } |
||||||
761 | } |
||||||
762 | return true; |
||||||
763 | } |
||||||
764 | |||||||
765 | /** |
||||||
766 | * Format an account-name for use in displayname |
||||||
767 | * |
||||||
768 | * @param int|array $account |
||||||
769 | * @return string |
||||||
770 | */ |
||||||
771 | public function account_name($account) |
||||||
772 | { |
||||||
773 | if (is_array($account)) |
||||||
774 | { |
||||||
775 | if ($account['account_id'] < 0) |
||||||
776 | { |
||||||
777 | $name = lang('Group').' '.$account['account_lid']; |
||||||
778 | } |
||||||
779 | else |
||||||
780 | { |
||||||
781 | $name = $account['account_fullname']; |
||||||
782 | } |
||||||
783 | } |
||||||
784 | else |
||||||
785 | { |
||||||
786 | if ($account < 0) |
||||||
787 | { |
||||||
788 | $name = lang('Group').' '.$this->accounts->id2name($account,'account_lid'); |
||||||
789 | } |
||||||
790 | else |
||||||
791 | { |
||||||
792 | $name = $this->accounts->id2name($account,'account_fullname'); |
||||||
793 | } |
||||||
794 | if (empty($name)) $name = '#'.$account; |
||||||
795 | } |
||||||
796 | return $name; |
||||||
797 | } |
||||||
798 | |||||||
799 | /** |
||||||
800 | * Add an application collection to a user home or the root |
||||||
801 | * |
||||||
802 | * @param string $app |
||||||
803 | * @param boolean $no_extra_types =false should the GroupDAV and CalDAV types be added (KAddressbook has problems with it in self URL) |
||||||
804 | * @param int $user =null owner of the collection, default current user |
||||||
805 | * @param string $path ='/' |
||||||
806 | * @return array with values for keys 'path' and 'props' |
||||||
807 | */ |
||||||
808 | protected function add_app($app,$no_extra_types=false,$user=null,$path='/') |
||||||
809 | { |
||||||
810 | if ($this->debug) error_log(__METHOD__."(app='$app', no_extra_types=$no_extra_types, user='$user', path='$path')"); |
||||||
811 | $user_preferences = $GLOBALS['egw_info']['user']['preferences']; |
||||||
0 ignored issues
–
show
|
|||||||
812 | if (is_string($user) && $user[0] == 'r' && ($resource = Principals::read_resource(substr($user, 1)))) |
||||||
0 ignored issues
–
show
|
|||||||
813 | { |
||||||
814 | $is_location = Principals::resource_is_location($resource); |
||||||
815 | $displayname = null; |
||||||
816 | list($principalType, $account_lid) = explode('/', Principals::resource2name($resource, $is_location, $displayname)); |
||||||
817 | } |
||||||
818 | elseif ($user) |
||||||
0 ignored issues
–
show
The expression
$user of type integer|null is loosely compared to true ; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||||
819 | { |
||||||
820 | $account_lid = $this->accounts->id2name($user); |
||||||
821 | if ($user >= 0 && $GLOBALS['egw']->preferences->account_id != $user) |
||||||
822 | { |
||||||
823 | $GLOBALS['egw']->preferences->__construct($user); |
||||||
824 | $user_preferences = $GLOBALS['egw']->preferences->read_repository(); |
||||||
825 | $GLOBALS['egw']->preferences->__construct($GLOBALS['egw_info']['user']['account_lid']); |
||||||
826 | } |
||||||
827 | $principalType = $user < 0 ? 'groups' : 'users'; |
||||||
828 | } |
||||||
829 | else |
||||||
830 | { |
||||||
831 | $account_lid = $GLOBALS['egw_info']['user']['account_lid']; |
||||||
832 | $principalType = 'users'; |
||||||
833 | } |
||||||
834 | if (!isset($displayname)) $displayname = $this->account_name($user); |
||||||
835 | |||||||
836 | $props = array( |
||||||
837 | 'owner' => array(self::mkprop('href',$this->base_uri.'/principals/'.$principalType.'/'.$account_lid.'/')), |
||||||
838 | ); |
||||||
839 | |||||||
840 | switch ($app) |
||||||
841 | { |
||||||
842 | case 'inbox': |
||||||
843 | $props['displayname'] = lang('Scheduling inbox').' '.$displayname; |
||||||
844 | break; |
||||||
845 | case 'outbox': |
||||||
846 | $props['displayname'] = lang('Scheduling outbox').' '.$displayname; |
||||||
847 | break; |
||||||
848 | case 'addressbook': |
||||||
849 | if ($path == '/addressbook/') |
||||||
850 | { |
||||||
851 | $props['displayname'] = lang('All addressbooks'); |
||||||
852 | break; |
||||||
853 | } |
||||||
854 | elseif(!$user && $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1') |
||||||
0 ignored issues
–
show
The expression
$user of type integer|null is loosely compared to false ; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||||
855 | { |
||||||
856 | unset($props['owner']); |
||||||
857 | $props['displayname'] = lang($app).' '.lang('Accounts'); |
||||||
858 | break; |
||||||
859 | } |
||||||
860 | // fall through |
||||||
861 | default: |
||||||
862 | $props['displayname'] = Translation::convert(lang($app).' '.$displayname, $this->egw_charset, 'utf-8'); |
||||||
863 | } |
||||||
864 | |||||||
865 | // rfc 5995 (Use POST to add members to WebDAV collections): we use collection path with add-member query param |
||||||
866 | // leaving it switched off, until further testing, because OS X iCal seem to ignore it and OS X Addressbook uses POST to full URL without ?add-member |
||||||
867 | if ($app && !in_array($app,array('inbox','outbox','principals'))) // not on inbox, outbox or principals |
||||||
868 | { |
||||||
869 | $props['add-member'][] = self::mkprop('href',$this->base_uri.$path.'?add-member'); |
||||||
870 | } |
||||||
871 | |||||||
872 | // add props modifyable via proppatch from client, eg. calendar-color, see self::$proppatch_props |
||||||
873 | $ns = null; |
||||||
874 | foreach((array)$GLOBALS['egw_info']['user']['preferences'][$app] as $name => $value) |
||||||
875 | { |
||||||
876 | unset($ns); |
||||||
877 | list($prop,$prop4user,$ns) = explode(':', $name, 3); |
||||||
878 | if ($prop4user == (string)$user && isset(self::$proppatch_props[$prop]) && !isset($ns)) |
||||||
879 | { |
||||||
880 | $props[$prop] = self::mkprop(self::$proppatch_props[$prop], $prop, $value); |
||||||
881 | //error_log(__METHOD__."() explicit ".self::$proppatch_props[$prop].":$prop=".array2string($value)); |
||||||
882 | } |
||||||
883 | // props in arbitrary namespaces not mentioned in self::$ns_needs_explicit_named_props |
||||||
884 | elseif(isset($ns) && !in_array($ns,self::$ns_needs_explicit_named_props)) |
||||||
885 | { |
||||||
886 | $props[] = self::mkprop($ns, $prop, $value); |
||||||
887 | //error_log(__METHOD__."() arbitrary $ns:$prop=".array2string($value)); |
||||||
888 | } |
||||||
889 | } |
||||||
890 | |||||||
891 | foreach((array)$this->root[$app] as $prop => $values) |
||||||
892 | { |
||||||
893 | switch($prop) |
||||||
894 | { |
||||||
895 | case 'resourcetype'; |
||||||
896 | if (!$no_extra_types) |
||||||
897 | { |
||||||
898 | foreach($this->root[$app]['resourcetype'] as $ns => $type) |
||||||
899 | { |
||||||
900 | $props['resourcetype'][] = self::mkprop($ns,$type,''); |
||||||
901 | } |
||||||
902 | // add /addressbook/ as directory gateway |
||||||
903 | if ($path == '/addressbook/') |
||||||
904 | { |
||||||
905 | $props['resourcetype'][] = self::mkprop(self::CARDDAV, 'directory', ''); |
||||||
906 | } |
||||||
907 | } |
||||||
908 | break; |
||||||
909 | case 'app': |
||||||
910 | case 'user-only': |
||||||
911 | break; // no props, already handled |
||||||
912 | default: |
||||||
913 | if (is_array($values)) |
||||||
914 | { |
||||||
915 | foreach($values as $ns => $value) |
||||||
916 | { |
||||||
917 | $props[$prop] = self::mkprop($ns,$prop,$value); |
||||||
918 | } |
||||||
919 | } |
||||||
920 | else |
||||||
921 | { |
||||||
922 | $props[$prop] = $values; |
||||||
923 | } |
||||||
924 | break; |
||||||
925 | } |
||||||
926 | } |
||||||
927 | // add other handler specific properties |
||||||
928 | if (($handler = self::app_handler($app))) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
929 | { |
||||||
930 | if (method_exists($handler,'extra_properties')) |
||||||
931 | { |
||||||
932 | $props = $handler->extra_properties($props, $displayname, $this->base_uri, $user, $path); |
||||||
0 ignored issues
–
show
The call to
EGroupware\Api\CalDAV\Handler::extra_properties() has too many arguments starting with $path .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
933 | } |
||||||
934 | // add ctag if handler implements it |
||||||
935 | if (method_exists($handler,'getctag') && $this->prop_requested('getctag') === true) |
||||||
936 | { |
||||||
937 | $props['getctag'] = self::mkprop( |
||||||
938 | self::CALENDARSERVER,'getctag',$handler->getctag($path,$user)); |
||||||
939 | } |
||||||
940 | // add sync-token url if handler supports sync-collection report |
||||||
941 | if (isset($props['supported-report-set']['sync-collection']) && $this->prop_requested('sync-token') === true) |
||||||
942 | { |
||||||
943 | $props['sync-token'] = $handler->get_sync_token($path,$user); |
||||||
944 | } |
||||||
945 | } |
||||||
946 | if ($handler && !is_null($user)) |
||||||
947 | { |
||||||
948 | return $this->add_collection($path, $props, $handler->current_user_privileges($path, $user)); |
||||||
949 | } |
||||||
950 | return $this->add_collection($path, $props); |
||||||
951 | } |
||||||
952 | |||||||
953 | /** |
||||||
954 | * CalDAV/CardDAV REPORT method handler |
||||||
955 | * |
||||||
956 | * just calls PROPFIND() |
||||||
957 | * |
||||||
958 | * @param array general parameter passing array |
||||||
959 | * @param array return array for file properties |
||||||
960 | * @return bool true on success |
||||||
961 | */ |
||||||
962 | function REPORT(&$options, &$files) |
||||||
963 | { |
||||||
964 | if ($this->debug > 1) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
965 | |||||||
966 | return $this->PROPFIND($options,$files,'REPORT'); |
||||||
967 | } |
||||||
968 | |||||||
969 | /** |
||||||
970 | * CalDAV/CardDAV REPORT method handler to get HTTP_WebDAV_Server to process REPORT requests |
||||||
971 | * |
||||||
972 | * Just calls http_PROPFIND() |
||||||
973 | */ |
||||||
974 | function http_REPORT() |
||||||
975 | { |
||||||
976 | parent::http_PROPFIND('REPORT'); |
||||||
977 | } |
||||||
978 | |||||||
979 | /** |
||||||
980 | * GET method handler |
||||||
981 | * |
||||||
982 | * @param array $options parameter passing array |
||||||
983 | * @return bool true on success |
||||||
984 | */ |
||||||
985 | function GET(&$options) |
||||||
986 | { |
||||||
987 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
988 | |||||||
989 | $id = $app = $user = null; |
||||||
990 | if (!$this->_parse_path($options['path'],$id,$app,$user) || $app == 'principals') |
||||||
991 | { |
||||||
992 | return $this->autoindex($options); |
||||||
993 | } |
||||||
994 | if (($handler = self::app_handler($app))) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
995 | { |
||||||
996 | return $handler->get($options,$id,$user); |
||||||
997 | } |
||||||
998 | error_log(__METHOD__."(".array2string($options).") 501 Not Implemented"); |
||||||
999 | return '501 Not Implemented'; |
||||||
1000 | } |
||||||
1001 | |||||||
1002 | /** |
||||||
1003 | * Display an automatic index (listing and properties) for a collection |
||||||
1004 | * |
||||||
1005 | * @param array $options parameter passing array, index "path" contains requested path |
||||||
1006 | */ |
||||||
1007 | protected function autoindex($options) |
||||||
1008 | { |
||||||
1009 | $propfind_options = array( |
||||||
1010 | 'path' => $options['path'], |
||||||
1011 | 'depth' => 1, |
||||||
1012 | ); |
||||||
1013 | $files = array(); |
||||||
1014 | if (($ret = $this->PROPFIND($propfind_options,$files)) !== true) |
||||||
1015 | { |
||||||
1016 | return $ret; // no collection |
||||||
1017 | } |
||||||
1018 | header('Content-type: text/html; charset='.Translation::charset()); |
||||||
1019 | echo "<html>\n<head>\n\t<title>".'EGroupware (Cal|Card|Group)DAV server '.htmlspecialchars($options['path'])."</title>\n"; |
||||||
1020 | echo "\t<meta http-equiv='content-type' content='text/html; charset=utf-8' />\n"; |
||||||
1021 | echo "\t<style type='text/css'>\n.th { background-color: #e0e0e0; }\n.row_on { background-color: #F1F1F1; vertical-align: top; }\n". |
||||||
1022 | ".row_off { background-color: #ffffff; vertical-align: top; }\ntd { padding-left: 5px; }\nth { padding-left: 5px; text-align: left; }\n\t</style>\n"; |
||||||
1023 | echo "</head>\n<body>\n"; |
||||||
1024 | |||||||
1025 | echo '<h1>(Cal|Card|Group)DAV '; |
||||||
1026 | $path = '/groupdav.php'; |
||||||
1027 | foreach(explode('/',$this->_unslashify($options['path'])) as $n => $name) |
||||||
1028 | { |
||||||
1029 | $path .= ($n != 1 ? '/' : '').$name; |
||||||
1030 | echo Html::a_href(htmlspecialchars($name.'/'),$path); |
||||||
1031 | } |
||||||
1032 | echo "</h1>\n"; |
||||||
1033 | |||||||
1034 | static $props2show = array( |
||||||
1035 | 'DAV:displayname' => 'Displayname', |
||||||
1036 | 'DAV:getlastmodified' => 'Last modified', |
||||||
1037 | 'DAV:getetag' => 'ETag', |
||||||
1038 | //'CalDAV:schedule-tag' => 'Schedule-Tag', |
||||||
1039 | 'DAV:getcontenttype' => 'Content type', |
||||||
1040 | 'DAV:resourcetype' => 'Resource type', |
||||||
1041 | //'http://calendarserver.org/ns/:created-by' => 'Created by', |
||||||
1042 | //'http://calendarserver.org/ns/:updated-by' => 'Updated by', |
||||||
1043 | //'DAV:owner' => 'Owner', |
||||||
1044 | //'DAV:current-user-privilege-set' => 'current-user-privilege-set', |
||||||
1045 | //'DAV:getcontentlength' => 'Size', |
||||||
1046 | //'DAV:sync-token' => 'sync-token', |
||||||
1047 | ); |
||||||
1048 | $n = 0; |
||||||
1049 | $collection_props = null; |
||||||
1050 | foreach($files['files'] as $file) |
||||||
1051 | { |
||||||
1052 | if (!isset($collection_props)) |
||||||
1053 | { |
||||||
1054 | $collection_props = $this->props2array($file['props']); |
||||||
1055 | echo '<h3>'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."</h3>\n"; |
||||||
1056 | continue; // own entry --> displaying properies later |
||||||
1057 | } |
||||||
1058 | if(!$n++) |
||||||
1059 | { |
||||||
1060 | echo "<table>\n\t<tr class='th'>\n\t\t<th>#</th>\n\t\t<th>".lang('Name')."</th>"; |
||||||
1061 | foreach($props2show as $label) |
||||||
1062 | { |
||||||
1063 | echo "\t\t<th>".lang($label)."</th>\n"; |
||||||
1064 | } |
||||||
1065 | echo "\t</tr>\n"; |
||||||
1066 | } |
||||||
1067 | $props = $this->props2array($file['props']); |
||||||
1068 | //echo $file['path']; _debug_array($props); |
||||||
1069 | $class = $class == 'row_on' ? 'row_off' : 'row_on'; |
||||||
1070 | |||||||
1071 | if (substr($file['path'],-1) == '/') |
||||||
1072 | { |
||||||
1073 | $name = basename(substr($file['path'],0,-1)).'/'; |
||||||
1074 | } |
||||||
1075 | else |
||||||
1076 | { |
||||||
1077 | $name = basename($file['path']); |
||||||
1078 | } |
||||||
1079 | |||||||
1080 | echo "\t<tr class='$class'>\n\t\t<td>$n</td>\n\t\t<td>". |
||||||
1081 | Html::a_href(htmlspecialchars($name),'/groupdav.php'.strtr($file['path'], array( |
||||||
1082 | '%' => '%25', |
||||||
1083 | '#' => '%23', |
||||||
1084 | '?' => '%3F', |
||||||
1085 | )))."</td>\n"; |
||||||
1086 | foreach($props2show as $prop => $label) |
||||||
1087 | { |
||||||
1088 | echo "\t\t<td>".($prop=='DAV:getlastmodified'&&!empty($props[$prop])?date('Y-m-d H:i:s',$props[$prop]):$props[$prop])."</td>\n"; |
||||||
1089 | } |
||||||
1090 | echo "\t</tr>\n"; |
||||||
1091 | } |
||||||
1092 | if (!$n) |
||||||
1093 | { |
||||||
1094 | echo '<p>'.lang('Collection empty.')."</p>\n"; |
||||||
1095 | } |
||||||
1096 | else |
||||||
1097 | { |
||||||
1098 | echo "</table>\n"; |
||||||
1099 | } |
||||||
1100 | echo '<h3>'.lang('Properties')."</h3>\n"; |
||||||
1101 | echo "<table>\n\t<tr class='th'><th>".lang('Namespace')."</th><th>".lang('Name')."</th><th>".lang('Value')."</th></tr>\n"; |
||||||
1102 | foreach($collection_props as $name => $value) |
||||||
1103 | { |
||||||
1104 | $class = $class == 'row_on' ? 'row_off' : 'row_on'; |
||||||
1105 | $parts = explode(':', $name); |
||||||
1106 | $name = array_pop($parts); |
||||||
1107 | $ns = implode(':', $parts); |
||||||
1108 | echo "\t<tr class='$class'>\n\t\t<td>".htmlspecialchars($ns)."</td><td style='white-space: nowrap'>".htmlspecialchars($name)."</td>\n"; |
||||||
1109 | echo "\t\t<td>".$value."</td>\n\t</tr>\n"; |
||||||
1110 | } |
||||||
1111 | echo "</table>\n"; |
||||||
1112 | $dav = array(1); |
||||||
1113 | $allow = false; |
||||||
1114 | $this->OPTIONS($options['path'], $dav, $allow); |
||||||
1115 | echo "<p>DAV: ".implode(', ', $dav)."</p>\n"; |
||||||
1116 | |||||||
1117 | echo "</body>\n</html>\n"; |
||||||
1118 | |||||||
1119 | exit; |
||||||
0 ignored issues
–
show
|
|||||||
1120 | } |
||||||
1121 | |||||||
1122 | /** |
||||||
1123 | * Format a property value for output |
||||||
1124 | * |
||||||
1125 | * @param mixed $value |
||||||
1126 | * @return string |
||||||
1127 | */ |
||||||
1128 | protected function prop_value($value) |
||||||
1129 | { |
||||||
1130 | if (is_array($value)) |
||||||
1131 | { |
||||||
1132 | if (isset($value[0]['ns'])) |
||||||
1133 | { |
||||||
1134 | $ns_defs = ''; |
||||||
1135 | $ns_hash = array(); |
||||||
1136 | $value = $this->_hierarchical_prop_encode($value, '', $ns_defs, $ns_hash); |
||||||
1137 | } |
||||||
1138 | $value = array2string($value); |
||||||
1139 | } |
||||||
1140 | if ($value[0] == '<' && function_exists('tidy_repair_string')) |
||||||
1141 | { |
||||||
1142 | $value = tidy_repair_string($value, array( |
||||||
1143 | 'indent' => true, |
||||||
1144 | 'show-body-only' => true, |
||||||
1145 | 'output-encoding' => 'utf-8', |
||||||
1146 | 'input-encoding' => 'utf-8', |
||||||
1147 | 'input-xml' => true, |
||||||
1148 | 'output-xml' => true, |
||||||
1149 | 'wrap' => 0, |
||||||
1150 | )); |
||||||
1151 | } |
||||||
1152 | if (($href=preg_match('/\<(D:)?href\>[^<]+\<\/(D:)?href\>/i',$value))) |
||||||
1153 | { |
||||||
1154 | $value = preg_replace('/\<(D:)?href\>('.preg_quote($this->base_uri.'/','/').')?([^<]+)\<\/(D:)?href\>/i','<\\1href><a href="\\2\\3">\\3</a></\\4href>',$value); |
||||||
1155 | } |
||||||
1156 | $ret = $value[0] == '<' || strpos($value, "\n") !== false ? '<pre>'.htmlspecialchars($value).'</pre>' : htmlspecialchars($value); |
||||||
1157 | |||||||
1158 | if ($href) |
||||||
1159 | { |
||||||
1160 | $ret = str_replace('</a>', '</a>', preg_replace('/<a href="(.+)">/', '<a href="\\1">', $ret)); |
||||||
1161 | } |
||||||
1162 | return $ret; |
||||||
1163 | } |
||||||
1164 | |||||||
1165 | /** |
||||||
1166 | * Return numeric indexed array with values for keys 'ns', 'name' and 'val' as array 'ns:name' => 'val' |
||||||
1167 | * |
||||||
1168 | * @param array $props |
||||||
1169 | * @return array |
||||||
1170 | */ |
||||||
1171 | protected function props2array(array $props) |
||||||
1172 | { |
||||||
1173 | $arr = array(); |
||||||
1174 | foreach($props as $prop) |
||||||
1175 | { |
||||||
1176 | $ns_hash = array('DAV:' => 'D'); |
||||||
1177 | switch($prop['ns']) |
||||||
1178 | { |
||||||
1179 | case 'DAV:'; |
||||||
1180 | $ns = 'DAV'; |
||||||
1181 | break; |
||||||
1182 | case self::CALDAV: |
||||||
1183 | $ns = $ns_hash[$prop['ns']] = 'CalDAV'; |
||||||
1184 | break; |
||||||
1185 | case self::CARDDAV: |
||||||
1186 | $ns = $ns_hash[$prop['ns']] = 'CardDAV'; |
||||||
1187 | break; |
||||||
1188 | case self::GROUPDAV: |
||||||
1189 | $ns = $ns_hash[$prop['ns']] = 'GroupDAV'; |
||||||
1190 | break; |
||||||
1191 | default: |
||||||
1192 | $ns = $prop['ns']; |
||||||
1193 | } |
||||||
1194 | if (is_array($prop['val'])) |
||||||
1195 | { |
||||||
1196 | $prop['val'] = $this->_hierarchical_prop_encode($prop['val'], $prop['ns'], $ns_defs='', $ns_hash); |
||||||
1197 | // hack to show real namespaces instead of not (visibly) defined shortcuts |
||||||
1198 | unset($ns_hash['DAV:']); |
||||||
1199 | $value = strtr($v=$this->prop_value($prop['val']),array_flip($ns_hash)); |
||||||
1200 | } |
||||||
1201 | else |
||||||
1202 | { |
||||||
1203 | $value = $this->prop_value($prop['val']); |
||||||
1204 | } |
||||||
1205 | $arr[$ns.':'.$prop['name']] = $value; |
||||||
1206 | } |
||||||
1207 | return $arr; |
||||||
1208 | } |
||||||
1209 | |||||||
1210 | /** |
||||||
1211 | * POST method handler |
||||||
1212 | * |
||||||
1213 | * @param array parameter passing array |
||||||
0 ignored issues
–
show
The type
EGroupware\Api\parameter was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
1214 | * @return bool true on success |
||||||
1215 | */ |
||||||
1216 | function POST(&$options) |
||||||
1217 | { |
||||||
1218 | // for some reason OS X Addressbook (CFNetwork user-agent) uses now (DAV:add-member given with collection URL+"?add-member") |
||||||
1219 | // POST to the collection URL plus a UID like name component (like for regular PUT) to create new entrys |
||||||
1220 | if (isset($_GET['add-member']) || Handler::get_agent() == 'cfnetwork') |
||||||
1221 | { |
||||||
1222 | $_GET['add-member'] = ''; // otherwise we give no Location header |
||||||
1223 | return $this->PUT($options); |
||||||
1224 | } |
||||||
1225 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
1226 | |||||||
1227 | $id = $app = $user = null; |
||||||
1228 | $this->_parse_path($options['path'],$id,$app,$user); |
||||||
1229 | |||||||
1230 | if (($handler = self::app_handler($app))) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1231 | { |
||||||
1232 | // managed attachments |
||||||
1233 | if (isset($_GET['action']) && substr($_GET['action'], 0, 11) === 'attachment-') |
||||||
1234 | { |
||||||
1235 | return $this->managed_attachements($options, $id, $handler, $_GET['action']); |
||||||
1236 | } |
||||||
1237 | |||||||
1238 | if (method_exists($handler, 'post')) |
||||||
1239 | { |
||||||
1240 | // read the content in a string, if a stream is given |
||||||
1241 | if (isset($options['stream'])) |
||||||
1242 | { |
||||||
1243 | $options['content'] = ''; |
||||||
1244 | while(!feof($options['stream'])) |
||||||
1245 | { |
||||||
1246 | $options['content'] .= fread($options['stream'],8192); |
||||||
1247 | } |
||||||
1248 | } |
||||||
1249 | return $handler->post($options,$id,$user); |
||||||
1250 | } |
||||||
1251 | } |
||||||
1252 | return '501 Not Implemented'; |
||||||
1253 | } |
||||||
1254 | |||||||
1255 | /** |
||||||
1256 | * HTTP header containing managed id |
||||||
1257 | */ |
||||||
1258 | const MANAGED_ID_HEADER = 'Cal-Managed-ID'; |
||||||
1259 | |||||||
1260 | /** |
||||||
1261 | * Add, update or remove attachments |
||||||
1262 | * |
||||||
1263 | * @param array &$options |
||||||
1264 | * @param string|int $id |
||||||
1265 | * @param Handler $handler |
||||||
1266 | * @param string $action 'attachment-add', 'attachment-update', 'attachment-remove' |
||||||
1267 | * @return string http status |
||||||
1268 | * |
||||||
1269 | * @todo support for rid parameter |
||||||
1270 | * @todo managed-id does NOT change on update |
||||||
1271 | * @todo updates of attachments through vfs need to call $handler->update_tags($id) too |
||||||
1272 | */ |
||||||
1273 | protected function managed_attachements(&$options, $id, Handler $handler, $action) |
||||||
1274 | { |
||||||
1275 | error_log(__METHOD__."(path=$options[path], id=$id, ..., action=$action) _GET=".array2string($_GET)); |
||||||
1276 | $entry = $handler->_common_get_put_delete('GET', $options, $id); |
||||||
1277 | |||||||
1278 | if (!is_array($entry)) |
||||||
0 ignored issues
–
show
|
|||||||
1279 | { |
||||||
1280 | return $entry ? $entry : "404 Not found"; |
||||||
1281 | } |
||||||
1282 | |||||||
1283 | if (!Link::file_access($handler->app, $entry['id'], Acl::EDIT)) |
||||||
1284 | { |
||||||
1285 | return '403 Forbidden'; |
||||||
1286 | } |
||||||
1287 | |||||||
1288 | switch($action) |
||||||
1289 | { |
||||||
1290 | case 'attachment-add': |
||||||
1291 | $matches = null; |
||||||
1292 | if (isset($this->_SERVER['HTTP_CONTENT_DISPOSITION']) && |
||||||
1293 | substr($this->_SERVER['HTTP_CONTENT_DISPOSITION'], 0, 10) === 'attachment' && |
||||||
1294 | preg_match('/filename="?([^";]+)/', $this->_SERVER['HTTP_CONTENT_DISPOSITION'], $matches)) |
||||||
1295 | { |
||||||
1296 | $filename = Vfs::basename($matches[1]); |
||||||
1297 | } |
||||||
1298 | $path = null; |
||||||
1299 | if (!($to = self::fopen_attachment($handler->app, $handler->get_id($entry), $filename, $this->_SERVER['CONTENT_TYPE'], $path)) || |
||||||
1300 | isset($options['stream']) && ($copied=stream_copy_to_stream($options['stream'], $to)) === false || |
||||||
1301 | isset($options['content']) && ($copied=fwrite($to, $options['content'])) === false) |
||||||
1302 | { |
||||||
1303 | return '403 Forbidden'; |
||||||
1304 | } |
||||||
1305 | fclose($to); |
||||||
1306 | error_log(__METHOD__."() content-type=$options[content_type], filename=$filename: $path created $copied bytes copied"); |
||||||
1307 | $ret = '201 Created'; |
||||||
1308 | header(self::MANAGED_ID_HEADER.': '.self::path2managed_id($path)); |
||||||
1309 | header('Location: '.self::path2location($path)); |
||||||
1310 | break; |
||||||
1311 | |||||||
1312 | case 'attachment-remove': |
||||||
1313 | case 'attachment-update': |
||||||
1314 | if (empty($_GET['managed-id']) || !($path = self::managed_id2path($_GET['managed-id'], $handler->app, $entry['id']))) |
||||||
1315 | { |
||||||
1316 | self::xml_error(self::mkprop(self::CALDAV, 'valid-managed-id-parameter', '')); |
||||||
1317 | return '403 Forbidden'; |
||||||
1318 | } |
||||||
1319 | if ($action == 'attachment-remove') |
||||||
1320 | { |
||||||
1321 | if (!Vfs::unlink($path)) |
||||||
1322 | { |
||||||
1323 | self::xml_error(self::mkprop(self::CALDAV, 'valid-managed-id-parameter', '')); |
||||||
1324 | return '403 Forbidden'; |
||||||
1325 | } |
||||||
1326 | $ret = '204 No content'; |
||||||
1327 | } |
||||||
1328 | else |
||||||
1329 | { |
||||||
1330 | // check for rename of attachment via Content-Disposition:filename= |
||||||
1331 | if (isset($this->_SERVER['HTTP_CONTENT_DISPOSITION']) && |
||||||
1332 | substr($this->_SERVER['HTTP_CONTENT_DISPOSITION'], 0, 10) === 'attachment' && |
||||||
1333 | preg_match('/filename="?([^";]+)/', $this->_SERVER['HTTP_CONTENT_DISPOSITION'], $matches) && |
||||||
1334 | ($filename = Vfs::basename($matches[1])) != Vfs::basename($path)) |
||||||
1335 | { |
||||||
1336 | $old_path = $path; |
||||||
1337 | if (!($dir = Vfs::dirname($path)) || !Vfs::rename($old_path, $path = Vfs::concat($dir, $filename))) |
||||||
1338 | { |
||||||
1339 | self::xml_error(self::mkprop(self::CALDAV, 'valid-managed-id-parameter', '')); |
||||||
1340 | return '403 Forbidden'; |
||||||
1341 | } |
||||||
1342 | } |
||||||
1343 | if (!($to = Vfs::fopen($path, 'w')) || |
||||||
1344 | isset($options['stream']) && ($copied=stream_copy_to_stream($options['stream'], $to)) === false || |
||||||
1345 | isset($options['content']) && ($copied=fwrite($to, $options['content'])) === false) |
||||||
1346 | { |
||||||
1347 | self::xml_error(self::mkprop(self::CALDAV, 'valid-managed-id-parameter', '')); |
||||||
1348 | return '403 Forbidden'; |
||||||
1349 | } |
||||||
1350 | fclose($to); |
||||||
1351 | error_log(__METHOD__."() content-type=$options[content_type], filename=$filename: $path updated $copied bytes copied"); |
||||||
1352 | $ret = '200 Ok'; |
||||||
1353 | header(self::MANAGED_ID_HEADER.': '.self::path2managed_id($path)); |
||||||
1354 | header('Location: '.self::path2location($path)); |
||||||
1355 | } |
||||||
1356 | break; |
||||||
1357 | |||||||
1358 | default: |
||||||
1359 | return '501 Unknown action parameter '.$action; |
||||||
1360 | } |
||||||
1361 | // update etag/ctag/sync-token by updating modification time |
||||||
1362 | $handler->update_tags($entry); |
||||||
1363 | |||||||
1364 | // check/handle Prefer: return-representation |
||||||
1365 | // we can NOT use 204 No content (forbidds a body) with return=representation, therefore we need to use 200 Ok instead! |
||||||
1366 | if ($handler->check_return_representation($options, $id) && (int)$ret == 204) |
||||||
1367 | { |
||||||
1368 | $ret = '200 Ok'; |
||||||
1369 | } |
||||||
1370 | |||||||
1371 | return $ret; |
||||||
1372 | } |
||||||
1373 | |||||||
1374 | /** |
||||||
1375 | * Handle ATTACH attribute on importing iCals |
||||||
1376 | * |
||||||
1377 | * - turn inline attachments into managed attachments |
||||||
1378 | * - delete NOT included attachments, $delete_via_put is true |
||||||
1379 | * @todo: store URLs not from our managed attachments |
||||||
1380 | * |
||||||
1381 | * @param string $app eg. 'calendar' |
||||||
1382 | * @param int|string $id |
||||||
1383 | * @param array $attach array of array with values for keys 'name', 'params', 'value' |
||||||
1384 | * @param boolean $delete_via_put |
||||||
1385 | * @return boolean false on error, eg. invalid managed id, for false an xml-error body has been send |
||||||
1386 | */ |
||||||
1387 | public static function handle_attach($app, $id, $attach, $delete_via_put=false) |
||||||
1388 | { |
||||||
1389 | //error_log(__METHOD__."('$app', $id, attach=".array2string($attach).", delete_via_put=".array2string($delete_via_put).')'); |
||||||
1390 | |||||||
1391 | if (!Link::file_access($app, $id, Acl::EDIT)) |
||||||
1392 | { |
||||||
1393 | error_log(__METHOD__."('$app', $id, ...) no rights to update attachments"); |
||||||
1394 | return; // no rights --> nothing to do |
||||||
1395 | } |
||||||
1396 | if (!is_array($attach)) $attach = array(); // could be PEAR_Error if not set |
||||||
0 ignored issues
–
show
|
|||||||
1397 | |||||||
1398 | if ($delete_via_put) |
||||||
1399 | { |
||||||
1400 | foreach(Vfs::find(Link::vfs_path($app, $id, '', true), array('type' => 'F')) as $path) |
||||||
1401 | { |
||||||
1402 | $found = false; |
||||||
1403 | foreach($attach as $key => $attr) |
||||||
1404 | { |
||||||
1405 | if ($attr['params']['MANAGED-ID'] === self::path2managed_id($path)) |
||||||
1406 | { |
||||||
1407 | $found = true; |
||||||
1408 | unset($attach[$key]); |
||||||
1409 | break; |
||||||
1410 | } |
||||||
1411 | } |
||||||
1412 | if (!$found) |
||||||
1413 | { |
||||||
1414 | $ok = Vfs::unlink($path); |
||||||
1415 | error_log(__METHOD__."('$app', $id, ...) Vfs::unlink('$path') returned ".array2string($ok)); |
||||||
1416 | } |
||||||
1417 | } |
||||||
1418 | } |
||||||
1419 | // turn inline attachments into managed ones |
||||||
1420 | foreach($attach as $key => $attr) |
||||||
1421 | { |
||||||
1422 | if (!empty($attr['params']['FMTTYPE'])) |
||||||
1423 | { |
||||||
1424 | if (isset($attr['params']['MANAGED-ID'])) |
||||||
1425 | { |
||||||
1426 | // invalid managed-id |
||||||
1427 | if (!($path = self::managed_id2path($attr['params']['MANAGED-ID'])) || !Vfs::is_readable($path)) |
||||||
1428 | { |
||||||
1429 | error_log(__METHOD__."('$app', $id, ...) invalid MANAGED-ID ".array2string($attr)); |
||||||
1430 | self::xml_error(self::mkprop(self::CALDAV, 'valid-managed-id', '')); |
||||||
1431 | return false; |
||||||
1432 | } |
||||||
1433 | if($path == ($link = Link::vfs_path($app, $id, Vfs::basename($path)))) |
||||||
1434 | { |
||||||
1435 | error_log(__METHOD__."('$app', $id, ...) trying to modify existing MANAGED-ID --> ignored! ".array2string($attr)); |
||||||
1436 | continue; |
||||||
1437 | } |
||||||
1438 | // reuse valid managed-id --> symlink attachment |
||||||
1439 | if (Vfs::file_exists($link)) |
||||||
1440 | { |
||||||
1441 | if (Vfs::readlink($link) === $path) continue; // no need to recreate identical link |
||||||
1442 | Vfs::unlink($link); // symlink will fail, if $link exists |
||||||
1443 | } |
||||||
1444 | if (!Vfs::symlink($path, $link)) |
||||||
1445 | { |
||||||
1446 | error_log(__METHOD__."('$app', $id, ...) failed to symlink($path, $link) --> ignored!"); |
||||||
1447 | } |
||||||
1448 | continue; |
||||||
1449 | } |
||||||
1450 | if (!($to = self::fopen_attachment($app, $id, $filename=$attr['params']['FILENAME'], $attr['params']['FMTTYPE'], $path)) || |
||||||
1451 | // Horde Icalendar does NOT decode automatic |
||||||
1452 | (/*$copied=*/fwrite($to, $attr['params']['ENCODING'] == 'BASE64' ? base64_decode($attr['value']) : $attr['value'])) === false) |
||||||
1453 | { |
||||||
1454 | error_log(__METHOD__."('$app', $id, ...) failed to add attachment ".array2string($attr).") "); |
||||||
1455 | continue; |
||||||
1456 | } |
||||||
1457 | fclose($to); |
||||||
1458 | //error_log(__METHOD__."('$app', $id, ...)) content-type={$attr['params']['FMTTYPE']}, filename=$filename: $path created $copied bytes copied"); |
||||||
1459 | } |
||||||
1460 | else |
||||||
1461 | { |
||||||
1462 | //error_log(__METHOD__."('$app', $id, ...) unsupported URI attachment ".array2string($attr)); |
||||||
1463 | } |
||||||
1464 | } |
||||||
1465 | } |
||||||
1466 | |||||||
1467 | /** |
||||||
1468 | * Open attachment for writing |
||||||
1469 | * |
||||||
1470 | * @param string $app |
||||||
1471 | * @param int|string $id |
||||||
1472 | * @param string $_filename defaults to 'attachment' |
||||||
1473 | * @param string $mime =null mime-type to generate extension |
||||||
1474 | * @param string &$path =null on return path opened |
||||||
1475 | * @return resource |
||||||
1476 | */ |
||||||
1477 | protected static function fopen_attachment($app, $id, $_filename, $mime=null, &$path=null) |
||||||
1478 | { |
||||||
1479 | $filename = empty($_filename) ? 'attachment' : Vfs::basename($_filename); |
||||||
1480 | |||||||
1481 | if (strpos($mime, ';')) list($mime) = explode(';', $mime); // in case it contains eg. charset info |
||||||
1482 | |||||||
1483 | $ext = !empty($mime) ? MimeMagic::mime2ext($mime) : ''; |
||||||
1484 | |||||||
1485 | $matches = null; |
||||||
1486 | if (!$ext || substr($filename, -strlen($ext)-1) == '.'.$ext || |
||||||
1487 | preg_match('/\.([^.]+)$/', $filename, $matches) && MimeMagic::ext2mime($matches[1]) == $mime) |
||||||
1488 | { |
||||||
1489 | $parts = explode('.', $filename); |
||||||
1490 | $ext = '.'.array_pop($parts); |
||||||
1491 | $filename = implode('.', $parts); |
||||||
1492 | } |
||||||
1493 | else |
||||||
1494 | { |
||||||
1495 | $ext = '.'.$ext; |
||||||
1496 | } |
||||||
1497 | for($i = 1; $i < 100; ++$i) |
||||||
1498 | { |
||||||
1499 | $path = Link::vfs_path($app, $id, $filename.($i > 1 ? '-'.$i : '').$ext, true); |
||||||
1500 | if (!Vfs::stat($path)) break; |
||||||
1501 | } |
||||||
1502 | if ($i >= 100) return null; |
||||||
1503 | |||||||
1504 | if (!($dir = Vfs::dirname($path)) || !Vfs::file_exists($dir) && !Vfs::mkdir($dir, 0777, STREAM_MKDIR_RECURSIVE)) |
||||||
1505 | { |
||||||
1506 | error_log(__METHOD__."('$app', $id, ...) failed to create entry dir $dir!"); |
||||||
1507 | return false; |
||||||
1508 | } |
||||||
1509 | |||||||
1510 | return Vfs::fopen($path, 'w'); |
||||||
1511 | } |
||||||
1512 | |||||||
1513 | /** |
||||||
1514 | * Get attachment location from path |
||||||
1515 | * |
||||||
1516 | * @param string $path |
||||||
1517 | * @return string |
||||||
1518 | */ |
||||||
1519 | protected static function path2location($path) |
||||||
1520 | { |
||||||
1521 | return Framework::getUrl(Framework::link(Vfs::download_url($path))); |
||||||
1522 | } |
||||||
1523 | |||||||
1524 | /** |
||||||
1525 | * Add ATTACH attribute(s) for iCal |
||||||
1526 | * |
||||||
1527 | * @param string $app eg. 'calendar' |
||||||
1528 | * @param int|string $id |
||||||
1529 | * @param array &$attributes |
||||||
1530 | * @param array &$parameters |
||||||
1531 | */ |
||||||
1532 | public static function add_attach($app, $id, array &$attributes, array &$parameters) |
||||||
1533 | { |
||||||
1534 | foreach(Vfs::find(Link::vfs_path($app, $id, '', true), array( |
||||||
1535 | 'type' => 'F', |
||||||
1536 | 'need_mime' => true, |
||||||
1537 | 'maxdepth' => 10, // set a limit to not run into an infinit recursion |
||||||
1538 | ), true) as $path => $stat) |
||||||
1539 | { |
||||||
1540 | // handle symlinks --> return target size and mime-type |
||||||
1541 | if (($target = Vfs::readlink($path))) |
||||||
1542 | { |
||||||
1543 | if (!($stat = Vfs::stat($target))) continue; // broken or inaccessible symlink |
||||||
1544 | |||||||
1545 | // check if target is in /apps, probably reused MANAGED-ID --> return it |
||||||
1546 | if (substr($target, 0, 6) == '/apps/') |
||||||
1547 | { |
||||||
1548 | $path = $target; |
||||||
1549 | } |
||||||
1550 | } |
||||||
1551 | $attributes['ATTACH'][] = self::path2location($path); |
||||||
1552 | $parameters['ATTACH'][] = array( |
||||||
1553 | 'MANAGED-ID' => self::path2managed_id($path), |
||||||
1554 | 'FMTTYPE' => $stat['mime'], |
||||||
1555 | 'SIZE' => (string)$stat['size'], // Horde_Icalendar renders int as empty string |
||||||
1556 | 'FILENAME' => Vfs::basename($path), |
||||||
1557 | ); |
||||||
1558 | // if we have attachments, set X-attribute to enable deleting them by put |
||||||
1559 | // (works around events synced before without ATTACH attributes) |
||||||
1560 | $attributes['X-EGROUPWARE-ATTACH-INCLUDED'] = 'TRUE'; |
||||||
1561 | } |
||||||
1562 | } |
||||||
1563 | |||||||
1564 | /** |
||||||
1565 | * Return managed-id of a vfs-path |
||||||
1566 | * |
||||||
1567 | * @param string $path "/apps/$app/$id/something" |
||||||
1568 | * @return string |
||||||
1569 | */ |
||||||
1570 | static public function path2managed_id($path) |
||||||
1571 | { |
||||||
1572 | return base64_encode($path); |
||||||
1573 | } |
||||||
1574 | |||||||
1575 | /** |
||||||
1576 | * Return vfs-path of a managed-id |
||||||
1577 | * |
||||||
1578 | * @param string $managed_id |
||||||
1579 | * @param string $app =null app-name to check against path |
||||||
1580 | * @param string|int $id =null id to check agains path |
||||||
1581 | * @return string|boolean "/apps/$app/$id/something" or false if not found or not belonging to given $app/$id |
||||||
1582 | */ |
||||||
1583 | static public function managed_id2path($managed_id, $app=null, $id=null) |
||||||
1584 | { |
||||||
1585 | $path = base64_decode($managed_id); |
||||||
1586 | |||||||
1587 | if (!$path || substr($path, 0, 6) != '/apps/' || !Vfs::stat($path)) |
||||||
1588 | { |
||||||
1589 | $path = false; |
||||||
1590 | } |
||||||
1591 | elseif (!empty($app) && !empty($id)) |
||||||
1592 | { |
||||||
1593 | list(,,$a,$i) = explode('/', $path); |
||||||
1594 | if ($a !== $app || $i !== (string)$id) |
||||||
1595 | { |
||||||
1596 | $path = false; |
||||||
1597 | } |
||||||
1598 | } |
||||||
1599 | error_log(__METHOD__."('$managed_id', $app, $id) base64_decode('$managed_id')=".array2string(base64_decode($managed_id)).' returning '.array2string($path)); |
||||||
1600 | return $path; |
||||||
1601 | } |
||||||
1602 | |||||||
1603 | /** |
||||||
1604 | * Namespaces which need to be eplicitly named in self::$proppatch_props, |
||||||
1605 | * because we consider them protected, if not explicitly named |
||||||
1606 | * |
||||||
1607 | * @var array |
||||||
1608 | */ |
||||||
1609 | static $ns_needs_explicit_named_props = array(self::DAV, self::CALDAV, self::CARDDAV, self::CALENDARSERVER); |
||||||
1610 | /** |
||||||
1611 | * props modifyable via proppatch from client for name-spaces mentioned in self::$ns_needs_explicit_named_props |
||||||
1612 | * |
||||||
1613 | * Props named here are stored in prefs without namespace! |
||||||
1614 | * |
||||||
1615 | * @var array name => namespace pairs |
||||||
1616 | */ |
||||||
1617 | static $proppatch_props = array( |
||||||
1618 | 'displayname' => self::DAV, |
||||||
1619 | 'calendar-description' => self::CALDAV, |
||||||
1620 | 'addressbook-description' => self::CARDDAV, |
||||||
1621 | 'calendar-color' => self::ICAL, // only mentioned that old prefs still work |
||||||
1622 | 'calendar-order' => self::ICAL, |
||||||
1623 | 'default-alarm-vevent-date' => self::CALDAV, |
||||||
1624 | 'default-alarm-vevent-datetime' => self::CALDAV, |
||||||
1625 | ); |
||||||
1626 | |||||||
1627 | /** |
||||||
1628 | * PROPPATCH method handler |
||||||
1629 | * |
||||||
1630 | * @param array &$options general parameter passing array |
||||||
1631 | * @return string with responsedescription or null, individual status in $options['props'][]['status'] |
||||||
1632 | */ |
||||||
1633 | function PROPPATCH(&$options) |
||||||
1634 | { |
||||||
1635 | if ($this->debug) error_log(__METHOD__."(".array2string($options).')'); |
||||||
1636 | |||||||
1637 | // parse path in form [/account_lid]/app[/more] |
||||||
1638 | $id = $app = $user = $user_prefix = null; |
||||||
1639 | self::_parse_path($options['path'],$id,$app,$user,$user_prefix); // allways returns false if eg. !$id |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::_parse_path() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1640 | if ($app == 'principals' || $id || $options['path'] == '/') |
||||||
1641 | { |
||||||
1642 | if ($this->debug > 1) error_log(__METHOD__.": user='$user', app='$app', id='$id': 404 not found!"); |
||||||
1643 | foreach($options['props'] as &$prop) |
||||||
1644 | { |
||||||
1645 | $prop['status'] = '403 Forbidden'; |
||||||
1646 | } |
||||||
1647 | return 'NOT allowed to PROPPATCH that resource!'; |
||||||
1648 | } |
||||||
1649 | // store selected props in preferences, eg. calendar-color, see self::$proppatch_props |
||||||
1650 | $need_save = array(); |
||||||
1651 | foreach($options['props'] as &$prop) |
||||||
1652 | { |
||||||
1653 | if ((isset(self::$proppatch_props[$prop['name']]) && self::$proppatch_props[$prop['name']] === $prop['xmlns'] || |
||||||
0 ignored issues
–
show
|
|||||||
1654 | !in_array($prop['xmlns'],self::$ns_needs_explicit_named_props))) |
||||||
1655 | { |
||||||
1656 | if (!$app) |
||||||
1657 | { |
||||||
1658 | $app = 'groupdav'; |
||||||
1659 | $name = $prop['name'].':'.$options['path'].':'.$prop['ns']; |
||||||
1660 | } |
||||||
1661 | else |
||||||
1662 | { |
||||||
1663 | $name = $prop['name'].':'.$user.(isset(self::$proppatch_props[$prop['name']]) && |
||||||
1664 | self::$proppatch_props[$prop['name']] == $prop['ns'] ? '' : ':'.$prop['ns']); |
||||||
1665 | } |
||||||
1666 | //error_log("preferences['user']['$app']['$name']=".array2string($GLOBALS['egw_info']['user']['preferences'][$app][$name]).($GLOBALS['egw_info']['user']['preferences'][$app][$name] !== $prop['val'] ? ' !== ':' === ')."prop['val']=".array2string($prop['val'])); |
||||||
1667 | if ($GLOBALS['egw_info']['user']['preferences'][$app][$name] !== $prop['val']) // nothing to change otherwise |
||||||
1668 | { |
||||||
1669 | if (isset($prop['val'])) |
||||||
1670 | { |
||||||
1671 | $GLOBALS['egw']->preferences->add($app, $name, $prop['val']); |
||||||
1672 | } |
||||||
1673 | else |
||||||
1674 | { |
||||||
1675 | $GLOBALS['egw']->preferences->delete($app, $name); |
||||||
1676 | } |
||||||
1677 | $need_save[] = $name; |
||||||
1678 | } |
||||||
1679 | $prop['status'] = '200 OK'; |
||||||
1680 | } |
||||||
1681 | else |
||||||
1682 | { |
||||||
1683 | $prop['status'] = '409 Conflict'; // could also be "403 Forbidden" |
||||||
1684 | } |
||||||
1685 | } |
||||||
1686 | if ($need_save) |
||||||
1687 | { |
||||||
1688 | $GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->save_repository(); |
||||||
1689 | // call calendar-hook, if default-alarms are changed, to sync them to calendar prefs |
||||||
1690 | if (class_exists('calendar_hooks')) |
||||||
1691 | { |
||||||
1692 | foreach($need_save as $name) |
||||||
1693 | { |
||||||
1694 | list($name) = explode(':', $name); |
||||||
1695 | if (in_array($name, array('default-alarm-vevent-date', 'default-alarm-vevent-datetime'))) |
||||||
1696 | { |
||||||
1697 | calendar_hooks::sync_default_alarms(); |
||||||
1698 | break; |
||||||
1699 | } |
||||||
1700 | } |
||||||
1701 | } |
||||||
1702 | } |
||||||
1703 | } |
||||||
1704 | |||||||
1705 | /** |
||||||
1706 | * PUT method handler |
||||||
1707 | * |
||||||
1708 | * @param array parameter passing array |
||||||
1709 | * @return bool true on success |
||||||
1710 | */ |
||||||
1711 | function PUT(&$options) |
||||||
1712 | { |
||||||
1713 | // read the content in a string, if a stream is given |
||||||
1714 | if (isset($options['stream'])) |
||||||
1715 | { |
||||||
1716 | $options['content'] = ''; |
||||||
1717 | while(!feof($options['stream'])) |
||||||
1718 | { |
||||||
1719 | $options['content'] .= fread($options['stream'],8192); |
||||||
1720 | } |
||||||
1721 | } |
||||||
1722 | |||||||
1723 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
1724 | |||||||
1725 | $id = $app = $user = $prefix = null; |
||||||
1726 | if (!$this->_parse_path($options['path'],$id,$app,$user,$prefix)) |
||||||
1727 | { |
||||||
1728 | return '404 Not Found'; |
||||||
1729 | } |
||||||
1730 | if (($handler = self::app_handler($app))) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1731 | { |
||||||
1732 | $status = $handler->put($options,$id,$user,$prefix); |
||||||
0 ignored issues
–
show
The call to
EGroupware\Api\CalDAV\Handler::put() has too many arguments starting with $prefix .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
1733 | |||||||
1734 | // set default stati: true --> 204 No Content, false --> should be already handled |
||||||
1735 | if (is_bool($status)) $status = $status ? '204 No Content' : '400 Something went wrong'; |
||||||
1736 | |||||||
1737 | // check/handle Prefer: return-representation |
||||||
1738 | if ($status[0] === '2' || $status === true) |
||||||
1739 | { |
||||||
1740 | // we can NOT use 204 No content (forbidds a body) with return=representation, therefore we need to use 200 Ok instead! |
||||||
1741 | if ($handler->check_return_representation($options, $id, $user) && (int)$status == 204) |
||||||
1742 | { |
||||||
1743 | $status = '200 Ok'; |
||||||
1744 | } |
||||||
1745 | } |
||||||
1746 | return $status; |
||||||
1747 | } |
||||||
1748 | return '501 Not Implemented'; |
||||||
1749 | } |
||||||
1750 | |||||||
1751 | /** |
||||||
1752 | * DELETE method handler |
||||||
1753 | * |
||||||
1754 | * @param array general parameter passing array |
||||||
0 ignored issues
–
show
The type
EGroupware\Api\general was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
1755 | * @return bool true on success |
||||||
1756 | */ |
||||||
1757 | function DELETE($options) |
||||||
1758 | { |
||||||
1759 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
1760 | |||||||
1761 | $id = $app = $user = null; |
||||||
1762 | if (!$this->_parse_path($options['path'],$id,$app,$user)) |
||||||
1763 | { |
||||||
1764 | return '404 Not Found'; |
||||||
1765 | } |
||||||
1766 | if (($handler = self::app_handler($app))) |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1767 | { |
||||||
1768 | $status = $handler->delete($options,$id,$user); |
||||||
1769 | // set default stati: true --> 204 No Content, false --> should be already handled |
||||||
1770 | if (is_bool($status)) $status = $status ? '204 No Content' : '400 Something went wrong'; |
||||||
1771 | return $status; |
||||||
1772 | } |
||||||
1773 | return '501 Not Implemented'; |
||||||
1774 | } |
||||||
1775 | |||||||
1776 | /** |
||||||
1777 | * MKCOL method handler |
||||||
1778 | * |
||||||
1779 | * @param array general parameter passing array |
||||||
1780 | * @return bool true on success |
||||||
1781 | */ |
||||||
1782 | function MKCOL($options) |
||||||
1783 | { |
||||||
1784 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
1785 | |||||||
1786 | return '501 Not Implemented'; |
||||||
1787 | } |
||||||
1788 | |||||||
1789 | /** |
||||||
1790 | * MOVE method handler |
||||||
1791 | * |
||||||
1792 | * @param array general parameter passing array |
||||||
1793 | * @return bool true on success |
||||||
1794 | */ |
||||||
1795 | function MOVE($options) |
||||||
1796 | { |
||||||
1797 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).')'); |
||||||
1798 | |||||||
1799 | return '501 Not Implemented'; |
||||||
1800 | } |
||||||
1801 | |||||||
1802 | /** |
||||||
1803 | * COPY method handler |
||||||
1804 | * |
||||||
1805 | * @param array general parameter passing array |
||||||
1806 | * @return bool true on success |
||||||
1807 | */ |
||||||
1808 | function COPY($options, $del=false) |
||||||
1809 | { |
||||||
1810 | if ($this->debug) error_log('self::'.($del ? 'MOVE' : 'COPY').'('.array2string($options).')'); |
||||||
1811 | |||||||
1812 | return '501 Not Implemented'; |
||||||
1813 | } |
||||||
1814 | |||||||
1815 | /** |
||||||
1816 | * LOCK method handler |
||||||
1817 | * |
||||||
1818 | * @param array general parameter passing array |
||||||
1819 | * @return bool true on success |
||||||
1820 | */ |
||||||
1821 | function LOCK(&$options) |
||||||
1822 | { |
||||||
1823 | $id = $app = $user = null; |
||||||
1824 | self::_parse_path($options['path'],$id,$app,$user); |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::_parse_path() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1825 | $path = Vfs::app_entry_lock_path($app,$id); |
||||||
1826 | |||||||
1827 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).") path=$path"); |
||||||
1828 | |||||||
1829 | // get the app handler, to check if the user has edit access to the entry (required to make locks) |
||||||
1830 | $handler = self::app_handler($app); |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::app_handler() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1831 | |||||||
1832 | // TODO recursive locks on directories not supported yet |
||||||
1833 | if (!$id || !empty($options['depth']) || !$handler->check_access(Acl::EDIT,$id)) |
||||||
1834 | { |
||||||
1835 | return '409 Conflict'; |
||||||
1836 | } |
||||||
1837 | $options['timeout'] = time()+300; // 5min. hardcoded |
||||||
1838 | |||||||
1839 | // dont know why, but HTTP_WebDAV_Server passes the owner in D:href tags, which get's passed unchanged to checkLock/PROPFIND |
||||||
1840 | // that's wrong according to the standard and cadaver does not show it on discover --> strip_tags removes eventual tags |
||||||
1841 | if (($ret = Vfs::lock($path,$options['locktoken'],$options['timeout'],strip_tags($options['owner']), |
||||||
0 ignored issues
–
show
strip_tags($options['owner']) cannot be passed to EGroupware\Api\Vfs::lock() as the parameter $owner expects a reference.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1842 | $options['scope'],$options['type'],isset($options['update']),false)) && !isset($options['update'])) // false = no ACL check |
||||||
1843 | { |
||||||
1844 | return $ret ? '200 OK' : '409 Conflict'; |
||||||
0 ignored issues
–
show
|
|||||||
1845 | } |
||||||
1846 | return $ret; |
||||||
1847 | } |
||||||
1848 | |||||||
1849 | /** |
||||||
1850 | * UNLOCK method handler |
||||||
1851 | * |
||||||
1852 | * @param array general parameter passing array |
||||||
1853 | * @return bool true on success |
||||||
1854 | */ |
||||||
1855 | function UNLOCK(&$options) |
||||||
1856 | { |
||||||
1857 | $id = $app = $user = null; |
||||||
1858 | self::_parse_path($options['path'],$id,$app,$user); |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::_parse_path() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1859 | $path = Vfs::app_entry_lock_path($app,$id); |
||||||
1860 | |||||||
1861 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).") path=$path"); |
||||||
1862 | return Vfs::unlock($path,$options['token']) ? '204 No Content' : '409 Conflict'; |
||||||
1863 | } |
||||||
1864 | |||||||
1865 | /** |
||||||
1866 | * checkLock() helper |
||||||
1867 | * |
||||||
1868 | * @param string resource path to check for locks |
||||||
1869 | * @return bool true on success |
||||||
1870 | */ |
||||||
1871 | function checkLock($path) |
||||||
1872 | { |
||||||
1873 | $id = $app = $user = null; |
||||||
1874 | self::_parse_path($path,$id,$app,$user); |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::_parse_path() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1875 | |||||||
1876 | return Vfs::checkLock(Vfs::app_entry_lock_path($app, $id)); |
||||||
1877 | } |
||||||
1878 | |||||||
1879 | /** |
||||||
1880 | * ACL method handler |
||||||
1881 | * |
||||||
1882 | * @param array general parameter passing array |
||||||
1883 | * @return string HTTP status |
||||||
1884 | */ |
||||||
1885 | function ACL(&$options) |
||||||
1886 | { |
||||||
1887 | $id = $app = $user = null; |
||||||
1888 | self::_parse_path($options['path'],$id,$app,$user); |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::_parse_path() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1889 | |||||||
1890 | if ($this->debug) error_log(__METHOD__.'('.array2string($options).") path=$options[path]"); |
||||||
1891 | |||||||
1892 | $options['errors'] = array(); |
||||||
1893 | switch ($app) |
||||||
1894 | { |
||||||
1895 | case 'calendar': |
||||||
1896 | case 'addressbook': |
||||||
1897 | case 'infolog': |
||||||
1898 | $status = '200 OK'; // grant all |
||||||
1899 | break; |
||||||
1900 | default: |
||||||
1901 | $options['errors'][] = 'no-inherited-ace-conflict'; |
||||||
1902 | $status = '403 Forbidden'; |
||||||
1903 | } |
||||||
1904 | |||||||
1905 | return $status; |
||||||
1906 | } |
||||||
1907 | |||||||
1908 | /** |
||||||
1909 | * Parse a path into it's id, app and user parts |
||||||
1910 | * |
||||||
1911 | * @param string $path |
||||||
1912 | * @param int &$id |
||||||
1913 | * @param string &$app addressbook, calendar, infolog (=infolog) |
||||||
1914 | * @param int &$user |
||||||
1915 | * @param string &$user_prefix =null |
||||||
1916 | * @return boolean true on success, false on error |
||||||
1917 | */ |
||||||
1918 | function _parse_path($path,&$id,&$app,&$user,&$user_prefix=null) |
||||||
1919 | { |
||||||
1920 | if ($this->debug) |
||||||
1921 | { |
||||||
1922 | error_log(__METHOD__." called with ('$path') id=$id, app='$app', user=$user"); |
||||||
1923 | } |
||||||
1924 | if ($path[0] == '/') |
||||||
1925 | { |
||||||
1926 | $path = substr($path, 1); |
||||||
1927 | } |
||||||
1928 | $parts = explode('/', $this->_unslashify($path)); |
||||||
1929 | |||||||
1930 | // /(resources|locations)/<resource-id>-<resource-name>/calendar |
||||||
1931 | if ($parts[0] == 'resources' || $parts[0] == 'locations') |
||||||
1932 | { |
||||||
1933 | if (!empty($parts[1])) |
||||||
1934 | { |
||||||
1935 | $user = $parts[0].'/'.$parts[1]; |
||||||
1936 | array_shift($parts); |
||||||
1937 | $res_id = (int)array_shift($parts); |
||||||
1938 | if (!Principals::read_resource($res_id)) |
||||||
1939 | { |
||||||
1940 | return false; |
||||||
1941 | } |
||||||
1942 | $account_id = 'r'.$res_id; |
||||||
1943 | $app = 'calendar'; |
||||||
1944 | } |
||||||
1945 | } |
||||||
1946 | elseif (($account_id = $this->accounts->name2id($parts[0], 'account_lid')) || |
||||||
1947 | ($account_id = $this->accounts->name2id($parts[0]=urldecode($parts[0])))) |
||||||
1948 | { |
||||||
1949 | // /$user/$app/... |
||||||
1950 | $user = array_shift($parts); |
||||||
1951 | } |
||||||
1952 | |||||||
1953 | if (!isset($app)) $app = array_shift($parts); |
||||||
1954 | |||||||
1955 | // /addressbook-accounts/ |
||||||
1956 | if (!$account_id && $app == 'addressbook-accounts') |
||||||
1957 | { |
||||||
1958 | $app = 'addressbook'; |
||||||
1959 | $user = 0; |
||||||
1960 | $user_prefix = '/'; |
||||||
1961 | } |
||||||
1962 | // shared calendars/addressbooks at /<currentuser>/(calendar|addressbook|infolog|resource|location)-<username> |
||||||
1963 | elseif ($account_id == $GLOBALS['egw_info']['user']['account_id'] && strpos($app, '-') !== false) |
||||||
1964 | { |
||||||
1965 | $user_prefix = '/'.$GLOBALS['egw_info']['user']['account_lid'].'/'.$app; |
||||||
1966 | list($app, $username) = explode('-', $app, 2); |
||||||
1967 | if ($username == 'accounts' && $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1') |
||||||
1968 | { |
||||||
1969 | $account_id = 0; |
||||||
1970 | } |
||||||
1971 | elseif($app == 'resource' || $app == 'location') |
||||||
1972 | { |
||||||
1973 | if (!Principals::read_resource($res_id = (int)$username)) |
||||||
1974 | { |
||||||
1975 | return false; |
||||||
1976 | } |
||||||
1977 | $account_id = 'r'.$res_id; |
||||||
1978 | $app = 'calendar'; |
||||||
1979 | } |
||||||
1980 | elseif (!($account_id = $this->accounts->name2id($username, 'account_lid')) && |
||||||
1981 | !($account_id = $this->accounts->name2id($username=urldecode($username)))) |
||||||
1982 | { |
||||||
1983 | return false; |
||||||
1984 | } |
||||||
1985 | $user = $account_id; |
||||||
1986 | } |
||||||
1987 | elseif ($user) |
||||||
1988 | { |
||||||
1989 | $user_prefix = '/'.$user; |
||||||
1990 | $user = $account_id; |
||||||
1991 | // /<currentuser>/inbox/ |
||||||
1992 | if ($user == $GLOBALS['egw_info']['user']['account_id'] && $app == 'inbox') |
||||||
1993 | { |
||||||
1994 | $app = 'calendar'; |
||||||
1995 | } |
||||||
1996 | } |
||||||
1997 | else |
||||||
1998 | { |
||||||
1999 | $user_prefix = ''; |
||||||
2000 | $user = $GLOBALS['egw_info']['user']['account_id']; |
||||||
2001 | } |
||||||
2002 | |||||||
2003 | // Api\WebDAV\Server encodes %, # and ? again, which leads to storing eg. '%' as '%25' |
||||||
2004 | $id = strtr(array_pop($parts), array( |
||||||
2005 | '%25' => '%', |
||||||
2006 | '%23' => '#', |
||||||
2007 | '%3F' => '?', |
||||||
2008 | )); |
||||||
2009 | |||||||
2010 | $ok = ($id || isset($_GET['add-member']) && $_SERVER['REQUEST_METHOD'] == 'POST') && |
||||||
2011 | ($user || $user === 0) && in_array($app,array('addressbook','calendar','infolog','principals')); |
||||||
2012 | |||||||
2013 | if ($this->debug) |
||||||
2014 | { |
||||||
2015 | error_log(__METHOD__."('$path') returning " . ($ok ? 'true' : 'false') . ": id='$id', app='$app', user='$user', user_prefix='$user_prefix'"); |
||||||
2016 | } |
||||||
2017 | return $ok; |
||||||
2018 | } |
||||||
2019 | |||||||
2020 | protected static $request_starttime; |
||||||
2021 | /** |
||||||
2022 | * Log level from user prefs: $GLOBALS['egw_info']['user']['preferences']['groupdav']['debug_level']) |
||||||
2023 | * - 'f' files directory |
||||||
2024 | * - 'r' to error-log, but only shortend requests |
||||||
2025 | * |
||||||
2026 | * @var string |
||||||
2027 | */ |
||||||
2028 | protected static $log_level; |
||||||
2029 | |||||||
2030 | /** |
||||||
2031 | * Serve WebDAV HTTP request |
||||||
2032 | * |
||||||
2033 | * Reimplemented to add logging |
||||||
2034 | * |
||||||
2035 | * @param $prefix =null prefix filesystem path with given path, eg. "/webdav" for owncloud 4.5 remote.php |
||||||
0 ignored issues
–
show
|
|||||||
2036 | */ |
||||||
2037 | function ServeRequest($prefix=null) |
||||||
2038 | { |
||||||
2039 | if ((self::$log_level=$GLOBALS['egw_info']['user']['preferences']['groupdav']['debug_level']) === 'r' || |
||||||
2040 | self::$log_level === 'f' || $this->debug) |
||||||
2041 | { |
||||||
2042 | self::$request_starttime = microtime(true); |
||||||
2043 | // do NOT log non-text attachments |
||||||
2044 | $this->store_request = $_SERVER['REQUEST_METHOD'] != 'POST' || !isset($_GET['action']) || |
||||||
2045 | !in_array($_GET['action'], array('attachment-add', 'attachment-update')) || |
||||||
2046 | substr($_SERVER['CONTENT_TYPE'], 0, 5) == 'text/'; |
||||||
2047 | ob_start(); |
||||||
2048 | } |
||||||
2049 | parent::ServeRequest($prefix); |
||||||
2050 | |||||||
2051 | if (self::$request_starttime) self::log_request(); |
||||||
0 ignored issues
–
show
The method
EGroupware\Api\CalDAV::log_request() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
2052 | } |
||||||
2053 | |||||||
2054 | /** |
||||||
2055 | * Sanitizing filename to gard agains path traversal and / eg. in UserAgent string |
||||||
2056 | * |
||||||
2057 | * @param string $filename |
||||||
2058 | * @return string |
||||||
2059 | */ |
||||||
2060 | public static function sanitize_filename($filename) |
||||||
2061 | { |
||||||
2062 | return str_replace(array('../', '/'), array('', '!'), $filename); |
||||||
2063 | } |
||||||
2064 | |||||||
2065 | /** |
||||||
2066 | * Log the request |
||||||
2067 | * |
||||||
2068 | * @param string $extra ='' extra text to add below request-log, eg. exception thrown |
||||||
2069 | */ |
||||||
2070 | protected function log_request($extra='') |
||||||
2071 | { |
||||||
2072 | if (self::$request_starttime) |
||||||
2073 | { |
||||||
2074 | if (self::$log_level === 'f') |
||||||
2075 | { |
||||||
2076 | $msg_file = $GLOBALS['egw_info']['server']['files_dir']; |
||||||
2077 | $msg_file .= '/groupdav'; |
||||||
2078 | $msg_file .= '/'.self::sanitize_filename($GLOBALS['egw_info']['user']['account_lid']).'/'; |
||||||
2079 | if (!file_exists($msg_file) && !mkdir($msg_file, 0700, true)) |
||||||
2080 | { |
||||||
2081 | error_log(__METHOD__."() Could NOT create directory '$msg_file'!"); |
||||||
2082 | return; |
||||||
2083 | } |
||||||
2084 | // stop CalDAVTester from creating one log per test-step |
||||||
2085 | if (substr($_SERVER['HTTP_USER_AGENT'], 0, 14) == 'scripts/tests/') |
||||||
2086 | { |
||||||
2087 | $msg_file .= 'CalDAVTester.log'; |
||||||
2088 | } |
||||||
2089 | else |
||||||
2090 | { |
||||||
2091 | $msg_file .= self::sanitize_filename($_SERVER['HTTP_USER_AGENT']).'.log'; |
||||||
2092 | } |
||||||
2093 | $content = '*** '.$_SERVER['REMOTE_ADDR'].' '.date('c')."\n"; |
||||||
2094 | } |
||||||
2095 | $content .= $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' HTTP/1.1'."\n"; |
||||||
2096 | // reconstruct headers |
||||||
2097 | foreach($_SERVER as $name => $value) |
||||||
2098 | { |
||||||
2099 | list($type,$name) = explode('_',$name,2); |
||||||
2100 | if ($type == 'HTTP' || $type == 'CONTENT') |
||||||
2101 | { |
||||||
2102 | $content .= str_replace(' ','-',ucwords(strtolower(($type=='HTTP'?'':$type.' ').str_replace('_',' ',$name)))). |
||||||
2103 | ': '.($name=='AUTHORIZATION'?'Basic ***************':$value)."\n"; |
||||||
2104 | } |
||||||
2105 | } |
||||||
2106 | $content .= "\n"; |
||||||
2107 | if ($this->request) |
||||||
2108 | { |
||||||
2109 | $content .= $this->request."\n"; |
||||||
2110 | } |
||||||
2111 | $content .= 'HTTP/1.1 '.$this->_http_status."\n"; |
||||||
2112 | $content .= 'Date: '.str_replace('+0000', 'GMT', gmdate('r'))."\n"; |
||||||
2113 | $content .= 'Server: '.$_SERVER['SERVER_SOFTWARE']."\n"; |
||||||
2114 | foreach(headers_list() as $line) |
||||||
2115 | { |
||||||
2116 | $content .= $line."\n"; |
||||||
2117 | } |
||||||
2118 | if (($c = ob_get_flush())) $content .= "\n"; |
||||||
2119 | if (self::$log_level !== 'f' && strlen($c) > 1536) $c = substr($c,0,1536)."\n*** LOG TRUNKATED\n"; |
||||||
2120 | $content .= $c; |
||||||
2121 | if ($extra) $content .= $extra; |
||||||
2122 | if ($this->to_log) $content .= "\n### ".implode("\n### ", $this->to_log)."\n"; |
||||||
0 ignored issues
–
show
The expression
$this->to_log of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||||
2123 | $content .= $this->_http_status[0] == '4' && substr($this->_http_status,0,3) != '412' || |
||||||
2124 | $this->_http_status[0] == '5' ? '###' : '***'; // mark failed requests with ###, instead of *** |
||||||
2125 | $content .= sprintf(' %s --> "%s" took %5.3f s',$_SERVER['REQUEST_METHOD'].($_SERVER['REQUEST_METHOD']=='REPORT'?' '.$this->propfind_options['root']['name']:'').' '.$_SERVER['PATH_INFO'],$this->_http_status,microtime(true)-self::$request_starttime)."\n\n"; |
||||||
2126 | |||||||
2127 | if ($msg_file && ($f = fopen($msg_file,'a'))) |
||||||
2128 | { |
||||||
2129 | flock($f,LOCK_EX); |
||||||
2130 | fwrite($f,$content); |
||||||
2131 | flock($f,LOCK_UN); |
||||||
2132 | fclose($f); |
||||||
2133 | } |
||||||
2134 | else |
||||||
2135 | { |
||||||
2136 | foreach(explode("\n",$content) as $line) |
||||||
2137 | { |
||||||
2138 | error_log($line); |
||||||
2139 | } |
||||||
2140 | } |
||||||
2141 | } |
||||||
2142 | } |
||||||
2143 | |||||||
2144 | /** |
||||||
2145 | * Output xml error element |
||||||
2146 | * |
||||||
2147 | * @param string|array $xml_error string with name for empty element in DAV NS or array with props |
||||||
2148 | * @param string $human_readable =null human readable error message |
||||||
2149 | */ |
||||||
2150 | public static function xml_error($xml_error, $human_readable=null) |
||||||
2151 | { |
||||||
2152 | header('Content-type: application/xml; charset=utf-8'); |
||||||
2153 | |||||||
2154 | $xml = new \XMLWriter; |
||||||
2155 | $xml->openMemory(); |
||||||
2156 | $xml->setIndent(true); |
||||||
2157 | $xml->startDocument('1.0', 'utf-8'); |
||||||
2158 | $xml->startElementNs(null, 'error', 'DAV:'); |
||||||
2159 | |||||||
2160 | self::add_prop($xml, $xml_error); |
||||||
2161 | |||||||
2162 | if (!empty($human_readable)) |
||||||
2163 | { |
||||||
2164 | $xml->writeElement('responsedescription', $human_readable); |
||||||
2165 | } |
||||||
2166 | |||||||
2167 | $xml->endElement(); // DAV:error |
||||||
2168 | $xml->endDocument(); |
||||||
2169 | echo $xml->outputMemory(); |
||||||
2170 | } |
||||||
2171 | |||||||
2172 | /** |
||||||
2173 | * Recursivly add properties to XMLWriter object |
||||||
2174 | * |
||||||
2175 | * @param \XMLWriter $xml |
||||||
2176 | * @param string|array $props string with name for empty element in DAV NS or array with props |
||||||
2177 | */ |
||||||
2178 | protected static function add_prop(\XMLWriter $xml, $props) |
||||||
2179 | { |
||||||
2180 | if (is_string($props)) $props = self::mkprop($props, ''); |
||||||
2181 | if (isset($props['name'])) $props = array($props); |
||||||
2182 | |||||||
2183 | foreach($props as $prop) |
||||||
2184 | { |
||||||
2185 | if (isset($prop['ns']) && $prop['ns'] !== 'DAV:') |
||||||
2186 | { |
||||||
2187 | $xml->startElementNs(null, $prop['name'], $prop['ns']); |
||||||
2188 | } |
||||||
2189 | else |
||||||
2190 | { |
||||||
2191 | $xml->startElement($prop['name']); |
||||||
2192 | } |
||||||
2193 | if (is_array($prop['val'])) |
||||||
2194 | { |
||||||
2195 | self::add_prop($xml, $prop['val']); |
||||||
2196 | } |
||||||
2197 | else |
||||||
2198 | { |
||||||
2199 | $xml->text((string)$prop['val']); |
||||||
2200 | } |
||||||
2201 | $xml->endElement(); |
||||||
2202 | } |
||||||
2203 | } |
||||||
2204 | |||||||
2205 | /** |
||||||
2206 | * Content of log() calls, to be appended to request_log |
||||||
2207 | * |
||||||
2208 | * @var array |
||||||
2209 | */ |
||||||
2210 | private $to_log = array(); |
||||||
2211 | |||||||
2212 | /** |
||||||
2213 | * Log unconditional to own request- and PHP error-log |
||||||
2214 | * |
||||||
2215 | * @param string $str |
||||||
2216 | */ |
||||||
2217 | public function log($str) |
||||||
2218 | { |
||||||
2219 | $this->to_log[] = $str; |
||||||
2220 | |||||||
2221 | error_log($str); |
||||||
2222 | } |
||||||
2223 | |||||||
2224 | /** |
||||||
2225 | * Exception handler, which additionally logs the request (incl. a trace) |
||||||
2226 | * |
||||||
2227 | * Does NOT return and get installed in constructor. |
||||||
2228 | * |
||||||
2229 | * @param \Exception|\Error $e |
||||||
2230 | */ |
||||||
2231 | public static function exception_handler($e) |
||||||
2232 | { |
||||||
2233 | // logging exception as regular egw_execption_hander does |
||||||
2234 | $headline = null; |
||||||
2235 | _egw_log_exception($e,$headline); |
||||||
2236 | |||||||
2237 | // exception handler sending message back to the client as basic auth message |
||||||
2238 | $error = str_replace(array("\r", "\n"), array('', ' | '), $e->getMessage()); |
||||||
2239 | header('WWW-Authenticate: Basic realm="'.$headline.': '.$error.'"'); |
||||||
2240 | header('HTTP/1.1 401 Unauthorized'); |
||||||
2241 | header('X-WebDAV-Status: 401 Unauthorized', true); |
||||||
2242 | |||||||
2243 | // if our own logging is active, log the request plus a trace, if enabled in server-config |
||||||
2244 | if (self::$request_starttime && isset(self::$instance)) |
||||||
2245 | { |
||||||
2246 | self::$instance->_http_status = '401 Unauthorized'; // to correctly log it |
||||||
2247 | if ($GLOBALS['egw_info']['server']['exception_show_trace']) |
||||||
2248 | { |
||||||
2249 | self::$instance->log_request("\n".$e->getTraceAsString()."\n"); |
||||||
2250 | } |
||||||
2251 | else |
||||||
2252 | { |
||||||
2253 | self::$instance->log_request(); |
||||||
2254 | } |
||||||
2255 | } |
||||||
2256 | exit; |
||||||
0 ignored issues
–
show
|
|||||||
2257 | } |
||||||
2258 | |||||||
2259 | /** |
||||||
2260 | * Generate a unique id, which can be used for syncronisation |
||||||
2261 | * |
||||||
2262 | * @param string $_appName the appname |
||||||
2263 | * @param string $_eventID the id of the content |
||||||
2264 | * @return string the unique id |
||||||
2265 | */ |
||||||
2266 | static function generate_uid($_appName, $_eventID) |
||||||
2267 | { |
||||||
2268 | if(empty($_appName) || empty($_eventID)) return false; |
||||||
2269 | |||||||
2270 | return $_appName.'-'.$_eventID.'-'.$GLOBALS['egw_info']['server']['install_id']; |
||||||
2271 | } |
||||||
2272 | } |
||||||
2273 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths