caldav-client.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. <?php
  2. /**
  3. * A Class for connecting to a caldav server
  4. *
  5. * @package awl
  6. * removed curl - now using fsockopen
  7. * changed 2009 by Andres Obrero - Switzerland andres@obrero.ch
  8. *
  9. * @subpackage caldav
  10. * @author Andrew McMillan <debian@mcmillan.net.nz>
  11. * @author Roland 'roslai' Liebl <myroundcube@mail4us.net>
  12. * @copyright Andrew McMillan
  13. * @license http://gnu.org/copyleft/gpl.html GNU GPL v2
  14. */
  15. /**
  16. * Mods: Added support for DIGEST authentication
  17. */
  18. /**
  19. * A class for accessing DAViCal via CalDAV, as a client
  20. *
  21. * @package awl
  22. */
  23. class CalDAVClient {
  24. /**
  25. * Server, username, password, calendar
  26. *
  27. * @var string
  28. */
  29. var $base_url, $user, $pass, $auth, $entry, $protocol, $server, $port;
  30. /**
  31. * The useragent which is send to the caldav server
  32. *
  33. * @var string
  34. */
  35. var $user_agent = 'DAViCalClient';
  36. var $headers = array();
  37. var $body = "";
  38. var $requestMethod = "GET";
  39. var $httpRequest = ""; // for debugging http headers sent
  40. var $xmlRequest = ""; // for debugging xml sent
  41. var $httpResponse = ""; // for debugging http headers received
  42. var $xmlResponse = ""; // for debugging xml received
  43. /**
  44. * Constructor, initialises the class
  45. *
  46. * @param string $base_url The URL for the calendar server
  47. * @param string $user The name of the user logging in
  48. * @param string $pass The password for that user
  49. * @param string $auth The authentication method
  50. * basic | detect
  51. */
  52. function CalDAVClient( $base_url, $user, $pass, $auth = 'basic' ) {
  53. $this->user = $user;
  54. $this->pass = $pass;
  55. $this->auth = $auth;
  56. $this->headers = array();
  57. if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) {
  58. $this->server = $matches[2];
  59. $this->base_url = $matches[5];
  60. if ( $matches[1] == 'https' ) {
  61. $this->protocol = 'ssl';
  62. $this->port = 443;
  63. }
  64. else {
  65. $this->protocol = 'tcp';
  66. $this->port = 80;
  67. }
  68. if ( $matches[4] != '' ) {
  69. $this->port = intval($matches[4]);
  70. }
  71. return true;
  72. }
  73. else {
  74. //trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
  75. return false;
  76. }
  77. }
  78. /**
  79. * Adds an If-Match or If-None-Match header
  80. *
  81. * @param bool $match to Match or Not to Match, that is the question!
  82. * @param string $etag The etag to match / not match against.
  83. */
  84. function SetMatch( $match, $etag = '*' ) {
  85. $this->headers[] = sprintf( "%s-Match: \"%s\"", ($match ? "If" : "If-None"), $etag);
  86. }
  87. /**
  88. * Add a Depth: header. Valid values are 0, 1 or infinity
  89. *
  90. * @param int $depth The depth, default to infinity
  91. */
  92. function SetDepth( $depth = '0' ) {
  93. $this->headers[] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") );
  94. }
  95. /**
  96. * Add a Depth: header. Valid values are 1 or infinity
  97. *
  98. * @param int $depth The depth, default to infinity
  99. */
  100. function SetUserAgent( $user_agent = null ) {
  101. if ( !isset($user_agent) ) $user_agent = $this->user_agent;
  102. $this->user_agent = $user_agent;
  103. }
  104. /**
  105. * Add a Content-type: header.
  106. *
  107. * @param int $type The content type
  108. */
  109. function SetContentType( $type ) {
  110. $this->headers[] = "Content-type: $type";
  111. }
  112. /**
  113. * Split response into httpResponse and xmlResponse
  114. *
  115. * @param string Response from server
  116. */
  117. function ParseResponse( $response ) {
  118. $pos = strpos($response, '<?xml');
  119. if ($pos === false) {
  120. $this->httpResponse = trim($response);
  121. }
  122. else {
  123. $this->httpResponse = trim(substr($response, 0, $pos));
  124. $this->xmlResponse = trim(substr($response, $pos));
  125. }
  126. }
  127. /**
  128. * Output http request headers
  129. *
  130. * @return HTTP headers
  131. */
  132. function GetHttpRequest() {
  133. return $this->httpRequest;
  134. }
  135. /**
  136. * Output http response headers
  137. *
  138. * @return HTTP headers
  139. */
  140. function GetHttpResponse() {
  141. return $this->httpResponse;
  142. }
  143. /**
  144. * Output xml request
  145. *
  146. * @return raw xml
  147. */
  148. function GetXmlRequest() {
  149. return $this->xmlRequest;
  150. }
  151. /**
  152. * Output xml response
  153. *
  154. * @return raw xml
  155. */
  156. function GetXmlResponse() {
  157. return $this->xmlResponse;
  158. }
  159. /**
  160. * Send a request to the server
  161. *
  162. * @param string $relative_url The URL to make the request to, relative to $base_url
  163. *
  164. * @return string The content of the response from the server
  165. */
  166. function DoRequest( $relative_url = "" ) {
  167. if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
  168. if(!function_exists('curl_init')){
  169. $headers = array();
  170. $headers[] = $this->requestMethod." ". $this->base_url . $relative_url . " HTTP/1.1";
  171. if($this->auth == 'detect'){
  172. if($auth = $this->GetAuthHeader())
  173. $headers[] = $auth;
  174. }
  175. else if($this->auth == 'basic')
  176. $headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
  177. $headers[] = "Host: ".$this->server .":".$this->port;
  178. foreach( $this->headers as $ii => $head ) {
  179. $headers[] = $head;
  180. }
  181. $headers[] = "Content-Length: " . strlen($this->body);
  182. $headers[] = "User-Agent: " . $this->user_agent;
  183. $headers[] = 'Connection: close';
  184. $this->httpRequest = join("\r\n",$headers);
  185. $this->xmlRequest = $this->body;
  186. //write_log('request', "\r\nRequest:\r\n__________\r\n" . $this->httpRequest."\r\n\r\n".$this->body);
  187. $fip = @fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling?
  188. if ( !(@get_resource_type($fip) == 'stream') ) return false;
  189. if ( !fwrite($fip, $this->httpRequest."\r\n\r\n".$this->body) ) { fclose($fip); return false; }
  190. $rsp = "";
  191. while( !feof($fip) ) { $rsp .= fgets($fip,8192);}
  192. fclose($fip);
  193. $this->headers = array();
  194. $this->ParseResponse($rsp);
  195. //write_log('request',"\r\n__________\r\nResponse:\r\n__________\r\n$rsp");
  196. return $rsp;
  197. }
  198. else{
  199. $s = '';
  200. if($this->protocol == ssl)
  201. $s = 's';
  202. $headers[] = $this->requestMethod." ". $this->base_url . $relative_url . " HTTP/1.1";
  203. $headers[] = "Host: ".$this->server .":".$this->port;
  204. foreach( $this->headers as $ii => $head ) {
  205. $headers[] = $head;
  206. }
  207. $headers[] = "Content-Length: " . strlen($this->body);
  208. $headers[] = "User-Agent: " . $this->user_agent;
  209. $headers[] = 'Connection: close';
  210. $ch = curl_init();
  211. curl_setopt($ch, CURLOPT_URL, 'http'.$s.'://'.$this->server.':'.$this->port.$this->base_url.$relative_url);
  212. //write_log('request', "\r\nRequest:\r\n__________\r\n" . 'http'.$s.'://'.$this->server.':'.$this->port.$this->base_url.$relative_url . "\r\n\r\n" . $this->body);
  213. curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  214. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  215. curl_setopt($ch, CURLOPT_HEADER, TRUE);
  216. curl_setopt($ch, CURLOPT_TIMEOUT, _FSOCK_TIMEOUT);
  217. if($this->auth == 'basic')
  218. $auth = CURLAUTH_BASIC;
  219. else
  220. $auth = CURLAUTH_DIGEST;
  221. curl_setopt($ch, CURLOPT_HTTPAUTH, $auth);
  222. curl_setopt($ch, CURLOPT_USERPWD, $this->user.':'.$this->pass);
  223. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->requestMethod);
  224. curl_setopt($ch, CURLOPT_POSTFIELDS, $this->body);
  225. foreach($this->headers as $header) {
  226. if(stripos($header, 'Content-type:') !== false) {
  227. curl_setopt($ch, CURLOPT_HTTPHEADER, array($header));
  228. break;
  229. }
  230. }
  231. $rsp = curl_exec($ch);
  232. if(curl_errno($ch)){
  233. curl_close($ch);
  234. $this->headers = array();
  235. return false;
  236. }
  237. else {
  238. curl_close($ch);
  239. $this->headers = array(); // reset the headers array for our next request
  240. $this->ParseResponse($rsp);
  241. //write_log('request',"\r\n__________\r\nResponse:\r\n__________\r\n$rsp");
  242. return $rsp;
  243. }
  244. }
  245. }
  246. /**
  247. * Send an OPTIONS request to the server
  248. *
  249. * @param string $relative_url The URL to make the request to, relative to $base_url
  250. *
  251. * @return array The allowed options
  252. */
  253. function DoOptionsRequest( $relative_url = "" ) {
  254. $this->requestMethod = "OPTIONS";
  255. $this->body = "";
  256. $headers = $this->DoRequest($relative_url);
  257. $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
  258. $options = array_flip( preg_split( '/[, ]+/', $options_header ));
  259. return $options;
  260. }
  261. /**
  262. * Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR)
  263. *
  264. * @param string $method The method (PROPFIND, REPORT, etc) to use with the request
  265. * @param string $xml The XML to send along with the request
  266. * @param string $relative_url The URL to make the request to, relative to $base_url
  267. *
  268. * @return array An array of the allowed methods
  269. */
  270. function DoXMLRequest( $request_method, $xml, $relative_url = '' ) {
  271. $this->body = $xml;
  272. $this->requestMethod = $request_method;
  273. $this->SetContentType("text/xml");
  274. return $this->DoRequest($relative_url);
  275. }
  276. /**
  277. * Get a single item from the server.
  278. *
  279. * @param string $relative_url The part of the URL after the calendar
  280. */
  281. function DoGETRequest( $relative_url ) {
  282. $this->body = "";
  283. $this->requestMethod = "GET";
  284. return $this->DoRequest( $relative_url );
  285. }
  286. /**
  287. * PUT a text/icalendar resource, returning the etag
  288. *
  289. * @param string $relative_url The URL to make the request to, relative to $base_url
  290. * @param string $icalendar The iCalendar resource to send to the server
  291. * @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
  292. *
  293. * @return string The content of the response from the server
  294. */
  295. function DoPUTRequest( $relative_url, $icalendar, $etag = null ) {
  296. $this->body = $icalendar;
  297. $this->requestMethod = "PUT";
  298. if ( $etag != null ) {
  299. $this->SetMatch( ($etag != '*'), $etag );
  300. }
  301. $this->SetContentType("text/calendar");
  302. $headers = $this->DoRequest($relative_url);
  303. /**
  304. * RSCDS will always return the real etag on PUT. Other CalDAV servers may need
  305. * more work, but we are assuming we are running against RSCDS in this case.
  306. */
  307. $etag = preg_replace( '/^.*Etag: "?([^"\r\n]+)"?\r?\n.*/is', '$1', $headers );
  308. return $etag;
  309. }
  310. /**
  311. * DELETE a text/icalendar resource
  312. *
  313. * @param string $relative_url The URL to make the request to, relative to $base_url
  314. * @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
  315. *
  316. * @return int The HTTP Result Code for the DELETE
  317. */
  318. function DoDELETERequest( $relative_url, $etag = null ) {
  319. $this->body = "";
  320. $this->requestMethod = "DELETE";
  321. if ( $etag != null ) {
  322. $this->SetMatch( true, $etag );
  323. }
  324. $this->DoRequest($relative_url);
  325. return $this->resultcode;
  326. }
  327. /**
  328. * Given XML for a calendar query, return an array of the events (/todos) in the
  329. * response. Each event in the array will have a 'href', 'etag' and '$response_type'
  330. * part, where the 'href' is relative to the calendar and the '$response_type' contains the
  331. * definition of the calendar data in iCalendar format.
  332. *
  333. * @param string $filter XML fragment which is the <filter> element of a calendar-query
  334. * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
  335. * @param string $report_type Used as a name for the array element containing the calendar data. @deprecated
  336. *
  337. * @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
  338. * be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
  339. * etag (which only varies when the data changes) and the calendar data in iCalendar format.
  340. */
  341. function DoCalendarQuery( $filter, $relative_url = '' ) {
  342. $xml = <<<EOXML
  343. <?xml version="1.0" encoding="utf-8" ?>
  344. <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
  345. <D:prop>
  346. <C:calendar-data/>
  347. <D:getetag/>
  348. </D:prop>
  349. $filter
  350. </C:calendar-query>
  351. EOXML;
  352. $this->DoXMLRequest( 'REPORT', $xml, $relative_url );
  353. $xml_parser = xml_parser_create_ns('UTF-8');
  354. $this->xml_tags = array();
  355. xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 0 );
  356. xml_parse_into_struct( $xml_parser, $this->xmlResponse, $this->xml_tags );
  357. xml_parser_free($xml_parser);
  358. $report = array();
  359. foreach( $this->xml_tags as $k => $v ) {
  360. switch( $v['tag'] ) {
  361. case 'DAV::RESPONSE':
  362. if ( $v['type'] == 'open' ) {
  363. $response = array();
  364. }
  365. elseif ( $v['type'] == 'close' ) {
  366. $report[] = $response;
  367. }
  368. break;
  369. case 'DAV::HREF':
  370. $response['href'] = basename( $v['value'] );
  371. break;
  372. case 'DAV::GETETAG':
  373. $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
  374. break;
  375. case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
  376. $response['data'] = $v['value'];
  377. break;
  378. }
  379. }
  380. return $report;
  381. }
  382. /**
  383. * Get the events in a range from $start to $finish. The dates should be in the
  384. * format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
  385. * array of event arrays. Each event array will have a 'href', 'etag' and 'event'
  386. * part, where the 'href' is relative to the calendar and the event contains the
  387. * definition of the event in iCalendar format.
  388. *
  389. * @param timestamp $start The start time for the period
  390. * @param timestamp $finish The finish time for the period
  391. * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
  392. *
  393. * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
  394. */
  395. function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
  396. $filter = "";
  397. if ( isset($start) && isset($finish) )
  398. $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
  399. else
  400. $range = '';
  401. $filter = <<<EOFILTER
  402. <C:filter>
  403. <C:comp-filter name="VCALENDAR">
  404. <C:comp-filter name="VEVENT">
  405. $range
  406. </C:comp-filter>
  407. </C:comp-filter>
  408. </C:filter>
  409. EOFILTER;
  410. return $this->DoCalendarQuery($filter, $relative_url);
  411. }
  412. /**
  413. * Get the event alarms in a range from $start to $finish. The dates should be in the
  414. * format yyyymmddThhmmssZ and should be in GMT. The event alarms are returned as an
  415. * array of event arrays. Each event array will have a 'href', 'etag' and 'event'
  416. * part, where the 'href' is relative to the calendar and the event contains the
  417. * definition of the event in iCalendar format.
  418. *
  419. * @param timestamp $start The start time for the period
  420. * @param timestamp $finish The finish time for the period
  421. * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
  422. *
  423. * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
  424. */
  425. function GetEventAlarms( $start = null, $finish = null, $relative_url = '' ) {
  426. $filter = "";
  427. if ( isset($start) && isset($finish) )
  428. $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
  429. else
  430. $range = '';
  431. $filter = <<<EOFILTER
  432. <C:filter>
  433. <C:comp-filter name="VCALENDAR">
  434. <C:comp-filter name="VEVENT">
  435. <C:comp-filter name="VALARM">
  436. $range
  437. </C:comp-filter>
  438. </C:comp-filter>
  439. </C:comp-filter>
  440. </C:filter>
  441. EOFILTER;
  442. return $this->DoCalendarQuery($filter, $relative_url);
  443. }
  444. /**
  445. * Get the todo's in a range from $start to $finish. The dates should be in the
  446. * format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
  447. * array of event arrays. Each event array will have a 'href', 'etag' and 'event'
  448. * part, where the 'href' is relative to the calendar and the event contains the
  449. * definition of the event in iCalendar format.
  450. *
  451. * @param timestamp $start The start time for the period
  452. * @param timestamp $finish The finish time for the period
  453. * @param boolean $completed Whether to include completed tasks
  454. * @param boolean $cancelled Whether to include cancelled tasks
  455. * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
  456. *
  457. * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
  458. */
  459. function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
  460. if ( $start && $finish ) {
  461. $time_range = <<<EOTIME
  462. <C:time-range start="$start" end="$finish"/>
  463. EOTIME;
  464. }
  465. // Warning! May contain traces of double negatives...
  466. $neg_cancelled = ( $cancelled === true ? "no" : "yes" );
  467. $neg_completed = ( $cancelled === true ? "no" : "yes" );
  468. $filter = <<<EOFILTER
  469. <C:filter>
  470. <C:comp-filter name="VCALENDAR">
  471. <C:comp-filter name="VTODO">
  472. <C:prop-filter name="STATUS">
  473. <C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
  474. </C:prop-filter>
  475. <C:prop-filter name="STATUS">
  476. <C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
  477. </C:prop-filter>$time_range
  478. </C:comp-filter>
  479. </C:comp-filter>
  480. </C:filter>
  481. EOFILTER;
  482. return $this->DoCalendarQuery($filter, $relative_url);
  483. }
  484. /**
  485. * Get the calendar entry by UID
  486. *
  487. * @param uid
  488. * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
  489. *
  490. * @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
  491. */
  492. function GetEntryByUid( $uid, $relative_url = '' ) {
  493. $filter = "";
  494. if ( $uid ) {
  495. $filter = <<<EOFILTER
  496. <C:filter>
  497. <C:comp-filter name="VCALENDAR">
  498. <C:comp-filter name="VEVENT">
  499. <C:prop-filter name="UID">
  500. <C:text-match icollation="i;octet">$uid</C:text-match>
  501. </C:prop-filter>
  502. </C:comp-filter>
  503. </C:comp-filter>
  504. </C:filter>
  505. EOFILTER;
  506. }
  507. return $this->DoCalendarQuery($filter, $relative_url);
  508. }
  509. /**
  510. * Get the calendar entry by HREF
  511. *
  512. * @param string $href The href from a call to GetEvents or GetTodos etc.
  513. * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
  514. *
  515. * @return string The iCalendar of the calendar entry
  516. */
  517. function GetEntryByHref( $href, $relative_url = '' ) {
  518. return $this->DoGETRequest( $relative_url . $href );
  519. }
  520. /**
  521. * Get the Authentication header (BASIC or DIGEST)
  522. *
  523. * @return string Authentication header
  524. */
  525. function GetAuthHeader() {
  526. $file = substr($this->base_url . $relative_url, 1);
  527. if (!$fp = fsockopen($this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT))
  528. return false;
  529. //first do the non-authenticated header so that the server
  530. //sends back a 401 error containing its nonce and opaque
  531. $out = $this->requestMethod . " /$file HTTP/1.1\r\n";
  532. $out .= "Host: " . $this->server . "\r\n";
  533. $out .= "Connection: Close\r\n\r\n";
  534. fwrite($fp, $out);
  535. //read the reply and look for the WWW-Authenticate element
  536. while (!feof($fp)) {
  537. $line = fgets($fp, 512);
  538. if (stripos($line,"WWW-Authenticate: BASIC") !== false){
  539. fclose($fp);
  540. return "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
  541. }
  542. else if (stripos($line,"WWW-Authenticate: Digest") !== false){
  543. $authline=trim(substr($line,18));
  544. }
  545. }
  546. fclose($fp);
  547. if(!$authline)
  548. return false;
  549. //split up the WWW-Authenticate string to find digest-realm,nonce and opaque values
  550. //if qop value is presented as a comma-seperated list (e.g auth,auth-int) then it won't be retrieved correctly
  551. //but that doesn't matter because going to use 'auth' anyway
  552. $authlinearr=explode(",",$authline);
  553. $autharr=array();
  554. foreach ($authlinearr as $el) {
  555. $elarr=explode("=",$el);
  556. //the substr here is used to remove the double quotes from the values
  557. $autharr[trim($elarr[0])]=substr($elarr[1],1,strlen($elarr[1])-2);
  558. }
  559. //these are all the vals required from the server
  560. $nonce=$autharr['nonce'];
  561. $opaque=$autharr['opaque'];
  562. $drealm=$autharr['Digest realm'];
  563. //client nonce can be anything since this authentication session is not going to be persistent
  564. //likewise for the cookie - just call it MyCookie
  565. $cnonce="sausages";
  566. //calculate the hashes of A1 and A2 as described in RFC 2617
  567. $a1=$this->user . ":$drealm:" . $this->pass ;$a2= $this->requestMethod . ":/$file";
  568. $ha1=md5($a1);$ha2=md5($a2);
  569. //calculate the response hash as described in RFC 2617
  570. $concat = $ha1.':'.$nonce.':00000001:'.$cnonce.':auth:'.$ha2;
  571. $response=md5($concat);
  572. //put together the Authorization Request Header
  573. $out = "Authorization: Digest username=\"" . $this->user . "\", realm=\"$drealm\", qop=\"auth\", algorithm=\"MD5\", uri=\"/$file\", nonce=\"$nonce\", nc=00000001, cnonce=\"$cnonce\", opaque=\"$opaque\", response=\"$response\"";
  574. return $out;
  575. }
  576. }
  577. /**
  578. * Usage example
  579. *
  580. * $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
  581. * $options = $cal->DoOptionsRequest();
  582. * if ( isset($options["PROPFIND"]) ) {
  583. * // Fetch some information about the events in that calendar
  584. * $cal->SetDepth(1);
  585. * $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
  586. * }
  587. * // Fetch all events for February
  588. * $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
  589. * foreach ( $events AS $k => $event ) {
  590. * do_something_with_event_data( $event['data'] );
  591. * }
  592. * $acc = array();
  593. * $acc["google"] = array(
  594. * "user"=>"kunsttherapie@gmail.com",
  595. * "pass"=>"xxxxx",
  596. * "server"=>"ssl://www.google.com",
  597. * "port"=>"443",
  598. * "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
  599. * );
  600. *
  601. * $acc["davical"] = array(
  602. * "user"=>"some_user",
  603. * "pass"=>"big secret",
  604. * "server"=>"calendar.foo.bar",
  605. * "port"=>"80",
  606. * "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
  607. * );
  608. * //*******************************
  609. *
  610. * $account = $acc["davical"];
  611. *
  612. * //*******************************
  613. * $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
  614. * $options = $cal->DoOptionsRequest();
  615. * print_r($options);
  616. *
  617. * //*******************************
  618. * //*******************************
  619. *
  620. * $xmlC = <<<PROPP
  621. * <?xml version="1.0" encoding="utf-8" ?>
  622. * <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
  623. * <D:prop>
  624. * <D:displayname />
  625. * <C:getctag />
  626. * <D:resourcetype />
  627. *
  628. * </D:prop>
  629. * </D:propfind>
  630. * PROPP;
  631. * //if ( isset($options["PROPFIND"]) ) {
  632. * // Fetch some information about the events in that calendar
  633. * // $cal->SetDepth(1);
  634. * // $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
  635. * // print_r( $folder_xml);
  636. * //}
  637. *
  638. * // Fetch all events for February
  639. * $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z");
  640. * foreach ( $events as $k => $event ) {
  641. * print_r($event['data']);
  642. * print "\n---------------------------------------------\n";
  643. * }
  644. *
  645. * //*******************************
  646. * //*******************************
  647. */
  648. ?>