PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/caldav-MKCOL.php

https://gitlab.com/tiggerben/davical
PHP | 265 lines | 165 code | 31 blank | 69 comment | 31 complexity | 6754d96412aae6447568b53848480b85 MD5 | raw file
  1. <?php
  2. /**
  3. * CalDAV Server - handle MKCOL and MKCALENDAR method
  4. *
  5. * @package davical
  6. * @subpackage caldav
  7. * @author Andrew McMillan <andrew@mcmillan.net.nz>
  8. * @copyright Catalyst IT Ltd, Morphoss Ltd - http://www.morphoss.com/
  9. * @license http://gnu.org/copyleft/gpl.html GNU GPL v2
  10. */
  11. dbg_error_log('MKCOL', 'method handler');
  12. require_once('AwlQuery.php');
  13. $request->NeedPrivilege('DAV::bind');
  14. $displayname = $request->path;
  15. // Enforce trailling '/' on collection name
  16. if ( ! preg_match( '#/$#', $request->path ) ) {
  17. dbg_error_log( 'MKCOL', 'Add trailling "/" to "%s"', $request->path);
  18. $request->path .= '/';
  19. }
  20. $parent_container = '/';
  21. if ( preg_match( '#^(.*/)([^/]+)(/)?$#', $request->path, $matches ) ) {
  22. $parent_container = $matches[1];
  23. $displayname = $matches[2];
  24. }
  25. require_once('DAVResource.php');
  26. $parent = new DAVResource( $parent_container );
  27. if ( $parent->IsSchedulingCollection( 'inbox' ) ) {
  28. $request->PreconditionFailed(403, 'urn:ietf:params:xml:ns:caldav:no-mkcol-in-inbox' );
  29. }
  30. $request_type = $request->method;
  31. $is_calendar = ($request_type == 'MKCALENDAR');
  32. $is_addressbook = false;
  33. $resourcetypes = '<DAV::collection/>';
  34. if ($is_calendar) $resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>';
  35. require_once('XMLDocument.php');
  36. $reply = new XMLDocument(array( 'DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C' ));
  37. $failure_code = null;
  38. $failure = array();
  39. $dav_properties = array();
  40. if ( isset($request->xml_tags) ) {
  41. /**
  42. * The MKCOL request may contain XML to set some DAV properties
  43. */
  44. $position = 0;
  45. $xmltree = BuildXMLTree( $request->xml_tags, $position);
  46. if ( $xmltree->GetNSTag() == 'DAV::mkcol' ) $request_type = 'extended-mkcol';
  47. if ( $xmltree->GetNSTag() != 'urn:ietf:params:xml:ns:caldav:mkcalendar' && $request_type != 'extended-mkcol' ) {
  48. $request->DoResponse( 406, sprintf('The XML is not a "DAV::mkcol" or "urn:ietf:params:xml:ns:caldav:mkcalendar" document (%s)', $xmltree->GetNSTag()) );
  49. }
  50. $setprops = $xmltree->GetContent(); // <set>
  51. $setprops = $setprops[0]->GetContent(); // <prop>
  52. $setprops = $setprops[0]->GetContent(); // the array of properties.
  53. foreach( $setprops AS $k => $setting ) {
  54. $tag = $setting->GetNSTag();
  55. $content = $setting->RenderContent(0,null,true);
  56. dbg_error_log( 'MKCOL', 'Processing tag "%s"', $tag);
  57. switch( $tag ) {
  58. case 'DAV::resourcetype':
  59. /** Any value for resourcetype other than 'calendar' is ignored */
  60. dbg_error_log( 'MKCOL', 'Extended MKCOL with resourcetype specified. "%s"', $content);
  61. $is_addressbook = count($setting->GetPath('DAV::resourcetype/urn:ietf:params:xml:ns:carddav:addressbook'));
  62. $is_calendar = count($setting->GetPath('DAV::resourcetype/urn:ietf:params:xml:ns:caldav:calendar'));
  63. if ( $is_addressbook && $is_calendar ) {
  64. $failure['set-'.$tag] = new XMLElement( 'propstat', array(
  65. new XMLElement( 'prop', new XMLElement($reply->Tag($tag))),
  66. new XMLElement( 'status', 'HTTP/1.1 409 Conflict' ),
  67. new XMLElement('responsedescription', translate('Collections may not be both CalDAV calendars and CardDAV addressbooks at the same time') )
  68. ));
  69. }
  70. else {
  71. $resourcetypes = $setting->GetPath('DAV::resourcetype/*');
  72. $types = '';
  73. foreach( $resourcetypes AS $k => $v ) {
  74. $types .= '<'.$v->GetNSTag().'/>';
  75. }
  76. $resourcetypes = $types;
  77. $success[$tag] = 1;
  78. }
  79. break;
  80. case 'DAV::displayname':
  81. $displayname = $content;
  82. /**
  83. * @todo This is definitely a bug in SOHO Organizer and we probably should respond
  84. * with an error, rather than silently doing what they *seem* to want us to do.
  85. */
  86. if ( preg_match( '/^SOHO.Organizer.6\./', $_SERVER['HTTP_USER_AGENT'] ) ) {
  87. dbg_error_log( 'MKCOL', 'Displayname is "/" to "%s"', $request->path);
  88. $parent_container = $request->path;
  89. $request->path .= $content . '/';
  90. }
  91. $success[$tag] = 1;
  92. break;
  93. case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
  94. /** We allow this to be written as a dead property */
  95. $dav_properties[$tag] = $content;
  96. $success[$tag] = 1;
  97. break;
  98. case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data': /** Ignored, since we will support iCalendar 2.0 */
  99. case 'urn:ietf:params:xml:ns:caldav:calendar-data': /** Ignored, since we will support iCalendar 2.0 */
  100. case 'urn:ietf:params:xml:ns:caldav:max-resource-size': /** Ignored, since we will support arbitrary size */
  101. case 'urn:ietf:params:xml:ns:caldav:min-date-time': /** Ignored, since we will support arbitrary time */
  102. case 'urn:ietf:params:xml:ns:caldav:max-date-time': /** Ignored, since we will support arbitrary time */
  103. case 'urn:ietf:params:xml:ns:caldav:max-instances': /** Ignored, since we will support arbitrary instances */
  104. $success[$tag] = 1;
  105. break;
  106. /**
  107. * The following properties are read-only, so they will cause the request to fail
  108. */
  109. case 'DAV::getetag':
  110. case 'DAV::getcontentlength':
  111. case 'DAV::getcontenttype':
  112. case 'DAV::getlastmodified':
  113. case 'DAV::creationdate':
  114. case 'DAV::lockdiscovery':
  115. case 'DAV::supportedlock':
  116. $failure['set-'.$tag] = new XMLElement( 'propstat', array(
  117. new XMLElement( 'prop', new XMLElement($reply->Tag($tag))),
  118. new XMLElement( 'status', 'HTTP/1.1 409 Conflict' ),
  119. new XMLElement('responsedescription', translate('Property is read-only') )
  120. ));
  121. if ( isset($failure_code) && $failure_code != 409 ) $failure_code = 207;
  122. else if ( !isset($failure_code) ) $failure_code = 409;
  123. break;
  124. /**
  125. * If we don't have any special processing for the property, we just store it verbatim (which will be an XML fragment).
  126. */
  127. default:
  128. $dav_properties[$tag] = $content;
  129. $success[$tag] = 1;
  130. break;
  131. }
  132. }
  133. /**
  134. * If we have encountered any instances of failure, the whole damn thing fails.
  135. */
  136. if ( count($failure) > 0 ) {
  137. $props = array();
  138. $status = array();
  139. foreach( $success AS $tag => $v ) {
  140. // Unfortunately although these succeeded, we failed overall, so they didn't happen...
  141. $props[] = new XMLElement($reply->Tag($tag));
  142. }
  143. $status[] = new XMLElement( 'propstat', array(
  144. new XMLElement('prop', $props),
  145. new XMLElement('status', 'HTTP/1.1 424 Failed Dependency' )
  146. ));
  147. if ( $request_type == 'extended-mkcol' ) {
  148. $request->DoResponse( $failure_code, $reply->Render('mkcol-response', array_merge( $status, $failure ), 'text/xml; charset="utf-8"' ) );
  149. }
  150. else {
  151. array_unshift( $failure, $reply->href( ConstructURL($request->path) ) );
  152. $failure[] = new XMLElement('responsedescription', translate('Some properties were not able to be set.') );
  153. $request->DoResponse( 207, $reply->Render('multistatus', new XMLElement( 'response', $failure )), 'text/xml; charset="utf-8"' );
  154. }
  155. }
  156. }
  157. $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name';
  158. $qry = new AwlQuery( $sql, array( ':dav_name' => $request->path) );
  159. if ( ! $qry->Exec('MKCOL',__LINE__,__FILE__) ) {
  160. $request->DoResponse( 500, translate('Error querying database.') );
  161. }
  162. if ( $qry->rows() != 0 ) {
  163. $request->DoResponse( 405, translate('A collection already exists at that location.') );
  164. }
  165. $qry = new AwlQuery();
  166. $qry->Begin();
  167. if ( ! $qry->QDo( 'INSERT INTO collection ( user_no, parent_container, dav_name, dav_etag, dav_displayname,
  168. is_calendar, is_addressbook, resourcetypes, created, modified )
  169. VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname,
  170. :is_calendar, :is_addressbook, :resourcetypes, current_timestamp, current_timestamp )',
  171. array(
  172. ':user_no' => $request->user_no,
  173. ':parent_container' => $parent_container,
  174. ':dav_name' => $request->path,
  175. ':dav_etag' => md5($request->user_no. $request->path),
  176. ':dav_displayname' => $displayname,
  177. ':is_calendar' => ($is_calendar ? 't' : 'f'),
  178. ':is_addressbook' => ($is_addressbook ? 't' : 'f'),
  179. ':resourcetypes' => $resourcetypes
  180. ) ) ) {
  181. $request->DoResponse( 500, translate('Error writing calendar details to database.') );
  182. }
  183. foreach( $dav_properties AS $k => $v ) {
  184. if ( ! $qry->QDo('SELECT set_dav_property( :dav_name, :user_no::integer, :tag::text, :value::text )',
  185. array( ':dav_name' => $request->path, ':user_no' => $request->user_no, ':tag' => $k, ':value' => $v) ) ) {
  186. $request->DoResponse( 500, translate('Error writing calendar properties to database.') );
  187. }
  188. }
  189. if ( !$qry->Commit() ) {
  190. $request->DoResponse( 500, translate('Error writing calendar details to database.') );
  191. }
  192. dbg_error_log( 'MKCOL', 'New calendar "%s" created named "%s" for user "%d" in parent "%s"', $request->path, $displayname, $session->user_no, $parent_container);
  193. header('Cache-Control: no-cache'); /** RFC4791 mandates this at 5.3.1 */
  194. $request->DoResponse( 201, '' );
  195. /**
  196. * @todo We could also respond to the request...
  197. *
  198. * <?xml version="1.0" encoding="utf-8" ?>
  199. * <C:mkcalendar xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
  200. * <D:set>
  201. * <D:prop>
  202. * <D:displayname>Lisa's Events</D:displayname>
  203. * <C:calendar-description xml:lang="en">Calendar restricted to events.</C:calendar-description>
  204. * <C:supported-calendar-component-set>
  205. * <C:comp name="VEVENT"/>
  206. * </C:supported-calendar-component-set>
  207. * <C:calendar-timezone><![CDATA[BEGIN:VCALENDAR
  208. * PRODID:-//Example Corp.//CalDAV Client//EN
  209. * VERSION:2.0
  210. * BEGIN:VTIMEZONE
  211. * TZID:US-Eastern
  212. * LAST-MODIFIED:19870101T000000Z
  213. * BEGIN:STANDARD
  214. * DTSTART:19671029T020000
  215. * RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
  216. * TZOFFSETFROM:-0400
  217. * TZOFFSETTO:-0500
  218. * TZNAME:Eastern Standard Time (US & Canada)
  219. * END:STANDARD
  220. * BEGIN:DAYLIGHT
  221. * DTSTART:19870405T020000
  222. * RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
  223. * TZOFFSETFROM:-0500
  224. * TZOFFSETTO:-0400
  225. * TZNAME:Eastern Daylight Time (US & Canada)
  226. * END:DAYLIGHT
  227. * END:VTIMEZONE
  228. * END:VCALENDAR
  229. * ]]></C:calendar-timezone>
  230. * </D:prop>
  231. * </D:set>
  232. * </C:mkcalendar>
  233. *
  234. */